2750 lines
78 KiB
C++
2750 lines
78 KiB
C++
/*
|
||
* SPDX-License-Identifier: GPL-3.0-only
|
||
* MuseScore-CLA-applies
|
||
*
|
||
* MuseScore
|
||
* Music Composition & Notation
|
||
*
|
||
* Copyright (C) 2021 MuseScore BVBA and others
|
||
*
|
||
* This program is free software: you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License version 3 as
|
||
* published by the Free Software Foundation.
|
||
*
|
||
* This program is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License
|
||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
/**
|
||
\file
|
||
Implementation of EngravingItem, ElementList
|
||
*/
|
||
|
||
#include "engravingitem.h"
|
||
|
||
#include <cmath>
|
||
#include <QBuffer>
|
||
|
||
#include "containers.h"
|
||
|
||
#include "draw/pen.h"
|
||
#include "style/style.h"
|
||
#include "rw/xml.h"
|
||
#include "rw/writecontext.h"
|
||
|
||
#include "accessibility/accessibleitem.h"
|
||
#include "accessibility/accessibleroot.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 "actionicon.h"
|
||
#include "image.h"
|
||
#include "instrumentname.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 "ottava.h"
|
||
#include "page.h"
|
||
#include "pedal.h"
|
||
#include "rehearsalmark.h"
|
||
#include "measurerepeat.h"
|
||
#include "rest.h"
|
||
#include "score.h"
|
||
#include "scorefont.h"
|
||
#include "segment.h"
|
||
#include "slide.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 "symbol.h"
|
||
#include "system.h"
|
||
#include "tempotext.h"
|
||
#include "textframe.h"
|
||
#include "text.h"
|
||
#include "measurenumber.h"
|
||
#include "mmrestrange.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 "systemdivider.h"
|
||
#include "stafftypechange.h"
|
||
#include "stafflines.h"
|
||
#include "letring.h"
|
||
#include "vibrato.h"
|
||
#include "palmmute.h"
|
||
#include "fermata.h"
|
||
#include "shape.h"
|
||
#include "factory.h"
|
||
#include "linkedobjects.h"
|
||
|
||
#include "masterscore.h"
|
||
|
||
//#include "musescoreCore.h"
|
||
|
||
#include "config.h"
|
||
|
||
#include "log.h"
|
||
#define LOG_PROP() if (0) LOGD()
|
||
|
||
using namespace mu;
|
||
using namespace mu::engraving;
|
||
|
||
namespace Ms {
|
||
// extern bool showInvisible;
|
||
|
||
EngravingItem* EngravingItemList::at(size_t i) const
|
||
{
|
||
return *std::next(begin(), i);
|
||
}
|
||
|
||
EngravingItem::EngravingItem(const ElementType& type, EngravingObject* se, ElementFlags f)
|
||
: EngravingObject(type, se)
|
||
{
|
||
_flags = f;
|
||
_color = engravingConfiguration()->defaultColor();
|
||
_mag = 1.0;
|
||
_tag = 1;
|
||
_z = -1;
|
||
_offsetChanged = OffsetChange::NONE;
|
||
_minDistance = Spatium(0.0);
|
||
}
|
||
|
||
EngravingItem::EngravingItem(const EngravingItem& e)
|
||
: EngravingObject(e)
|
||
{
|
||
_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;
|
||
|
||
//! TODO Please don't remove (igor.korsukov@gmail.com)
|
||
//m_accessible = e.m_accessible->clone(this);
|
||
}
|
||
|
||
EngravingItem::~EngravingItem()
|
||
{
|
||
delete m_accessible;
|
||
Score::onElementDestruction(this);
|
||
}
|
||
|
||
void EngravingItem::setupAccessible()
|
||
{
|
||
if (m_accessible) {
|
||
return;
|
||
}
|
||
|
||
static std::list<ElementType> accessibleDisabled = {
|
||
ElementType::LEDGER_LINE
|
||
};
|
||
|
||
if (score() && !score()->isPaletteScore()) {
|
||
if (std::find(accessibleDisabled.begin(), accessibleDisabled.end(), type()) == accessibleDisabled.end()) {
|
||
m_accessible = createAccessible();
|
||
m_accessible->setup();
|
||
}
|
||
}
|
||
}
|
||
|
||
bool EngravingItem::accessibleEnabled() const
|
||
{
|
||
return m_accessibleEnabled;
|
||
}
|
||
|
||
void EngravingItem::setAccessibleEnabled(bool enabled)
|
||
{
|
||
m_accessibleEnabled = enabled;
|
||
}
|
||
|
||
EngravingItem* EngravingItem::parentItem() const
|
||
{
|
||
EngravingObject* p = explicitParent();
|
||
if (p && p->isEngravingItem()) {
|
||
return static_cast<EngravingItem*>(p);
|
||
}
|
||
|
||
return nullptr;
|
||
}
|
||
|
||
EngravingItemList EngravingItem::childrenItems() const
|
||
{
|
||
EngravingItemList list;
|
||
for (EngravingObject* ch : children()) {
|
||
if (ch->isEngravingItem()) {
|
||
list.push_back(static_cast<EngravingItem*>(ch));
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
|
||
mu::engraving::AccessibleItem* EngravingItem::createAccessible()
|
||
{
|
||
return new mu::engraving::AccessibleItem(this);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// spatiumChanged
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::spatiumChanged(qreal oldValue, qreal newValue)
|
||
{
|
||
if (offsetIsSpatiumDependent()) {
|
||
_offset *= (newValue / oldValue);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// localSpatiumChanged
|
||
// the scale of a staff changed
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::localSpatiumChanged(qreal oldValue, qreal newValue)
|
||
{
|
||
if (offsetIsSpatiumDependent()) {
|
||
_offset *= (newValue / oldValue);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// spatium
|
||
//---------------------------------------------------------
|
||
|
||
qreal EngravingItem::spatium() const
|
||
{
|
||
if (systemFlag() || (explicitParent() && parentItem()->systemFlag())) {
|
||
return score()->spatium();
|
||
}
|
||
Staff* s = staff();
|
||
return s ? s->spatium(this) : score()->spatium();
|
||
}
|
||
|
||
bool EngravingItem::isInteractionAvailable() const
|
||
{
|
||
if (!visible() && (score()->printing() || !score()->showInvisible())) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// offsetIsSpatiumDependent
|
||
//---------------------------------------------------------
|
||
|
||
bool EngravingItem::offsetIsSpatiumDependent() const
|
||
{
|
||
return sizeIsSpatiumDependent() || (_flags & ElementFlag::ON_STAFF);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// magS
|
||
//---------------------------------------------------------
|
||
|
||
qreal EngravingItem::magS() const
|
||
{
|
||
return mag() * (score()->spatium() / SPATIUM20);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// name
|
||
//---------------------------------------------------------
|
||
|
||
QString EngravingItem::subtypeName() const
|
||
{
|
||
return "";
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// linkedClone
|
||
//---------------------------------------------------------
|
||
|
||
EngravingItem* EngravingItem::linkedClone()
|
||
{
|
||
EngravingItem* e = clone();
|
||
e->setAutoplace(true);
|
||
score()->undo(new Link(e, this));
|
||
return e;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// deleteLater
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::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 EngravingItem::scanElements(void* data, void (* func)(void*, EngravingItem*), bool all)
|
||
{
|
||
if (scanChildren().size() == 0) {
|
||
if (all || visible() || score()->showInvisible()) {
|
||
func(data, this);
|
||
}
|
||
} else {
|
||
EngravingObject::scanElements(data, func, all);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// reset
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::reset()
|
||
{
|
||
undoResetProperty(Pid::AUTOPLACE);
|
||
undoResetProperty(Pid::PLACEMENT);
|
||
undoResetProperty(Pid::MIN_DISTANCE);
|
||
undoResetProperty(Pid::OFFSET);
|
||
setOffsetChanged(false);
|
||
EngravingObject::reset();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// change
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::change(EngravingItem* o, EngravingItem* n)
|
||
{
|
||
remove(o);
|
||
add(n);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// staff
|
||
//---------------------------------------------------------
|
||
|
||
Staff* EngravingItem::staff() const
|
||
{
|
||
if (!hasStaff() || score()->staves().empty()) {
|
||
return nullptr;
|
||
}
|
||
|
||
return score()->staff(staffIdx());
|
||
}
|
||
|
||
bool EngravingItem::hasStaff() const
|
||
{
|
||
return _track != mu::nidx;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// staffType
|
||
//---------------------------------------------------------
|
||
|
||
const StaffType* EngravingItem::staffType() const
|
||
{
|
||
Staff* s = staff();
|
||
return s ? s->staffTypeForElement(this) : nullptr;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// onTabStaff
|
||
//---------------------------------------------------------
|
||
|
||
bool EngravingItem::onTabStaff() const
|
||
{
|
||
const StaffType* stt = staffType();
|
||
return stt ? stt->isTabStaff() : false;
|
||
}
|
||
|
||
bool EngravingItem::hasGrips() const
|
||
{
|
||
return gripsCount() > 0;
|
||
}
|
||
|
||
track_idx_t EngravingItem::track() const
|
||
{
|
||
return _track;
|
||
}
|
||
|
||
void EngravingItem::setTrack(track_idx_t val)
|
||
{
|
||
_track = val;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// z
|
||
//---------------------------------------------------------
|
||
|
||
int EngravingItem::z() const
|
||
{
|
||
if (_z == -1) {
|
||
_z = int(type()) * 100;
|
||
}
|
||
return _z;
|
||
}
|
||
|
||
void EngravingItem::setZ(int val)
|
||
{
|
||
_z = val;
|
||
}
|
||
|
||
staff_idx_t EngravingItem::staffIdx() const
|
||
{
|
||
return track2staff(_track);
|
||
}
|
||
|
||
void EngravingItem::setStaffIdx(staff_idx_t val)
|
||
{
|
||
voice_idx_t voiceIdx = voice();
|
||
_track = staff2track(val, voiceIdx == mu::nidx ? 0 : voiceIdx);
|
||
}
|
||
|
||
staff_idx_t EngravingItem::staffIdxOrNextVisible() const
|
||
{
|
||
// for system objects, sometimes the staff they're on is hidden so we have to find the next
|
||
// best staff for them
|
||
if (!staff()) {
|
||
return mu::nidx;
|
||
}
|
||
|
||
staff_idx_t si = staff()->idx();
|
||
if (!systemFlag()) {
|
||
return si;
|
||
}
|
||
Measure* m = nullptr;
|
||
if (parent() && parent()->isSegment()) {
|
||
Segment* s = parent() ? toSegment(parent()) : nullptr;
|
||
m = s ? s->measure() : nullptr;
|
||
} else if (parent() && parent()->isMeasure()) {
|
||
m = parent() ? toMeasure(parent()) : nullptr;
|
||
}
|
||
if (!m || !m->system() || !m->system()->staff(si)) {
|
||
return si;
|
||
}
|
||
staff_idx_t firstVis = m->system()->firstVisibleStaff();
|
||
if (isTopSystemObject()) {
|
||
// original, put on the top of the score
|
||
return firstVis;
|
||
}
|
||
if (si <= firstVis) {
|
||
// we already know this staff will be replaced by the original
|
||
return mu::nidx;
|
||
}
|
||
bool foundStaff = false;
|
||
if (!m->system()->staff(si)->show()) {
|
||
std::vector<Staff*> soStaves = score()->getSystemObjectStaves();
|
||
for (staff_idx_t i = 0; i < soStaves.size(); ++i) {
|
||
staff_idx_t idxOrig = soStaves[i]->idx();
|
||
if (idxOrig == si) {
|
||
// this is the staff we are supposed to be on
|
||
for (staff_idx_t idxNew = si + 1; idxNew < score()->staves().size(); ++idxNew) {
|
||
if (i + 1 < soStaves.size() && idxNew >= score()->staffIdx(soStaves[i + 1]->part())) {
|
||
// This is the flag to not show this element
|
||
si = mu::nidx;
|
||
break;
|
||
} else if (m->system()->staff(idxNew)->show()) {
|
||
// Move current element to this staff and finish
|
||
foundStaff = true;
|
||
si = idxNew;
|
||
break;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
// the staff this object should be on is visible, so npnp
|
||
foundStaff = true;
|
||
}
|
||
return foundStaff ? si : mu::nidx;
|
||
}
|
||
|
||
bool EngravingItem::isTopSystemObject() const
|
||
{
|
||
if (!systemFlag()) {
|
||
return false; // non system object
|
||
}
|
||
if (!_links) {
|
||
return true; // a system object, but not one with any linked clones
|
||
}
|
||
// this is part of a link ecosystem, see if we're the main one
|
||
EngravingObject* mainElement = _links->mainElement();
|
||
return track() == 0
|
||
&& (mainElement->score() != score() || !toEngravingItem(mainElement)->enabled());
|
||
}
|
||
|
||
staff_idx_t EngravingItem::vStaffIdx() const
|
||
{
|
||
return staffIdx();
|
||
}
|
||
|
||
voice_idx_t EngravingItem::voice() const
|
||
{
|
||
return track2voice(_track);
|
||
}
|
||
|
||
void EngravingItem::setVoice(voice_idx_t v)
|
||
{
|
||
_track = (_track / VOICES) * VOICES + v;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// tick
|
||
//---------------------------------------------------------
|
||
|
||
Fraction EngravingItem::tick() const
|
||
{
|
||
const EngravingItem* e = this;
|
||
while (e->explicitParent()) {
|
||
if (e->explicitParent()->isSegment()) {
|
||
return toSegment(e->explicitParent())->tick();
|
||
} else if (e->explicitParent()->isMeasureBase()) {
|
||
return toMeasureBase(e->explicitParent())->tick();
|
||
}
|
||
e = e->parentItem();
|
||
}
|
||
return Fraction(0, 1);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// rtick
|
||
//---------------------------------------------------------
|
||
|
||
Fraction EngravingItem::rtick() const
|
||
{
|
||
const EngravingItem* e = this;
|
||
while (e->explicitParent()) {
|
||
if (e->explicitParent()->isSegment()) {
|
||
return toSegment(e->explicitParent())->rtick();
|
||
}
|
||
e = e->parentItem();
|
||
}
|
||
return Fraction(0, 1);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// playTick
|
||
//---------------------------------------------------------
|
||
|
||
Fraction EngravingItem::playTick() const
|
||
{
|
||
// Play from the element's tick position by default.
|
||
return tick();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// beat
|
||
//---------------------------------------------------------
|
||
|
||
Fraction EngravingItem::beat() const
|
||
{
|
||
// Returns an appropriate fraction of ticks for use as a "Beat" reference
|
||
// in the Select All Similar filter.
|
||
int bar, beat, ticks;
|
||
TimeSigMap* tsm = score()->sigmap();
|
||
tsm->tickValues(tick().ticks(), &bar, &beat, &ticks);
|
||
int ticksB = ticks_beat(tsm->timesig(tick().ticks()).timesig().denominator());
|
||
|
||
Fraction complexFraction((++beat * ticksB) + ticks, ticksB);
|
||
return complexFraction.reduced();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// part
|
||
//---------------------------------------------------------
|
||
|
||
Part* EngravingItem::part() const
|
||
{
|
||
Staff* s = staff();
|
||
return s ? s->part() : 0;
|
||
}
|
||
|
||
draw::Color EngravingItem::color() const
|
||
{
|
||
return _color;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// curColor
|
||
//---------------------------------------------------------
|
||
|
||
mu::draw::Color EngravingItem::curColor() const
|
||
{
|
||
return curColor(visible());
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// curColor
|
||
//---------------------------------------------------------
|
||
|
||
mu::draw::Color EngravingItem::curColor(bool isVisible) const
|
||
{
|
||
return curColor(isVisible, color());
|
||
}
|
||
|
||
mu::draw::Color EngravingItem::curColor(bool isVisible, Color normalColor) const
|
||
{
|
||
// the default element color is always interpreted as black in printing
|
||
if (score() && score()->printing()) {
|
||
return (normalColor == engravingConfiguration()->defaultColor()) ? Color::black : normalColor;
|
||
}
|
||
|
||
if (flag(ElementFlag::DROP_TARGET)) {
|
||
return engravingConfiguration()->highlightSelectionColor(track() == mu::nidx ? 0 : voice());
|
||
}
|
||
|
||
bool marked = false;
|
||
if (isNote()) {
|
||
marked = toNote(this)->mark();
|
||
}
|
||
|
||
if (selected() || marked) {
|
||
Color originalColor = engravingConfiguration()->selectionColor(track() == mu::nidx ? 0 : voice());
|
||
|
||
if (isVisible) {
|
||
return originalColor;
|
||
}
|
||
|
||
constexpr float tint = .6f; // Between 0 and 1. Higher means lighter, lower means darker
|
||
|
||
int red = originalColor.red();
|
||
int green = originalColor.green();
|
||
int blue = originalColor.blue();
|
||
|
||
return Color(red + tint * (255 - red), green + tint * (255 - green), blue + tint * (255 - blue));
|
||
}
|
||
|
||
if (!isVisible) {
|
||
return engravingConfiguration()->invisibleColor();
|
||
}
|
||
|
||
if (m_colorsInversionEnabled && engravingConfiguration()->scoreInversionEnabled()) {
|
||
return engravingConfiguration()->scoreInversionColor();
|
||
}
|
||
|
||
return normalColor;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// pagePos
|
||
// return position in canvas coordinates
|
||
//---------------------------------------------------------
|
||
|
||
PointF EngravingItem::pagePos() const
|
||
{
|
||
PointF p(pos());
|
||
if (explicitParent() == nullptr) {
|
||
return p;
|
||
}
|
||
|
||
staff_idx_t idx = mu::nidx;
|
||
if (systemFlag()) {
|
||
idx = staffIdxOrNextVisible();
|
||
}
|
||
|
||
if (idx == mu::nidx) {
|
||
idx = vStaffIdx();
|
||
}
|
||
|
||
if (_flags & ElementFlag::ON_STAFF) {
|
||
System* system = nullptr;
|
||
Measure* measure = nullptr;
|
||
if (explicitParent()->isSegment()) {
|
||
measure = toSegment(explicitParent())->measure();
|
||
} else if (explicitParent()->isMeasure()) { // used in measure number
|
||
measure = toMeasure(explicitParent());
|
||
} else if (explicitParent()->isSystem()) {
|
||
system = toSystem(explicitParent());
|
||
} else if (explicitParent()->isFretDiagram()) {
|
||
return p + parentItem()->pagePos();
|
||
} else {
|
||
ASSERT_X(QString::asprintf("this %s parent %s\n", typeName(), explicitParent()->typeName()));
|
||
}
|
||
if (measure) {
|
||
system = measure->system();
|
||
p.ry() += measure->staffLines(idx)->y();
|
||
}
|
||
if (system) {
|
||
if (system->staves().size() <= idx) {
|
||
LOGD("staffIdx out of bounds: %s", typeName());
|
||
}
|
||
p.ry() += system->staffYpage(idx);
|
||
}
|
||
p.rx() = pageX();
|
||
} else {
|
||
if (explicitParent()->explicitParent()) {
|
||
p += parentItem()->pagePos();
|
||
}
|
||
}
|
||
return p;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// canvasPos
|
||
//---------------------------------------------------------
|
||
|
||
PointF EngravingItem::canvasPos() const
|
||
{
|
||
PointF p(pos());
|
||
if (explicitParent() == nullptr) {
|
||
return p;
|
||
}
|
||
|
||
staff_idx_t idx = mu::nidx;
|
||
|
||
if (systemFlag()) {
|
||
idx = staffIdxOrNextVisible();
|
||
}
|
||
|
||
if (idx == mu::nidx) {
|
||
idx = vStaffIdx();
|
||
}
|
||
|
||
if (_flags & ElementFlag::ON_STAFF) {
|
||
System* system = nullptr;
|
||
Measure* measure = nullptr;
|
||
if (explicitParent()->isSegment()) {
|
||
measure = toSegment(explicitParent())->measure();
|
||
} else if (explicitParent()->isMeasure()) { // used in measure number
|
||
measure = toMeasure(explicitParent());
|
||
}
|
||
// system = toMeasure(parent())->system();
|
||
else if (explicitParent()->isSystem()) {
|
||
system = toSystem(explicitParent());
|
||
} else if (explicitParent()->isChord()) { // grace chord
|
||
measure = toSegment(explicitParent()->explicitParent())->measure();
|
||
} else if (explicitParent()->isFretDiagram()) {
|
||
return p + parentItem()->canvasPos() + PointF(toFretDiagram(explicitParent())->centerX(), 0.0);
|
||
} else {
|
||
ASSERT_X(QString::asprintf("this %s parent %s\n", typeName(), explicitParent()->typeName()));
|
||
}
|
||
if (measure) {
|
||
const StaffLines* lines = measure->staffLines(idx);
|
||
p.ry() += lines ? lines->y() : 0;
|
||
|
||
system = measure->system();
|
||
|
||
if (system) {
|
||
Page* page = system->page();
|
||
if (page) {
|
||
p.ry() += page->y();
|
||
}
|
||
}
|
||
}
|
||
if (system) {
|
||
p.ry() += system->staffYpage(idx);
|
||
}
|
||
p.rx() = canvasX();
|
||
} else {
|
||
p += parentItem()->canvasPos();
|
||
}
|
||
return p;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// pageX
|
||
//---------------------------------------------------------
|
||
|
||
qreal EngravingItem::pageX() const
|
||
{
|
||
qreal xp = x();
|
||
for (EngravingItem* e = parentItem(); e && e->parentItem(); e = e->parentItem()) {
|
||
xp += e->x();
|
||
}
|
||
return xp;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// canvasX
|
||
//---------------------------------------------------------
|
||
|
||
qreal EngravingItem::canvasX() const
|
||
{
|
||
qreal xp = x();
|
||
for (EngravingItem* e = parentItem(); e; e = e->parentItem()) {
|
||
xp += e->x();
|
||
}
|
||
return xp;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// contains
|
||
// Return true if p is inside the shape of the object.
|
||
// Note: p is in page coordinates
|
||
//---------------------------------------------------------
|
||
|
||
bool EngravingItem::contains(const mu::PointF& 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 EngravingItem::intersects(const RectF& rr) const
|
||
{
|
||
return shape().intersects(rr.translated(-pagePos()));
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// writeProperties
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::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.context()->clipboardmode()) {
|
||
WriteContext* ctx = xml.context();
|
||
IF_ASSERT_FAILED(ctx) {
|
||
return;
|
||
}
|
||
|
||
if (MScore::debugMode) {
|
||
xml.tag("lid", _links->lid());
|
||
}
|
||
|
||
EngravingItem* me = static_cast<EngravingItem*>(_links->mainElement());
|
||
Q_ASSERT(type() == me->type());
|
||
Staff* s = staff();
|
||
if (!s) {
|
||
s = score()->staff(xml.context()->curTrack() / VOICES);
|
||
if (!s) {
|
||
LOGW("EngravingItem::writeProperties: linked element's staff not found (%s)", typeName());
|
||
}
|
||
}
|
||
Location loc = Location::positionForElement(this);
|
||
if (me == this) {
|
||
xml.tagE("linkedMain");
|
||
int index = ctx->assignLocalIndex(loc);
|
||
ctx->setLidLocalIndex(_links->lid(), index);
|
||
} else {
|
||
if (s->links()) {
|
||
Staff* linkedStaff = toStaff(s->links()->mainElement());
|
||
loc.setStaff(static_cast<int>(linkedStaff->idx()));
|
||
}
|
||
xml.startObject("linked");
|
||
if (!me->score()->isMaster()) {
|
||
if (me->score() == score()) {
|
||
xml.tag("score", "same");
|
||
} else {
|
||
LOGW(
|
||
"EngravingItem::writeProperties: linked elements belong to different scores but none of them is master score: (%s lid=%d)",
|
||
typeName(), _links->lid());
|
||
}
|
||
}
|
||
|
||
Location mainLoc = Location::positionForElement(me);
|
||
const int guessedLocalIndex = ctx->assignLocalIndex(mainLoc);
|
||
if (loc != mainLoc) {
|
||
mainLoc.toRelative(loc);
|
||
mainLoc.write(xml);
|
||
}
|
||
const int indexDiff = ctx->lidLocalIndex(_links->lid()) - guessedLocalIndex;
|
||
xml.tag("indexDiff", indexDiff, 0);
|
||
xml.endObject(); // </linked>
|
||
}
|
||
}
|
||
if ((xml.context()->writeTrack() || track() != xml.context()->curTrack())
|
||
&& (track() != mu::nidx) && !isBeam()) {
|
||
// Writing track number for beams is redundant as it is calculated
|
||
// during layout.
|
||
int t = static_cast<int>(track()) + xml.context()->trackDiff();
|
||
xml.tag("track", t);
|
||
}
|
||
if (xml.context()->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 EngravingItem::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.context()->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")) {
|
||
ReadContext* ctx = e.context();
|
||
IF_ASSERT_FAILED(ctx) {
|
||
return false;
|
||
}
|
||
|
||
Staff* s = staff();
|
||
if (!s) {
|
||
s = score()->staff(e.context()->track() / VOICES);
|
||
if (!s) {
|
||
LOGW("EngravingItem::readProperties: linked element's staff not found (%s)", typeName());
|
||
e.skipCurrentElement();
|
||
return true;
|
||
}
|
||
}
|
||
if (tag == "linkedMain") {
|
||
_links = new LinkedObjects(score());
|
||
_links->push_back(this);
|
||
|
||
ctx->addLink(s, _links, e.context()->location(true));
|
||
|
||
e.readNext();
|
||
} else {
|
||
Staff* ls = s->links() ? toStaff(s->links()->mainElement()) : nullptr;
|
||
bool linkedIsMaster = ls ? ls->score()->isMaster() : false;
|
||
Location loc = e.context()->location(true);
|
||
if (ls) {
|
||
loc.setStaff(static_cast<int>(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;
|
||
}
|
||
LinkedObjects* link = ctx->getLink(linkedIsMaster, mainLoc, localIndexDiff);
|
||
if (link) {
|
||
EngravingObject* linked = link->mainElement();
|
||
if (linked->type() == type()) {
|
||
linkTo(linked);
|
||
} else {
|
||
LOGW("EngravingItem::readProperties: linked elements have different types: %s, %s. Input file corrupted?",
|
||
typeName(), linked->typeName());
|
||
}
|
||
}
|
||
if (!_links) {
|
||
LOGW("EngravingItem::readProperties: could not link %s at staff %d", typeName(), mainLoc.staff() + 1);
|
||
}
|
||
}
|
||
} else if (tag == "lid") {
|
||
if (score()->mscVersion() >= 301) {
|
||
e.skipCurrentElement();
|
||
return true;
|
||
}
|
||
int id = e.readInt();
|
||
_links = mu::value(e.context()->linkIds(), id, nullptr);
|
||
if (!_links) {
|
||
if (!score()->isMaster()) { // DEBUG
|
||
LOGD("---link %d not found (%zu)", id, e.context()->linkIds().size());
|
||
}
|
||
_links = new LinkedObjects(score(), id);
|
||
e.context()->linkIds().insert({ id, _links });
|
||
}
|
||
#ifndef NDEBUG
|
||
else {
|
||
for (EngravingObject* eee : *_links) {
|
||
EngravingItem* ee = static_cast<EngravingItem*>(eee);
|
||
if (ee->type() != type()) {
|
||
ASSERT_X(QString::asprintf("link %s(%d) type mismatch %s linked to %s", ee->typeName(), id, ee->typeName(),
|
||
typeName()));
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
Q_ASSERT(!_links->contains(this));
|
||
_links->push_back(this);
|
||
} else if (tag == "tick") {
|
||
int val = e.readInt();
|
||
if (val >= 0) {
|
||
e.context()->setTick(Fraction::fromTicks(score()->fileDivision(val))); // obsolete
|
||
}
|
||
} else if (tag == "pos") { // obsolete
|
||
readProperty(e, Pid::OFFSET);
|
||
} else if (tag == "voice") {
|
||
setVoice(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 EngravingItem::write(XmlWriter& xml) const
|
||
{
|
||
xml.startObject(this);
|
||
writeProperties(xml);
|
||
xml.endObject();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// read
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::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(EngravingItem* el)
|
||
{
|
||
auto i = find(begin(), end(), el);
|
||
if (i == end()) {
|
||
return false;
|
||
}
|
||
erase(i);
|
||
return true;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// replace
|
||
//---------------------------------------------------------
|
||
|
||
void ElementList::replace(EngravingItem* o, EngravingItem* n)
|
||
{
|
||
auto i = find(begin(), end(), o);
|
||
if (i == end()) {
|
||
LOGD("ElementList::replace: element not found");
|
||
return;
|
||
}
|
||
*i = n;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// write
|
||
//---------------------------------------------------------
|
||
|
||
void ElementList::write(XmlWriter& xml) const
|
||
{
|
||
for (const EngravingItem* e : *this) {
|
||
e->write(xml);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// Compound
|
||
//---------------------------------------------------------
|
||
|
||
Compound::Compound(const ElementType& type, Score* s)
|
||
: EngravingItem(type, s)
|
||
{
|
||
}
|
||
|
||
Compound::Compound(const Compound& c)
|
||
: EngravingItem(c)
|
||
{
|
||
elements.clear();
|
||
for (EngravingItem* e : c.elements) {
|
||
elements.push_back(e->clone());
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// draw
|
||
//---------------------------------------------------------
|
||
|
||
void Compound::draw(mu::draw::Painter* painter) const
|
||
{
|
||
foreach (EngravingItem* e, elements) {
|
||
PointF 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(EngravingItem* e, qreal x, qreal y)
|
||
{
|
||
e->setPos(x, y);
|
||
e->setParent(this);
|
||
elements.push_back(e);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// layout
|
||
//---------------------------------------------------------
|
||
|
||
void Compound::layout()
|
||
{
|
||
setbbox(RectF());
|
||
for (auto i = elements.begin(); i != elements.end(); ++i) {
|
||
EngravingItem* e = *i;
|
||
e->layout();
|
||
addbbox(e->bbox().translated(e->pos()));
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// setSelected
|
||
//---------------------------------------------------------
|
||
|
||
void Compound::setSelected(bool f)
|
||
{
|
||
EngravingItem::setSelected(f);
|
||
for (auto i = elements.begin(); i != elements.end(); ++i) {
|
||
(*i)->setSelected(f);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// setVisible
|
||
//---------------------------------------------------------
|
||
|
||
void Compound::setVisible(bool f)
|
||
{
|
||
EngravingItem::setVisible(f);
|
||
for (auto i = elements.begin(); i != elements.end(); ++i) {
|
||
(*i)->setVisible(f);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// clear
|
||
//---------------------------------------------------------
|
||
|
||
void Compound::clear()
|
||
{
|
||
foreach (EngravingItem* e, elements) {
|
||
if (e->selected()) {
|
||
score()->deselect(e);
|
||
}
|
||
delete e;
|
||
}
|
||
elements.clear();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// dump
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::dump() const
|
||
{
|
||
LOGD("---EngravingItem: %s, pos(%4.2f,%4.2f)"
|
||
"\n bbox(%g,%g,%g,%g)"
|
||
"\n abox(%g,%g,%g,%g)"
|
||
"\n parent: %p",
|
||
typeName(), ipos().x(), ipos().y(),
|
||
_bbox.x(), _bbox.y(), _bbox.width(), _bbox.height(),
|
||
abbox().x(), abbox().y(), abbox().width(), abbox().height(),
|
||
explicitParent());
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// mimeData
|
||
//---------------------------------------------------------
|
||
|
||
QByteArray EngravingItem::mimeData(const PointF& dragOffset) const
|
||
{
|
||
QBuffer buffer;
|
||
buffer.open(QIODevice::WriteOnly);
|
||
XmlWriter xml(&buffer);
|
||
xml.context()->setClipboardmode(true);
|
||
xml.startObject("EngravingItem");
|
||
if (isNote()) {
|
||
xml.tag("duration", toNote(this)->chord()->ticks());
|
||
}
|
||
if (!dragOffset.isNull()) {
|
||
xml.tag("dragOffset", dragOffset);
|
||
}
|
||
write(xml);
|
||
xml.endObject();
|
||
buffer.close();
|
||
return buffer.buffer();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// readType
|
||
// return new position of QDomElement in e
|
||
//---------------------------------------------------------
|
||
|
||
ElementType EngravingItem::readType(XmlReader& e, PointF* dragOffset,
|
||
Fraction* duration)
|
||
{
|
||
while (e.readNextStartElement()) {
|
||
if (e.name() == "EngravingItem") {
|
||
while (e.readNextStartElement()) {
|
||
const QStringRef& tag = e.name();
|
||
if (tag == "dragOffset") {
|
||
*dragOffset = e.readPoint();
|
||
} else if (tag == "duration") {
|
||
*duration = e.readFraction();
|
||
} else {
|
||
ElementType type = Factory::name2type(tag);
|
||
if (type == ElementType::INVALID) {
|
||
break;
|
||
}
|
||
return type;
|
||
}
|
||
}
|
||
} else {
|
||
e.unknown();
|
||
}
|
||
}
|
||
return ElementType::INVALID;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// readMimeData
|
||
//---------------------------------------------------------
|
||
|
||
EngravingItem* EngravingItem::readMimeData(Score* score, const QByteArray& data, PointF* dragOffset, Fraction* duration)
|
||
{
|
||
XmlReader e(data);
|
||
const ElementType type = EngravingItem::readType(e, dragOffset, duration);
|
||
e.context()->setPasteMode(true);
|
||
|
||
if (type == ElementType::INVALID) {
|
||
LOGD("cannot read type");
|
||
return nullptr;
|
||
}
|
||
|
||
EngravingItem* el = Factory::createItem(type, score->dummy(), false);
|
||
if (el) {
|
||
el->read(e);
|
||
}
|
||
|
||
return el;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// add
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::add(EngravingItem* e)
|
||
{
|
||
LOGD("EngravingItem: cannot add %s to %s", e->typeName(), typeName());
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// remove
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::remove(EngravingItem* e)
|
||
{
|
||
ASSERT_X(QString::asprintf("EngravingItem: cannot remove %s from %s", e->typeName(), typeName()));
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// elementLessThan
|
||
//---------------------------------------------------------
|
||
|
||
bool elementLessThan(const EngravingItem* const e1, const EngravingItem* const e2)
|
||
{
|
||
if (e1->z() == e2->z()) {
|
||
if (e1->selected() && !e2->selected()) {
|
||
return false;
|
||
}
|
||
if (!e1->selected() && e2->selected()) {
|
||
return true;
|
||
}
|
||
if (e1->visible() && !e2->visible()) {
|
||
return false;
|
||
}
|
||
if (!e1->visible() && e2->visible()) {
|
||
return true;
|
||
}
|
||
|
||
return e1->track() < e2->track();
|
||
}
|
||
|
||
return e1->z() < e2->z();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// collectElements
|
||
//---------------------------------------------------------
|
||
|
||
void collectElements(void* data, EngravingItem* e)
|
||
{
|
||
std::vector<EngravingItem*>* el = static_cast<std::vector<EngravingItem*>*>(data);
|
||
el->push_back(e);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// autoplace
|
||
//---------------------------------------------------------
|
||
|
||
bool EngravingItem::autoplace() const
|
||
{
|
||
if (!score() || !score()->styleB(Sid::autoplaceEnabled)) {
|
||
return false;
|
||
}
|
||
return !flag(ElementFlag::NO_AUTOPLACE);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// getProperty
|
||
//---------------------------------------------------------
|
||
|
||
PropertyValue EngravingItem::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 PropertyValue::fromValue(color());
|
||
case Pid::VISIBLE:
|
||
return visible();
|
||
case Pid::SELECTED:
|
||
return selected();
|
||
case Pid::OFFSET:
|
||
return PropertyValue::fromValue(_offset);
|
||
case Pid::MIN_DISTANCE:
|
||
return _minDistance;
|
||
case Pid::PLACEMENT:
|
||
return 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 (explicitParent()) {
|
||
return explicitParent()->getProperty(propertyId);
|
||
}
|
||
|
||
return PropertyValue();
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// setProperty
|
||
//---------------------------------------------------------
|
||
|
||
bool EngravingItem::setProperty(Pid propertyId, const PropertyValue& v)
|
||
{
|
||
switch (propertyId) {
|
||
case Pid::TRACK:
|
||
setTrack(v.value<track_idx_t>());
|
||
break;
|
||
case Pid::VOICE:
|
||
setVoice(v.toInt());
|
||
break;
|
||
case Pid::GENERATED:
|
||
setGenerated(v.toBool());
|
||
break;
|
||
case Pid::COLOR:
|
||
setColor(v.value<mu::draw::Color>());
|
||
break;
|
||
case Pid::VISIBLE:
|
||
setVisible(v.toBool());
|
||
break;
|
||
case Pid::SELECTED:
|
||
setSelected(v.toBool());
|
||
break;
|
||
case Pid::OFFSET:
|
||
_offset = v.value<PointF>();
|
||
break;
|
||
case Pid::MIN_DISTANCE:
|
||
setMinDistance(v.value<Spatium>());
|
||
break;
|
||
case Pid::PLACEMENT:
|
||
setPlacement(v.value<PlacementV>());
|
||
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 (explicitParent()) {
|
||
return explicitParent()->setProperty(propertyId, v);
|
||
}
|
||
|
||
LOG_PROP() << typeName() << " unknown <" << propertyName(propertyId) << ">(" << int(propertyId) << "), data: " << v.toString();
|
||
return false;
|
||
}
|
||
triggerLayout();
|
||
return true;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// undoChangeProperty
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::undoChangeProperty(Pid pid, const PropertyValue& 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);
|
||
}
|
||
EngravingObject::undoChangeProperty(pid, val, ps);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// propertyDefault
|
||
//---------------------------------------------------------
|
||
|
||
PropertyValue EngravingItem::propertyDefault(Pid pid) const
|
||
{
|
||
switch (pid) {
|
||
case Pid::GENERATED:
|
||
return false;
|
||
case Pid::VISIBLE:
|
||
return true;
|
||
case Pid::COLOR:
|
||
return PropertyValue::fromValue(engravingConfiguration()->defaultColor());
|
||
case Pid::PLACEMENT: {
|
||
PropertyValue v = EngravingObject::propertyDefault(pid);
|
||
if (v.isValid()) { // if it's a styled property
|
||
return v;
|
||
}
|
||
return PlacementV::BELOW;
|
||
}
|
||
case Pid::SELECTED:
|
||
return false;
|
||
case Pid::OFFSET: {
|
||
PropertyValue v = EngravingObject::propertyDefault(pid);
|
||
if (v.isValid()) { // if it's a styled property
|
||
return v;
|
||
}
|
||
return PropertyValue::fromValue(PointF());
|
||
}
|
||
case Pid::MIN_DISTANCE: {
|
||
PropertyValue v = EngravingObject::propertyDefault(pid);
|
||
if (v.isValid()) {
|
||
return v;
|
||
}
|
||
return 0.0;
|
||
}
|
||
case Pid::AUTOPLACE:
|
||
return true;
|
||
case Pid::Z:
|
||
return int(type()) * 100;
|
||
default: {
|
||
PropertyValue v = EngravingObject::propertyDefault(pid);
|
||
|
||
if (v.isValid()) {
|
||
return v;
|
||
}
|
||
|
||
if (explicitParent()) {
|
||
return explicitParent()->propertyDefault(pid);
|
||
}
|
||
|
||
return PropertyValue();
|
||
}
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// propertyId
|
||
//---------------------------------------------------------
|
||
|
||
Pid EngravingItem::propertyId(const QStringRef& name) const
|
||
{
|
||
if (name == "pos" || name == "offset") {
|
||
return Pid::OFFSET;
|
||
}
|
||
return EngravingObject::propertyId(name);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// custom
|
||
// check if property is != default
|
||
//---------------------------------------------------------
|
||
|
||
bool EngravingItem::custom(Pid id) const
|
||
{
|
||
return propertyDefault(id) != getProperty(id);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// isPrintable
|
||
//---------------------------------------------------------
|
||
|
||
bool EngravingItem::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;
|
||
}
|
||
}
|
||
|
||
bool EngravingItem::isPlayable() const
|
||
{
|
||
switch (type()) {
|
||
case ElementType::NOTE:
|
||
case ElementType::CHORD:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// findAncestor
|
||
//---------------------------------------------------------
|
||
|
||
EngravingItem* EngravingItem::findAncestor(ElementType t)
|
||
{
|
||
EngravingItem* e = this;
|
||
while (e && e->type() != t) {
|
||
e = e->parentItem();
|
||
}
|
||
return e;
|
||
}
|
||
|
||
const EngravingItem* EngravingItem::findAncestor(ElementType t) const
|
||
{
|
||
const EngravingItem* e = this;
|
||
while (e && e->type() != t) {
|
||
e = e->parentItem();
|
||
}
|
||
return e;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// findMeasure
|
||
//---------------------------------------------------------
|
||
|
||
Measure* EngravingItem::findMeasure()
|
||
{
|
||
if (isMeasure()) {
|
||
return toMeasure(this);
|
||
} else if (explicitParent()) {
|
||
return parentItem()->findMeasure();
|
||
} else {
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// findMeasure
|
||
//---------------------------------------------------------
|
||
|
||
const Measure* EngravingItem::findMeasure() const
|
||
{
|
||
EngravingItem* e = const_cast<EngravingItem*>(this);
|
||
return e->findMeasure();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// findMeasureBase
|
||
//---------------------------------------------------------
|
||
|
||
MeasureBase* EngravingItem::findMeasureBase()
|
||
{
|
||
if (isMeasureBase()) {
|
||
return toMeasureBase(this);
|
||
} else if (explicitParent()) {
|
||
return parentItem()->findMeasureBase();
|
||
} else {
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// findMeasureBase
|
||
//---------------------------------------------------------
|
||
|
||
const MeasureBase* EngravingItem::findMeasureBase() const
|
||
{
|
||
EngravingItem* e = const_cast<EngravingItem*>(this);
|
||
return e->findMeasureBase();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// undoSetColor
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::undoSetColor(const mu::draw::Color& c)
|
||
{
|
||
undoChangeProperty(Pid::COLOR, PropertyValue::fromValue(c));
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// undoSetVisible
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::undoSetVisible(bool v)
|
||
{
|
||
undoChangeProperty(Pid::VISIBLE, v);
|
||
}
|
||
|
||
void EngravingItem::undoAddElement(EngravingItem* element)
|
||
{
|
||
score()->undoAddElement(element);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// drawSymbol
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::drawSymbol(SymId id, mu::draw::Painter* p, const mu::PointF& o, qreal scale) const
|
||
{
|
||
score()->scoreFont()->draw(id, p, magS() * scale, o);
|
||
}
|
||
|
||
void EngravingItem::drawSymbol(SymId id, mu::draw::Painter* p, const mu::PointF& o, int n) const
|
||
{
|
||
score()->scoreFont()->draw(id, p, magS(), o, n);
|
||
}
|
||
|
||
void EngravingItem::drawSymbols(const SymIdList& symbols, mu::draw::Painter* p, const PointF& o, qreal scale) const
|
||
{
|
||
score()->scoreFont()->draw(symbols, p, magS() * scale, o);
|
||
}
|
||
|
||
void EngravingItem::drawSymbols(const SymIdList& symbols, mu::draw::Painter* p, const PointF& o, const SizeF& scale) const
|
||
{
|
||
score()->scoreFont()->draw(symbols, p, SizeF(magS() * scale), PointF(o));
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// symHeight
|
||
//---------------------------------------------------------
|
||
|
||
qreal EngravingItem::symHeight(SymId id) const
|
||
{
|
||
return score()->scoreFont()->height(id, magS());
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// symWidth
|
||
//---------------------------------------------------------
|
||
|
||
qreal EngravingItem::symWidth(SymId id) const
|
||
{
|
||
return score()->scoreFont()->width(id, magS());
|
||
}
|
||
|
||
qreal EngravingItem::symWidth(const SymIdList& symbols) const
|
||
{
|
||
return score()->scoreFont()->width(symbols, magS());
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// symAdvance
|
||
//---------------------------------------------------------
|
||
|
||
qreal EngravingItem::symAdvance(SymId id) const
|
||
{
|
||
return score()->scoreFont()->advance(id, magS());
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// symBbox
|
||
//---------------------------------------------------------
|
||
|
||
RectF EngravingItem::symBbox(SymId id) const
|
||
{
|
||
return score()->scoreFont()->bbox(id, magS());
|
||
}
|
||
|
||
RectF EngravingItem::symBbox(const SymIdList& symbols) const
|
||
{
|
||
return score()->scoreFont()->bbox(symbols, magS());
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// symSmuflAnchor
|
||
//---------------------------------------------------------
|
||
|
||
PointF EngravingItem::symSmuflAnchor(SymId symId, SmuflAnchorId anchorId) const
|
||
{
|
||
return score()->scoreFont()->smuflAnchor(symId, anchorId, magS());
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// symIsValid
|
||
//---------------------------------------------------------
|
||
|
||
bool EngravingItem::symIsValid(SymId id) const
|
||
{
|
||
return score()->scoreFont()->isValid(id);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// concertPitch
|
||
//---------------------------------------------------------
|
||
|
||
bool EngravingItem::concertPitch() const
|
||
{
|
||
return score()->styleB(Sid::concertPitch);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// nextElement
|
||
// selects the next score element
|
||
//---------------------------------------------------------
|
||
|
||
EngravingItem* EngravingItem::nextElement()
|
||
{
|
||
EngravingItem* e = score()->selection().element();
|
||
if (!e && !score()->selection().elements().empty()) {
|
||
e = score()->selection().elements().front();
|
||
}
|
||
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->parentItem()->nextElement();
|
||
}
|
||
}
|
||
}
|
||
return nullptr;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// prevElement
|
||
// selects the previous score element
|
||
//---------------------------------------------------------
|
||
|
||
EngravingItem* EngravingItem::prevElement()
|
||
{
|
||
EngravingItem* e = score()->selection().element();
|
||
if (!e && !score()->selection().elements().empty()) {
|
||
e = score()->selection().elements().back();
|
||
}
|
||
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->parentItem()->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
|
||
//------------------------------------------------------------------------------------------
|
||
|
||
EngravingItem* EngravingItem::nextSegmentElement()
|
||
{
|
||
EngravingItem* 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->parentItem();
|
||
}
|
||
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
|
||
//------------------------------------------------------------------------------------------
|
||
|
||
EngravingItem* EngravingItem::prevSegmentElement()
|
||
{
|
||
EngravingItem* 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->parentItem();
|
||
}
|
||
return score()->firstElement();
|
||
}
|
||
|
||
mu::engraving::AccessibleItem* EngravingItem::accessible() const
|
||
{
|
||
return m_accessible;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// accessibleInfo
|
||
//---------------------------------------------------------
|
||
|
||
QString EngravingItem::accessibleInfo() const
|
||
{
|
||
return EngravingItem::typeUserName();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// nextGrip
|
||
//---------------------------------------------------------
|
||
|
||
bool EngravingItem::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 EngravingItem::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 EngravingItem::isUserModified() const
|
||
{
|
||
for (const StyledProperty& spp : *styledProperties()) {
|
||
Pid pid = spp.pid;
|
||
PropertyValue val = getProperty(pid);
|
||
PropertyValue defaultValue = propertyDefault(pid);
|
||
|
||
if (propertyType(pid) == P_TYPE::MILLIMETRE) {
|
||
if (qAbs(val.value<Millimetre>() - defaultValue.value<Millimetre>()) > 0.0001) { // we don’t 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 EngravingItem::triggerLayout() const
|
||
{
|
||
if (explicitParent()) {
|
||
score()->setLayout(tick(), staffIdx(), this);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// triggerLayoutAll
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::triggerLayoutAll() const
|
||
{
|
||
if (explicitParent()) {
|
||
score()->setLayoutAll(staffIdx(), this);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// control
|
||
//---------------------------------------------------------
|
||
|
||
bool EditData::control(bool textEditing) const
|
||
{
|
||
if (textEditing) {
|
||
return modifiers & CONTROL_MODIFIER;
|
||
} else {
|
||
return modifiers & Qt::ControlModifier;
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// clear
|
||
//---------------------------------------------------------
|
||
|
||
void EditData::clear()
|
||
{
|
||
*this = EditData(view_);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// getData
|
||
//---------------------------------------------------------
|
||
|
||
std::shared_ptr<ElementEditData> EditData::getData(const EngravingItem* e) const
|
||
{
|
||
for (std::shared_ptr<ElementEditData> ed : data) {
|
||
if (ed->e == e) {
|
||
return ed;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// addData
|
||
//---------------------------------------------------------
|
||
|
||
void EditData::addData(std::shared_ptr<ElementEditData> ed)
|
||
{
|
||
data.push_back(ed);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// drawEditMode
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::drawEditMode(mu::draw::Painter* p, EditData& ed, qreal /*currentViewScaling*/)
|
||
{
|
||
using namespace mu::draw;
|
||
Pen pen(engravingConfiguration()->defaultColor(), 0.0);
|
||
p->setPen(pen);
|
||
for (int i = 0; i < ed.grips; ++i) {
|
||
if (Grip(i) == ed.curGrip) {
|
||
p->setBrush(engravingConfiguration()->formattingMarksColor());
|
||
} else {
|
||
p->setBrush(BrushStyle::NoBrush);
|
||
}
|
||
p->drawRect(ed.grip[i]);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// startDrag
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::startDrag(EditData& ed)
|
||
{
|
||
if (!isMovable()) {
|
||
return;
|
||
}
|
||
std::shared_ptr<ElementEditData> eed = std::make_shared<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.
|
||
//---------------------------------------------------------
|
||
|
||
RectF EngravingItem::drag(EditData& ed)
|
||
{
|
||
if (!isMovable()) {
|
||
return RectF();
|
||
}
|
||
|
||
const RectF r0(canvasBoundingRect());
|
||
|
||
const ElementEditDataPtr eed = ed.getData(this);
|
||
|
||
const PointF 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(PointF(x, y));
|
||
setOffsetChanged(true);
|
||
// setGenerated(false);
|
||
|
||
if (isTextBase()) { // TODO: check for other types
|
||
//
|
||
// restrict move to page boundaries
|
||
//
|
||
const RectF r(canvasBoundingRect());
|
||
Page* p = 0;
|
||
EngravingItem* e = this;
|
||
while (e) {
|
||
if (e->isPage()) {
|
||
p = toPage(e);
|
||
break;
|
||
}
|
||
e = e->parentItem();
|
||
}
|
||
if (p) {
|
||
bool move = false;
|
||
RectF 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(PointF(x, y));
|
||
}
|
||
}
|
||
}
|
||
return canvasBoundingRect().united(r0);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// endDrag
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::endDrag(EditData& ed)
|
||
{
|
||
if (!isMovable()) {
|
||
return;
|
||
}
|
||
ElementEditDataPtr eed = ed.getData(this);
|
||
if (!eed) {
|
||
return;
|
||
}
|
||
for (const PropertyData& pd : qAsConst(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
|
||
//---------------------------------------------------------
|
||
|
||
std::vector<LineF> EngravingItem::genericDragAnchorLines() const
|
||
{
|
||
qreal xp = 0.0;
|
||
for (EngravingItem* e = parentItem(); e; e = e->parentItem()) {
|
||
xp += e->x();
|
||
}
|
||
qreal yp;
|
||
if (explicitParent()->isSegment() || explicitParent()->isMeasure()) {
|
||
Measure* meas = explicitParent()->isSegment() ? toSegment(explicitParent())->measure() : toMeasure(explicitParent());
|
||
System* system = meas->system();
|
||
const staff_idx_t stIdx = staffIdxOrNextVisible();
|
||
if (stIdx == mu::nidx) {
|
||
return { LineF() };
|
||
}
|
||
yp = system ? system->staffCanvasYpage(stIdx) : 0.0;
|
||
if (placement() == PlacementV::BELOW) {
|
||
yp += system ? system->staff(stIdx)->bbox().height() : 0.0;
|
||
}
|
||
//adjust anchor Y positions to staffType offset
|
||
if (staff()) {
|
||
yp += staff()->staffTypeForElement(this)->yoffset().val() * spatium();
|
||
}
|
||
} else {
|
||
yp = parentItem()->canvasPos().y();
|
||
}
|
||
PointF p1(xp, yp);
|
||
LineF anchorLine(p1, canvasPos());
|
||
return { anchorLine };
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// updateGrips
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::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 EngravingItem::startEdit(EditData& ed)
|
||
{
|
||
std::shared_ptr<ElementEditData> elementData = std::make_shared<ElementEditData>();
|
||
elementData->e = this;
|
||
ed.addData(elementData);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// isEditAllowed
|
||
//---------------------------------------------------------
|
||
|
||
bool EngravingItem::isEditAllowed(EditData& ed) const
|
||
{
|
||
return ed.key == Qt::Key_Home;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// edit
|
||
// return true if event is accepted
|
||
//---------------------------------------------------------
|
||
|
||
bool EngravingItem::edit(EditData& ed)
|
||
{
|
||
if (ed.key == Qt::Key_Home) {
|
||
setOffset(PointF());
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// startEditDrag
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::startEditDrag(EditData& ed)
|
||
{
|
||
ElementEditDataPtr eed = ed.getData(this);
|
||
if (!eed) {
|
||
eed = std::make_shared<ElementEditData>();
|
||
eed->e = this;
|
||
ed.addData(eed);
|
||
}
|
||
eed->pushProperty(Pid::OFFSET);
|
||
eed->pushProperty(Pid::AUTOPLACE);
|
||
if (ed.modifiers & Qt::AltModifier) {
|
||
setAutoplace(false);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// editDrag
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::editDrag(EditData& ed)
|
||
{
|
||
score()->addRefresh(canvasBoundingRect());
|
||
setOffset(offset() + ed.delta);
|
||
setOffsetChanged(true);
|
||
score()->addRefresh(canvasBoundingRect());
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// endEditDrag
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::endEditDrag(EditData& ed)
|
||
{
|
||
ElementEditDataPtr eed = ed.getData(this);
|
||
bool changed = false;
|
||
if (eed) {
|
||
for (const PropertyData& pd : qAsConst(eed->propertyData)) {
|
||
setPropertyFlags(pd.id, pd.f); // reset initial property flags state
|
||
PropertyFlags f = pd.f;
|
||
if (f == PropertyFlags::STYLED) {
|
||
f = PropertyFlags::UNSTYLED;
|
||
}
|
||
if (score()->undoPropertyChanged(this, pd.id, pd.data, f)) {
|
||
changed = true;
|
||
}
|
||
}
|
||
eed->propertyData.clear();
|
||
}
|
||
if (changed) {
|
||
undoChangeProperty(Pid::GENERATED, false);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// endEdit
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::endEdit(EditData&)
|
||
{
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// styleP
|
||
//---------------------------------------------------------
|
||
|
||
qreal EngravingItem::styleP(Sid idx) const
|
||
{
|
||
return score()->styleMM(idx);
|
||
}
|
||
|
||
bool EngravingItem::colorsInversionEnabled() const
|
||
{
|
||
return m_colorsInversionEnabled;
|
||
}
|
||
|
||
void EngravingItem::setColorsInverionEnabled(bool enabled)
|
||
{
|
||
m_colorsInversionEnabled = enabled;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// setOffsetChanged
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::setOffsetChanged(bool v, bool absolute, const PointF& 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 EngravingItem::rebaseOffset(bool nox)
|
||
{
|
||
PointF off = offset();
|
||
PointF p = _changedPos - pos();
|
||
if (nox) {
|
||
p.rx() = 0.0;
|
||
}
|
||
//OffsetChange saveChangedValue = _offsetChanged;
|
||
|
||
bool staffRelative = staff() && explicitParent() && !(explicitParent()->isNote() || explicitParent()->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
|
||
RectF r = bbox().translated(_changedPos);
|
||
qreal staffHeight = staff()->height();
|
||
EngravingItem* 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, PropertyValue::fromValue(off + p));
|
||
_offsetChanged = OffsetChange::ABSOLUTE_OFFSET; //saveChangedValue;
|
||
rypos() += above ? staffHeight : -staffHeight;
|
||
PropertyFlags pf = e->propertyFlags(Pid::PLACEMENT);
|
||
if (pf == PropertyFlags::STYLED) {
|
||
pf = PropertyFlags::UNSTYLED;
|
||
}
|
||
PlacementV place = above ? PlacementV::BELOW : PlacementV::ABOVE;
|
||
e->undoChangeProperty(Pid::PLACEMENT, int(place), pf);
|
||
undoResetProperty(Pid::MIN_DISTANCE);
|
||
return 0.0;
|
||
}
|
||
}
|
||
|
||
if (offsetChanged() == OffsetChange::ABSOLUTE_OFFSET) {
|
||
undoChangeProperty(Pid::OFFSET, PropertyValue::fromValue(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 EngravingItem::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
|
||
PointF 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;
|
||
}
|
||
}
|
||
return rc;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// autoplaceSegmentElement
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::autoplaceSegmentElement(bool above, bool add)
|
||
{
|
||
// rebase vertical offset on drag
|
||
qreal rebase = 0.0;
|
||
if (offsetChanged() != OffsetChange::NONE) {
|
||
rebase = rebaseOffset();
|
||
}
|
||
|
||
if (autoplace() && explicitParent()) {
|
||
Segment* s = toSegment(explicitParent());
|
||
Measure* m = s->measure();
|
||
|
||
qreal sp = score()->spatium();
|
||
staff_idx_t si = staffIdxOrNextVisible();
|
||
|
||
// if there's no good staff for this object, obliterate it
|
||
_skipDraw = (si == mu::nidx);
|
||
setSelectable(!_skipDraw);
|
||
if (_skipDraw) {
|
||
return;
|
||
}
|
||
|
||
qreal mag = staff()->staffMag(this);
|
||
sp *= mag;
|
||
qreal minDistance = _minDistance.val() * sp;
|
||
|
||
SysStaff* ss = m->system()->staff(si);
|
||
RectF r = bbox().translated(m->pos() + s->pos() + pos());
|
||
|
||
// Adjust bbox Y pos for staffType offset
|
||
if (staffType()) {
|
||
qreal stYOffset = staffType()->yoffset().val() * sp;
|
||
r.translate(0.0, stYOffset);
|
||
}
|
||
|
||
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(PointF(0.0, yd));
|
||
}
|
||
if (add && addToSkyline()) {
|
||
ss->skyline().add(r);
|
||
}
|
||
}
|
||
setOffsetChanged(false);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// autoplaceMeasureElement
|
||
//---------------------------------------------------------
|
||
|
||
void EngravingItem::autoplaceMeasureElement(bool above, bool add)
|
||
{
|
||
// rebase vertical offset on drag
|
||
qreal rebase = 0.0;
|
||
if (offsetChanged() != OffsetChange::NONE) {
|
||
rebase = rebaseOffset();
|
||
}
|
||
|
||
if (autoplace() && explicitParent()) {
|
||
Measure* m = toMeasure(explicitParent());
|
||
staff_idx_t si = staffIdxOrNextVisible();
|
||
|
||
// if there's no good staff for this object, obliterate it
|
||
_skipDraw = (si == mu::nidx);
|
||
setSelectable(!_skipDraw);
|
||
if (_skipDraw) {
|
||
return;
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
bool EngravingItem::selected() const
|
||
{
|
||
return flag(ElementFlag::SELECTED);
|
||
}
|
||
|
||
void EngravingItem::setSelected(bool f)
|
||
{
|
||
setFlag(ElementFlag::SELECTED, f);
|
||
|
||
if (f) {
|
||
initAccessibleIfNeed();
|
||
|
||
if (m_accessible) {
|
||
AccessibleRoot* accRoot = score()->rootItem()->accessible()->accessibleRoot();
|
||
if (accRoot && accRoot->registered()) {
|
||
accRoot->setFocusedElement(nullptr);
|
||
}
|
||
|
||
AccessibleRoot* dummyAccRoot = score()->dummy()->rootItem()->accessible()->accessibleRoot();
|
||
if (dummyAccRoot && dummyAccRoot->registered()) {
|
||
dummyAccRoot->setFocusedElement(nullptr);
|
||
}
|
||
|
||
AccessibleRoot* currAccRoot = m_accessible->accessibleRoot();
|
||
if (currAccRoot && currAccRoot->registered()) {
|
||
currAccRoot->setFocusedElement(m_accessible);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void EngravingItem::initAccessibleIfNeed()
|
||
{
|
||
if (!engravingConfiguration()->isAccessibleEnabled()) {
|
||
return;
|
||
}
|
||
|
||
if (m_accessible || !m_accessibleEnabled) {
|
||
return;
|
||
}
|
||
|
||
EngravingItemList parents;
|
||
auto parent = parentItem();
|
||
while (parent && parent->accessibleEnabled()) {
|
||
parents.push_front(parent);
|
||
parent = parent->parentItem();
|
||
}
|
||
|
||
for (EngravingItem* parent : parents) {
|
||
parent->setupAccessible();
|
||
}
|
||
|
||
setupAccessible();
|
||
}
|
||
}
|