Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "line.h"
24 :
25 : #include <vector>
26 :
27 : #include "containers.h"
28 :
29 : #include "iengravingconfiguration.h" // IWYU pragma: keep
30 :
31 : #include "../editing/mscoreview.h"
32 : #include "../editing/navigation.h"
33 : #include "../rendering/iscorerenderer.h"
34 :
35 : #include "anchors.h"
36 : #include "barline.h"
37 : #include "chord.h"
38 : #include "dynamic.h"
39 : #include "measure.h"
40 : #include "note.h"
41 : #include "page.h"
42 : #include "part.h"
43 : #include "score.h"
44 : #include "segment.h"
45 : #include "staff.h"
46 : #include "system.h"
47 : #include "utils.h"
48 :
49 : #include "log.h"
50 :
51 : using namespace mu;
52 : using namespace mu::engraving;
53 :
54 : namespace mu::engraving {
55 1673 : LineSegment::LineSegment(const ElementType& type, SLine* sp, ElementFlags f)
56 1673 : : SpannerSegment(type, sp, f)
57 : {
58 1673 : }
59 :
60 : //---------------------------------------------------------
61 : // LineSegment
62 : //---------------------------------------------------------
63 :
64 108 : LineSegment::LineSegment(const LineSegment& s)
65 108 : : SpannerSegment(s)
66 : {
67 108 : }
68 :
69 : //---------------------------------------------------------
70 : // gripsPositions
71 : //---------------------------------------------------------
72 :
73 0 : std::vector<PointF> LineSegment::gripsPositions(const EditData&) const
74 : {
75 0 : std::vector<PointF> grips(gripsCount());
76 0 : PointF pp(pagePos());
77 0 : grips[int(Grip::START)] = pp;
78 0 : grips[int(Grip::END)] = pos2() + pp;
79 0 : grips[int(Grip::MIDDLE)] = pos2() * .5 + pp;
80 0 : return grips;
81 0 : }
82 :
83 : //---------------------------------------------------------
84 : // leftAnchorPosition
85 : //---------------------------------------------------------
86 :
87 0 : PointF LineSegment::leftAnchorPosition(const double& systemPositionY) const
88 : {
89 0 : if (isMiddleType() || isEndType()) {
90 0 : return PointF(system()->firstMeasure()->pageBoundingRect().left(), systemPositionY);
91 : }
92 :
93 0 : PointF result;
94 :
95 0 : Segment* startSeg = line()->startSegment();
96 0 : if (!startSeg) {
97 0 : return result;
98 : }
99 :
100 0 : System* s = startSeg->measure()->system();
101 0 : result = startSeg->pos() + startSeg->measure()->pos();
102 0 : result.ry() += systemPositionY - system()->pos().y();
103 :
104 0 : if (s) {
105 0 : result += s->pos(); // to page coordinates
106 : }
107 0 : return result;
108 : }
109 :
110 : //---------------------------------------------------------
111 : // rightAnchorPosition
112 : //---------------------------------------------------------
113 :
114 0 : PointF LineSegment::rightAnchorPosition(const double& systemPositionY) const
115 : {
116 0 : const System* sys = system();
117 0 : IF_ASSERT_FAILED(sys) {
118 0 : return PointF();
119 : }
120 :
121 0 : bool endsAtSystemEnd = isSingleEndType() && line()->tick2() == sys->endTick();
122 0 : if (isMiddleType() || isBeginType() || endsAtSystemEnd) {
123 0 : return PointF(sys->endingXForOpenEndedLines() + sys->x(), systemPositionY);
124 : }
125 :
126 0 : PointF result;
127 :
128 0 : Segment* endSeg = line()->endSegment();
129 0 : if (!endSeg) {
130 0 : return result;
131 : }
132 :
133 0 : System* s = endSeg->measure()->system();
134 0 : result = endSeg->pos() + endSeg->measure()->pos();
135 0 : result.ry() += systemPositionY - system()->pos().y();
136 :
137 0 : if (s) {
138 0 : result += s->pos(); // to page coordinates
139 : }
140 0 : return result;
141 : }
142 :
143 : //---------------------------------------------------------
144 : // gripAnchorLines
145 : // return page coordinates
146 : //---------------------------------------------------------
147 :
148 0 : std::vector<LineF> LineSegment::gripAnchorLines(Grip grip) const
149 : {
150 0 : std::vector<LineF> result;
151 :
152 : // Middle or aperture grip have no anchor
153 0 : if (!system() || grip == Grip::APERTURE) {
154 0 : return result;
155 : }
156 :
157 0 : const staff_idx_t stIdx = staffIdx();
158 0 : double y = system()->staffYpage(stIdx);
159 0 : if (line()->placement() == PlacementV::BELOW) {
160 0 : y += system()->staff(stIdx)->bbox().height();
161 : }
162 : // adjust Y to staffType offset
163 0 : y += staffOffsetY();
164 :
165 0 : const Page* p = system()->page();
166 0 : const PointF pageOffset = p ? p->pos() : PointF();
167 0 : switch (grip) {
168 0 : case Grip::START:
169 0 : result.push_back(LineF(leftAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::START))).translated(pageOffset));
170 0 : break;
171 0 : case Grip::END:
172 0 : result.push_back(LineF(rightAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::END))).translated(pageOffset));
173 0 : break;
174 0 : case Grip::MIDDLE:
175 0 : result.push_back(LineF(leftAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::START))).translated(pageOffset));
176 0 : result.push_back(LineF(rightAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::END))).translated(pageOffset));
177 0 : break;
178 0 : default:
179 0 : break;
180 : }
181 :
182 0 : return result;
183 0 : }
184 :
185 : //---------------------------------------------------------
186 : // startDrag
187 : //---------------------------------------------------------
188 :
189 0 : void LineSegment::startDrag(EditData& ed)
190 : {
191 0 : SpannerSegment::startDrag(ed);
192 0 : ElementEditDataPtr eed = ed.getData(this);
193 0 : if (!eed) {
194 0 : return;
195 : }
196 0 : eed->pushProperty(Pid::OFFSET2);
197 0 : }
198 :
199 : //---------------------------------------------------------
200 : // startDragGrip
201 : //---------------------------------------------------------
202 :
203 0 : void LineSegment::startDragGrip(EditData& ed)
204 : {
205 0 : ElementEditDataPtr eed = ed.getData(this);
206 0 : if (!eed) {
207 0 : return;
208 : }
209 0 : eed->pushProperty(Pid::OFFSET);
210 0 : eed->pushProperty(Pid::OFFSET2);
211 0 : eed->pushProperty(Pid::AUTOPLACE);
212 0 : if (ed.modifiers & AltModifier) {
213 0 : setAutoplace(false);
214 : }
215 :
216 0 : EditTimeTickAnchors::updateAnchors(this);
217 0 : }
218 :
219 0 : bool LineSegment::isEditAllowed(EditData& ed) const
220 : {
221 0 : return ed.modifiers & ShiftModifier;
222 : }
223 :
224 : //---------------------------------------------------------
225 : // edit
226 : // return true if event is accepted
227 : //---------------------------------------------------------
228 :
229 0 : bool LineSegment::edit(EditData& ed)
230 : {
231 0 : const bool moveStart = ed.curGrip == Grip::START;
232 0 : const bool moveEnd = ed.curGrip == Grip::END || ed.curGrip == Grip::MIDDLE;
233 :
234 0 : LineSegment* ls = 0;
235 0 : SpannerSegmentType st = spannerSegmentType(); // may change later
236 0 : SLine* l = line();
237 :
238 0 : if (!isEditAllowed(ed)) {
239 0 : return false;
240 : }
241 :
242 0 : switch (l->anchor()) {
243 0 : case Spanner::Anchor::SEGMENT:
244 : {
245 0 : Segment* s1 = spanner()->startSegment();
246 0 : Segment* s2 = spanner()->endSegment();
247 : // check for line going to end of score
248 0 : if (spanner()->tick2() >= score()->lastSegment()->tick()) {
249 : // endSegment calculated above will be the last chord/rest of score
250 : // but that is not correct - it should be an imaginary note *after* the end of the score
251 : // best we can do is set s2 to lastSegment (probably the end barline)
252 0 : s2 = score()->lastSegment();
253 : }
254 0 : if (!s1 && !s2) {
255 0 : LOGD("LineSegment::edit: no start/end segment");
256 0 : return true;
257 : }
258 0 : if (moveStart) {
259 0 : s1 = MoveElementAnchors::findNewAnchorSegmentForLine(this, ed, s1);
260 : } else {
261 0 : s2 = MoveElementAnchors::findNewAnchorSegmentForLine(this, ed, s2);
262 : }
263 0 : if (!s1 || !s2 || s1->tick() >= s2->tick()) {
264 0 : return true;
265 : }
266 :
267 0 : undoMoveStartEndAndSnappedItems(ed, moveStart, moveEnd, s1, s2);
268 :
269 0 : EditTimeTickAnchors::updateAnchors(this);
270 : }
271 0 : break;
272 0 : case Spanner::Anchor::NOTE:
273 : {
274 0 : Note* note1 = toNote(l->startElement());
275 0 : Note* note2 = toNote(l->endElement());
276 0 : Note* oldNote1 = note1;
277 0 : Note* oldNote2 = note2;
278 0 : if (!note1 && !note2) {
279 0 : LOGD("LineSegment::edit: no start/end note");
280 0 : return true; // accept the event without doing anything
281 : }
282 :
283 0 : switch (ed.key) {
284 0 : case Key_Left:
285 0 : if (moveStart) {
286 0 : note1 = prevChordNote(note1);
287 0 : } else if (moveEnd) {
288 0 : note2 = prevChordNote(note2);
289 : }
290 0 : break;
291 0 : case Key_Right:
292 0 : if (moveStart) {
293 0 : note1 = nextChordNote(note1);
294 0 : } else if (moveEnd) {
295 0 : note2 = nextChordNote(note2);
296 : }
297 0 : break;
298 0 : case Key_Up:
299 0 : if (moveStart) {
300 0 : note1 = toNote(Navigation::chordNoteAbove(score(), note1));
301 0 : } else if (moveEnd) {
302 0 : note2 = toNote(Navigation::chordNoteAbove(score(), note2));
303 : }
304 0 : break;
305 0 : case Key_Down:
306 0 : if (moveStart) {
307 0 : note1 = toNote(Navigation::chordNoteBelow(score(), note1));
308 0 : } else if (moveEnd) {
309 0 : note2 = toNote(Navigation::chordNoteBelow(score(), note2));
310 : }
311 0 : break;
312 0 : default:
313 0 : return true;
314 : }
315 :
316 : // check prevChordNote() and nextchordNote() didn't return null
317 : // OR Score::upAlt() and Score::downAlt() didn't return non-Note (notably rests)
318 : // OR spanner duration is > 0
319 : // OR note1 and note2 didn't end up in different instruments
320 : // if this is the case, accepts the event and return without doing nothing
321 0 : if (!note1 || !note2
322 0 : || !note1->isNote() || !note2->isNote()
323 0 : || note1->chord()->tick() >= note2->chord()->tick()
324 0 : || note1->chord()->originPart()->instrument(note1->chord()->tick())
325 0 : != note2->chord()->originPart()->instrument(note2->chord()->tick())) {
326 0 : return true;
327 : }
328 0 : if (note1 != oldNote1 || note2 != oldNote2) {
329 0 : score()->undoChangeSpannerElements(spanner(), note1, note2);
330 : }
331 : }
332 0 : break;
333 0 : case Spanner::Anchor::MEASURE:
334 : case Spanner::Anchor::CHORDREST:
335 : {
336 0 : Measure* m1 = l->startMeasure();
337 0 : Measure* m2 = l->endMeasure();
338 :
339 0 : if (ed.key == Key_Left) {
340 0 : if (moveStart) {
341 0 : if (m1->prevMeasure()) {
342 0 : m1 = m1->prevMeasure();
343 : }
344 0 : } else if (moveEnd) {
345 0 : Measure* m = m2->prevMeasure();
346 0 : if (m) {
347 0 : m2 = m;
348 : }
349 : }
350 0 : } else if (ed.key == Key_Right) {
351 0 : if (moveStart) {
352 0 : if (m1->nextMeasure()) {
353 0 : m1 = m1->nextMeasure();
354 : }
355 0 : } else if (moveEnd) {
356 0 : if (m2->nextMeasure()) {
357 0 : m2 = m2->nextMeasure();
358 : }
359 : }
360 0 : } else if (ed.key == Key_Home) {
361 0 : if (moveStart) {
362 0 : m1 = m1->system()->firstMeasure();
363 0 : } else if (moveEnd) {
364 0 : m2 = m2->system()->firstMeasure();
365 : }
366 0 : } else if (ed.key == Key_End) {
367 0 : if (moveStart) {
368 0 : m1 = m1->system()->lastMeasure();
369 0 : } else if (moveEnd) {
370 0 : m2 = m2->system()->lastMeasure();
371 : }
372 : }
373 0 : if (m1->tick() > m2->tick()) {
374 0 : return true;
375 : }
376 0 : if (l->startElement() != m1) {
377 0 : spanner()->undoChangeProperty(Pid::SPANNER_TICK, m1->tick());
378 0 : spanner()->undoChangeProperty(Pid::SPANNER_TICKS, m2->endTick() - m1->tick());
379 0 : } else if (l->endElement() != m2) {
380 0 : spanner()->undoChangeProperty(Pid::SPANNER_TICKS, m2->endTick() - m1->tick());
381 : }
382 : }
383 : }
384 0 : triggerLayout();
385 : // recompute segment list, segment type may change
386 0 : renderer()->layoutItem(l);
387 :
388 0 : LineSegment* nls = 0;
389 0 : if (st == SpannerSegmentType::SINGLE) {
390 0 : if (moveStart) {
391 0 : nls = l->frontSegment();
392 0 : } else if (moveEnd) {
393 0 : nls = l->backSegment();
394 : }
395 0 : } else if (st == SpannerSegmentType::BEGIN) {
396 0 : nls = l->frontSegment();
397 0 : } else if (st == SpannerSegmentType::END) {
398 0 : nls = l->backSegment();
399 : } else {
400 0 : LOGD("spannerSegmentType %d", int(spannerSegmentType()));
401 : }
402 :
403 0 : if (nls && (nls != this)) {
404 0 : ed.view()->changeEditElement(nls);
405 : }
406 0 : if (ls) {
407 0 : score()->undoRemoveElement(ls);
408 : }
409 :
410 0 : triggerLayout();
411 0 : return true;
412 : }
413 :
414 : //---------------------------------------------------------
415 : // findSegmentForGrip
416 : //---------------------------------------------------------
417 :
418 0 : Segment* LineSegment::findSegmentForGrip(Grip grip, PointF pos) const
419 : {
420 0 : if (grip != Grip::START && grip != Grip::END) {
421 0 : return nullptr;
422 : }
423 :
424 0 : SLine* l = line();
425 0 : const bool left = (grip == Grip::START);
426 :
427 0 : const staff_idx_t oldStaffIndex = left ? staffIdx() : track2staff(l->effectiveTrack2());
428 :
429 0 : const double spacingFactor = 0.5; // defines the point where canvas is divided between segments, systems etc.
430 :
431 0 : System* sys = system();
432 0 : const std::vector<System*> foundSystems = score()->searchSystem(pos, /*includeMeasurelessSystems*/ false, sys, spacingFactor);
433 :
434 0 : if (!foundSystems.empty() && !muse::contains(foundSystems, sys) && foundSystems[0]->staves().size()) {
435 0 : sys = foundSystems[0];
436 : }
437 :
438 0 : if (!sys) {
439 0 : return nullptr;
440 : }
441 :
442 : // Restrict searching segment to the correct staff
443 0 : pos.setY(sys->staffCanvasYpage(oldStaffIndex));
444 :
445 0 : Segment* seg = nullptr; // don't prefer any segment while searching line position
446 0 : staff_idx_t staffIndex = oldStaffIndex;
447 0 : score()->dragPosition(pos, &staffIndex, &seg, spacingFactor, allowTimeAnchor());
448 :
449 : // Lines that don't allow time anchors can anchor to the last segment in the score (see also LineSegment::findNewAnchorSegment)
450 0 : if (seg && !allowTimeAnchor() && grip == Grip::END) {
451 0 : Segment* last = score()->lastSegment();
452 0 : const bool lastSegIsClosest = std::abs(last->canvasX() - pos.x()) < std::abs(seg->canvasX() - pos.x());
453 0 : if (lastSegIsClosest) {
454 0 : return last;
455 : }
456 : }
457 :
458 0 : return seg;
459 0 : }
460 :
461 : //---------------------------------------------------------
462 : // deltaRebaseLeft
463 : /// Helper function for anchors rebasing when dragging.
464 : //---------------------------------------------------------
465 :
466 0 : PointF LineSegment::deltaRebaseLeft(const Segment* oldSeg, const Segment* newSeg)
467 : {
468 0 : if (oldSeg == newSeg) {
469 0 : return PointF();
470 : }
471 0 : return oldSeg->canvasPos() - newSeg->canvasPos();
472 : }
473 :
474 : //---------------------------------------------------------
475 : // deltaRebaseRight
476 : /// Helper function for anchors rebasing when dragging.
477 : //---------------------------------------------------------
478 :
479 0 : PointF LineSegment::deltaRebaseRight(const Segment* oldSeg, const Segment* newSeg)
480 : {
481 0 : if (oldSeg == newSeg) {
482 0 : return PointF();
483 : }
484 :
485 0 : const PointF oldBase = oldSeg->canvasPos();
486 0 : const PointF newBase = newSeg->canvasPos();
487 0 : return oldBase - newBase;
488 : }
489 :
490 : //---------------------------------------------------------
491 : // lastSegmentEndTick
492 : /// Helper function for anchors rebasing when dragging.
493 : //---------------------------------------------------------
494 :
495 0 : Fraction LineSegment::lastSegmentEndTick(const Segment* seg, const Spanner* s)
496 : {
497 0 : return seg->tick() + seg->ticksInStaff(track2staff(s->effectiveTrack2()));
498 : }
499 :
500 : //---------------------------------------------------------
501 : // rebaseAnchor
502 : /// If system has changed, returns the new line segment
503 : /// (may appear to be reused \c this segment), otherwise
504 : /// returns nullptr.
505 : //---------------------------------------------------------
506 :
507 0 : LineSegment* LineSegment::rebaseAnchor(Grip grip, Segment* newSeg)
508 : {
509 0 : switch (grip) {
510 0 : case Grip::START:
511 0 : if (!isSingleBeginType()) {
512 0 : return nullptr;
513 : }
514 0 : break;
515 0 : case Grip::END:
516 0 : if (!isSingleEndType()) {
517 0 : return nullptr;
518 : }
519 0 : break;
520 0 : default:
521 0 : return nullptr;
522 : }
523 :
524 0 : SLine* l = line();
525 0 : const bool left = (grip == Grip::START);
526 0 : Segment* const oldSeg = left ? l->startSegment() : l->endSegment();
527 0 : System* const oldLineSegSystem = system();
528 0 : System* const oldSpannerSystem = oldSeg->measure()->system();
529 :
530 0 : if (!newSeg || oldSeg == newSeg) {
531 0 : return nullptr;
532 : }
533 :
534 0 : Fraction newSegTick = newSeg->tick();
535 0 : Fraction startTick = left ? newSegTick : l->tick();
536 0 : Fraction endTick = left ? l->tick2() : (newSegTick == startTick ? newSegTick + newSeg->ticks() : newSegTick);
537 :
538 0 : if (endTick <= startTick) {
539 0 : if (left) {
540 0 : endTick = lastSegmentEndTick(newSeg, l);
541 : } else {
542 0 : startTick = newSeg->tick();
543 : }
544 : }
545 :
546 0 : bool anchorChanged = false;
547 :
548 0 : System* lineSegSys = system();
549 0 : PointF oldPos = line()->linePos(grip, &lineSegSys);
550 :
551 0 : if (l->tick() != startTick) {
552 0 : l->undoChangeProperty(Pid::SPANNER_TICK, startTick);
553 0 : anchorChanged = true;
554 : }
555 :
556 0 : const Fraction ticksLength = endTick - startTick;
557 0 : if (ticksLength != l->ticks()) {
558 0 : l->undoChangeProperty(Pid::SPANNER_TICKS, ticksLength);
559 0 : anchorChanged = true;
560 : }
561 :
562 0 : const Segment* newTickSeg = left ? l->startSegment() : l->endSegment();
563 0 : const System* newSpannerSystem = newTickSeg->measure()->system();
564 :
565 0 : if ((oldLineSegSystem && newSeg->system() != oldLineSegSystem) || oldSpannerSystem != newSpannerSystem) {
566 0 : renderer()->layoutItem(l);
567 0 : return l->segmentsEmpty() ? nullptr : left ? l->frontSegment() : l->backSegment();
568 0 : } else if (anchorChanged) {
569 0 : rebaseOffsetsOnAnchorChanged(grip, oldPos, oldLineSegSystem);
570 : }
571 :
572 0 : return nullptr;
573 : }
574 :
575 0 : void LineSegment::rebaseOffsetsOnAnchorChanged(Grip grip, const PointF& oldPos, System* sys)
576 : {
577 0 : PointF newPos = line()->linePos(grip, &sys);
578 0 : PointF delta = oldPos - newPos;
579 0 : if (grip == Grip::START) {
580 0 : setOffset(offset() + delta);
581 0 : m_offset2 -= delta;
582 : } else {
583 0 : m_offset2 += delta;
584 : }
585 0 : }
586 :
587 : //---------------------------------------------------------
588 : // rebaseAnchors
589 : //---------------------------------------------------------
590 :
591 0 : void LineSegment::rebaseAnchors(EditData& ed, Grip grip)
592 : {
593 0 : if (line()->anchor() != Spanner::Anchor::SEGMENT) {
594 0 : return;
595 : }
596 :
597 : // don't change anchors on keyboard adjustment or if Ctrl is pressed
598 : // (Ctrl+Left/Right is handled elsewhere!)
599 0 : if (ed.key == Key_Left || ed.key == Key_Right || ed.key == Key_Up || ed.key == Key_Down || ed.modifiers & ControlModifier) {
600 0 : return;
601 : }
602 :
603 0 : const Segment* startSeg = line()->startSegment();
604 0 : const Segment* endSeg = line()->endSegment();
605 0 : const double xDistanceFromStartSegment = startSeg && startSeg->system() == system()
606 0 : ? (startSeg->x() + startSeg->measure()->x()) - ldata()->pos().x() : 0.0;
607 0 : const double xDistanceFromEndSegment = endSeg && endSeg->system() == system()
608 0 : ? (endSeg->x() + endSeg->measure()->x()) - (ldata()->pos().x() + ipos2().x()) : 0.0;
609 :
610 0 : switch (grip) {
611 0 : case Grip::START:
612 : case Grip::END: {
613 0 : const bool left = (grip == Grip::START);
614 :
615 0 : if (left && !isSingleBeginType()) {
616 0 : return;
617 : }
618 0 : if (!left && !isSingleEndType()) {
619 0 : return;
620 : }
621 :
622 : // Find an appropriate segment to bind from actual mouse
623 : // position. This allows changing systems while dragging
624 : // while not setting line position to something
625 : // inappropriate.
626 0 : Segment* seg = findSegmentForGrip(grip, ed.pos
627 0 : + (left ? PointF(xDistanceFromStartSegment, 0.0) : PointF(xDistanceFromEndSegment, 0.0)));
628 0 : LineSegment* newLineSegment = rebaseAnchor(grip, seg);
629 :
630 0 : if (newLineSegment) {
631 : // If new line segment is not the same as this one,
632 : // switch to dragging that new segment.
633 0 : if (newLineSegment != this) {
634 : // Reset offset for the old line segment
635 0 : if (left) {
636 0 : m_offset2.rx() -= offset().x();
637 0 : setOffset(PointF());
638 : } else {
639 0 : setUserOff2(PointF());
640 : }
641 :
642 : // Switch to dragging the new line segment
643 0 : ed.view()->changeEditElement(newLineSegment);
644 : }
645 :
646 : // Set offset for the new line segment for grip to appear under the mouse cursor
647 : System* sys;
648 0 : const PointF lp = line()->linePos(grip, &sys);
649 0 : const double xoff = sys->mapFromCanvas(ed.pos).x() - lp.x();
650 :
651 0 : if (left) {
652 0 : newLineSegment->rxoffset() = xoff;
653 0 : newLineSegment->setUserOff2(PointF(-xoff, 0.0));
654 : } else {
655 0 : newLineSegment->setUserXoffset2(xoff);
656 : }
657 : }
658 : }
659 0 : break;
660 0 : case Grip::MIDDLE: {
661 0 : if (!isSingleType()) {
662 0 : return;
663 : }
664 :
665 0 : System* sys = system();
666 0 : if (!sys) {
667 0 : return;
668 : }
669 :
670 0 : SLine* l = line();
671 :
672 : // If dragging middle grip (or the entire hairpin), mouse position
673 : // does not directly correspond to any sensible position, so use
674 : // actual line coordinates instead. This method doesn't allow for
675 : // system changes, but that seems OK when dragging the entire line:
676 : // the line will just push away other systems according to autoplacement
677 : // rules if necessary.
678 0 : PointF cpos = canvasPos();
679 0 : cpos.setY(system()->staffCanvasYpage(l->staffIdx())); // prevent cross-system move
680 :
681 0 : Segment* seg1 = findSegmentForGrip(Grip::START, cpos + PointF(xDistanceFromStartSegment, 0.0));
682 0 : Segment* seg2 = findSegmentForGrip(Grip::END, cpos + pos2() + PointF(xDistanceFromEndSegment, 0.0));
683 :
684 0 : if (!(seg1 && seg2 && seg1->system() == seg2->system() && seg1->system() == system())) {
685 0 : return;
686 : }
687 :
688 0 : PointF oldStartPos = line()->linePos(Grip::START, &sys);
689 0 : PointF oldEndPos = line()->linePos(Grip::END, &sys);
690 :
691 0 : undoMoveStartEndAndSnappedItems(ed, true, true, seg1, seg2);
692 :
693 0 : rebaseOffsetsOnAnchorChanged(Grip::START, oldStartPos, sys);
694 0 : rebaseOffsetsOnAnchorChanged(Grip::END, oldEndPos, sys);
695 : }
696 : default:
697 0 : break;
698 : }
699 : }
700 :
701 : //---------------------------------------------------------
702 : // dragGrip
703 : //---------------------------------------------------------
704 :
705 0 : void LineSegment::dragGrip(EditData& ed)
706 : {
707 : // Only for resizing according to the diagonal properties
708 0 : const PointF deltaResize(ed.evtDelta.x(), line()->diagonal() ? ed.evtDelta.y() : 0.0);
709 :
710 0 : switch (ed.curGrip) {
711 0 : case Grip::START: // Resize the begin of element (left grip)
712 0 : setOffset(offset() + deltaResize);
713 0 : m_offset2 -= deltaResize;
714 0 : rebaseAnchors(ed, ed.curGrip);
715 0 : break;
716 0 : case Grip::END: // Resize the end of element (right grip)
717 0 : m_offset2 += deltaResize;
718 0 : rebaseAnchors(ed, ed.curGrip);
719 0 : break;
720 0 : case Grip::MIDDLE: { // Move the element (middle grip)
721 : // Only for moving, no y limitation
722 0 : const PointF deltaMove(ed.evtDelta);
723 0 : setOffset(offset() + deltaMove);
724 0 : rebaseAnchors(ed, ed.curGrip);
725 : }
726 0 : break;
727 0 : default:
728 0 : UNREACHABLE;
729 : return;
730 : }
731 0 : if (line()->anchor() == Spanner::Anchor::NOTE && ed.isStartEndGrip()) {
732 : //
733 : // if we touch a different note, change anchor
734 : //
735 0 : EngravingItem* e = ed.view()->elementNear(ed.pos);
736 0 : if (e && e->isNote()) {
737 0 : SLine* l = line();
738 0 : if (ed.curGrip == Grip::END && e != line()->endElement()) {
739 0 : LOGD("LineSegment: move end anchor");
740 0 : Note* noteOld = toNote(l->endElement());
741 0 : Note* noteNew = toNote(e);
742 0 : Note* sNote = toNote(l->startElement());
743 : // do not change anchor if new note is before start note
744 0 : if (sNote && sNote->chord() && noteNew->chord() && sNote->chord()->tick() < noteNew->chord()->tick()) {
745 0 : score()->undoChangeSpannerElements(l, sNote, noteNew);
746 :
747 0 : m_offset2 += noteOld->canvasPos() - noteNew->canvasPos();
748 : }
749 0 : } else if (ed.curGrip == Grip::START && e != l->startElement()) {
750 0 : LOGD("LineSegment: move start anchor (not impl.)");
751 : }
752 : }
753 : }
754 :
755 0 : EditTimeTickAnchors::updateAnchors(this);
756 :
757 0 : triggerLayout();
758 0 : }
759 :
760 : //---------------------------------------------------------
761 : // spatiumChanged
762 : //---------------------------------------------------------
763 :
764 12 : void LineSegment::spatiumChanged(double ov, double nv)
765 : {
766 12 : EngravingItem::spatiumChanged(ov, nv);
767 12 : double scale = nv / ov;
768 12 : m_offset2 *= scale;
769 12 : }
770 :
771 : //---------------------------------------------------------
772 : // localSpatiumChanged
773 : //---------------------------------------------------------
774 :
775 0 : void LineSegment::localSpatiumChanged(double ov, double nv)
776 : {
777 0 : EngravingItem::localSpatiumChanged(ov, nv);
778 0 : m_offset2 *= nv / ov;
779 0 : }
780 :
781 : //---------------------------------------------------------
782 : // propertyDelegate
783 : //---------------------------------------------------------
784 :
785 6596 : EngravingObject* LineSegment::propertyDelegate(Pid pid) const
786 : {
787 6596 : if (pid == Pid::DIAGONAL
788 6596 : || pid == Pid::COLOR
789 6584 : || pid == Pid::LINE_WIDTH
790 6584 : || pid == Pid::LINE_STYLE
791 6584 : || pid == Pid::DASH_LINE_LEN
792 6584 : || pid == Pid::DASH_GAP_LEN) {
793 12 : return spanner();
794 : }
795 6584 : return SpannerSegment::propertyDelegate(pid);
796 : }
797 :
798 : //---------------------------------------------------------
799 : // dragAnchorLines
800 : //---------------------------------------------------------
801 :
802 0 : std::vector<LineF> LineSegment::dragAnchorLines() const
803 : {
804 0 : return gripAnchorLines(Grip::MIDDLE);
805 : }
806 :
807 0 : RectF LineSegment::drag(EditData& ed)
808 : {
809 0 : setOffset(offset() + ed.evtDelta);
810 0 : rebaseAnchors(ed, Grip::MIDDLE);
811 :
812 0 : return canvasBoundingRect();
813 : }
814 :
815 1096 : Spatium LineSegment::lineWidth() const
816 : {
817 1096 : if (!line()) {
818 0 : return 0.0_sp;
819 : }
820 :
821 1096 : return line()->lineWidth();
822 : }
823 :
824 1667 : double LineSegment::absoluteFromSpatium(const Spatium& sp) const
825 : {
826 1667 : if (!line()) {
827 0 : return EngravingItem::absoluteFromSpatium(sp);
828 : }
829 :
830 1667 : return line()->absoluteFromSpatium(sp);
831 : }
832 :
833 0 : void LineSegment::undoMoveStartEndAndSnappedItems(EditData& ed, bool moveStart, bool moveEnd, Segment* s1, Segment* s2)
834 : {
835 0 : bool moveSnapped = !(ed.modifiers & AltModifier);
836 0 : SLine* thisLine = line();
837 0 : if (moveStart) {
838 0 : Fraction tickDiff = s1->tick() - thisLine->tick();
839 0 : if (EngravingItem* itemSnappedBefore = ldata()->itemSnappedBefore(); itemSnappedBefore && moveSnapped) {
840 0 : if (itemSnappedBefore->isTextBase()) {
841 0 : MoveElementAnchors::moveSegment(itemSnappedBefore, s1, tickDiff);
842 0 : } else if (itemSnappedBefore->isLineSegment()) {
843 0 : toLineSegment(itemSnappedBefore)->line()->undoMoveEnd(tickDiff);
844 0 : thisLine->undoMoveStart(tickDiff);
845 : }
846 : } else {
847 0 : thisLine->undoMoveStart(tickDiff);
848 : }
849 : }
850 0 : if (moveEnd) {
851 0 : Fraction tickDiff = s2->tick() - thisLine->tick2();
852 0 : if (EngravingItem* itemSnappedAfter = thisLine->backSegment()->ldata()->itemSnappedAfter(); itemSnappedAfter && moveSnapped) {
853 0 : if (itemSnappedAfter->isTextBase()) {
854 0 : MoveElementAnchors::moveSegment(itemSnappedAfter, s2, tickDiff);
855 0 : } else if (itemSnappedAfter->isLineSegment()) {
856 0 : toLineSegment(itemSnappedAfter)->line()->undoMoveStart(tickDiff);
857 0 : thisLine->undoMoveEnd(tickDiff);
858 : }
859 : } else {
860 0 : thisLine->undoMoveEnd(tickDiff);
861 : }
862 : }
863 0 : }
864 :
865 : //---------------------------------------------------------
866 : // SLine
867 : //---------------------------------------------------------
868 :
869 2414 : SLine::SLine(const ElementType& type, EngravingObject* parent, ElementFlags f)
870 2414 : : Spanner(type, parent, f)
871 : {
872 2414 : setTrack(0);
873 2414 : m_lineColor = configuration()->defaultColor();
874 2414 : m_lineWidth = 0.15_sp;
875 2414 : }
876 :
877 281 : SLine::SLine(const SLine& s)
878 281 : : Spanner(s)
879 : {
880 281 : m_diagonal = s.m_diagonal;
881 281 : m_lineWidth = s.m_lineWidth;
882 281 : m_lineColor = s.m_lineColor;
883 281 : m_lineStyle = s.m_lineStyle;
884 281 : m_dashLineLen = s.m_dashLineLen;
885 281 : m_dashGapLen = s.m_dashGapLen;
886 281 : }
887 :
888 1401 : PointF SLine::linePos(Grip grip, System** system) const
889 : {
890 1401 : bool start = grip == Grip::START;
891 1401 : bool mmRest = style().styleB(Sid::createMultiMeasureRests);
892 :
893 1401 : switch (anchor()) {
894 920 : case Spanner::Anchor::NOTE:
895 : {
896 920 : EngravingItem* item = start ? startElement() : endElement();
897 920 : Note* note = item && item->isNote() ? toNote(item) : nullptr;
898 920 : if (!note) {
899 0 : return PointF();
900 : }
901 920 : *system = note->chord()->segment()->measure()->system();
902 920 : if (!(*system)) {
903 0 : return PointF();
904 : }
905 920 : return note->pagePos() - (*system)->pagePos();
906 : }
907 481 : case Spanner::Anchor::CHORDREST:
908 : case Spanner::Anchor::SEGMENT:
909 : {
910 481 : Segment* segment = start ? startSegment() : endSegment();
911 481 : if (!segment) {
912 0 : return PointF();
913 : }
914 481 : if (anchor() == Spanner::Anchor::CHORDREST && !segment->isChordRestType()) {
915 0 : return PointF();
916 : }
917 481 : if (!start) {
918 163 : Fraction curTick = segment->tick();
919 : while (true) {
920 239 : Segment* prevSeg = mmRest ? segment->prev1MM() : segment->prev1();
921 239 : if (prevSeg && prevSeg->tick() == curTick) {
922 76 : segment = prevSeg;
923 : } else {
924 163 : break;
925 : }
926 76 : }
927 : }
928 481 : *system = segment->measure()->system();
929 481 : double x = segment->x() + segment->measure()->x() - (start ? 0.0 : spatium());
930 481 : return PointF(x, 0.0);
931 : }
932 0 : case Spanner::Anchor::MEASURE:
933 : {
934 0 : Measure* measure = score()->tick2measureMM(start ? tick() : tick2());
935 0 : if (!measure) {
936 0 : return PointF();
937 : }
938 0 : *system = measure->system();
939 0 : double x = measure->x();
940 0 : return PointF(x, 0.0);
941 : }
942 0 : default:
943 0 : return PointF();
944 : }
945 : }
946 :
947 : //---------------------------------------------------------
948 : // setLen
949 : // used to create an element suitable for palette
950 : //---------------------------------------------------------
951 :
952 0 : void SLine::setLen(double l)
953 : {
954 0 : if (spannerSegments().empty()) {
955 0 : add(createLineSegment());
956 : }
957 0 : LineSegment* s = frontSegment();
958 0 : s->setPos(PointF());
959 0 : s->setPos2(PointF(l, 0));
960 0 : }
961 :
962 : //---------------------------------------------------------
963 : // getProperty
964 : //---------------------------------------------------------
965 :
966 13710 : PropertyValue SLine::getProperty(Pid id) const
967 : {
968 13710 : switch (id) {
969 0 : case Pid::DIAGONAL:
970 0 : return m_diagonal;
971 1089 : case Pid::COLOR:
972 1089 : return PropertyValue::fromValue(m_lineColor);
973 424 : case Pid::LINE_WIDTH:
974 424 : return m_lineWidth;
975 429 : case Pid::LINE_STYLE:
976 429 : return m_lineStyle;
977 416 : case Pid::DASH_LINE_LEN:
978 416 : return dashLineLen();
979 416 : case Pid::DASH_GAP_LEN:
980 416 : return dashGapLen();
981 10936 : default:
982 10936 : return Spanner::getProperty(id);
983 : }
984 : }
985 :
986 : //---------------------------------------------------------
987 : // setProperty
988 : //---------------------------------------------------------
989 :
990 13360 : bool SLine::setProperty(Pid id, const PropertyValue& v)
991 : {
992 13360 : switch (id) {
993 269 : case Pid::DIAGONAL:
994 269 : m_diagonal = v.toBool();
995 269 : break;
996 105 : case Pid::COLOR:
997 105 : setLineColor(v.value<Color>());
998 105 : break;
999 3341 : case Pid::LINE_WIDTH:
1000 3341 : if (v.type() == P_TYPE::ABSOLUTE) {
1001 0 : m_lineWidth = Spatium::fromAbsolute(v.value<double>(), spatium());
1002 3341 : } else if (v.type() == P_TYPE::SPATIUM) {
1003 3341 : m_lineWidth = v.value<Spatium>();
1004 : }
1005 3341 : break;
1006 2697 : case Pid::LINE_STYLE:
1007 2697 : m_lineStyle = v.value<LineType>();
1008 2697 : break;
1009 2536 : case Pid::DASH_LINE_LEN:
1010 2536 : setDashLineLen(v.toDouble());
1011 2536 : break;
1012 2536 : case Pid::DASH_GAP_LEN:
1013 2536 : setDashGapLen(v.toDouble());
1014 2536 : break;
1015 1876 : default:
1016 1876 : return Spanner::setProperty(id, v);
1017 : }
1018 11484 : triggerLayout();
1019 11484 : return true;
1020 : }
1021 :
1022 : //---------------------------------------------------------
1023 : // propertyDefault
1024 : //---------------------------------------------------------
1025 :
1026 14325 : PropertyValue SLine::propertyDefault(Pid pid) const
1027 : {
1028 14325 : switch (pid) {
1029 0 : case Pid::DIAGONAL:
1030 0 : return false;
1031 1085 : case Pid::COLOR:
1032 1085 : return PropertyValue::fromValue(configuration()->defaultColor());
1033 357 : case Pid::LINE_WIDTH:
1034 357 : if (propertyFlags(pid) != PropertyFlags::NOSTYLE) {
1035 9 : return Spanner::propertyDefault(pid);
1036 : }
1037 348 : return 0.15_sp;
1038 374 : case Pid::LINE_STYLE:
1039 374 : if (propertyFlags(pid) != PropertyFlags::NOSTYLE) {
1040 18 : return Spanner::propertyDefault(pid);
1041 : }
1042 356 : return LineType::SOLID;
1043 752 : case Pid::DASH_LINE_LEN:
1044 : case Pid::DASH_GAP_LEN:
1045 752 : if (propertyFlags(pid) != PropertyFlags::NOSTYLE) {
1046 40 : return Spanner::propertyDefault(pid);
1047 : }
1048 712 : return 5.0;
1049 11757 : default:
1050 11757 : return Spanner::propertyDefault(pid);
1051 : }
1052 : }
1053 :
1054 0 : void SLine::undoMoveStart(Fraction tickDiff)
1055 : {
1056 0 : Fraction newTick = tick() + tickDiff;
1057 0 : if (newTick >= Fraction(0, 1)) {
1058 0 : undoChangeProperty(Pid::SPANNER_TICK, newTick);
1059 : }
1060 0 : Fraction newDuration = ticks() - tickDiff;
1061 0 : if (newDuration > Fraction(0, 1)) {
1062 0 : undoChangeProperty(Pid::SPANNER_TICKS, newDuration);
1063 : }
1064 0 : }
1065 :
1066 0 : void SLine::undoMoveEnd(Fraction tickDiff)
1067 : {
1068 0 : Fraction newDuration = ticks() + tickDiff;
1069 0 : if (newDuration > Fraction(0, 1)) {
1070 0 : undoChangeProperty(Pid::SPANNER_TICKS, newDuration);
1071 : } else {
1072 0 : Fraction newTick = tick() + tickDiff;
1073 0 : if (newTick >= Fraction(0, 1)) {
1074 0 : undoChangeProperty(Pid::SPANNER_TICK, tick() + tickDiff);
1075 : }
1076 : }
1077 0 : }
1078 :
1079 11 : Note* SLine::guessFinalNote(Note* startNote)
1080 : {
1081 11 : Chord* chord = startNote->chord();
1082 11 : if (chord->isGraceBefore()) {
1083 1 : Chord* parentChord = toChord(chord->parent());
1084 1 : GraceNotesGroup& gracesBefore = parentChord->graceNotesBefore();
1085 1 : auto positionOfThis = std::find(gracesBefore.begin(), gracesBefore.end(), chord);
1086 1 : if (positionOfThis != gracesBefore.end()) {
1087 1 : auto nextPosition = ++positionOfThis;
1088 1 : if (nextPosition != gracesBefore.end()) {
1089 0 : return (*nextPosition)->upNote();
1090 : }
1091 : }
1092 1 : return parentChord->upNote();
1093 10 : } else if (chord->isGraceAfter()) {
1094 1 : Chord* parentChord = toChord(chord->parent());
1095 1 : GraceNotesGroup& gracesAfter = parentChord->graceNotesAfter();
1096 1 : auto positionOfThis = std::find(gracesAfter.begin(), gracesAfter.end(), chord);
1097 1 : if (positionOfThis != gracesAfter.end()) {
1098 1 : auto nextPosition = ++positionOfThis;
1099 1 : if (nextPosition != gracesAfter.end()) {
1100 0 : return (*nextPosition)->upNote();
1101 : }
1102 : }
1103 1 : chord = toChord(chord->parent());
1104 : } else {
1105 9 : std::vector<Chord*> graces = chord->graceNotesAfter();
1106 9 : if (graces.size() > 0) {
1107 1 : return graces.front()->upNote();
1108 : }
1109 9 : }
1110 :
1111 9 : if (!chord->ownershipParent()->isSegment()) {
1112 0 : return 0;
1113 : }
1114 :
1115 9 : Segment* segm = chord->score()->tick2rightSegment(chord->endTick());
1116 9 : while (segm && !segm->isChordRestType()) {
1117 0 : segm = segm->next1();
1118 : }
1119 :
1120 9 : if (!segm) {
1121 0 : return nullptr;
1122 : }
1123 :
1124 9 : track_idx_t chordTrack = chord->track();
1125 9 : Part* part = chord->part();
1126 :
1127 9 : Chord* target = nullptr;
1128 9 : if (segm->element(chordTrack) && segm->element(chordTrack)->isChord()) {
1129 7 : target = toChord(segm->element(chordTrack));
1130 : } else {
1131 6 : for (EngravingItem* currChord : segm->elist()) {
1132 6 : if (currChord && currChord->isChord() && toChord(currChord)->part() == part) {
1133 2 : target = toChord(currChord);
1134 2 : break;
1135 : }
1136 : }
1137 : }
1138 :
1139 9 : if (target && target->notes().size() > 0) {
1140 9 : const std::vector<Chord*>& graces = target->graceNotesBefore();
1141 9 : if (graces.size() > 0) {
1142 2 : return graces.front()->upNote();
1143 : }
1144 : // normal case: try to return the note in the next chord that is in the
1145 : // same position as the start note relative to the end chord
1146 7 : size_t startNoteIdx = muse::indexOf(chord->notes(), startNote);
1147 7 : size_t endNoteIdx = std::min(startNoteIdx, target->notes().size() - 1);
1148 7 : return target->notes().at(endNoteIdx);
1149 : }
1150 :
1151 0 : LOGD("no second note for note anchored line found");
1152 0 : return nullptr;
1153 : }
1154 : }
|