MuseScore/libmscore/element.cpp
Kumar Kartikay a0ecbba225 Reimplement scanElements function using score tree
Now its just a simple iteration over generic children, rather than
having to specify each child manually as we did before. This makes the
code simpler, and we have to override the virtual function in fewer
places.

Also,  whether an element should be scanned or not is now checked
in the child class rather than the parent class. E.g. showLedgerLines()
is checked in LedgerLines class now, instead of Chord, etc.
2020-08-08 13:53:46 +05:30

2651 lines
78 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//=============================================================================
// 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 "mmrest.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 "sticking.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"
//#include "musescoreCore.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
{
if (systemFlag() || (parent() && parent()->systemFlag())) {
return score()->spatium();
} else {
Staff* s = staff();
return s ? s->spatium(this) : 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;
_offsetChanged = OffsetChange::NONE;
_minDistance = Spatium(0.0);
}
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;
setFlag(ElementFlag::SELECTED, false);
_tag = e._tag;
_z = e._z;
_color = e._color;
_offsetChanged = e._offsetChanged;
_minDistance = e._minDistance;
itemDiscovered = false;
}
//---------------------------------------------------------
// ~Element
//---------------------------------------------------------
Element::~Element()
{
Score::onElementDestruction(this);
}
//---------------------------------------------------------
// linkedClone
//---------------------------------------------------------
Element* Element::linkedClone()
{
Element* e = clone();
e->setAutoplace(true);
score()->undo(new Link(e, this));
return e;
}
//---------------------------------------------------------
// deleteLater
//---------------------------------------------------------
void Element::deleteLater()
{
if (selected()) {
score()->deselect(this);
}
masterScore()->deleteLater(this);
}
//---------------------------------------------------------
// scanElements
/// If leaf node, apply `func` on this element (after checking if it is visible).
/// Otherwise, recurse over all children (see ScoreElement::scanElements).
/// Note: This function is overridden in some classes to skip certain children,
/// or to apply `func` even to non-leaf nodes.
//---------------------------------------------------------
void Element::scanElements(void* data, void (* func)(void*, Element*), bool all)
{
if (treeChildCount() == 0) {
if (all || visible() || score()->showInvisible()) {
func(data, this);
}
} else {
ScoreElement::scanElements(data, func, all);
}
}
//---------------------------------------------------------
// reset
//---------------------------------------------------------
void Element::reset()
{
undoResetProperty(Pid::AUTOPLACE);
undoResetProperty(Pid::PLACEMENT);
undoResetProperty(Pid::MIN_DISTANCE);
undoResetProperty(Pid::OFFSET);
setOffsetChanged(false);
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);
}
//---------------------------------------------------------
// staffType
//---------------------------------------------------------
const StaffType* Element::staffType() const
{
Staff* s = staff();
return s ? s->staffTypeForElement(this) : nullptr;
}
//---------------------------------------------------------
// onTabStaff
//---------------------------------------------------------
bool Element::onTabStaff() const
{
const StaffType* stt = staffType();
return stt ? stt->isTabStaff() : false;
}
//---------------------------------------------------------
// z
//---------------------------------------------------------
int Element::z() const
{
if (_z == -1) {
_z = int(type()) * 100;
}
return _z;
}
//---------------------------------------------------------
// tick
//---------------------------------------------------------
Fraction Element::tick() const
{
const Element* e = this;
while (e->parent()) {
if (e->parent()->isSegment()) {
return toSegment(e->parent())->tick();
} else if (e->parent()->isMeasureBase()) {
return toMeasureBase(e->parent())->tick();
}
e = e->parent();
}
return Fraction(0, 1);
}
//---------------------------------------------------------
// rtick
//---------------------------------------------------------
Fraction Element::rtick() const
{
const Element* e = this;
while (e->parent()) {
if (e->parent()->isSegment()) {
return toSegment(e->parent())->rtick();
}
e = e->parent();
}
return Fraction(0, 1);
}
//---------------------------------------------------------
// playTick
//---------------------------------------------------------
Fraction Element::playTick() const
{
// Play from the element's tick position by default.
return tick();
}
//---------------------------------------------------------
// 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() + QPointF(toFretDiagram(parent())->centerX(), 0.0);
} 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
{
bool autoplaceEnabled = score()->styleB(Sid::autoplaceEnabled);
if (!autoplaceEnabled) {
score()->setStyleValue(Sid::autoplaceEnabled, true);
writeProperty(xml, Pid::AUTOPLACE);
score()->setStyleValue(Sid::autoplaceEnabled, autoplaceEnabled);
} else {
writeProperty(xml, Pid::AUTOPLACE);
}
// 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 ((xml.writeTrack() || track() != xml.curTrack())
&& (track() != -1) && !isBeam()) {
// Writing track number for beams is redundant as it is calculated
// during layout.
int t = track() + xml.trackDiff();
xml.tag("track", t);
}
if (xml.writePosition()) {
xml.tag(Pid::POSITION, rtick());
}
if (_tag != 0x1) {
for (int i = 1; i < MAX_TAGS; i++) {
if (_tag == ((unsigned)1 << i)) {
xml.tag("tag", score()->layerTags()[i]);
break;
}
}
}
for (Pid pid : { Pid::OFFSET, 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 (readProperty(tag, e, Pid::MIN_DISTANCE)) {
} else if (readProperty(tag, e, Pid::AUTOPLACE)) {
} 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.setTick(Fraction::fromTicks(score()->fileDivision(val))); // obsolete
}
} 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(this);
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()->ticks());
}
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;
}
//---------------------------------------------------------
// readMimeData
//---------------------------------------------------------
Element* Element::readMimeData(Score* score, const QByteArray& data, QPointF* dragOffset, Fraction* duration)
{
XmlReader e(data);
const ElementType type = Element::readType(e, dragOffset, duration);
e.setPasteMode(true);
if (type == ElementType::INVALID) {
qDebug("cannot read type");
return nullptr;
}
Element* el = Element::create(type, score);
if (el) {
el->read(e);
}
return el;
}
//---------------------------------------------------------
// 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::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::MMREST: return new MMRest(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::STICKING: return new Sticking(score);
case ElementType::LYRICSLINE:
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)
{
if (e1->z() == e2->z()) {
if (e1->selected()) {
return false;
} else if (e2->selected()) {
return true;
} else if (!e1->visible()) {
return true;
} else if (!e2->visible()) {
return false;
} else {
return e1->track() > e2->track();
}
}
return e1->z() <= e2->z();
}
//---------------------------------------------------------
// collectElements
//---------------------------------------------------------
void collectElements(void* data, Element* e)
{
QList<Element*>* el = static_cast<QList<Element*>*>(data);
el->append(e);
}
//---------------------------------------------------------
// autoplace
//---------------------------------------------------------
bool Element::autoplace() const
{
if (!score() || !score()->styleB(Sid::autoplaceEnabled)) {
return false;
}
return !flag(ElementFlag::NO_AUTOPLACE);
}
//---------------------------------------------------------
// getProperty
//---------------------------------------------------------
QVariant Element::getProperty(Pid propertyId) const
{
switch (propertyId) {
case Pid::TICK:
return tick();
case Pid::TRACK:
return track();
case Pid::VOICE:
return voice();
case Pid::POSITION:
return rtick();
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::MIN_DISTANCE:
return _minDistance;
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:
if (parent()) {
return parent()->getProperty(propertyId);
}
return QVariant();
}
}
//---------------------------------------------------------
// setProperty
//---------------------------------------------------------
bool Element::setProperty(Pid propertyId, const QVariant& v)
{
switch (propertyId) {
case Pid::TRACK:
setTrack(v.toInt());
break;
case Pid::VOICE:
setVoice(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::MIN_DISTANCE:
setMinDistance(v.value<Spatium>());
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:
if (parent()) {
return parent()->setProperty(propertyId, v);
}
qDebug("%s unknown <%s>(%d), data <%s>", name(), propertyName(propertyId), int(propertyId),
qPrintable(v.toString()));
return false;
}
triggerLayout();
return true;
}
//---------------------------------------------------------
// undoChangeProperty
//---------------------------------------------------------
void Element::undoChangeProperty(Pid pid, const QVariant& val, PropertyFlags ps)
{
if (pid == Pid::AUTOPLACE && (val.toBool() == true && !autoplace())) {
// Switching autoplacement on. Save user-defined
// placement properties to undo stack.
undoPushProperty(Pid::PLACEMENT);
undoPushProperty(Pid::OFFSET);
}
ScoreElement::undoChangeProperty(pid, val, ps);
}
//---------------------------------------------------------
// 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: {
QVariant v = ScoreElement::propertyDefault(pid);
if (v.isValid()) { // if it's a styled property
return v;
}
return int(Placement::BELOW);
}
case Pid::SELECTED:
return false;
case Pid::OFFSET: {
QVariant v = ScoreElement::propertyDefault(pid);
if (v.isValid()) { // if it's a styled property
return v;
}
return QPointF();
}
case Pid::MIN_DISTANCE: {
QVariant v = ScoreElement::propertyDefault(pid);
if (v.isValid()) {
return v;
}
return 0.0;
}
case Pid::AUTOPLACE:
return true;
case Pid::Z:
return int(type()) * 100;
default: {
QVariant v = ScoreElement::propertyDefault(pid);
if (v.isValid()) {
return v;
}
if (parent()) {
return parent()->propertyDefault(pid);
}
return QVariant();
}
}
}
//---------------------------------------------------------
// propertyId
//---------------------------------------------------------
Pid Element::propertyId(const QStringRef& name) const
{
if (name == "pos" || name == "offset") {
return Pid::OFFSET;
}
return ScoreElement::propertyId(name);
}
//---------------------------------------------------------
// propertyUserValue
//---------------------------------------------------------
QString Element::propertyUserValue(Pid pid) const
{
switch (pid) {
case Pid::SUBTYPE:
return subtypeName();
default:
return ScoreElement::propertyUserValue(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;
}
}
//---------------------------------------------------------
// findAncestor
//---------------------------------------------------------
Element* Element::findAncestor(ElementType t)
{
Element* e = this;
while (e && e->type() != t) {
e = e->parent();
}
return e;
}
const Element* Element::findAncestor(ElementType t) const
{
const Element* e = this;
while (e && e->type() != t) {
e = e->parent();
}
return e;
}
//---------------------------------------------------------
// findMeasure
//---------------------------------------------------------
Measure* Element::findMeasure()
{
if (isMeasure()) {
return toMeasure(this);
} else if (_parent) {
return _parent->findMeasure();
} else {
return 0;
}
}
//---------------------------------------------------------
// findMeasure
//---------------------------------------------------------
const Measure* 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:
case ElementType::MMREST:
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:
case ElementType::MMREST:
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;
}
}
}
for (Pid p : { Pid::VISIBLE, Pid::OFFSET, Pid::COLOR, Pid::Z, Pid::AUTOPLACE }) {
if (getProperty(p) != propertyDefault(p)) {
return true;
}
}
return false;
}
//---------------------------------------------------------
// triggerLayout
//---------------------------------------------------------
void Element::triggerLayout() const
{
if (parent()) {
score()->setLayout(tick(), staffIdx(), this);
}
}
//---------------------------------------------------------
// triggerLayoutAll
//---------------------------------------------------------
void Element::triggerLayoutAll() const
{
if (parent()) {
score()->setLayoutAll(staffIdx(), this);
}
}
//---------------------------------------------------------
// ~EditData
//---------------------------------------------------------
EditData::~EditData()
{
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);
eed->pushProperty(Pid::AUTOPLACE);
eed->initOffset = offset();
ed.addData(eed);
if (ed.modifiers & Qt::AltModifier) {
setAutoplace(false);
}
}
//---------------------------------------------------------
// drag
/// Return update Rect relative to canvas.
//---------------------------------------------------------
QRectF Element::drag(EditData& ed)
{
if (!isMovable()) {
return QRectF();
}
const QRectF r0(canvasBoundingRect());
const ElementEditData* eed = ed.getData(this);
const QPointF offset0 = ed.moveDelta + eed->initOffset;
qreal x = offset0.x();
qreal y = offset0.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));
setOffsetChanged(true);
// setGenerated(false);
if (isTextBase()) { // TODO: check for other types
//
// restrict move to page boundaries
//
const QRectF r(canvasBoundingRect());
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() | r0;
}
//---------------------------------------------------------
// endDrag
//---------------------------------------------------------
void Element::endDrag(EditData& ed)
{
if (!isMovable()) {
return;
}
ElementEditData* eed = ed.getData(this);
if (!eed) {
return;
}
for (PropertyData pd : eed->propertyData) {
setPropertyFlags(pd.id, pd.f); // reset initial property flags state
PropertyFlags f = pd.f;
if (f == PropertyFlags::STYLED) {
f = PropertyFlags::UNSTYLED;
}
score()->undoPropertyChanged(this, pd.id, pd.data, f);
setGenerated(false);
}
}
//---------------------------------------------------------
// genericDragAnchorLines
//---------------------------------------------------------
QVector<QLineF> Element::genericDragAnchorLines() const
{
qreal xp = 0.0;
for (Element* e = parent(); e; e = e->parent()) {
xp += e->x();
}
qreal yp;
if (parent()->isSegment()) {
System* system = toSegment(parent())->measure()->system();
const int stIdx = staffIdx();
yp = system->staffCanvasYpage(stIdx);
if (placement() == Placement::BELOW) {
yp += system->staff(stIdx)->bbox().height();
}
} else {
yp = parent()->canvasPos().y();
}
QPointF p1(xp, yp);
QLineF anchorLine(p1, canvasPos());
return { anchorLine };
}
//---------------------------------------------------------
// updateGrips
//---------------------------------------------------------
void Element::updateGrips(EditData& ed) const
{
const auto positions(gripsPositions(ed));
const size_t ngrips = positions.size();
for (int i = 0; i < int(ngrips); ++i) {
ed.grip[i].translate(positions[i]);
}
}
//---------------------------------------------------------
// 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);
if (!eed) {
eed = new ElementEditData();
eed->e = this;
ed.addData(eed);
}
eed->pushProperty(Pid::OFFSET);
eed->pushProperty(Pid::AUTOPLACE);
if (ed.modifiers & Qt::AltModifier) {
setAutoplace(false);
}
}
//---------------------------------------------------------
// editDrag
//---------------------------------------------------------
void Element::editDrag(EditData& ed)
{
score()->addRefresh(canvasBoundingRect());
setOffset(offset() + ed.delta);
setOffsetChanged(true);
score()->addRefresh(canvasBoundingRect());
}
//---------------------------------------------------------
// endEditDrag
//---------------------------------------------------------
void Element::endEditDrag(EditData& ed)
{
ElementEditData* eed = ed.getData(this);
bool changed = false;
if (eed) {
for (PropertyData pd : eed->propertyData) {
setPropertyFlags(pd.id, pd.f); // reset initial property flags state
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
//---------------------------------------------------------
// setOffsetChanged
//---------------------------------------------------------
void Element::setOffsetChanged(bool v, bool absolute, const QPointF& diff)
{
if (v) {
_offsetChanged = absolute ? OffsetChange::ABSOLUTE_OFFSET : OffsetChange::RELATIVE_OFFSET;
} else {
_offsetChanged = OffsetChange::NONE;
}
_changedPos = pos() + diff;
}
//---------------------------------------------------------
// rebaseOffset
// calculates new offset for moved elements
// for drag & other actions that result in absolute position, apply the new offset
// for nudge & other actions that result in relative adjustment, return the vertical difference
//---------------------------------------------------------
qreal Element::rebaseOffset(bool nox)
{
QPointF off = offset();
QPointF p = _changedPos - pos();
if (nox) {
p.rx() = 0.0;
}
//OffsetChange saveChangedValue = _offsetChanged;
bool staffRelative = staff() && parent() && !(parent()->isNote() || parent()->isRest());
if (staffRelative && propertyFlags(Pid::PLACEMENT) != PropertyFlags::NOSTYLE) {
// check if flipped
// TODO: elements that support PLACEMENT but not as a styled property (add supportsPlacement() method?)
// TODO: refactor to take advantage of existing cmdFlip() algorithms
// TODO: adjustPlacement() (from read206.cpp) on read for 3.0 as well
QRectF r = bbox().translated(_changedPos);
qreal staffHeight = staff()->height();
Element* e = isSpannerSegment() ? toSpannerSegment(this)->spanner() : this;
bool multi = e->isSpanner() && toSpanner(e)->spannerSegments().size() > 1;
bool above = e->placeAbove();
bool flipped = above ? r.top() > staffHeight : r.bottom() < 0.0;
if (flipped && !multi) {
off.ry() += above ? -staffHeight : staffHeight;
undoChangeProperty(Pid::OFFSET, off + p);
_offsetChanged = OffsetChange::ABSOLUTE_OFFSET; //saveChangedValue;
rypos() += above ? staffHeight : -staffHeight;
PropertyFlags pf = e->propertyFlags(Pid::PLACEMENT);
if (pf == PropertyFlags::STYLED) {
pf = PropertyFlags::UNSTYLED;
}
Placement place = above ? Placement::BELOW : Placement::ABOVE;
e->undoChangeProperty(Pid::PLACEMENT, int(place), pf);
undoResetProperty(Pid::MIN_DISTANCE);
// TODO
//MuseScoreCore::mscoreCore->updateInspector();
return 0.0;
}
}
if (offsetChanged() == OffsetChange::ABSOLUTE_OFFSET) {
undoChangeProperty(Pid::OFFSET, off + p);
_offsetChanged = OffsetChange::ABSOLUTE_OFFSET; //saveChangedValue;
// allow autoplace to manage min distance even when not needed
undoResetProperty(Pid::MIN_DISTANCE);
return 0.0;
}
// allow autoplace to manage min distance even when not needed
undoResetProperty(Pid::MIN_DISTANCE);
return p.y();
}
//---------------------------------------------------------
// rebaseMinDistance
// calculates new minDistance for moved elements
// if necessary, also rebases offset
// updates md, yd
// returns true if shape needs to be rebased
//---------------------------------------------------------
bool Element::rebaseMinDistance(qreal& md, qreal& yd, qreal sp, qreal rebase, bool above, bool fix)
{
bool rc = false;
PropertyFlags pf = propertyFlags(Pid::MIN_DISTANCE);
if (pf == PropertyFlags::STYLED) {
pf = PropertyFlags::UNSTYLED;
}
qreal adjustedY = pos().y() + yd;
qreal diff = _changedPos.y() - adjustedY;
if (fix) {
undoChangeProperty(Pid::MIN_DISTANCE, -999.0, pf);
yd = 0.0;
} else if (!isStyled(Pid::MIN_DISTANCE)) {
md = (above ? md + yd : md - yd) / sp;
undoChangeProperty(Pid::MIN_DISTANCE, md, pf);
yd += diff;
} else {
// min distance still styled
// user apparently moved element into skyline
// but perhaps not really, if performing a relative adjustment
if (_offsetChanged == OffsetChange::RELATIVE_OFFSET) {
// relative movement (cursor): fix only if moving vertically into direction of skyline
if ((above && diff > 0.0) || (!above && diff < 0.0)) {
// rebase offset
QPointF p = offset();
p.ry() += rebase;
undoChangeProperty(Pid::OFFSET, p);
md = (above ? md - diff : md + diff) / sp;
undoChangeProperty(Pid::MIN_DISTANCE, md, pf);
rc = true;
yd = 0.0;
}
} else {
// absolute movement (drag): fix unconditionally
md = (above ? md + yd : md - yd) / sp;
undoChangeProperty(Pid::MIN_DISTANCE, md, pf);
yd = 0.0;
}
}
// TODO
//MuseScoreCore::mscoreCore->updateInspector();
return rc;
}
//---------------------------------------------------------
// autoplaceSegmentElement
//---------------------------------------------------------
void Element::autoplaceSegmentElement(bool above, bool add)
{
// rebase vertical offset on drag
qreal rebase = 0.0;
if (offsetChanged() != OffsetChange::NONE) {
rebase = rebaseOffset();
}
if (autoplace() && parent()) {
Segment* s = toSegment(parent());
Measure* m = s->measure();
qreal sp = score()->spatium();
int si = staffIdx();
if (systemFlag()) {
const int firstVis = m->system()->firstVisibleStaff();
if (firstVis < score()->nstaves()) {
si = firstVis;
}
} else {
qreal mag = staff()->staffMag(this);
sp *= mag;
}
qreal minDistance = _minDistance.val() * sp;
SysStaff* ss = m->system()->staff(si);
QRectF r = bbox().translated(m->pos() + s->pos() + pos());
SkylineLine sk(!above);
qreal d;
if (above) {
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 (above) {
yd *= -1.0;
}
if (offsetChanged() != OffsetChange::NONE) {
// user moved element within the skyline
// we may need to adjust minDistance, yd, and/or offset
bool inStaff = above ? r.bottom() + rebase > 0.0 : r.top() + rebase < staff()->height();
if (rebaseMinDistance(minDistance, yd, sp, rebase, above, inStaff)) {
r.translate(0.0, rebase);
}
}
rypos() += yd;
r.translate(QPointF(0.0, yd));
}
if (add && addToSkyline()) {
ss->skyline().add(r);
}
}
setOffsetChanged(false);
}
//---------------------------------------------------------
// autoplaceMeasureElement
//---------------------------------------------------------
void Element::autoplaceMeasureElement(bool above, bool add)
{
// rebase vertical offset on drag
qreal rebase = 0.0;
if (offsetChanged() != OffsetChange::NONE) {
rebase = rebaseOffset();
}
if (autoplace() && parent()) {
Measure* m = toMeasure(parent());
int si = staffIdx();
qreal sp = score()->spatium();
qreal minDistance = _minDistance.val() * sp;
SysStaff* ss = m->system()->staff(si);
// shape rather than bbox is good for tuplets especially
Shape sh = shape().translated(m->pos() + pos());
SkylineLine sk(!above);
qreal d;
if (above) {
sk.add(sh);
d = sk.minDistance(ss->skyline().north());
} else {
sk.add(sh);
d = ss->skyline().south().minDistance(sk);
}
if (d > -minDistance) {
qreal yd = d + minDistance;
if (above) {
yd *= -1.0;
}
if (offsetChanged() != OffsetChange::NONE) {
// user moved element within the skyline
// we may need to adjust minDistance, yd, and/or offset
bool inStaff = above ? sh.bottom() + rebase > 0.0 : sh.top() + rebase < staff()->height();
if (rebaseMinDistance(minDistance, yd, sp, rebase, above, inStaff)) {
sh.translateY(rebase);
}
}
rypos() += yd;
sh.translateY(yd);
}
if (add && addToSkyline()) {
ss->skyline().add(sh);
}
}
setOffsetChanged(false);
}
}