//============================================================================= // MuseScore // Music Composition & Notation // // Copyright (C) 2018 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 //============================================================================= #include "connector.h" #include "element.h" #include "score.h" #include "scoreElement.h" #include "xml.h" namespace Ms { //--------------------------------------------------------- // ConnectorInfo //--------------------------------------------------------- ConnectorInfo::ConnectorInfo(const Element* current, int track, Fraction frac) : _current(current), _score(current->score()), _currentLoc(Location::absolute()) { if (!current) qFatal("ConnectorInfo::ConnectorInfo(): invalid argument: %p", current); // It is not always possible to determine the track number correctly from // the current element (for example, in case of a Segment). // If the caller does not know the track number and passes -1 // it may be corrected later. if (track >= 0) _currentLoc.setTrack(track); if (frac >= Fraction(0,1)) _currentLoc.setFrac(frac); } //--------------------------------------------------------- // ConnectorInfo //--------------------------------------------------------- ConnectorInfo::ConnectorInfo(const Score* score, const Location& currentLocation) : _score(score), _currentLoc(currentLocation) {} //--------------------------------------------------------- // ConnectorInfo::updateLocation //--------------------------------------------------------- void ConnectorInfo::updateLocation(const Element* e, Location& l, bool clipboardmode) { l.fillForElement(e, clipboardmode); } //--------------------------------------------------------- // ConnectorInfo::updateCurrentInfo //--------------------------------------------------------- void ConnectorInfo::updateCurrentInfo(bool clipboardmode) { if (!currentUpdated() && _current) updateLocation(_current, _currentLoc, clipboardmode); setCurrentUpdated(true); } //--------------------------------------------------------- // ConnectorInfo::connect //--------------------------------------------------------- bool ConnectorInfo::connect(ConnectorInfo* other) { if (!other || (this == other)) return false; if (_type != other->_type || _score != other->_score) return false; if (hasPrevious() && _prev == nullptr && other->hasNext() && other->_next == nullptr ) { if ((_prevLoc == other->_currentLoc) && (_currentLoc == other->_nextLoc) ) { _prev = other; other->_next = this; return true; } } if (hasNext() && _next == nullptr && other->hasPrevious() && other->_prev == nullptr ) { if ((_nextLoc == other->_currentLoc) && (_currentLoc == other->_prevLoc) ) { _next = other; other->_prev = this; return true; } } return false; } //--------------------------------------------------------- // ConnectorInfo::forceConnect //--------------------------------------------------------- void ConnectorInfo::forceConnect(ConnectorInfo* other) { if (!other || (this == other)) return; _next = other; other->_prev = this; } //--------------------------------------------------------- // distance //--------------------------------------------------------- static int distance(const Location& l1, const Location& l2) { constexpr int commonDenominator = 1000; Fraction dfrac = (l2.frac() - l1.frac()).absValue(); int dpos = dfrac.numerator() * commonDenominator / dfrac.denominator(); dpos += 10000 * qAbs(l2.measure() - l1.measure()); return 1000 * dpos + 100 * qAbs(l2.track() - l1.track()) + 10 * qAbs(l2.note() - l1.note()) + qAbs(l2.graceIndex() - l1.graceIndex()); } //--------------------------------------------------------- // ConnectorInfo::orderedConnectionDistance //--------------------------------------------------------- int ConnectorInfo::orderedConnectionDistance(const ConnectorInfo& c1, const ConnectorInfo& c2) { Location c1Next = c1._nextLoc; c1Next.toRelative(c1._currentLoc); Location c2Prev = c2._currentLoc; // inversed order to get equal signs c2Prev.toRelative(c2._prevLoc); if (c1Next == c2Prev) return distance(c1._nextLoc, c2._currentLoc); return INT_MAX; } //--------------------------------------------------------- // ConnectorInfo::connectionDistance // Returns a "distance" representing a likelihood of // that the checked connectors should be connected. // Returns 0 if can be readily connected via connect(), // < 0 if other is likely to be the first, // INT_MAX if cannot be connected //--------------------------------------------------------- int ConnectorInfo::connectionDistance(const ConnectorInfo& other) const { if (_type != other._type || _score != other._score) return INT_MAX; int distThisOther = INT_MAX; int distOtherThis = INT_MAX; if (hasNext() && _next == nullptr && other.hasPrevious() && other._prev == nullptr) distThisOther = orderedConnectionDistance(*this, other); if (hasPrevious() && _prev == nullptr && other.hasNext() && other._next == nullptr) distOtherThis = orderedConnectionDistance(other, *this); if (distOtherThis < distThisOther) return -distOtherThis; return distThisOther; } //--------------------------------------------------------- // ConnectorInfo::findFirst //--------------------------------------------------------- ConnectorInfo* ConnectorInfo::findFirst() { ConnectorInfo* i = this; while (i->_prev) { i = i->_prev; if (i == this) { qWarning("ConnectorInfo::findFirst: circular connector %p", this); return nullptr; } } return i; } //--------------------------------------------------------- // ConnectorInfo::findFirst //--------------------------------------------------------- const ConnectorInfo* ConnectorInfo::findFirst() const { return const_cast(this)->findFirst(); } //--------------------------------------------------------- // ConnectorInfo::findLast //--------------------------------------------------------- ConnectorInfo* ConnectorInfo::findLast() { ConnectorInfo* i = this; while (i->_next) { i = i->_next; if (i == this) { qWarning("ConnectorInfo::findLast: circular connector %p", this); return nullptr; } } return i; } //--------------------------------------------------------- // ConnectorInfo::findLast //--------------------------------------------------------- const ConnectorInfo* ConnectorInfo::findLast() const { return const_cast(this)->findLast(); } //--------------------------------------------------------- // ConnectorInfo::finished //--------------------------------------------------------- bool ConnectorInfo::finished() const { return (finishedLeft() && finishedRight()); } //--------------------------------------------------------- // ConnectorInfo::finishedLeft //--------------------------------------------------------- bool ConnectorInfo::finishedLeft() const { const ConnectorInfo* i = findFirst(); return (i && !i->hasPrevious()); } //--------------------------------------------------------- // ConnectorInfo::finishedRight //--------------------------------------------------------- bool ConnectorInfo::finishedRight() const { const ConnectorInfo* i = findLast(); return (i && !i->hasNext()); } //--------------------------------------------------------- // ConnectorInfo::start //--------------------------------------------------------- ConnectorInfo* ConnectorInfo::start() { ConnectorInfo* i = findFirst(); if (i && i->hasPrevious()) return nullptr; return i; } //--------------------------------------------------------- // ConnectorInfo::end //--------------------------------------------------------- ConnectorInfo* ConnectorInfo::end() { ConnectorInfo* i = findLast(); if (i && i->hasNext()) return nullptr; return i; } //--------------------------------------------------------- // ConnectorInfoReader //--------------------------------------------------------- ConnectorInfoReader::ConnectorInfoReader(XmlReader& e, Element* current, int track) : ConnectorInfo(current, track), _reader(&e), _connector(nullptr), _connectorReceiver(current) {} //--------------------------------------------------------- // readPositionInfo //--------------------------------------------------------- static Location readPositionInfo(const XmlReader& e, int track) { Location info = e.location(); info.setTrack(track); return info; } //--------------------------------------------------------- // ConnectorInfoReader //--------------------------------------------------------- ConnectorInfoReader::ConnectorInfoReader(XmlReader& e, Score* current, int track) : ConnectorInfo(current, readPositionInfo(e, track)), _reader(&e), _connector(nullptr), _connectorReceiver(current) { setCurrentUpdated(true); } //--------------------------------------------------------- // ConnectorInfoWriter //--------------------------------------------------------- ConnectorInfoWriter::ConnectorInfoWriter(XmlWriter& xml, const Element* current, const Element* connector, int track, Fraction frac) : ConnectorInfo(current, track, frac), _xml(&xml), _connector(connector) { if (!connector) { qFatal("ConnectorInfoWriter::ConnectorInfoWriter(): invalid arguments: %p, %p", connector, current); return; } _type = connector->type(); updateCurrentInfo(xml.clipboardmode()); } //--------------------------------------------------------- // ConnectorInfoWriter::write //--------------------------------------------------------- void ConnectorInfoWriter::write() { XmlWriter& xml = *_xml; if (!xml.canWrite(_connector)) return; xml.stag(QString("%1 type=\"%2\"").arg(tagName()).arg(_connector->name())); if (isStart()) _connector->write(xml); if (hasPrevious()) { xml.stag("prev"); _prevLoc.toRelative(_currentLoc); _prevLoc.write(xml); xml.etag(); } if (hasNext()) { xml.stag("next"); _nextLoc.toRelative(_currentLoc); _nextLoc.write(xml); xml.etag(); } xml.etag(); } //--------------------------------------------------------- // ConnectorInfoReader::read //--------------------------------------------------------- bool ConnectorInfoReader::read() { XmlReader& e = *_reader; const QString name(e.attribute("type")); _type = ScoreElement::name2type(&name); e.fillLocation(_currentLoc); while (e.readNextStartElement()) { const QStringRef& tag(e.name()); if (tag == "prev") readEndpointLocation(_prevLoc); else if (tag == "next") readEndpointLocation(_nextLoc); else { if (tag == name) _connector = Element::name2Element(tag, _connectorReceiver->score()); else qWarning("ConnectorInfoReader::read: element tag (%s) does not match connector type (%s). Is the file corrupted?", tag.toLatin1().constData(), name.toLatin1().constData()); if (!_connector) { e.unknown(); return false; } _connector->setTrack(_currentLoc.track()); _connector->read(e); } } return true; } //--------------------------------------------------------- // ConnectorInfoReader::readEndpointLocation //--------------------------------------------------------- void ConnectorInfoReader::readEndpointLocation(Location& l) { XmlReader& e = *_reader; while (e.readNextStartElement()) { const QStringRef& tag(e.name()); if (tag == "location") { l = Location::relative(); l.read(e); } else e.unknown(); } } //--------------------------------------------------------- // ConnectorInfoReader::update //--------------------------------------------------------- void ConnectorInfoReader::update() { if (!currentUpdated()) updateCurrentInfo(_reader->pasteMode()); if (hasPrevious()) _prevLoc.toAbsolute(_currentLoc); if (hasNext()) _nextLoc.toAbsolute(_currentLoc); } //--------------------------------------------------------- // ConnectorInfoReader::addToScore //--------------------------------------------------------- void ConnectorInfoReader::addToScore(bool pasteMode) { ConnectorInfoReader* r = this; while (r->prev()) r = r->prev(); while (r) { r->_connectorReceiver->readAddConnector(r, pasteMode); r = r->next(); } } //--------------------------------------------------------- // ConnectorInfoReader::readConnector //--------------------------------------------------------- void ConnectorInfoReader::readConnector(std::unique_ptr info, XmlReader& e) { if (!info->read()) { e.skipCurrentElement(); return; } e.addConnectorInfoLater(std::move(info)); } //--------------------------------------------------------- // ConnectorInfoReader::connector //--------------------------------------------------------- Element* ConnectorInfoReader::connector() { // connector should be contained in the first node normally. ConnectorInfo* i = findFirst(); if (i) return static_cast(i)->_connector; return nullptr; } //--------------------------------------------------------- // ConnectorInfoReader::connector //--------------------------------------------------------- const Element* ConnectorInfoReader::connector() const { return const_cast(this)->connector(); } //--------------------------------------------------------- // ConnectorInfoReader::releaseConnector //--------------------------------------------------------- Element* ConnectorInfoReader::releaseConnector() { ConnectorInfoReader* i = static_cast(findFirst()); if (!i) { // circular connector? ConnectorInfoReader* ii = this; Element* c = nullptr; while (ii->prev()) { if (ii->_connector) { c = ii->_connector; ii->_connector = nullptr; } ii = ii->prev(); if (ii == this) break; } return c; } Element* c = i->_connector; i->_connector = nullptr; return c; } }