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 "chordline.h"
24 :
25 : #include <functional>
26 :
27 : #include "types/translatablestring.h"
28 :
29 : #include "../editing/elementeditdata.h"
30 : #include "../types/typesconv.h"
31 :
32 : #include "chord.h"
33 : #include "note.h"
34 :
35 : #include "log.h"
36 :
37 : using namespace mu;
38 : using namespace muse::draw;
39 : using namespace mu::engraving;
40 :
41 : namespace mu::engraving {
42 : //---------------------------------------------------------
43 : // ChordLine
44 : //---------------------------------------------------------
45 :
46 159 : ChordLine::ChordLine(DummyParentOr<Chord> parent)
47 159 : : EngravingItem(ElementType::CHORDLINE, parent, ElementFlag::MOVABLE)
48 : {
49 159 : }
50 :
51 8 : ChordLine::ChordLine(const ChordLine& cl)
52 8 : : EngravingItem(cl)
53 : {
54 8 : m_chordLineType = cl.m_chordLineType;
55 8 : m_straight = cl.m_straight;
56 8 : m_wavy = cl.m_wavy;
57 8 : m_bezierOffsets = cl.m_bezierOffsets;
58 8 : m_playChordLine = cl.m_playChordLine;
59 8 : m_note = cl.m_note;
60 8 : }
61 :
62 : //---------------------------------------------------------
63 : // setChordLineType
64 : //---------------------------------------------------------
65 :
66 164 : void ChordLine::setChordLineType(ChordLineType st)
67 : {
68 164 : m_chordLineType = st;
69 164 : }
70 :
71 0 : const TranslatableString& ChordLine::chordLineTypeName() const
72 : {
73 0 : return TConv::userName(m_chordLineType, m_straight, m_wavy);
74 : }
75 :
76 178 : ChordLine::Points ChordLine::defaultPoints() const
77 : {
78 178 : const Spatium x = isToTheLeft() ? -1.2_sp : 1.2_sp; // let the symbols extend a bit more horizontally
79 178 : const Spatium y = isBelow() ? 1.0_sp : -1.0_sp;
80 :
81 178 : if (isToTheLeft()) {
82 154 : return { PointF(), PointF(0.0, (y / 2).val()), PointF((x / 2).val(), y.val()), PointF(x.val(), y.val()) };
83 : }
84 :
85 202 : return { PointF(), PointF((x / 2).val(), 0.0), PointF(x.val(), (y / 2).val()), PointF(x.val(), y.val()) };
86 : }
87 :
88 178 : double ChordLine::chordMag() const
89 : {
90 178 : const EngravingObject* parent = ownershipParent();
91 178 : return parent && parent->isChord() ? toChord(parent)->intrinsicMag() : 1.0;
92 : }
93 :
94 410 : Pid ChordLine::bezierOffsetPid(Point point)
95 : {
96 410 : switch (point) {
97 102 : case Point::START: return Pid::BEZIER_OFFSET1;
98 102 : case Point::BEZIER1: return Pid::BEZIER_OFFSET2;
99 102 : case Point::BEZIER2: return Pid::BEZIER_OFFSET3;
100 104 : case Point::END: return Pid::BEZIER_OFFSET4;
101 0 : case Point::POINTS_COUNT: break;
102 : }
103 :
104 0 : UNREACHABLE;
105 : return Pid::END;
106 : }
107 :
108 2 : ChordLine::Point ChordLine::gripPoint(Grip grip) const
109 : {
110 : // straight lines only have one grip, for the end point
111 2 : return m_straight ? Point::END : Point(static_cast<int>(grip));
112 : }
113 :
114 6 : int ChordLine::gripsCount() const
115 : {
116 6 : if (m_wavy || m_chordLineType == ChordLineType::NOTYPE) {
117 2 : return 0;
118 : }
119 :
120 4 : return m_straight ? 1 : static_cast<int>(Point::POINTS_COUNT);
121 : }
122 :
123 : //---------------------------------------------------------
124 : // startDragGrip
125 : //---------------------------------------------------------
126 :
127 0 : void ChordLine::startDragGrip(EditData& ed)
128 : {
129 0 : EngravingItem::startDragGrip(ed);
130 0 : ElementEditDataPtr eed = ed.getData(this);
131 :
132 0 : for (size_t i = 0; i < size_t(Point::POINTS_COUNT); ++i) {
133 0 : eed->pushProperty(bezierOffsetPid(Point(i)));
134 : }
135 0 : }
136 :
137 : //---------------------------------------------------------
138 : // dragGrip
139 : //---------------------------------------------------------
140 :
141 3 : void ChordLine::dragGrip(EditData& ed)
142 : {
143 3 : if (m_wavy || ed.curGrip == Grip::NO_GRIP || int(ed.curGrip) >= gripsCount()) {
144 1 : return;
145 : }
146 :
147 2 : const Point point = gripPoint(ed.curGrip);
148 : // offsets are at the chord's normal size, the drawn shape is scaled by its mag
149 2 : const PointF offset = bezierOffset(point) + ed.delta / chordMag();
150 :
151 2 : if (m_straight) {
152 : // stop the user from being able to turn one kind of slide into another
153 : // (the shape at the chord's normal size is enough to check the direction)
154 0 : const Points points = defaultPoints();
155 0 : const double sp = spatium();
156 0 : const PointF start = points[size_t(Point::START)] * sp + bezierOffset(Point::START);
157 0 : const PointF end = points[size_t(Point::END)] * sp + offset;
158 0 : if ((m_chordLineType == ChordLineType::FALL && (end.x() < start.x() || end.y() < start.y()))
159 0 : || (m_chordLineType == ChordLineType::DOIT && (end.x() < start.x() || end.y() > start.y()))
160 0 : || (m_chordLineType == ChordLineType::SCOOP && (end.x() > start.x() || end.y() < start.y()))
161 0 : || (m_chordLineType == ChordLineType::PLOP && (end.x() > start.x() || end.y() > start.y()))) {
162 0 : return;
163 : }
164 : }
165 :
166 2 : setBezierOffset(point, offset);
167 2 : triggerLayout();
168 : }
169 :
170 : //---------------------------------------------------------
171 : // gripsPositions
172 : //---------------------------------------------------------
173 :
174 0 : std::vector<PointF> ChordLine::gripsPositions(const EditData&) const
175 : {
176 0 : if (m_wavy) {
177 0 : NOT_IMPLEMENTED;
178 0 : return {};
179 : }
180 :
181 0 : const PainterPath& path = ldata()->path;
182 :
183 0 : size_t n = path.elementCount();
184 0 : if (n != (m_straight ? 2 : size_t(Point::POINTS_COUNT))) {
185 0 : return {};
186 : }
187 :
188 0 : PointF cp(pagePos());
189 0 : if (m_straight) {
190 : // limit the number of grips to one
191 0 : double offset = 0.5;
192 0 : PointF p;
193 :
194 0 : if (m_chordLineType == ChordLineType::FALL) {
195 0 : p = PointF(offset, -offset);
196 0 : } else if (m_chordLineType == ChordLineType::DOIT) {
197 0 : p = PointF(offset, offset);
198 0 : } else if (m_chordLineType == ChordLineType::SCOOP) {
199 0 : p = PointF(-offset, offset);
200 0 : } else if (m_chordLineType == ChordLineType::PLOP) {
201 0 : p = PointF(-offset, -offset);
202 : }
203 :
204 : // translate on the length and height - stops the grips from going past boundaries of slide
205 0 : p += (cp + PointF(path.elementAt(1).x, path.elementAt(1).y));
206 0 : return { p };
207 : } else {
208 0 : std::vector<PointF> grips(n);
209 0 : for (size_t i = 0; i < n; ++i) {
210 0 : grips[i] = cp + PointF(path.elementAt(i).x, path.elementAt(i).y);
211 : }
212 0 : return grips;
213 0 : }
214 : }
215 :
216 : //---------------------------------------------------------
217 : // slideType
218 : //---------------------------------------------------------
219 :
220 119 : static Note::SlideType slideType(ChordLineType type)
221 : {
222 : static const std::unordered_map<ChordLineType, Note::SlideType> chordLineToSlideTypes {
223 : { ChordLineType::FALL, Note::SlideType::DownFromNote },
224 : { ChordLineType::DOIT, Note::SlideType::UpFromNote },
225 : { ChordLineType::SCOOP, Note::SlideType::UpToNote },
226 : { ChordLineType::PLOP, Note::SlideType::DownToNote }
227 125 : };
228 :
229 119 : auto it = chordLineToSlideTypes.find(type);
230 119 : if (it != chordLineToSlideTypes.end()) {
231 119 : return it->second;
232 : }
233 :
234 0 : return Note::SlideType::Undefined;
235 : }
236 :
237 : //---------------------------------------------------------
238 : // setNote
239 : //---------------------------------------------------------
240 :
241 119 : void ChordLine::setNote(Note* note)
242 : {
243 119 : m_note = note;
244 :
245 119 : if (note) {
246 119 : note->attachSlide(slideType(m_chordLineType));
247 : }
248 119 : }
249 :
250 6 : SymId ChordLine::waveSym() const
251 : {
252 6 : if (m_chordLineType == ChordLineType::FALL || m_chordLineType == ChordLineType::PLOP) {
253 4 : return SymId::brassFallRoughShort;
254 : }
255 :
256 2 : return SymId::brassLiftShort;
257 : }
258 :
259 : //---------------------------------------------------------
260 : // accessibleInfo
261 : //---------------------------------------------------------
262 :
263 0 : String ChordLine::accessibleInfo() const
264 : {
265 0 : String rez = EngravingItem::accessibleInfo();
266 0 : if (chordLineType() != ChordLineType::NOTYPE) {
267 0 : rez = String(u"%1: %2").arg(rez, chordLineTypeName().translated());
268 : }
269 0 : return rez;
270 0 : }
271 :
272 0 : int ChordLine::subtype() const
273 : {
274 0 : size_t h1 = std::hash<ChordLineType> {}(m_chordLineType);
275 0 : size_t h2 = std::hash<bool> {}(m_straight);
276 0 : size_t h3 = std::hash<bool> {}(m_wavy);
277 :
278 0 : return static_cast<int>(h1 ^ (h2 << 1) ^ (h3 << 2));
279 : }
280 :
281 0 : muse::TranslatableString ChordLine::subtypeUserName() const
282 : {
283 0 : return chordLineTypeName();
284 : }
285 :
286 : //---------------------------------------------------------
287 : // getProperty
288 : //---------------------------------------------------------
289 :
290 1674 : PropertyValue ChordLine::getProperty(Pid propertyId) const
291 : {
292 1674 : switch (propertyId) {
293 99 : case Pid::BEZIER_OFFSET1:
294 99 : return bezierOffset(Point::START);
295 99 : case Pid::BEZIER_OFFSET2:
296 99 : return bezierOffset(Point::BEZIER1);
297 99 : case Pid::BEZIER_OFFSET3:
298 99 : return bezierOffset(Point::BEZIER2);
299 108 : case Pid::BEZIER_OFFSET4:
300 108 : return bezierOffset(Point::END);
301 97 : case Pid::CHORD_LINE_TYPE:
302 97 : return int(m_chordLineType);
303 97 : case Pid::CHORD_LINE_STRAIGHT:
304 97 : return m_straight;
305 97 : case Pid::CHORD_LINE_WAVY:
306 97 : return m_wavy;
307 97 : case Pid::PLAY:
308 97 : return m_playChordLine;
309 881 : default:
310 881 : break;
311 : }
312 881 : return EngravingItem::getProperty(propertyId);
313 : }
314 :
315 : //---------------------------------------------------------
316 : // setProperty
317 : //---------------------------------------------------------
318 :
319 10 : bool ChordLine::setProperty(Pid propertyId, const PropertyValue& val)
320 : {
321 10 : switch (propertyId) {
322 1 : case Pid::BEZIER_OFFSET1:
323 1 : setBezierOffset(Point::START, val.value<PointF>());
324 1 : break;
325 2 : case Pid::BEZIER_OFFSET2:
326 2 : setBezierOffset(Point::BEZIER1, val.value<PointF>());
327 2 : break;
328 1 : case Pid::BEZIER_OFFSET3:
329 1 : setBezierOffset(Point::BEZIER2, val.value<PointF>());
330 1 : break;
331 6 : case Pid::BEZIER_OFFSET4:
332 6 : setBezierOffset(Point::END, val.value<PointF>());
333 6 : break;
334 0 : case Pid::CHORD_LINE_TYPE:
335 0 : setChordLineType(ChordLineType(val.toInt()));
336 0 : break;
337 0 : case Pid::CHORD_LINE_STRAIGHT:
338 0 : setStraight(val.toBool());
339 0 : break;
340 0 : case Pid::CHORD_LINE_WAVY:
341 0 : setWavy(val.toBool());
342 0 : break;
343 0 : case Pid::PLAY:
344 0 : setPlayChordLine(val.toBool());
345 0 : break;
346 0 : default:
347 0 : return EngravingItem::setProperty(propertyId, val);
348 : }
349 10 : triggerLayout();
350 10 : return true;
351 : }
352 :
353 : //---------------------------------------------------------
354 : // propertyDefault
355 : //---------------------------------------------------------
356 :
357 1662 : PropertyValue ChordLine::propertyDefault(Pid pid) const
358 : {
359 1662 : switch (pid) {
360 97 : case Pid::CHORD_LINE_STRAIGHT:
361 97 : return false;
362 97 : case Pid::CHORD_LINE_WAVY:
363 97 : return false;
364 97 : case Pid::PLAY:
365 97 : return true;
366 396 : case Pid::BEZIER_OFFSET1:
367 : case Pid::BEZIER_OFFSET2:
368 : case Pid::BEZIER_OFFSET3:
369 : case Pid::BEZIER_OFFSET4:
370 792 : return PointF();
371 975 : default:
372 975 : break;
373 : }
374 975 : return EngravingItem::propertyDefault(pid);
375 : }
376 :
377 : //---------------------------------------------------------
378 : // reset
379 : //---------------------------------------------------------
380 :
381 1 : void ChordLine::reset()
382 : {
383 5 : for (size_t i = 0; i < size_t(Point::POINTS_COUNT); ++i) {
384 4 : undoResetProperty(bezierOffsetPid(Point(i)));
385 : }
386 1 : EngravingItem::reset();
387 1 : }
388 :
389 : //---------------------------------------------------------
390 : // spatiumChanged
391 : //---------------------------------------------------------
392 :
393 2 : void ChordLine::spatiumChanged(double oldValue, double newValue)
394 : {
395 2 : EngravingItem::spatiumChanged(oldValue, newValue);
396 10 : for (PointF& offset : m_bezierOffsets) {
397 8 : offset *= newValue / oldValue;
398 : }
399 2 : }
400 :
401 : //---------------------------------------------------------
402 : // localSpatiumChanged
403 : //---------------------------------------------------------
404 :
405 0 : void ChordLine::localSpatiumChanged(double oldValue, double newValue)
406 : {
407 0 : EngravingItem::localSpatiumChanged(oldValue, newValue);
408 0 : for (PointF& offset : m_bezierOffsets) {
409 0 : offset *= newValue / oldValue;
410 : }
411 0 : }
412 : }
|