1 //=============================================================================
2 // MuseScore
3 // Music Composition & Notation
4 //
5 // Copyright (C) 2019 Werner Schweer and others
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License version 2.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 //=============================================================================
19
20 #include "palettetree.h"
21
22 #include "globals.h"
23 #include "musescore.h"
24 #include "palette.h"
25 #include "preferences.h"
26 #include "shortcut.h"
27
28 #include "libmscore/articulation.h"
29 #include "libmscore/fret.h"
30 #include "libmscore/icon.h"
31 #include "libmscore/image.h"
32 #include "libmscore/imageStore.h"
33 #include "libmscore/mscore.h"
34 #include "libmscore/score.h"
35 #include "libmscore/textbase.h"
36 #include "libmscore/element.h"
37 #include "libmscore/bracket.h"
38
39 #include "thirdparty/qzip/qzipreader_p.h"
40 #include "thirdparty/qzip/qzipwriter_p.h"
41
42 namespace Ms {
43
44 //---------------------------------------------------------
45 // needsStaff
46 // should a staff been drawn if e is used as icon in
47 // a palette
48 //---------------------------------------------------------
49
needsStaff(Element * e)50 static bool needsStaff(Element* e)
51 {
52 if (!e)
53 return false;
54 switch(e->type()) {
55 case ElementType::CHORD:
56 case ElementType::BAR_LINE:
57 case ElementType::CLEF:
58 case ElementType::KEYSIG:
59 case ElementType::TIMESIG:
60 case ElementType::REST:
61 case ElementType::BAGPIPE_EMBELLISHMENT:
62 return true;
63 default:
64 return false;
65 }
66 }
67
68 //---------------------------------------------------------
69 // mimeData
70 //---------------------------------------------------------
71
72 template<class T>
mimeData(T * t)73 static QByteArray mimeData(T* t)
74 {
75 QBuffer buffer;
76 buffer.open(QIODevice::WriteOnly);
77 XmlWriter xml(/* score */ nullptr, &buffer);
78 xml.setClipboardmode(true);
79 t->write(xml);
80 buffer.close();
81 return buffer.buffer();
82 }
83
84 //---------------------------------------------------------
85 // readMimeData
86 //---------------------------------------------------------
87
88 template<class T>
readMimeData(const QByteArray & data,const QString & tagName)89 static std::unique_ptr<T> readMimeData(const QByteArray& data, const QString& tagName)
90 {
91 XmlReader e(data);
92 e.setPasteMode(true);
93 while (e.readNextStartElement()) {
94 const QStringRef tag(e.name());
95 if (tag == tagName) {
96 std::unique_ptr<T> t(new T);
97 if (!t->read(e))
98 return nullptr;
99 return t;
100 }
101 else {
102 return nullptr;
103 }
104 }
105 return nullptr;
106 }
107
108 //---------------------------------------------------------
109 // PaletteCell::PaletteCell
110 //---------------------------------------------------------
111
PaletteCell(std::unique_ptr<Element> e,const QString & _name,QString _tag,qreal _mag)112 PaletteCell::PaletteCell(std::unique_ptr<Element> e, const QString& _name, QString _tag, qreal _mag)
113 : element(std::move(e)), name(_name), tag(_tag), mag(_mag)
114 {
115 drawStaff = needsStaff(element.get());
116 }
117
118 //---------------------------------------------------------
119 // PaletteCell::translationContext
120 //---------------------------------------------------------
121
translationContext() const122 const char* PaletteCell::translationContext() const
123 {
124 const ElementType type = element ? element->type() : ElementType::INVALID;
125 switch (type) {
126 case ElementType::ACCIDENTAL:
127 case ElementType::ARTICULATION:
128 case ElementType::BAR_LINE:
129 case ElementType::BREATH:
130 case ElementType::FERMATA:
131 case ElementType::SYMBOL:
132 case ElementType::TIMESIG:
133 return "symUserNames"; // libmscore/sym.cpp, Sym::symUserNames
134 case ElementType::CLEF:
135 return "clefTable"; // libmscore/clef.cpp, ClefInfo::clefTable[]
136 case ElementType::KEYSIG:
137 return "MuseScore"; // libmscore/keysig.cpp, keyNames[]
138 case ElementType::MARKER:
139 return "markerType"; // libmscore/marker.cpp, markerTypeTable[]
140 case ElementType::JUMP:
141 return "jumpType"; // libmscore/jump.cpp, jumpTypeTable[]
142 case ElementType::TREMOLO:
143 return "Tremolo"; // libmscore/tremolo.cpp, tremoloName[]
144 case ElementType::BAGPIPE_EMBELLISHMENT:
145 return "bagpipe"; // libmscore/bagpembell.cpp, BagpipeEmbellishment::BagpipeEmbellishmentList[]
146 case ElementType::TRILL:
147 return "trillType"; // libmscore/trill.cpp, trillTable[]
148 case ElementType::VIBRATO:
149 return "vibratoType"; // libmscore/vibrato.cpp, vibratoTable[]
150 case ElementType::CHORDLINE:
151 return "Ms"; // libmscore/chordline.cpp, scorelineNames[]
152 case ElementType::NOTEHEAD:
153 return "noteheadnames"; // libmscore/note.cpp, noteHeadGroupNames[]
154 case ElementType::ICON:
155 return "action"; // mscore/shortcut.cpp, Shortcut::_sc[]
156 default:
157 break;
158 }
159 return "Palette";
160 }
161
162 //---------------------------------------------------------
163 // PaletteCell::translatedName
164 //---------------------------------------------------------
165
translatedName() const166 QString PaletteCell::translatedName() const
167 {
168 const QString trName(qApp->translate(translationContext(), name.toUtf8()));
169
170 if (element && element->isTextBase() && name.contains("%1"))
171 return trName.arg(toTextBase(element.get())->plainText());
172 return trName;
173 }
174
175 //---------------------------------------------------------
176 // PaletteCell::retranslate
177 /// Retranslates cell content, e.g. text if the element
178 /// is TextBase.
179 //---------------------------------------------------------
180
retranslate()181 void PaletteCell::retranslate()
182 {
183 if (untranslatedElement && element->isTextBase()) {
184 TextBase* target = toTextBase(element.get());
185 TextBase* orig = toTextBase(untranslatedElement.get());
186 const QString& text = orig->xmlText();
187 target->setXmlText(qApp->translate("Palette", text.toUtf8().constData()));
188 }
189 }
190
191 //---------------------------------------------------------
192 // PaletteCell::setElementTranslated
193 //---------------------------------------------------------
194
setElementTranslated(bool translate)195 void PaletteCell::setElementTranslated(bool translate)
196 {
197 if (translate && element) {
198 untranslatedElement = std::move(element);
199 element.reset(untranslatedElement->clone());
200 retranslate();
201 }
202 else
203 untranslatedElement.reset();
204 }
205
206 //---------------------------------------------------------
207 // PaletteCell::write
208 //---------------------------------------------------------
209
write(XmlWriter & xml) const210 void PaletteCell::write(XmlWriter& xml) const
211 {
212 if (!element) {
213 xml.tagE("Cell");
214 return;
215 }
216
217 // using attributes for `custom` and `visible`
218 // properties instead of nested tags for pre-3.3
219 // version compatibility
220 xml.stag(QString("Cell")
221 + (!name.isEmpty() ? " name=\"" + XmlWriter::xmlString(name) + "\"" : "")
222 + (custom ? " custom=\"1\"" : "")
223 + (!visible ? " visible=\"0\"" : "")
224 + (untranslatedElement ? " trElement=\"1\"" : "")
225 );
226
227 if (drawStaff)
228 xml.tag("staff", drawStaff);
229 if (xoffset)
230 xml.tag("xoffset", xoffset);
231 if (yoffset)
232 xml.tag("yoffset", yoffset);
233 if (!tag.isEmpty())
234 xml.tag("tag", tag);
235 if (mag != 1.0)
236 xml.tag("mag", mag);
237
238 if (untranslatedElement)
239 untranslatedElement->write(xml);
240 else
241 element->write(xml);
242 xml.etag();
243 }
244
245 //---------------------------------------------------------
246 // PaletteCell::read
247 //---------------------------------------------------------
248
read(XmlReader & e)249 bool PaletteCell::read(XmlReader& e)
250 {
251 bool add = true;
252 name = e.attribute("name");
253
254 // using attributes instead of nested tags for
255 // pre-3.3 version compatibility
256 custom = e.hasAttribute("custom") ? e.intAttribute("custom") : false; // TODO: actually check master palette?
257 visible = e.hasAttribute("visible") ? e.intAttribute("visible") : true;
258
259 const bool translateElement = e.hasAttribute("trElement") ? e.intAttribute("trElement") : false;
260
261 while (e.readNextStartElement()) {
262 const QStringRef& s(e.name());
263 if (s == "staff")
264 drawStaff = e.readInt();
265 else if (s == "xoffset")
266 xoffset = e.readDouble();
267 else if (s == "yoffset")
268 yoffset = e.readDouble();
269 else if (s == "mag")
270 mag = e.readDouble();
271 else if (s == "tag")
272 tag = e.readElementText();
273
274 // added on palettes rework
275 // TODO: remove or leave to switch from using attributes later?
276 else if (s == "custom")
277 custom = e.readBool();
278 else if (s == "visible")
279 visible = e.readBool();
280
281 else {
282 element.reset(Element::name2Element(s, gscore));
283 if (!element)
284 e.unknown();
285 else {
286 element->read(e);
287 element->styleChanged();
288 if (element->type() == ElementType::ICON) {
289 Icon* icon = static_cast<Icon*>(element.get());
290 QAction* ac = getAction(icon->action());
291 if (ac) {
292 QIcon qicon(ac->icon());
293 icon->setAction(icon->action(), qicon);
294 }
295 else {
296 add = false; // action is not valid, don't add it to the palette.
297 }
298 }
299 }
300 }
301 }
302
303 setElementTranslated(translateElement);
304
305 return add && element;
306 }
307
308 //---------------------------------------------------------
309 // PaletteCell::readMimeData
310 //---------------------------------------------------------
311
readMimeData(const QByteArray & data)312 PaletteCellPtr PaletteCell::readMimeData(const QByteArray& data)
313 {
314 return Ms::readMimeData<PaletteCell>(data, "Cell");
315 }
316
317 //---------------------------------------------------------
318 // PaletteCell::readMimeData
319 //---------------------------------------------------------
320
readElementMimeData(const QByteArray & data)321 PaletteCellPtr PaletteCell::readElementMimeData(const QByteArray& data)
322 {
323 QPointF dragOffset;
324 Fraction duration(1, 4);
325 std::unique_ptr<Element> e(Element::readMimeData(gscore, data, &dragOffset, &duration));
326
327 if (!e)
328 return nullptr;
329
330 if (!e->isSymbol()) // not sure this check is necessary, it was so in the old palette
331 e->setTrack(0);
332
333 if (e->isIcon()) {
334 Icon* i = toIcon(e.get());
335 const QByteArray& action = i->action();
336 if (!action.isEmpty()) {
337 const Shortcut* s = Shortcut::getShortcut(action);
338 if (s) {
339 QAction* a = s->action();
340 QIcon icon(a->icon());
341 i->setAction(action, icon);
342 }
343 }
344 }
345
346 const QString name = (e->isFretDiagram()) ? toFretDiagram(e.get())->harmonyText() : e->userName();
347
348 return PaletteCellPtr(new PaletteCell(std::move(e), name));
349 }
350
351 //---------------------------------------------------------
352 // PaletteCell::mimeData
353 //---------------------------------------------------------
354
mimeData() const355 QByteArray PaletteCell::mimeData() const
356 {
357 return Ms::mimeData(this);
358 }
359
360 //---------------------------------------------------------
361 // PaletteCell::readMimeData
362 //---------------------------------------------------------
363
readMimeData(const QByteArray & data)364 std::unique_ptr<PalettePanel> PalettePanel::readMimeData(const QByteArray& data)
365 {
366 return Ms::readMimeData<PalettePanel>(data, "Palette");
367 }
368
369 //---------------------------------------------------------
370 // PalettePanel::read
371 //---------------------------------------------------------
372
read(XmlReader & e)373 bool PalettePanel::read(XmlReader& e)
374 {
375 _name = e.attribute("name");
376 _type = Type::Unknown;
377 while (e.readNextStartElement()) {
378 const QStringRef tag(e.name());
379 if (tag == "gridWidth")
380 _gridSize.setWidth(e.readDouble());
381 else if (tag == "gridHeight")
382 _gridSize.setHeight(e.readDouble());
383 else if (tag == "mag")
384 _mag = e.readDouble();
385 else if (tag == "grid")
386 _drawGrid = e.readInt();
387 else if (tag == "moreElements")
388 setMoreElements(e.readInt());
389 else if (tag == "yoffset")
390 _yOffset = e.readDouble();
391 else if (tag == "drumPalette") // obsolete
392 e.skipCurrentElement();
393 else if (tag == "type") {
394 bool ok;
395 const int t = QMetaEnum::fromType<Type>().keyToValue(e.readElementText().toLatin1().constData(), &ok);
396 if (ok)
397 _type = Type(t);
398 }
399 else if (tag == "visible")
400 _visible = e.readBool();
401 else if (e.pasteMode() && tag == "expanded")
402 _expanded = e.readBool();
403 else if (tag == "editable")
404 _editable = e.readBool();
405 else if (tag == "Cell") {
406 PaletteCellPtr cell(new PaletteCell);
407 if (!cell->read(e))
408 continue;
409
410 auto cellHandler = cellHandlerByPaletteType(_type);
411
412 if (cellHandler)
413 cellHandler(cell.get());
414
415 cells.push_back(std::move(cell));
416 }
417 else
418 e.unknown();
419 }
420 // (from old palette): make sure hgrid and vgrid are not 0, we divide by them later
421 if (_gridSize.width() <= 0)
422 _gridSize.setWidth(28);
423 if (_gridSize.width() <= 0)
424 _gridSize.setHeight(28);
425
426 if (_type == Type::Unknown)
427 _type = guessType();
428
429 return true;
430 }
431
432 //---------------------------------------------------------
433 // PalettePanel::mimeData
434 //---------------------------------------------------------
435
mimeData() const436 QByteArray PalettePanel::mimeData() const
437 {
438 return Ms::mimeData(this);
439 }
440
441 //---------------------------------------------------------
442 // PalettePanel::write
443 //---------------------------------------------------------
444
write(XmlWriter & xml) const445 void PalettePanel::write(XmlWriter& xml) const
446 {
447 xml.stag(QString("Palette name=\"%1\"").arg(XmlWriter::xmlString(_name)));
448 xml.tag("type", QMetaEnum::fromType<Type>().valueToKey(int(_type)));
449 xml.tag("gridWidth", _gridSize.width());
450 xml.tag("gridHeight", _gridSize.height());
451 xml.tag("mag", _mag);
452 if (_drawGrid)
453 xml.tag("grid", _drawGrid);
454
455 xml.tag("moreElements", _moreElements);
456 if (_yOffset != 0.0)
457 xml.tag("yoffset", _yOffset);
458
459 xml.tag("visible", _visible, true);
460 xml.tag("editable", _editable, true);
461
462 if (xml.clipboardmode())
463 xml.tag("expanded", _expanded, false);
464
465 for (auto& cell: cells) {
466 // if (cells[i] && cells[i]->tag == "ShowMore")
467 // continue;
468 if (!cell) { // from old palette, not sure if it is still needed
469 xml.tagE("Cell");
470 continue;
471 }
472 cell->write(xml);
473 }
474 xml.etag();
475 }
476
477 //---------------------------------------------------------
478 // writePaletteFailed
479 //---------------------------------------------------------
480
writePaletteFailed(const QString & path)481 static void writePaletteFailed(const QString& path)
482 {
483 QString s = qApp->translate("Palette", "Writing Palette File\n%1\nfailed: ").arg(path); // reason?
484 QMessageBox::critical(mscore, qApp->translate("Palette", "Writing Palette File"), s);
485 }
486
487 //---------------------------------------------------------
488 // PalettePanel::writeToFile
489 /// write as compressed zip file and include
490 /// images as needed
491 //---------------------------------------------------------
492
writeToFile(const QString & p) const493 bool PalettePanel::writeToFile(const QString& p) const
494 {
495 QSet<ImageStoreItem*> images;
496 size_t n = cells.size();
497 for (size_t i = 0; i < n; ++i) {
498 if (cells[i] == 0 || cells[i]->element == 0 || cells[i]->element->type() != ElementType::IMAGE)
499 continue;
500 images.insert(toImage(cells[i]->element.get())->storeItem());
501 }
502
503 QString path(p);
504 if (!path.endsWith(".mpal"))
505 path += ".mpal";
506
507 MQZipWriter f(path);
508 // f.setCompressionPolicy(QZipWriter::NeverCompress);
509 f.setCreationPermissions(
510 QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner
511 | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser
512 | QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup
513 | QFile::ReadOther | QFile::WriteOther | QFile::ExeOther);
514
515 if (f.status() != MQZipWriter::NoError) {
516 writePaletteFailed(path);
517 return false;
518 }
519 QBuffer cbuf;
520 cbuf.open(QIODevice::ReadWrite);
521 XmlWriter xml(gscore, &cbuf);
522 xml.header();
523 xml.stag("container");
524 xml.stag("rootfiles");
525 xml.stag(QString("rootfile full-path=\"%1\"").arg(XmlWriter::xmlString("palette.xml")));
526 xml.etag();
527 foreach (ImageStoreItem* ip, images) {
528 QString ipath = QString("Pictures/") + ip->hashName();
529 xml.tag("file", ipath);
530 }
531 xml.etag();
532 xml.etag();
533 cbuf.seek(0);
534 //f.addDirectory("META-INF");
535 //f.addDirectory("Pictures");
536 f.addFile("META-INF/container.xml", cbuf.data());
537
538 // save images
539 foreach(ImageStoreItem* ip, images) {
540 QString ipath = QString("Pictures/") + ip->hashName();
541 f.addFile(ipath, ip->buffer());
542 }
543 {
544 QBuffer cbuf1;
545 cbuf1.open(QIODevice::ReadWrite);
546 XmlWriter xml1(gscore, &cbuf1);
547 xml1.header();
548 xml1.stag("museScore version=\"" MSC_VERSION "\"");
549 write(xml1);
550 xml1.etag();
551 cbuf1.close();
552 f.addFile("palette.xml", cbuf1.data());
553 }
554 f.close();
555 if (f.status() != MQZipWriter::NoError) {
556 writePaletteFailed(path);
557 return false;
558 }
559 return true;
560 }
561
562 //---------------------------------------------------------
563 // PalettePanel::readFromFile
564 //---------------------------------------------------------
565
readFromFile(const QString & p)566 bool PalettePanel::readFromFile(const QString& p)
567 {
568 QString path(p);
569 if (!path.endsWith(".mpal"))
570 path += ".mpal";
571
572 MQZipReader f(path);
573 if (!f.exists()) {
574 qDebug("palette <%s> not found", qPrintable(path));
575 return false;
576 }
577 cells.clear();
578
579 QByteArray ba = f.fileData("META-INF/container.xml");
580
581 XmlReader e(ba);
582 // extract first rootfile
583 QString rootfile = "";
584 QList<QString> images;
585 while (e.readNextStartElement()) {
586 if (e.name() != "container") {
587 e.unknown();
588 break;;
589 }
590 while (e.readNextStartElement()) {
591 if (e.name() != "rootfiles") {
592 e.unknown();
593 break;
594 }
595 while (e.readNextStartElement()) {
596 const QStringRef& tag(e.name());
597
598 if (tag == "rootfile") {
599 if (rootfile.isEmpty())
600 rootfile = e.attribute("full-path");
601 e.readNext();
602 }
603 else if (tag == "file")
604 images.append(e.readElementText());
605 else
606 e.unknown();
607 }
608 }
609 }
610 //
611 // load images
612 //
613 foreach(const QString& s, images)
614 imageStore.add(s, f.fileData(s));
615
616 if (rootfile.isEmpty()) {
617 qDebug("can't find rootfile in: %s", qPrintable(path));
618 return false;
619 }
620
621 ba = f.fileData(rootfile);
622 e.clear();
623 e.addData(ba);
624 while (e.readNextStartElement()) {
625 if (e.name() == "museScore") {
626 QString version = e.attribute("version");
627 QStringList sl = version.split('.');
628 int versionId = sl[0].toInt() * 100 + sl[1].toInt();
629 gscore->setMscVersion(versionId); // TODO: what is this?
630
631 while (e.readNextStartElement()) {
632 if (e.name() == "Palette")
633 read(e);
634 else
635 e.unknown();
636 }
637 }
638 else
639 e.unknown();
640 }
641 return true;
642 }
643
644 //---------------------------------------------------------
645 // PalettePanel::insert
646 //---------------------------------------------------------
647
insert(int idx,Element * e,const QString & name,QString tag,qreal mag)648 PaletteCell* PalettePanel::insert(int idx, Element* e, const QString& name, QString tag, qreal mag)
649 {
650 if (e)
651 e->layout(); // layout may be important for comparing cells, e.g. filtering "More" popup content
652 PaletteCell* cell = new PaletteCell(std::unique_ptr<Element>(e), name, tag, mag);
653
654 auto cellHandler = cellHandlerByPaletteType(_type);
655
656 if (cellHandler)
657 cellHandler(cell);
658
659 cells.emplace(cells.begin() + idx, cell);
660 return cell;
661 }
662
663 //---------------------------------------------------------
664 // PalettePanel::append
665 //---------------------------------------------------------
666
append(Element * e,const QString & name,QString tag,qreal mag)667 PaletteCell* PalettePanel::append(Element* e, const QString& name, QString tag, qreal mag)
668 {
669 if (e)
670 e->layout(); // layout may be important for comparing cells, e.g. filtering "More" popup content
671 PaletteCell* cell = new PaletteCell(std::unique_ptr<Element>(e), name, tag, mag);
672
673 auto cellHandler = cellHandlerByPaletteType(_type);
674
675 if (cellHandler)
676 cellHandler(cell);
677
678 cells.emplace_back(cell);
679 return cell;
680 }
681
682 //---------------------------------------------------------
683 // PalettePanel::takeCells
684 //---------------------------------------------------------
685
takeCells(int idx,int count)686 std::vector<PaletteCellPtr> PalettePanel::takeCells(int idx, int count)
687 {
688 std::vector<PaletteCellPtr> removedCells;
689 removedCells.reserve(count);
690
691 if (idx < 0 || idx + count > int(cells.size()))
692 return removedCells;
693
694 auto removeBegin = cells.begin() + idx;
695 auto removeEnd = removeBegin + count;
696
697 removedCells.insert(removedCells.end(), std::make_move_iterator(removeBegin), std::make_move_iterator(removeEnd));
698 cells.erase(removeBegin, removeEnd);
699
700 return removedCells;
701 }
702
703 //---------------------------------------------------------
704 // PalettePanel::insertCells
705 //---------------------------------------------------------
706
insertCells(int idx,std::vector<PaletteCellPtr> insertedCells)707 bool PalettePanel::insertCells(int idx, std::vector<PaletteCellPtr> insertedCells)
708 {
709 if (idx < 0 || idx > int(cells.size()))
710 return false;
711
712 cells.insert(cells.begin() + idx, std::make_move_iterator(insertedCells.begin()), std::make_move_iterator(insertedCells.end()));
713
714 return true;
715 }
716
717 //---------------------------------------------------------
718 // PalettePanel::insertCell
719 //---------------------------------------------------------
720
insertCell(int idx,PaletteCellPtr cell)721 bool PalettePanel::insertCell(int idx, PaletteCellPtr cell)
722 {
723 if (idx < 0 || idx > int(cells.size()))
724 return false;
725
726 cells.insert(cells.begin() + idx, std::move(cell));
727
728 return true;
729 }
730
731 //---------------------------------------------------------
732 // PalettePanel::scaledGridSize
733 //---------------------------------------------------------
734
scaledGridSize() const735 QSize PalettePanel::scaledGridSize() const
736 {
737 return gridSize() * Palette::guiMag();
738 }
739
740 //---------------------------------------------------------
741 // isSame
742 /// Helper function to compare two Elements
743 // TODO: make it operator==?
744 //---------------------------------------------------------
745
isSame(const Element & e1,const Element & e2)746 static bool isSame(const Element& e1, const Element& e2)
747 {
748 return e1.type() == e2.type()
749 && e1.subtype() == e2.subtype()
750 && e1.mimeData(QPointF()) == e2.mimeData(QPointF());
751 }
752
753 //---------------------------------------------------------
754 // PalettePanel::findPaletteCell
755 //---------------------------------------------------------
756
findPaletteCell(const PaletteCell & cell,bool matchName) const757 int PalettePanel::findPaletteCell(const PaletteCell& cell, bool matchName) const
758 {
759 const Element* el = cell.element.get();
760 if (!el)
761 return -1;
762
763 for (int i = 0; i < int(cells.size()); ++i) {
764 const PaletteCell& localCell = *cells[i];
765 if (matchName && localCell.name != cell.name)
766 continue;
767 const Element* exElement = localCell.element.get();
768 if (exElement && !isSame(*exElement, *el))
769 continue;
770 if (localCell.tag != cell.tag
771 || localCell.drawStaff != cell.drawStaff
772 || localCell.xoffset != cell.xoffset
773 || localCell.yoffset != cell.yoffset
774 || localCell.mag != cell.mag
775 || localCell.readOnly != cell.readOnly
776 || localCell.visible != cell.visible
777 || localCell.custom != cell.custom
778 )
779 continue;
780 return i;
781 }
782 return -1;
783 }
784
785 //---------------------------------------------------------
786 // PalettePanel::guessType
787 //---------------------------------------------------------
788
guessType() const789 PalettePanel::Type PalettePanel::guessType() const
790 {
791 if (cells.empty())
792 return Type::Custom;
793
794 const Element* e = nullptr;
795 for (const auto& c : cells) {
796 if (c->element) {
797 e = c->element.get();
798 break;
799 }
800 }
801
802 if (!e)
803 return Type::Custom;
804
805 switch (e->type()) {
806 case ElementType::CLEF:
807 return Type::Clef;
808 case ElementType::KEYSIG:
809 return Type::KeySig;
810 case ElementType::TIMESIG:
811 return Type::TimeSig;
812 case ElementType::BRACKET:
813 case ElementType::BRACKET_ITEM:
814 return Type::Bracket;
815 case ElementType::ACCIDENTAL:
816 return Type::Accidental;
817 case ElementType::ARTICULATION:
818 case ElementType::BEND:
819 return toArticulation(e)->isOrnament() ? Type::Ornament : Type::Articulation;
820 case ElementType::FERMATA:
821 return Type::Articulation;
822 case ElementType::BREATH:
823 return Type::Breath;
824 case ElementType::NOTEHEAD:
825 return Type::NoteHead;
826 case ElementType::BAR_LINE:
827 return Type::BarLine;
828 case ElementType::ARPEGGIO:
829 case ElementType::GLISSANDO:
830 return Type::Arpeggio;
831 case ElementType::TREMOLO:
832 return Type::Tremolo;
833 case ElementType::TEMPO_TEXT:
834 return Type::Tempo;
835 case ElementType::DYNAMIC:
836 return Type::Dynamic;
837 case ElementType::FINGERING:
838 return Type::Fingering;
839 case ElementType::MARKER:
840 case ElementType::JUMP:
841 case ElementType::REPEAT_MEASURE:
842 return Type::Repeat;
843 case ElementType::FRET_DIAGRAM:
844 return Type::FretboardDiagram;
845 case ElementType::BAGPIPE_EMBELLISHMENT:
846 return Type::BagpipeEmbellishment;
847 case ElementType::LAYOUT_BREAK:
848 case ElementType::SPACER:
849 return Type::Break;
850 case ElementType::SYMBOL:
851 return Type::Accordion;
852 case ElementType::ICON: {
853 const Icon* i = toIcon(e);
854 const QByteArray& action = i->action();
855 if (action.contains("beam"))
856 return Type::Beam;
857 if (action.contains("grace") || action.contains("acciaccatura") || action.contains("appoggiatura"))
858 return Type::GraceNote;
859 if (action.contains("frame") || action.contains("box") || action.contains("measure"))
860 return Type::Frame;
861 return Type::Custom;
862 }
863 default: {
864 if (e->isSpanner())
865 return Type::Line;
866 if (e->isTextBase())
867 return Type::Text;
868 }
869 };
870
871 return Type::Custom;
872 }
873
cellHandlerByPaletteType(const PalettePanel::Type & type) const874 std::function<void (PaletteCell*)> PalettePanel::cellHandlerByPaletteType(const PalettePanel::Type& type) const
875 {
876 switch (type) {
877 case Type::Bracket: return [](PaletteCell* cellPtr) {
878 if (!cellPtr || !cellPtr->element || !cellPtr->element.get()->isBracket())
879 return;
880
881 Bracket* bracket = toBracket(cellPtr->element.get());
882
883 if (bracket->bracketType() == BracketType::BRACE) {
884 bracket->setStaffSpan(0, 1);
885 cellPtr->mag = 1.2;
886 }
887 };
888 default:
889 return nullptr;
890 }
891 }
892
893 //---------------------------------------------------------
894 // PalettePanel::contentType
895 /// Returns palette type if it is defined or deduces it
896 /// from the palette content for custom palettes.
897 //---------------------------------------------------------
898
contentType() const899 PalettePanel::Type PalettePanel::contentType() const
900 {
901 Type t = type();
902 if (t == Type::Unknown || t == Type::Custom)
903 t = guessType();
904
905 if (t == Type::Unknown || t == Type::Custom)
906 return Type::Clef; // if no type can be deduced, use Clef type by default
907
908 return t;
909 }
910
911 //---------------------------------------------------------
912 // PalettePanel::retranslate
913 //---------------------------------------------------------
914
retranslate()915 void PalettePanel::retranslate()
916 {
917 for (auto& c : cells)
918 c->retranslate();
919 }
920
921 //---------------------------------------------------------
922 // PaletteTree::insert
923 /// PaletteTree takes the ownership over the PalettePanel
924 //---------------------------------------------------------
925
insert(int idx,PalettePanel * palette)926 void PaletteTree::insert(int idx, PalettePanel* palette)
927 {
928 palettes.emplace(palettes.begin() + idx, palette);
929 }
930
931 //---------------------------------------------------------
932 // PaletteTreeModel::append
933 /// PaletteTree takes the ownership over the PalettePanel
934 //---------------------------------------------------------
935
append(PalettePanel * palette)936 void PaletteTree::append(PalettePanel* palette)
937 {
938 palettes.emplace_back(palette);
939 }
940
941 //---------------------------------------------------------
942 // PaletteTree::write
943 //---------------------------------------------------------
944
write(XmlWriter & xml) const945 void PaletteTree::write(XmlWriter& xml) const
946 {
947 xml.stag("PaletteBox"); // for compatibility with old palettes file format
948 for (const auto& p : palettes)
949 p->write(xml);
950 xml.etag();
951 }
952
953 //---------------------------------------------------------
954 // PaletteTree::read
955 //---------------------------------------------------------
956
read(XmlReader & e)957 bool PaletteTree::read(XmlReader& e)
958 {
959 while (e.readNextStartElement()) {
960 const QStringRef tag(e.name());
961 if (tag == "Palette") {
962 std::unique_ptr<PalettePanel> p(new PalettePanel);
963 p->read(e);
964 palettes.push_back(std::move(p));
965 }
966 else
967 e.unknown();
968 }
969 return true;
970 }
971
972 //---------------------------------------------------------
973 // PaletteTree::retranslate
974 //---------------------------------------------------------
975
retranslate()976 void PaletteTree::retranslate()
977 {
978 for (auto& p : palettes)
979 p->retranslate();
980 }
981
982 //---------------------------------------------------------
983 // paintIconElement
984 /// Paint an icon element so that it fills a QRect, preserving
985 /// aspect ratio, and leaving a small margin around the edges.
986 //---------------------------------------------------------
987
paintIconElement(QPainter & painter,const QRect & rect,Element * e)988 static void paintIconElement(QPainter& painter, const QRect& rect, Element* e)
989 {
990 Q_ASSERT(e && e->isIcon());
991 painter.save(); // so we can restore it after we are done using it
992
993 constexpr int margin = 4.0;
994 qreal extent = qMin(rect.height(), rect.width()) - margin;
995
996 Icon* icon = toIcon(e);
997 icon->setExtent(extent);
998
999 extent /= 2.0;
1000 QPointF iconCenter(extent, extent);
1001
1002 painter.translate(rect.center() - iconCenter); // change coordinates
1003 icon->draw(&painter);
1004 painter.restore(); // restore coordinates
1005 }
1006
1007 //---------------------------------------------------------
1008 // paintPaletteElement
1009 /// Function object for use with Element::scanElements() to
1010 /// paint an element and its child elements.
1011 //---------------------------------------------------------
1012
paintPaletteElement(void * data,Element * e)1013 static void paintPaletteElement(void* data, Element* e)
1014 {
1015 QPainter* p = static_cast<QPainter*>(data);
1016 p->save();
1017 p->translate(e->pos()); // necessary for drawing child elements
1018 e->draw(p);
1019 p->restore();
1020 }
1021
1022 //---------------------------------------------------------
1023 // paintScoreElement
1024 /// Paint a non-icon element centered at the origin of the
1025 /// painter's coordinate system. If alignToStaff is true then
1026 /// the element is only centered horizontally; i.e. vertical
1027 /// alignment is unchanged from the default so that item will
1028 /// appear at the correct height on the staff.
1029 //---------------------------------------------------------
1030
paintScoreElement(QPainter & p,Element * e,qreal spatium,bool alignToStaff)1031 static void paintScoreElement(QPainter& p, Element* e, qreal spatium, bool alignToStaff)
1032 {
1033 Q_ASSERT(e && !e->isIcon());
1034 p.save(); // so we can restore painter after we are done using it
1035
1036 const qreal sizeRatio = spatium / gscore->spatium();
1037 p.scale(sizeRatio, sizeRatio); // scale coordinates so element is drawn at correct size
1038
1039 e->layout(); // calculate bbox
1040
1041 //! NOTE `static_cast<const Element*>(e)` is needed for the `const QRectF& bbox() const` method to be called
1042 //! This method is overridden for some elements, in particular for Glissando
1043 QPointF origin = static_cast<const Element* >(e)->bbox().center();
1044
1045 if (alignToStaff) {
1046 origin.setY(0.0); // y = 0 is position of the element's parent.
1047 // If the parent is the staff (or a segment on the staff) then
1048 // y = 0 corresponds to the position of the top staff line.
1049 }
1050
1051 p.translate(-1.0 * origin); // shift coordinates so element is drawn at correct position
1052
1053 e->scanElements(&p, paintPaletteElement);
1054 p.restore(); // return painter to saved initial state
1055 }
1056
1057 //---------------------------------------------------------
1058 // paintStaff
1059 /// Paint a 5 line staff centered within a QRect and return the
1060 /// distance from the top of the QRect to the uppermost staff line.
1061 //---------------------------------------------------------
1062
paintStaff(QPainter & p,const QRect & rect,qreal spatium)1063 static qreal paintStaff(QPainter& p, const QRect& rect, qreal spatium)
1064 {
1065 p.save(); // so we can restore painter after we are done using it
1066 QPen pen(Qt::black);
1067 pen.setWidthF(MScore::defaultStyle().value(Sid::staffLineWidth).toDouble() * spatium);
1068 p.setPen(pen);
1069
1070 constexpr int numStaffLines = 5;
1071 const qreal staffHeight = spatium * (numStaffLines - 1);
1072 const qreal topLineDist = rect.center().y() - (staffHeight / 2.0);
1073
1074 // lines bounded horizontally by edge of target (with small margin)
1075 constexpr qreal margin = 3.0;
1076 const qreal x1 = rect.left() + margin;
1077 const qreal x2 = rect.right() - margin;
1078
1079 // draw staff lines with middle line centered vertically on target
1080 qreal y = topLineDist;
1081 for (int i = 0; i < numStaffLines; ++i) {
1082 p.drawLine(QLineF(x1, y, x2, y));
1083 y += spatium;
1084 }
1085
1086 p.restore(); // return painter to saved initial state
1087 return topLineDist;
1088 }
1089
1090 //---------------------------------------------------------
1091 // paintBackground
1092 //---------------------------------------------------------
1093
paintBackground(QPainter & p,const QRect & r,bool selected,bool current)1094 static void paintBackground(QPainter& p, const QRect& r, bool selected, bool current)
1095 {
1096 QColor c(MScore::selectColor[0]);
1097 if (current || selected) {
1098 c.setAlpha(selected ? 100 : 60);
1099 p.fillRect(r, c);
1100 }
1101 }
1102
1103 //---------------------------------------------------------
1104 // paintTag
1105 //---------------------------------------------------------
1106
paintTag(QPainter & painter,const QRect & rect,QString tag)1107 static void paintTag(QPainter& painter, const QRect& rect, QString tag)
1108 {
1109 if (tag.isEmpty())
1110 return;
1111
1112 painter.save(); // so we can restore it after we are done using it
1113 painter.setPen(Qt::darkGray);
1114 QFont f(painter.font());
1115 f.setPointSize(12);
1116 painter.setFont(f);
1117
1118 if (tag == "ShowMore")
1119 painter.drawText(rect, Qt::AlignCenter, "???");
1120 else
1121 painter.drawText(rect, Qt::AlignLeft | Qt::AlignTop, tag);
1122
1123 painter.restore(); // return to saved initial state (undo pen and font changes, etc.)
1124 }
1125
1126 //---------------------------------------------------------
1127 // elementColor
1128 //---------------------------------------------------------
1129
elementColor(Element * el,bool selected)1130 static QColor elementColor(Element* el, bool selected)
1131 {
1132 Q_ASSERT(el);
1133
1134 if (selected)
1135 return QApplication::palette(mscore).color(QPalette::Normal, QPalette::HighlightedText);
1136
1137 if (el->isChord()) {
1138 return el->curColor(); // Show voice colors for notes.
1139 // This is used in the "drumtools" palette that appears
1140 // when entering notes on an unpitched percussion staff.
1141 }
1142
1143 return QApplication::palette(mscore).color(QPalette::Normal, QPalette::Text);
1144 }
1145
1146 //---------------------------------------------------------
1147 // PaletteCellIconEngine::paintCell
1148 //---------------------------------------------------------
1149
paintCell(QPainter & p,const QRect & r,bool selected,bool current) const1150 void PaletteCellIconEngine::paintCell(QPainter& p, const QRect& r, bool selected, bool current) const
1151 {
1152 const qreal _yOffset = 0.0; // TODO
1153
1154 paintBackground(p, r, selected, current);
1155
1156 if (!_cell)
1157 return;
1158
1159 paintTag(p, r, _cell->tag);
1160
1161 Element* el = _cell->element.get();
1162 if (!el)
1163 return;
1164
1165 if (el->isIcon()) {
1166 paintIconElement(p, r, el);
1167 return; // never draw staff for icon elements
1168 }
1169
1170 const bool drawStaff = _cell->drawStaff;
1171 const qreal spatium = PALETTE_SPATIUM * _extraMag * _cell->mag;
1172
1173 QPointF origin = r.center(); // draw element at center of cell by default
1174 p.translate(0, _yOffset * spatium); // offset both element and staff
1175
1176 if (drawStaff) {
1177 const qreal topLinePos = paintStaff(p, r, spatium); // draw dummy staff lines onto rect.
1178 origin.setY(topLinePos); // vertical position relative to staff instead of cell center.
1179 }
1180
1181 p.translate(origin);
1182 p.translate(_cell->xoffset * spatium, _cell->yoffset * spatium); // additional offset for element only
1183
1184 QColor color(elementColor(el, selected));
1185 p.setPen(QPen(color));
1186
1187 paintScoreElement(p, el, spatium, drawStaff);
1188 }
1189
1190 //---------------------------------------------------------
1191 // PaletteCellIconEngine::paint
1192 //---------------------------------------------------------
1193
paint(QPainter * painter,const QRect & r,QIcon::Mode mode,QIcon::State state)1194 void PaletteCellIconEngine::paint(QPainter* painter, const QRect& r, QIcon::Mode mode, QIcon::State state)
1195 {
1196 QPainter& p = *painter;
1197 p.save(); // so we can restore it later
1198 p.setRenderHint(QPainter::Antialiasing, true);
1199 paintCell(p, r, mode == QIcon::Selected, state == QIcon::On);
1200 p.restore(); // return painter to saved initial state (undo any changes to pen, coordinates, font, etc.)
1201 }
1202
1203 } // namespace Ms
1204