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