Skip to content

Commit

Permalink
Merge pull request #25741 from mike-spa/extendAnchorsToAllTextItems
Browse files Browse the repository at this point in the history
Improved editing of text-based elements
  • Loading branch information
mike-spa authored Jan 10, 2025
2 parents 7020aab + 37051ab commit efb2f29
Show file tree
Hide file tree
Showing 19 changed files with 459 additions and 374 deletions.
39 changes: 39 additions & 0 deletions src/engraving/compat/engravingcompat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ using namespace mu::engraving;
namespace mu::engraving::compat {
void EngravingCompat::doPreLayoutCompatIfNeeded(MasterScore* score)
{
if (score->mscVersion() < 450) {
replaceEmptyCRSegmentsWithTimeTick(score);
}

if (score->mscVersion() >= 440) {
resetMarkerLeftFontSize(score);
return;
Expand Down Expand Up @@ -177,6 +181,41 @@ void EngravingCompat::resetMarkerLeftFontSize(MasterScore* masterScore)
}
}

void EngravingCompat::replaceEmptyCRSegmentsWithTimeTick(MasterScore* masterScore)
{
auto segmentHasNoChordRest = [](const Segment* segment) {
for (EngravingItem* item : segment->elist()) {
if (item && item->isChordRest()) {
return false;
}
}
return true;
};

for (Score* score : masterScore->scoreList()) {
for (MeasureBase* mb = score->first(); mb; mb = mb->next()) {
if (!mb->isMeasure()) {
continue;
}
Measure* measure = toMeasure(mb);
for (Segment* segment = measure->first(SegmentType::ChordRest); segment;) {
Segment* next = segment->next(SegmentType::ChordRest);
if (segmentHasNoChordRest(segment)) {
measure->remove(segment);
Segment* timeTickSegment = measure->getSegmentR(SegmentType::TimeTick, segment->rtick());
for (EngravingItem* annotation : segment->annotations()) {
segment->remove(annotation);
annotation->setParent(timeTickSegment);
timeTickSegment->add(annotation);
}
delete segment;
}
segment = next;
}
}
}
}

void EngravingCompat::doPostLayoutCompatIfNeeded(MasterScore* score)
{
if (score->mscVersion() >= 440) {
Expand Down
1 change: 1 addition & 0 deletions src/engraving/compat/engravingcompat.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class EngravingCompat
static void undoStaffTextExcludeFromPart(MasterScore* masterScore);
static void migrateDynamicPosOnVocalStaves(MasterScore* masterScore);
static void resetMarkerLeftFontSize(MasterScore* masterScore);
static void replaceEmptyCRSegmentsWithTimeTick(MasterScore* masterScore);

static bool relayoutUserModifiedCrossStaffBeams(MasterScore* score);
};
Expand Down
260 changes: 0 additions & 260 deletions src/engraving/dom/dynamic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ Dynamic::Dynamic(const Dynamic& d)
_avoidBarLines = d._avoidBarLines;
_dynamicsSize = d._dynamicsSize;
_centerOnNotehead = d._centerOnNotehead;
m_anchorToEndOfPrevious = d.m_anchorToEndOfPrevious;
}

//---------------------------------------------------------
Expand Down Expand Up @@ -275,22 +274,6 @@ double Dynamic::customTextOffset() const
return 0.0;
}

bool Dynamic::isEditAllowed(EditData& ed) const
{
if (ed.editTextualProperties) {
return TextBase::isEditAllowed(ed);
}

static const std::set<KeyboardKey> ARROW_KEYS {
Key_Left,
Key_Right,
Key_Up,
Key_Down
};

return muse::contains(ARROW_KEYS, static_cast<KeyboardKey>(ed.key));
}

//-------------------------------------------------------------------
// doAutoplace
//
Expand Down Expand Up @@ -549,249 +532,6 @@ TranslatableString Dynamic::subtypeUserName() const
}
}

void Dynamic::startEdit(EditData& ed)
{
if (ed.editTextualProperties) {
TextBase::startEdit(ed);
} else {
startEditNonTextual(ed);
}
}

bool Dynamic::edit(EditData& ed)
{
if (ed.editTextualProperties) {
return TextBase::edit(ed);
} else {
return editNonTextual(ed);
}
}

bool Dynamic::editNonTextual(EditData& ed)
{
if (ed.key == Key_Shift) {
if (ed.isKeyRelease) {
score()->hideAnchors();
} else {
EditTimeTickAnchors::updateAnchors(this, track());
}
triggerLayout();
return true;
}

if (!isEditAllowed(ed)) {
return false;
}

bool leftRightKey = ed.key == Key_Left || ed.key == Key_Right;
bool altMod = ed.modifiers & AltModifier;
bool shiftMod = ed.modifiers & ShiftModifier;

bool changeAnchorType = shiftMod && altMod && leftRightKey;
if (changeAnchorType) {
undoChangeProperty(Pid::ANCHOR_TO_END_OF_PREVIOUS, !anchorToEndOfPrevious(), propertyFlags(Pid::ANCHOR_TO_END_OF_PREVIOUS));
}
bool doesntNeedMoveSeg = changeAnchorType && ((ed.key == Key_Left && anchorToEndOfPrevious())
|| (ed.key == Key_Right && !anchorToEndOfPrevious()));
if (doesntNeedMoveSeg) {
checkMeasureBoundariesAndMoveIfNeed();
return true;
}

bool moveSeg = shiftMod && (ed.key == Key_Left || ed.key == Key_Right);
if (moveSeg) {
bool moved = moveSegment(ed);
EditTimeTickAnchors::updateAnchors(this, track());
checkMeasureBoundariesAndMoveIfNeed();
return moved;
}

if (shiftMod) {
return false;
}

if (!nudge(ed)) {
return false;
}

triggerLayout();
return true;
}

void Dynamic::checkMeasureBoundariesAndMoveIfNeed()
{
/* Dynamics are always assigned to a ChordRest segment if available at this tick,
* EXCEPT if we are at a measure boundary. In this case, if anchorToEndOfPrevious()
* we must assign to a TimeTick segment at the end of previous measure, otherwise to a
* ChordRest segment at the start of the next measure. */

Segment* curSeg = segment();
Fraction curTick = curSeg->tick();
Measure* curMeasure = curSeg->measure();
Measure* prevMeasure = curMeasure->prevMeasure();
bool needMoveToNext = curTick == curMeasure->endTick() && !anchorToEndOfPrevious();
bool needMoveToPrevious = curSeg->rtick().isZero() && anchorToEndOfPrevious() && prevMeasure;

if (!needMoveToPrevious && !needMoveToNext) {
return;
}

Segment* newSeg = nullptr;
if (needMoveToPrevious) {
newSeg = prevMeasure->findSegment(SegmentType::TimeTick, curSeg->tick());
if (!newSeg) {
TimeTickAnchor* anchor = EditTimeTickAnchors::createTimeTickAnchor(prevMeasure, curTick - prevMeasure->tick(), staffIdx());
EditTimeTickAnchors::updateLayout(prevMeasure);
newSeg = anchor->segment();
}
} else {
newSeg = curSeg->next1(SegmentType::ChordRest);
}

if (newSeg && newSeg->tick() == curTick) {
score()->undoChangeParent(this, newSeg, staffIdx());
if (snappedExpression()) {
score()->undoChangeParent(snappedExpression(), newSeg, staffIdx());
}
}
}

bool Dynamic::moveSegment(const EditData& ed)
{
bool forward = ed.key == Key_Right;
if (!(ed.modifiers & AltModifier)) {
if (anchorToEndOfPrevious()) {
undoResetProperty(Pid::ANCHOR_TO_END_OF_PREVIOUS);
if (forward) {
return true;
}
}
}

Segment* curSeg = segment();
IF_ASSERT_FAILED(curSeg) {
return false;
}

Segment* newSeg = forward ? curSeg->next1ChordRestOrTimeTick() : curSeg->prev1ChordRestOrTimeTick();
if (!newSeg) {
return false;
}

undoMoveSegment(newSeg, newSeg->tick() - curSeg->tick());

return true;
}

void Dynamic::undoMoveSegment(Segment* newSeg, Fraction tickDiff)
{
score()->undoChangeParent(this, newSeg, staffIdx());
moveSnappedItems(newSeg, tickDiff);
}

void Dynamic::moveSnappedItems(Segment* newSeg, Fraction tickDiff) const
{
if (EngravingItem* itemSnappedBefore = ldata()->itemSnappedBefore()) {
if (itemSnappedBefore->isHairpinSegment()) {
Hairpin* hairpinBefore = toHairpinSegment(itemSnappedBefore)->hairpin();
hairpinBefore->undoMoveEnd(tickDiff);
}
}

if (EngravingItem* itemSnappedAfter = ldata()->itemSnappedAfter()) {
Hairpin* hairpinAfter = itemSnappedAfter->isHairpinSegment() ? toHairpinSegment(itemSnappedAfter)->hairpin() : nullptr;
if (itemSnappedAfter->isExpression()) {
Expression* expressionAfter = toExpression(itemSnappedAfter);
Segment* curExpressionSegment = expressionAfter->segment();
if (curExpressionSegment != newSeg) {
score()->undoChangeParent(expressionAfter, newSeg, expressionAfter->staffIdx());
}
EngravingItem* possibleHairpinAfterExpr = expressionAfter->ldata()->itemSnappedAfter();
if (!hairpinAfter && possibleHairpinAfterExpr && possibleHairpinAfterExpr->isHairpinSegment()) {
hairpinAfter = toHairpinSegment(possibleHairpinAfterExpr)->hairpin();
}
}
if (hairpinAfter) {
hairpinAfter->undoMoveStart(tickDiff);
}
}
}

bool Dynamic::nudge(const EditData& ed)
{
bool ctrlMod = ed.modifiers & ControlModifier;
double step = spatium() * (ctrlMod ? MScore::nudgeStep10 : MScore::nudgeStep);
PointF addOffset = PointF();
switch (ed.key) {
case Key_Up:
addOffset = PointF(0.0, -step);
break;
case Key_Down:
addOffset = PointF(0.0, step);
break;
case Key_Left:
addOffset = PointF(-step, 0.0);
break;
case Key_Right:
addOffset = PointF(step, 0.0);
break;
default:
return false;
}
undoChangeProperty(Pid::OFFSET, offset() + addOffset, PropertyFlags::UNSTYLED);
return true;
}

void Dynamic::editDrag(EditData& ed)
{
ElementEditDataPtr eed = ed.getData(this);
if (!eed) {
return;
}

EditTimeTickAnchors::updateAnchors(this, track());

KeyboardModifiers km = ed.modifiers;
if (km != (ShiftModifier | ControlModifier)) {
staff_idx_t si = staffIdx();
Segment* seg = nullptr; // don't prefer any segment while dragging, just snap to the closest
static constexpr double spacingFactor = 0.5;
score()->dragPosition(canvasPos(), &si, &seg, spacingFactor, allowTimeAnchor());
if ((seg && seg != segment()) || staffIdx() != si) {
const PointF oldOffset = offset();
PointF pos1(canvasPos());
score()->undoChangeParent(this, seg, staffIdx());
Expression* snappedExpr = snappedExpression();
if (snappedExpr) {
score()->undoChangeParent(snappedExpr, seg, staffIdx());
}
setOffset(PointF());

renderer()->layoutItem(this);

PointF pos2(canvasPos());
const PointF newOffset = pos1 - pos2;
setOffset(newOffset);
setOffsetChanged(true);
eed->initOffset += newOffset - oldOffset;
}
}

EngravingItem::editDrag(ed);
}

void Dynamic::endEdit(EditData& ed)
{
if (ed.editTextualProperties) {
TextBase::endEdit(ed);
if (!xmlText().contains(String::fromUtf8(DYN_LIST[int(m_dynamicType)].text))) {
m_dynamicType = DynamicType::OTHER;
}
} else {
endEditNonTextual(ed);
}
}

//---------------------------------------------------------
// reset
//---------------------------------------------------------
Expand Down
Loading

0 comments on commit efb2f29

Please sign in to comment.