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 "box.h"
24 :
25 : #include <cmath>
26 :
27 : #include "../editing/elementeditdata.h"
28 : #include "../editing/editfretboarddiagram.h"
29 :
30 : #include "actionicon.h"
31 : #include "factory.h"
32 : #include "fret.h"
33 : #include "harmony.h"
34 : #include "layoutbreak.h"
35 : #include "masterscore.h"
36 : #include "measure.h"
37 : #include "mscore.h"
38 : #include "score.h"
39 : #include "stafftext.h"
40 : #include "system.h"
41 : #include "text.h"
42 :
43 : #include "log.h"
44 :
45 : using namespace mu;
46 : using namespace mu::engraving;
47 : using namespace muse::draw;
48 :
49 : namespace mu::engraving {
50 : static const ElementStyle boxStyle {
51 : { Sid::systemFrameDistance, Pid::TOP_GAP },
52 : { Sid::frameSystemDistance, Pid::BOTTOM_GAP },
53 : { Sid::paddingToNotationAbove, Pid::PADDING_TO_NOTATION_ABOVE },
54 : { Sid::paddingToNotationBelow, Pid::PADDING_TO_NOTATION_BELOW },
55 : };
56 :
57 : static const ElementStyle hBoxStyle {
58 : };
59 :
60 : static const String FRET_BOX_DIAGRAMS_SEPARATOR = u",";
61 :
62 1423 : Box::Box(const ElementType& type, Score* parent)
63 1423 : : MeasureBase(type, parent)
64 : {
65 1423 : }
66 :
67 : //---------------------------------------------------------
68 : // computeMinWidth
69 : //---------------------------------------------------------
70 :
71 358 : void HBox::computeMinWidth()
72 : {
73 358 : setWidth(absoluteFromSpatium(boxWidth() + topGap() + bottomGap())); // top/bottom is really left/right
74 358 : }
75 :
76 0 : bool Box::isEditAllowed(EditData&) const
77 : {
78 0 : return false;
79 : }
80 :
81 : //---------------------------------------------------------
82 : // edit
83 : //---------------------------------------------------------
84 :
85 0 : bool Box::edit(EditData&)
86 : {
87 0 : return false;
88 : }
89 :
90 : //---------------------------------------------------------
91 : // startDragGrip
92 : //---------------------------------------------------------
93 :
94 0 : void Box::startDragGrip(EditData& ed)
95 : {
96 0 : ElementEditDataPtr eed = ed.getData(this);
97 0 : if (isHBox()) {
98 0 : eed->pushProperty(Pid::BOX_WIDTH);
99 : } else {
100 0 : eed->pushProperty(Pid::BOX_HEIGHT);
101 : }
102 0 : }
103 :
104 0 : void Box::dragGrip(EditData& ed)
105 : {
106 0 : const double sp = sizeIsSpatiumDependent() ? spatium() : style().defaultSpatium();
107 0 : if (isVBox()) {
108 0 : m_boxHeight += Spatium(ed.delta.y() / sp);
109 0 : if (ed.vRaster) {
110 0 : double vRaster = 1.0 / MScore::vRaster();
111 0 : int n = lrint(m_boxHeight.val() / vRaster);
112 0 : m_boxHeight = Spatium(vRaster * n);
113 : }
114 0 : mutldata()->setBbox(0.0, 0.0, system()->width(), absoluteFromSpatium(boxHeight()));
115 0 : system()->setHeight(height());
116 : } else {
117 0 : m_boxWidth += Spatium(ed.delta.x() / sp);
118 0 : if (ed.hRaster) {
119 0 : double hRaster = 1.0 / MScore::hRaster();
120 0 : int n = lrint(m_boxWidth.val() / hRaster);
121 0 : m_boxWidth = Spatium(hRaster * n);
122 : }
123 : }
124 0 : triggerLayout();
125 0 : }
126 :
127 : //---------------------------------------------------------
128 : // gripsPositions
129 : //---------------------------------------------------------
130 :
131 0 : std::vector<PointF> HBox::gripsPositions(const EditData&) const
132 : {
133 0 : RectF r(pageBoundingRect());
134 0 : return { PointF(r.right(), r.top() + r.height() * .5) };
135 : }
136 :
137 0 : std::vector<PointF> VBox::gripsPositions(const EditData&) const
138 : {
139 0 : RectF r(pageBoundingRect());
140 0 : return { PointF(r.x() + r.width() * .5, r.bottom()) };
141 : }
142 :
143 : //---------------------------------------------------------
144 : // add
145 : /// Add new EngravingItem \a el to Box
146 : //---------------------------------------------------------
147 :
148 2363 : void Box::add(EngravingItem* e)
149 : {
150 2363 : if (e->isText()) {
151 2331 : toText(e)->setLayoutToParentWidth(true);
152 : }
153 2363 : MeasureBase::add(e);
154 2363 : }
155 :
156 14882 : double Box::absoluteFromSpatium(const Spatium& val) const
157 : {
158 14882 : const double sp = sizeIsSpatiumDependent() ? spatium() : style().defaultSpatium();
159 14882 : return val.val() * sp;
160 : }
161 :
162 1504 : RectF Box::contentRect() const
163 : {
164 1504 : RectF result;
165 :
166 4339 : for (const EngravingItem* element : el()) {
167 2835 : result = result.united(element->ldata()->bbox());
168 : }
169 :
170 1504 : return result;
171 : }
172 :
173 : //---------------------------------------------------------
174 : // getProperty
175 : //---------------------------------------------------------
176 :
177 10603 : PropertyValue Box::getProperty(Pid propertyId) const
178 : {
179 10603 : switch (propertyId) {
180 653 : case Pid::BOX_HEIGHT:
181 653 : return m_boxHeight;
182 653 : case Pid::BOX_WIDTH:
183 653 : return m_boxWidth;
184 31 : case Pid::TOP_GAP:
185 31 : return m_topGap;
186 33 : case Pid::BOTTOM_GAP:
187 33 : return m_bottomGap;
188 653 : case Pid::LEFT_MARGIN:
189 653 : return m_leftMargin;
190 653 : case Pid::RIGHT_MARGIN:
191 653 : return m_rightMargin;
192 665 : case Pid::TOP_MARGIN:
193 665 : return m_topMargin;
194 665 : case Pid::BOTTOM_MARGIN:
195 665 : return m_bottomMargin;
196 30 : case Pid::BOX_AUTOSIZE:
197 30 : return (score()->mscVersion() >= 302) ? m_isAutoSizeEnabled : false;
198 6567 : default:
199 6567 : return MeasureBase::getProperty(propertyId);
200 : }
201 : }
202 :
203 : //---------------------------------------------------------
204 : // setProperty
205 : //---------------------------------------------------------
206 :
207 4540 : bool Box::setProperty(Pid propertyId, const PropertyValue& v)
208 : {
209 4540 : switch (propertyId) {
210 1438 : case Pid::BOX_HEIGHT:
211 1438 : m_boxHeight = v.value<Spatium>();
212 1438 : break;
213 87 : case Pid::BOX_WIDTH:
214 87 : m_boxWidth = v.value<Spatium>();
215 87 : break;
216 1410 : case Pid::TOP_GAP:
217 1410 : m_topGap = v.value<Spatium>();
218 1410 : break;
219 1409 : case Pid::BOTTOM_GAP:
220 1409 : m_bottomGap = v.value<Spatium>();
221 1409 : break;
222 10 : case Pid::LEFT_MARGIN:
223 10 : m_leftMargin = v.toDouble();
224 10 : break;
225 10 : case Pid::RIGHT_MARGIN:
226 10 : m_rightMargin = v.toDouble();
227 10 : break;
228 5 : case Pid::TOP_MARGIN:
229 5 : m_topMargin = v.toDouble();
230 5 : break;
231 5 : case Pid::BOTTOM_MARGIN:
232 5 : m_bottomMargin = v.toDouble();
233 5 : break;
234 0 : case Pid::BOX_AUTOSIZE:
235 0 : m_isAutoSizeEnabled = v.toBool();
236 0 : break;
237 166 : default:
238 166 : return MeasureBase::setProperty(propertyId, v);
239 : }
240 4374 : triggerLayout();
241 4374 : return true;
242 : }
243 :
244 : //---------------------------------------------------------
245 : // propertyDefault
246 : //---------------------------------------------------------
247 :
248 10846 : PropertyValue Box::propertyDefault(Pid id) const
249 : {
250 10846 : switch (id) {
251 653 : case Pid::BOX_HEIGHT:
252 : case Pid::BOX_WIDTH:
253 653 : return 0.0_sp;
254 :
255 52 : case Pid::TOP_GAP:
256 52 : return isHBox() ? 0.0_sp : style().styleS(Sid::systemFrameDistance);
257 53 : case Pid::BOTTOM_GAP:
258 53 : return isHBox() ? 0.0_sp : style().styleS(Sid::frameSystemDistance);
259 :
260 2642 : case Pid::LEFT_MARGIN:
261 : case Pid::RIGHT_MARGIN:
262 : case Pid::TOP_MARGIN:
263 : case Pid::BOTTOM_MARGIN:
264 2642 : return 0.0;
265 653 : case Pid::BOX_AUTOSIZE:
266 653 : return true;
267 902 : case Pid::SIZE_SPATIUM_DEPENDENT:
268 902 : return !isTitleFrame();
269 5891 : default:
270 5891 : return MeasureBase::propertyDefault(id);
271 : }
272 : }
273 :
274 993 : bool Box::isTitleFrame() const
275 : {
276 993 : return this == score()->first() && isVBox();
277 : }
278 :
279 : //---------------------------------------------------------
280 : // copyValues
281 : //---------------------------------------------------------
282 :
283 39 : void Box::copyValues(Box* origin)
284 : {
285 39 : m_boxHeight = origin->boxHeight();
286 39 : m_boxWidth = origin->boxWidth();
287 :
288 39 : double factor = magS() / origin->magS();
289 39 : m_bottomGap = origin->bottomGap() * factor;
290 39 : m_topGap = origin->topGap() * factor;
291 39 : m_bottomMargin = origin->bottomMargin() * factor;
292 39 : m_topMargin = origin->topMargin() * factor;
293 39 : m_leftMargin = origin->leftMargin() * factor;
294 39 : m_rightMargin = origin->rightMargin() * factor;
295 :
296 39 : setSizeIsSpatiumDependent(origin->sizeIsSpatiumDependent());
297 39 : }
298 :
299 : //---------------------------------------------------------
300 : // HBox
301 : //---------------------------------------------------------
302 :
303 87 : HBox::HBox(Score* parent)
304 87 : : Box(ElementType::HBOX, parent)
305 : {
306 87 : initElementStyle(&hBoxStyle);
307 87 : resetProperty(Pid::BOX_WIDTH);
308 87 : }
309 :
310 : //---------------------------------------------------------
311 : // acceptDrop
312 : //---------------------------------------------------------
313 :
314 0 : bool Box::acceptDrop(EditData& data) const
315 : {
316 0 : if (data.dropElement->flag(ElementFlag::ON_STAFF)) {
317 0 : return false;
318 : }
319 0 : if (MScore::debugMode) {
320 0 : LOGD("<%s>", data.dropElement->typeName());
321 : }
322 0 : ElementType t = data.dropElement->type();
323 0 : switch (t) {
324 0 : case ElementType::TEXT:
325 : case ElementType::STAFF_TEXT:
326 : case ElementType::IMAGE:
327 : case ElementType::SYMBOL:
328 0 : return true;
329 0 : case ElementType::ACTION_ICON:
330 0 : switch (toActionIcon(data.dropElement)->actionType()) {
331 0 : case ActionIconType::VFRAME:
332 : case ActionIconType::TFRAME:
333 : case ActionIconType::FFRAME:
334 : case ActionIconType::HFRAME:
335 : case ActionIconType::MEASURE:
336 0 : return true;
337 0 : default:
338 0 : break;
339 : }
340 0 : break;
341 0 : case ElementType::BAR_LINE:
342 0 : return isHBox();
343 0 : default:
344 0 : break;
345 : }
346 0 : return MeasureBase::acceptDrop(data);
347 : }
348 :
349 : //---------------------------------------------------------
350 : // drop
351 : //---------------------------------------------------------
352 :
353 0 : EngravingItem* Box::drop(Transaction& tx, EditData& data)
354 : {
355 0 : EngravingItem* e = data.dropElement;
356 0 : if (e->flag(ElementFlag::ON_STAFF)) {
357 0 : return 0;
358 : }
359 0 : if (MScore::debugMode) {
360 0 : LOGD("<%s>", e->typeName());
361 : }
362 0 : switch (e->type()) {
363 0 : case ElementType::STAFF_TEXT:
364 : {
365 0 : Text* text = Factory::createText(this, TextStyleType::FRAME);
366 0 : text->setXmlText(toStaffText(e)->xmlText());
367 0 : score()->undoAddElement(text);
368 0 : delete e;
369 0 : return text;
370 : }
371 :
372 0 : case ElementType::ACTION_ICON:
373 0 : switch (toActionIcon(e)->actionType()) {
374 0 : case ActionIconType::VFRAME:
375 0 : score()->insertBox(ElementType::VBOX, this);
376 0 : break;
377 0 : case ActionIconType::TFRAME:
378 0 : score()->insertBox(ElementType::TBOX, this);
379 0 : break;
380 0 : case ActionIconType::FFRAME:
381 0 : score()->insertBox(ElementType::FBOX, this);
382 0 : break;
383 0 : case ActionIconType::HFRAME:
384 0 : score()->insertBox(ElementType::HBOX, this);
385 0 : break;
386 0 : case ActionIconType::MEASURE:
387 0 : score()->insertMeasure(ElementType::MEASURE, this);
388 0 : break;
389 0 : default:
390 0 : break;
391 : }
392 0 : break;
393 :
394 0 : case ElementType::TEXT:
395 : case ElementType::IMAGE:
396 : case ElementType::SYMBOL:
397 0 : e->setOwnershipParent(this);
398 0 : score()->undoAddElement(e);
399 0 : return e;
400 0 : default:
401 0 : break;
402 : }
403 :
404 0 : return MeasureBase::drop(tx, data);
405 : }
406 :
407 51 : void Box::manageExclusionFromParts(bool exclude)
408 : {
409 : // manage Layout Breaks - remove old ones first
410 51 : LayoutBreak* sectionBreak = sectionBreakElement();
411 51 : if (sectionBreak) {
412 0 : toEngravingItem(sectionBreak)->manageExclusionFromParts(true);
413 : }
414 :
415 51 : bool titleFrame = isTitleFrame();
416 51 : if (exclude) {
417 2 : const std::list<EngravingObject*> links = linkList();
418 6 : for (EngravingObject* linkedObject : links) {
419 : // Only remove title frame from score
420 4 : if (linkedObject->score() == score() || (!this->score()->isMaster() && titleFrame && !linkedObject->score()->isMaster())) {
421 2 : continue;
422 : }
423 2 : EngravingItem* linkedItem = toEngravingItem(linkedObject);
424 2 : if (linkedItem->selected()) {
425 0 : linkedItem->score()->deselect(linkedItem);
426 : }
427 2 : linkedItem->score()->undoRemoveElement(linkedItem, false);
428 2 : linkedItem->undoUnlink();
429 : }
430 :
431 : // manage Layout Breaks - there are no linked boxes, so add linked Line Breaks to previous measure
432 2 : if (sectionBreak && !titleFrame) {
433 0 : if (MeasureBase* prevMeasure = this->prevMeasure()) {
434 0 : for (Score* score : masterScore()->scoreList()) {
435 0 : if (score == this->score()) {
436 0 : continue;
437 : }
438 0 : if (MeasureBase* localPrevMeasure = score->tick2measure(prevMeasure->tick())) {
439 0 : EngravingItem* newSectionBreak = sectionBreak->linkedClone();
440 0 : newSectionBreak->setScore(score);
441 0 : newSectionBreak->setOwnershipParent(localPrevMeasure);
442 0 : score->doUndoAddElement(newSectionBreak);
443 : }
444 0 : }
445 : }
446 : }
447 2 : } else {
448 101 : for (Score* score : masterScore()->scoreList()) {
449 52 : if (score == this->score() || (titleFrame && !this->score()->isMaster() && !score->isMaster())) {
450 33 : continue;
451 : }
452 :
453 19 : MeasureBase* newMB = next() ? next()->getInScore(score, true) : nullptr;
454 19 : Score::InsertMeasureOptions options;
455 19 : options.cloneBoxToAllParts = false;
456 19 : MeasureBase* newFrame = score->insertBox(type(), newMB, options);
457 19 : newFrame->setExcludeFromOtherParts(false);
458 : // newFrame->setSizeIsSpatiumDependent(!titleFrame);
459 :
460 19 : if (!isFBox()) {
461 19 : for (EngravingItem* item : el()) {
462 : // Don't add instrument name from current part
463 0 : if (item->isText() && toText(item)->textStyleType() == TextStyleType::INSTRUMENT_EXCERPT) {
464 0 : continue;
465 : }
466 : // add frame items (Layout Break, Title, ...)
467 0 : newFrame->add(item->linkedClone());
468 : }
469 : }
470 :
471 19 : if (isTBox()) {
472 0 : Text* thisText = toTBox(this)->text();
473 0 : toTBox(newFrame)->add(thisText->linkedClone());
474 : }
475 :
476 19 : if (!score->isMaster() && titleFrame) {
477 : // Title frame - add part name
478 1 : String partLabel = score->name();
479 1 : if (!partLabel.empty()) {
480 1 : Text* txt = Factory::createText(newFrame, TextStyleType::INSTRUMENT_EXCERPT);
481 1 : txt->setPlainText(partLabel);
482 1 : newFrame->add(txt);
483 :
484 1 : score->setMetaTag(u"partName", partLabel);
485 : }
486 1 : }
487 :
488 19 : newFrame->linkTo(this);
489 49 : }
490 : }
491 51 : }
492 :
493 : //---------------------------------------------------------
494 : // drag
495 : //---------------------------------------------------------
496 :
497 0 : RectF HBox::drag(EditData& data)
498 : {
499 0 : if (!isMovable()) {
500 0 : return RectF();
501 : }
502 0 : RectF r(canvasBoundingRect());
503 0 : double diff = data.evtDelta.x();
504 0 : double x1 = offset().x() + diff;
505 0 : EngravingItem* parent = layoutParent();
506 0 : if (parent && parent->isVBox()) {
507 0 : VBox* vb = toVBox(parent);
508 0 : double x2 = parent->width() - width() - (vb->leftMargin() + vb->rightMargin()) * DPMM;
509 0 : if (x1 < 0.0) {
510 0 : x1 = 0.0;
511 0 : } else if (x1 > x2) {
512 0 : x1 = x2;
513 : }
514 : }
515 0 : setOffset(PointF(x1, 0.0));
516 : // setStartDragPosition(data.delta);
517 0 : return canvasBoundingRect().united(r);
518 : }
519 :
520 : //---------------------------------------------------------
521 : // isMovable
522 : //---------------------------------------------------------
523 :
524 0 : bool HBox::isMovable() const
525 : {
526 0 : return ownershipParent() && (ownershipParent()->isHBox() || ownershipParent()->isVBox());
527 : }
528 :
529 : //---------------------------------------------------------
530 : // getProperty
531 : //---------------------------------------------------------
532 :
533 612 : PropertyValue HBox::getProperty(Pid propertyId) const
534 : {
535 612 : switch (propertyId) {
536 30 : case Pid::CREATE_SYSTEM_HEADER:
537 30 : return createSystemHeader();
538 582 : default:
539 582 : return Box::getProperty(propertyId);
540 : }
541 : }
542 :
543 : //---------------------------------------------------------
544 : // setProperty
545 : //---------------------------------------------------------
546 :
547 89 : bool HBox::setProperty(Pid propertyId, const PropertyValue& v)
548 : {
549 89 : switch (propertyId) {
550 0 : case Pid::CREATE_SYSTEM_HEADER:
551 0 : setCreateSystemHeader(v.toBool());
552 0 : triggerLayout();
553 0 : break;
554 89 : default:
555 89 : return Box::setProperty(propertyId, v);
556 : }
557 0 : return true;
558 : }
559 :
560 : //---------------------------------------------------------
561 : // propertyDefault
562 : //---------------------------------------------------------
563 :
564 671 : PropertyValue HBox::propertyDefault(Pid id) const
565 : {
566 671 : switch (id) {
567 30 : case Pid::CREATE_SYSTEM_HEADER:
568 30 : return true;
569 87 : case Pid::BOX_WIDTH:
570 87 : return 5.0_sp;
571 554 : default:
572 554 : return Box::propertyDefault(id);
573 : }
574 : }
575 :
576 : //---------------------------------------------------------
577 : // VBox
578 : //---------------------------------------------------------
579 :
580 1336 : VBox::VBox(const ElementType& type, Score* parent)
581 1336 : : Box(type, parent)
582 : {
583 1336 : initElementStyle(&boxStyle);
584 :
585 1336 : resetProperty(Pid::BOX_HEIGHT);
586 :
587 1336 : setLineBreak(true);
588 1336 : }
589 :
590 1229 : VBox::VBox(Score* parent)
591 1229 : : VBox(ElementType::VBOX, parent)
592 : {
593 1229 : }
594 :
595 2348 : void VBox::add(EngravingItem* e)
596 : {
597 2348 : IF_ASSERT_FAILED(e) {
598 0 : return;
599 : }
600 2348 : if (e->isLayoutBreak()) {
601 11 : const LayoutBreakType type = toLayoutBreak(e)->layoutBreakType();
602 11 : if (type == LayoutBreakType::LINE) {
603 0 : LOGW("Cannot add %s", e->typeName());
604 0 : return;
605 : }
606 : }
607 2348 : if (e->isActionIcon()) {
608 0 : const ActionIconType type = toActionIcon(e)->actionType();
609 0 : if (type == ActionIconType::SYSTEM_LOCK) {
610 0 : LOGW("Cannot add %s", e->typeName());
611 0 : return;
612 : }
613 : }
614 2348 : Box::add(e);
615 : }
616 :
617 2296 : double VBox::minHeight() const
618 : {
619 2296 : return absoluteFromSpatium(10_sp);
620 : }
621 :
622 0 : double VBox::maxHeight() const
623 : {
624 0 : return absoluteFromSpatium(30_sp);
625 : }
626 :
627 12791 : PropertyValue VBox::getProperty(Pid propertyId) const
628 : {
629 12791 : switch (propertyId) {
630 2768 : case Pid::BOX_AUTOSIZE:
631 2768 : return isAutoSizeEnabled();
632 1 : case Pid::PADDING_TO_NOTATION_ABOVE:
633 1 : return m_paddingToNotationAbove;
634 1 : case Pid::PADDING_TO_NOTATION_BELOW:
635 1 : return m_paddingToNotationBelow;
636 10021 : default:
637 10021 : return Box::getProperty(propertyId);
638 : }
639 : }
640 :
641 : //---------------------------------------------------------
642 : // propertyDefault
643 : //---------------------------------------------------------
644 :
645 11628 : PropertyValue VBox::propertyDefault(Pid id) const
646 : {
647 11628 : switch (id) {
648 1336 : case Pid::BOX_HEIGHT:
649 1336 : return 10.0_sp;
650 10292 : default:
651 10292 : return Box::propertyDefault(id);
652 : }
653 : }
654 :
655 7263 : bool VBox::setProperty(Pid propertyId, const PropertyValue& v)
656 : {
657 7263 : switch (propertyId) {
658 1406 : case Pid::PADDING_TO_NOTATION_ABOVE:
659 1406 : m_paddingToNotationAbove = v.value<Spatium>();
660 1406 : break;
661 1406 : case Pid::PADDING_TO_NOTATION_BELOW:
662 1406 : m_paddingToNotationBelow = v.value<Spatium>();
663 1406 : break;
664 4451 : default:
665 4451 : return Box::setProperty(propertyId, v);
666 : }
667 :
668 2812 : triggerLayout();
669 2812 : return true;
670 : }
671 :
672 : //---------------------------------------------------------
673 : // startDragGrip
674 : //---------------------------------------------------------
675 :
676 0 : void VBox::startDragGrip(EditData& ed)
677 : {
678 0 : const double sp = sizeIsSpatiumDependent() ? spatium() : style().defaultSpatium();
679 0 : if (isAutoSizeEnabled()) {
680 0 : setAutoSizeEnabled(false);
681 0 : setBoxHeight(Spatium(height() / sp));
682 : }
683 0 : Box::startDragGrip(ed);
684 0 : }
685 :
686 0 : bool VBox::acceptDrop(EditData& data) const
687 : {
688 0 : const EngravingItem* e = data.dropElement;
689 0 : IF_ASSERT_FAILED(e) {
690 0 : return false;
691 : }
692 0 : if (e->isLayoutBreak()) {
693 0 : const LayoutBreakType type = toLayoutBreak(e)->layoutBreakType();
694 0 : if (type == LayoutBreakType::LINE) {
695 0 : return false;
696 : }
697 : }
698 0 : if (e->isActionIcon()) {
699 0 : const ActionIconType type = toActionIcon(e)->actionType();
700 0 : if (type == ActionIconType::SYSTEM_LOCK) {
701 0 : return false;
702 : }
703 : }
704 0 : return Box::acceptDrop(data);
705 : }
706 :
707 : //---------------------------------------------------------
708 : // add
709 : /// Add new EngravingItem \a e to fret diagram box
710 : //---------------------------------------------------------
711 :
712 5 : FBox::FBox(Score* parent)
713 5 : : VBox(ElementType::FBOX, parent)
714 : {
715 5 : resetProperty(Pid::FRET_FRAME_TEXT_SCALE);
716 5 : resetProperty(Pid::FRET_FRAME_DIAGRAM_SCALE);
717 5 : resetProperty(Pid::FRET_FRAME_COLUMN_GAP);
718 5 : resetProperty(Pid::FRET_FRAME_ROW_GAP);
719 5 : resetProperty(Pid::FRET_FRAME_CHORDS_PER_ROW);
720 5 : resetProperty(Pid::FRET_FRAME_H_ALIGN);
721 :
722 5 : resetProperty(Pid::LEFT_MARGIN);
723 5 : resetProperty(Pid::RIGHT_MARGIN);
724 5 : resetProperty(Pid::TOP_MARGIN);
725 5 : resetProperty(Pid::BOTTOM_MARGIN);
726 5 : resetProperty(Pid::TOP_GAP);
727 5 : resetProperty(Pid::BOTTOM_GAP);
728 5 : resetProperty(Pid::EXCLUDE_FROM_OTHER_PARTS);
729 5 : resetProperty(Pid::APPEARANCE_LINKED_TO_MASTER);
730 5 : resetProperty(Pid::POSITION_LINKED_TO_MASTER);
731 5 : }
732 :
733 7 : void FBox::init()
734 : {
735 7 : StringList oldDiagramsNames;
736 7 : std::vector<FretDiagram*> oldDiagrams;
737 19 : for (EngravingItem* element : el()) {
738 12 : FretDiagram* diagram = toFretDiagram(element);
739 12 : oldDiagrams.push_back(diagram);
740 12 : oldDiagramsNames.push_back(diagram->harmonyDisplayText());
741 : }
742 :
743 7 : StringList diagramsNamesInScore;
744 7 : std::vector<EngravingItem*> harmonyOrDiagramsInScore;
745 105 : for (mu::engraving::Segment* segment = score()->firstSegment(mu::engraving::SegmentType::ChordRest); segment;
746 98 : segment = segment->next1(mu::engraving::SegmentType::ChordRest)) {
747 133 : for (EngravingItem* item : segment->annotations()) {
748 35 : if (!item || !item->part()) {
749 1 : continue;
750 : }
751 :
752 35 : if (item->isHarmony() && toHarmony(item)->harmonyType() != HarmonyType::STANDARD) {
753 0 : continue;
754 : }
755 :
756 35 : if (!(item->isHarmony() || item->isFretDiagram())) {
757 0 : continue;
758 : }
759 :
760 35 : String harmonyName = item->isHarmony() ? toHarmony(item)->displayText()
761 35 : : item->isFretDiagram() ? toFretDiagram(item)->harmonyDisplayText()
762 35 : : String();
763 35 : if (harmonyName.empty() || muse::contains(diagramsNamesInScore, harmonyName)) {
764 1 : continue;
765 : }
766 :
767 34 : harmonyOrDiagramsInScore.push_back(item);
768 34 : diagramsNamesInScore.push_back(harmonyName);
769 35 : }
770 : }
771 :
772 7 : m_diagramsOrderInScore = diagramsNamesInScore;
773 :
774 19 : for (size_t i = 0; i < oldDiagramsNames.size(); ++i) {
775 12 : String oldName = oldDiagramsNames[i];
776 12 : if (!muse::contains(diagramsNamesInScore, oldName)) {
777 0 : score()->undo(new RemoveFretDiagramFromFretBox(oldDiagrams[i]));
778 : }
779 12 : }
780 :
781 41 : for (size_t i = 0; i < diagramsNamesInScore.size(); ++i) {
782 34 : String newName = diagramsNamesInScore[i];
783 34 : if (!muse::contains(oldDiagramsNames, newName)) {
784 22 : FretDiagram* newDiagram = FretDiagram::makeFromHarmonyOrFretDiagram(harmonyOrDiagramsInScore[i]);
785 22 : newDiagram->setOwnershipParent(this);
786 22 : String nameOfDiagramBeforeThis = i > 0 ? diagramsNamesInScore[i - 1] : String();
787 22 : size_t idx = computeInsertionIdx(nameOfDiagramBeforeThis);
788 22 : score()->undo(new AddFretDiagramToFretBox(newDiagram, idx));
789 22 : }
790 34 : }
791 7 : }
792 :
793 0 : void FBox::add(EngravingItem* e)
794 : {
795 0 : e->setOwnershipParent(this);
796 0 : if (e->isFretDiagram()) {
797 0 : addAtIdx(toFretDiagram(e), muse::nidx);
798 : } else {
799 0 : LOGD("FBox::add: element not allowed");
800 0 : return;
801 : }
802 : }
803 :
804 22 : void FBox::addAtIdx(FretDiagram* fretDiagram, size_t idx)
805 : {
806 22 : fretDiagram->setTrack(muse::nidx);
807 22 : fretDiagram->setFlag(ElementFlag::MOVABLE, false);
808 22 : fretDiagram->setFlag(ElementFlag::ON_STAFF, false);
809 :
810 22 : Harmony* harmony = fretDiagram->harmony();
811 22 : harmony->setTrack(muse::nidx);
812 22 : harmony->setFlag(ElementFlag::MOVABLE, false);
813 22 : harmony->setFlag(ElementFlag::ON_STAFF, false);
814 :
815 22 : if (idx < m_el.size()) {
816 0 : m_el.insert(m_el.begin() + idx, fretDiagram);
817 : } else {
818 22 : m_el.push_back(fretDiagram);
819 : }
820 :
821 22 : fretDiagram->added();
822 22 : }
823 :
824 22 : size_t FBox::computeInsertionIdx(const String& nameOfDiagramBeforeThis)
825 : {
826 22 : if (nameOfDiagramBeforeThis.empty()) {
827 5 : return 0;
828 : }
829 :
830 60 : for (size_t i = 0; i < m_el.size(); ++i) {
831 43 : FretDiagram* fretDiagram = toFretDiagram(m_el[i]);
832 43 : if (fretDiagram->harmonyDisplayText() == nameOfDiagramBeforeThis) {
833 0 : return i + 1;
834 : }
835 : }
836 :
837 17 : return muse::nidx;
838 : }
839 :
840 53 : PropertyValue FBox::getProperty(Pid propertyId) const
841 : {
842 53 : switch (propertyId) {
843 0 : case Pid::FRET_FRAME_TEXT_SCALE:
844 0 : return m_textScale;
845 0 : case Pid::FRET_FRAME_DIAGRAM_SCALE:
846 0 : return m_diagramScale;
847 0 : case Pid::FRET_FRAME_COLUMN_GAP:
848 0 : return m_columnGap;
849 0 : case Pid::FRET_FRAME_ROW_GAP:
850 0 : return m_rowGap;
851 0 : case Pid::FRET_FRAME_CHORDS_PER_ROW:
852 0 : return m_chordsPerRow;
853 0 : case Pid::FRET_FRAME_H_ALIGN:
854 0 : return static_cast<int>(m_contentAlignmentH);
855 12 : case Pid::LEFT_MARGIN:
856 12 : return m_contentAlignmentH == AlignH::LEFT ? VBox::getProperty(propertyId) : PropertyValue();
857 12 : case Pid::RIGHT_MARGIN:
858 12 : return m_contentAlignmentH == AlignH::RIGHT ? VBox::getProperty(propertyId) : PropertyValue();
859 4 : case Pid::FRET_FRAME_DIAGRAMS_ORDER:
860 4 : return diagramsOrder().join(FRET_BOX_DIAGRAMS_SEPARATOR);
861 25 : default:
862 25 : return VBox::getProperty(propertyId);
863 : }
864 : }
865 :
866 87 : bool FBox::setProperty(Pid propertyId, const PropertyValue& val)
867 : {
868 87 : switch (propertyId) {
869 5 : case Pid::FRET_FRAME_TEXT_SCALE:
870 5 : m_textScale = val.toDouble();
871 5 : break;
872 5 : case Pid::FRET_FRAME_DIAGRAM_SCALE:
873 5 : m_diagramScale = val.toDouble();
874 5 : break;
875 5 : case Pid::FRET_FRAME_COLUMN_GAP:
876 5 : m_columnGap = val.value<Spatium>();
877 5 : break;
878 5 : case Pid::FRET_FRAME_ROW_GAP:
879 5 : m_rowGap = val.value<Spatium>();
880 5 : break;
881 5 : case Pid::FRET_FRAME_CHORDS_PER_ROW:
882 5 : m_chordsPerRow = val.toInt();
883 5 : break;
884 5 : case Pid::FRET_FRAME_H_ALIGN:
885 5 : m_contentAlignmentH = static_cast<AlignH>(val.toInt());
886 5 : resetProperty(Pid::LEFT_MARGIN);
887 5 : resetProperty(Pid::RIGHT_MARGIN);
888 5 : break;
889 2 : case Pid::FRET_FRAME_DIAGRAMS_ORDER:
890 2 : reorderElements(val.value<String>().split(FRET_BOX_DIAGRAMS_SEPARATOR));
891 2 : break;
892 55 : default:
893 55 : return VBox::setProperty(propertyId, val);
894 : }
895 :
896 32 : triggerLayout();
897 32 : return true;
898 : }
899 :
900 85 : PropertyValue FBox::propertyDefault(Pid propertyId) const
901 : {
902 85 : switch (propertyId) {
903 10 : case Pid::FRET_FRAME_TEXT_SCALE:
904 : case Pid::FRET_FRAME_DIAGRAM_SCALE:
905 10 : return 1.0;
906 10 : case Pid::FRET_FRAME_COLUMN_GAP:
907 : case Pid::FRET_FRAME_ROW_GAP:
908 10 : return 3.0_sp;
909 5 : case Pid::FRET_FRAME_CHORDS_PER_ROW:
910 5 : return 8;
911 5 : case Pid::FRET_FRAME_H_ALIGN:
912 5 : return static_cast<int>(AlignH::HCENTER);
913 0 : case Pid::FRET_FRAME_DIAGRAMS_ORDER:
914 0 : return m_diagramsOrderInScore.join(FRET_BOX_DIAGRAMS_SEPARATOR);
915 5 : case Pid::EXCLUDE_FROM_OTHER_PARTS:
916 5 : return true;
917 10 : case Pid::APPEARANCE_LINKED_TO_MASTER:
918 : case Pid::POSITION_LINKED_TO_MASTER:
919 10 : return false;
920 40 : default:
921 40 : return VBox::propertyDefault(propertyId);
922 : }
923 : }
924 :
925 0 : int FBox::gripsCount() const
926 : {
927 0 : return 0;
928 : }
929 :
930 0 : Grip FBox::initialEditModeGrip() const
931 : {
932 0 : return Grip::NO_GRIP;
933 : }
934 :
935 0 : Grip FBox::defaultGrip() const
936 : {
937 0 : return Grip::NO_GRIP;
938 : }
939 :
940 0 : std::vector<PointF> FBox::gripsPositions(const EditData&) const
941 : {
942 0 : return {};
943 : }
944 :
945 1 : void FBox::undoReorderElements(const StringList& newOrder)
946 : {
947 1 : undoChangeProperty(Pid::FRET_FRAME_DIAGRAMS_ORDER, newOrder.join(FRET_BOX_DIAGRAMS_SEPARATOR));
948 1 : triggerLayout();
949 1 : }
950 :
951 2 : void FBox::reorderElements(const StringList& newOrder)
952 : {
953 2 : std::sort(m_el.begin(), m_el.end(), [&](EngravingItem* a, EngravingItem* b) {
954 14 : String nameA = toFretDiagram(a)->harmonyDisplayText();
955 14 : String nameB = toFretDiagram(b)->harmonyDisplayText();
956 14 : auto iterA = std::find(newOrder.begin(), newOrder.end(), nameA);
957 14 : auto iterB = std::find(newOrder.begin(), newOrder.end(), nameB);
958 28 : return iterA < iterB;
959 14 : });
960 2 : }
961 :
962 4 : StringList FBox::diagramsOrder() const
963 : {
964 4 : StringList result;
965 20 : for (EngravingItem* item : m_el) {
966 16 : result.push_back(toFretDiagram(item)->harmonyDisplayText());
967 : }
968 :
969 4 : return result;
970 0 : }
971 :
972 : //---------------------------------------------------------
973 : // TBox
974 : //---------------------------------------------------------
975 :
976 102 : TBox::TBox(Score* parent)
977 102 : : VBox(ElementType::TBOX, parent)
978 : {
979 102 : resetProperty(Pid::BOX_HEIGHT);
980 102 : m_text = Factory::createText(this, TextStyleType::FRAME);
981 102 : m_text->setLayoutToParentWidth(true);
982 102 : }
983 :
984 0 : TBox::TBox(const TBox& tbox)
985 0 : : VBox(tbox)
986 : {
987 0 : if (tbox.m_text) {
988 0 : add(Factory::copyText(*(tbox.m_text)));
989 : }
990 0 : }
991 :
992 202 : TBox::~TBox()
993 : {
994 101 : delete m_text;
995 202 : }
996 :
997 0 : void TBox::scanElements(std::function<void(EngravingItem*)> func)
998 : {
999 0 : m_text->scanElements(func);
1000 0 : Box::scanElements(func);
1001 0 : }
1002 :
1003 : //---------------------------------------------------------
1004 : // drop
1005 : //---------------------------------------------------------
1006 :
1007 0 : EngravingItem* TBox::drop(Transaction& tx, EditData& data)
1008 : {
1009 0 : EngravingItem* e = data.dropElement;
1010 0 : switch (e->type()) {
1011 0 : case ElementType::TEXT:
1012 0 : m_text->undoChangeProperty(Pid::TEXT, toText(e)->xmlText());
1013 0 : delete e;
1014 0 : return m_text;
1015 0 : default:
1016 0 : return VBox::drop(tx, data);
1017 : }
1018 : }
1019 :
1020 : //---------------------------------------------------------
1021 : // add
1022 : /// Add new EngravingItem \a el to TBox
1023 : //---------------------------------------------------------
1024 :
1025 9 : void TBox::add(EngravingItem* e)
1026 : {
1027 9 : if (e->isText()) {
1028 0 : if (m_text) {
1029 0 : delete m_text;
1030 : }
1031 0 : m_text = toText(e);
1032 0 : m_text->setLayoutToParentWidth(true);
1033 0 : e->setOwnershipParent(this);
1034 0 : e->added();
1035 : } else {
1036 9 : VBox::add(e);
1037 : }
1038 9 : }
1039 :
1040 : //---------------------------------------------------------
1041 : // remove
1042 : //---------------------------------------------------------
1043 :
1044 0 : void TBox::remove(EngravingItem* el)
1045 : {
1046 0 : if (el == m_text) {
1047 : // does not normally happen, since Score::deleteItem() handles this directly
1048 : // but if it does:
1049 : // replace with new empty text element
1050 : // this keeps undo/redo happier than just clearing the text
1051 0 : LOGD("TBox::remove() - replacing _text");
1052 0 : m_text = Factory::createText(this, TextStyleType::FRAME);
1053 0 : m_text->setLayoutToParentWidth(true);
1054 0 : el->removed();
1055 : } else {
1056 0 : VBox::remove(el);
1057 : }
1058 0 : }
1059 :
1060 : //---------------------------------------------------------
1061 : // propertyDefault
1062 : //---------------------------------------------------------
1063 :
1064 125 : PropertyValue TBox::propertyDefault(Pid id) const
1065 : {
1066 125 : switch (id) {
1067 103 : case Pid::BOX_HEIGHT:
1068 103 : return 1_sp;
1069 22 : default:
1070 22 : return VBox::propertyDefault(id);
1071 : }
1072 : }
1073 :
1074 : //---------------------------------------------------------
1075 : // accessibleExtraInfo
1076 : //---------------------------------------------------------
1077 :
1078 0 : String Box::accessibleExtraInfo() const
1079 : {
1080 0 : String rez;
1081 0 : for (EngravingItem* e : el()) {
1082 0 : rez += u' ' + e->screenReaderInfo();
1083 : }
1084 0 : return rez;
1085 0 : }
1086 :
1087 : //---------------------------------------------------------
1088 : // accessibleExtraInfo
1089 : //---------------------------------------------------------
1090 :
1091 0 : String TBox::accessibleExtraInfo() const
1092 : {
1093 0 : String rez = m_text->screenReaderInfo();
1094 0 : return rez;
1095 : }
1096 :
1097 0 : int TBox::gripsCount() const
1098 : {
1099 0 : return 0;
1100 : }
1101 :
1102 0 : Grip TBox::initialEditModeGrip() const
1103 : {
1104 0 : return Grip::NO_GRIP;
1105 : }
1106 :
1107 0 : Grip TBox::defaultGrip() const
1108 : {
1109 0 : return Grip::NO_GRIP;
1110 : }
1111 : }
|