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 "palettemodel.h"
21 
22 #include "libmscore/beam.h"
23 #include "libmscore/chordrest.h"
24 #include "libmscore/icon.h"
25 #include "libmscore/select.h"
26 #include "palettetree.h"
27 #include "palette.h"
28 #include "preferences.h"
29 #include "scoreaccessibility.h"
30 
31 namespace Ms {
32 //---------------------------------------------------------
33 //   PaletteTreeModel::PaletteTreeModel
34 //---------------------------------------------------------
35 
PaletteTreeModel(std::unique_ptr<PaletteTree> tree,QObject * parent)36 PaletteTreeModel::PaletteTreeModel(std::unique_ptr<PaletteTree> tree, QObject* parent)
37    : QAbstractItemModel(parent), _paletteTree(std::move(tree))
38       {
39       connect(this, &QAbstractItemModel::dataChanged, this, &PaletteTreeModel::onDataChanged);
40       connect(this, &QAbstractItemModel::layoutChanged, this, &PaletteTreeModel::setTreeChanged);
41       connect(this, &QAbstractItemModel::modelReset, this, &PaletteTreeModel::setTreeChanged);
42       connect(this, &QAbstractItemModel::rowsInserted, this, &PaletteTreeModel::setTreeChanged);
43       connect(this, &QAbstractItemModel::rowsMoved, this, &PaletteTreeModel::setTreeChanged);
44       connect(this, &QAbstractItemModel::rowsRemoved, this, &PaletteTreeModel::setTreeChanged);
45       }
46 
47 //---------------------------------------------------------
48 //   PaletteTreeModel::onDataChanged
49 //---------------------------------------------------------
50 
onDataChanged(const QModelIndex & topLeft,const QModelIndex & bottomRight,const QVector<int> & roles)51 void PaletteTreeModel::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
52       {
53       Q_UNUSED(topLeft);
54       Q_UNUSED(bottomRight);
55       static const std::set<int> nonPersistentRoles({ CellActiveRole, PaletteExpandedRole });
56 
57       bool treeChanged = false;
58       for (int role : roles) {
59             if (!nonPersistentRoles.count(role)) {
60                   treeChanged = true;
61                   break;
62                   }
63             }
64 
65       if (treeChanged)
66             setTreeChanged();
67       }
68 
69 //---------------------------------------------------------
70 //   PaletteTreeModel::setTreeChanged
71 //---------------------------------------------------------
72 
setTreeChanged()73 void PaletteTreeModel::setTreeChanged()
74       {
75       _treeChanged = true;
76       if (!_treeChangedSignalBlocked)
77             emit treeChanged();
78       }
79 
80 //---------------------------------------------------------
81 //   PaletteTreeModel::blockTreeChanged
82 //---------------------------------------------------------
83 
blockTreeChanged(bool block)84 bool PaletteTreeModel::blockTreeChanged(bool block)
85       {
86       const bool wasBlocked = _treeChangedSignalBlocked;
87       _treeChangedSignalBlocked = block;
88 
89       if (wasBlocked && !block && _treeChanged)
90             emit treeChanged();
91 
92       return wasBlocked;
93       }
94 
95 //---------------------------------------------------------
96 //   PaletteTreeModel::iptrToPalettePanel
97 //---------------------------------------------------------
98 
iptrToPalettePanel(void * iptr,int * idx)99 PalettePanel* PaletteTreeModel::iptrToPalettePanel(void* iptr, int* idx)
100       {
101       const auto palette = std::find_if(
102          palettes().begin(), palettes().end(),
103          [iptr](const std::unique_ptr<PalettePanel>& p) {
104                return iptr == p.get();
105                });
106 
107       if (idx)
108             (*idx) = palette - palettes().begin();
109 
110       if (palette != palettes().end())
111             return static_cast<PalettePanel*>(iptr);
112       return nullptr;
113       }
114 
115 //---------------------------------------------------------
116 //   PaletteTreeModel::findPalettePanel
117 //---------------------------------------------------------
118 
findPalettePanel(const QModelIndex & index) const119 const PalettePanel* PaletteTreeModel::findPalettePanel(const QModelIndex& index) const
120       {
121       if (index.internalPointer() != _paletteTree.get())
122             return nullptr;
123 
124       const int row = index.row();
125       if (index.column() != 0 || row < 0 || row > int(palettes().size()))
126             return nullptr;
127 
128       return palettes()[row].get();
129       }
130 
131 //---------------------------------------------------------
132 //   PaletteTreeModel::findPalettePanel
133 //---------------------------------------------------------
134 
findPalettePanel(const QModelIndex & index)135 PalettePanel* PaletteTreeModel::findPalettePanel(const QModelIndex& index)
136       {
137       return const_cast<PalettePanel*>(const_cast<const PaletteTreeModel*>(this)->findPalettePanel(index));
138       }
139 
140 //---------------------------------------------------------
141 //   PaletteTreeModel::findCell
142 //---------------------------------------------------------
143 
findCell(const QModelIndex & index) const144 PaletteCellConstPtr PaletteTreeModel::findCell(const QModelIndex& index) const
145       {
146       if (const PalettePanel* pp = iptrToPalettePanel(index.internalPointer())) {
147             const int row = index.row();
148             if (index.column() != 0 || row < 0 || row >= pp->ncells())
149                   return nullptr;
150             return pp->cell(row);
151             }
152 
153       return nullptr;
154       }
155 
156 //---------------------------------------------------------
157 //   PaletteTreeModel::findCell
158 //---------------------------------------------------------
159 
findCell(const QModelIndex & index)160 PaletteCellPtr PaletteTreeModel::findCell(const QModelIndex& index)
161       {
162       return std::const_pointer_cast<PaletteCell>(const_cast<const PaletteTreeModel*>(this)->findCell(index));
163       }
164 
165 //---------------------------------------------------------
166 //   PaletteTreeModel::setPaletteTree
167 //---------------------------------------------------------
168 
setPaletteTree(std::unique_ptr<PaletteTree> newTree)169 void PaletteTreeModel::setPaletteTree(std::unique_ptr<PaletteTree> newTree)
170       {
171       beginResetModel();
172       _paletteTree = std::move(newTree);
173       endResetModel();
174 
175       _treeChanged = false;
176       }
177 
178 //---------------------------------------------------------
179 //   PaletteTreeModel::index
180 //---------------------------------------------------------
181 
index(int row,int column,const QModelIndex & parent) const182 QModelIndex PaletteTreeModel::index(int row, int column, const QModelIndex& parent) const
183       {
184       if (!hasIndex(row, column, parent))
185             return QModelIndex();
186 
187       if (!parent.isValid())
188             return createIndex(row, column, const_cast<void*>(static_cast<const void*>(_paletteTree.get())));
189 
190       void* iptr = parent.internalPointer();
191 
192       if (iptr == _paletteTree.get())
193             return createIndex(row, column, palettes()[parent.row()].get());
194 
195       return QModelIndex();
196       }
197 
198 //---------------------------------------------------------
199 //   PaletteTreeModel::parent
200 //---------------------------------------------------------
201 
parent(const QModelIndex & modelIndex) const202 QModelIndex PaletteTreeModel::parent(const QModelIndex& modelIndex) const
203       {
204       void* iptr = modelIndex.internalPointer();
205 
206       if (iptr == _paletteTree.get())
207             return QModelIndex();
208 
209       int row;
210       if (iptrToPalettePanel(iptr, &row))
211             return index(row, /* column */ 0, QModelIndex());
212 
213       return QModelIndex();
214       }
215 
216 //---------------------------------------------------------
217 //   PaletteTreeModel::rowCount
218 //---------------------------------------------------------
219 
rowCount(const QModelIndex & parent) const220 int PaletteTreeModel::rowCount(const QModelIndex& parent) const
221       {
222       if (!parent.isValid())
223             return int(palettes().size());
224 
225       void* iptr = parent.internalPointer();
226 
227       if (iptr == _paletteTree.get()) {
228             const int row = parent.row();
229             if (parent.column() != 0 || row < 0 || row >= int(palettes().size()))
230                   return 0;
231             return palettes()[row]->ncells();
232             }
233 
234       return 0;
235       }
236 
237 //---------------------------------------------------------
238 //   PaletteTreeModel::columnCount
239 //---------------------------------------------------------
240 
columnCount(const QModelIndex & parent) const241 int PaletteTreeModel::columnCount(const QModelIndex& parent) const
242       {
243       Q_UNUSED(parent);
244       return 1;
245       }
246 
247 //---------------------------------------------------------
248 //   PaletteTreeModel::data
249 //---------------------------------------------------------
250 
data(const QModelIndex & index,int role) const251 QVariant PaletteTreeModel::data(const QModelIndex& index, int role) const
252       {
253       if (const PalettePanel* pp = findPalettePanel(index)) {
254             switch (role) {
255                   case Qt::DisplayRole:
256                   case Qt::ToolTipRole:
257                         return pp->translatedName();
258                   case Qt::AccessibleTextRole:
259                         return QString("%1 palette").arg(pp->translatedName());
260                   case VisibleRole:
261                         return pp->visible();
262                   case CustomRole:
263                         return pp->custom();
264                   case EditableRole:
265                         return pp->editable();
266                   case GridSizeRole:
267                         return pp->scaledGridSize();
268                   case DrawGridRole:
269                         return pp->drawGrid();
270                   case PaletteExpandedRole:
271                         return pp->expanded();
272                   // TODO showMore?
273                   case PaletteTypeRole:
274                         return QVariant::fromValue(pp->type());
275                   case PaletteContentTypeRole:
276                         return QVariant::fromValue(pp->contentType());
277                   }
278             return QVariant();
279             }
280 
281       if (PaletteCellConstPtr cell = findCell(index)) {
282             switch (role) {
283                   case Qt::DisplayRole:
284                         return QVariant(); // Don't show element names in
285                         // item views (i.e. just show icons). If you need
286                         // to know the name, use the ToolTip instead.
287                   case Qt::ToolTipRole:
288                         return cell->translatedName();
289                   case Qt::AccessibleTextRole: {
290                         QString name = cell->translatedName();
291                         ScoreAccessibility::makeReadable(name);
292                         return name;
293                         }
294                   case Qt::DecorationRole: {
295                         qreal extraMag = 1.0;
296                         if (const PalettePanel* pp = iptrToPalettePanel(index.internalPointer()))
297                               extraMag = pp->mag();
298                         return QIcon(new PaletteCellIconEngine(cell, extraMag * Palette::guiMag()));
299                         }
300                   case PaletteCellRole:
301                         return QVariant::fromValue(cell.get());
302                   case VisibleRole:
303                         return cell->visible;
304                   case CustomRole:
305                         return cell->custom;
306                   case EditableRole: {
307                         if (const PalettePanel* pp = iptrToPalettePanel(index.internalPointer()))
308                               return pp->editable();
309                         return false;
310                         }
311                   case MimeDataRole: {
312                         QVariantMap map;
313                         if (cell->element)
314                               map[mimeSymbolFormat] = cell->element->mimeData(QPointF());
315                         map[PaletteCell::mimeDataFormat] = cell->mimeData();
316                         return map;
317                         }
318                   case CellActiveRole:
319                         return cell->active;
320                   default:
321                         break;
322                   }
323             return QVariant();
324             }
325 
326       // data for root item
327       switch (role) {
328             case EditableRole:
329                   return true;
330             default:
331                   break;
332             }
333 
334       return QVariant();
335       }
336 
337 //---------------------------------------------------------
338 //   PaletteTreeModel::setData
339 //---------------------------------------------------------
340 
setData(const QModelIndex & index,const QVariant & value,int role)341 bool PaletteTreeModel::setData(const QModelIndex& index, const QVariant& value, int role)
342       {
343       if (PalettePanel* pp = findPalettePanel(index)) {
344             switch (role) {
345                   case VisibleRole:
346                         if (value.canConvert<bool>()) {
347                               const bool val = value.toBool();
348                               if (val != pp->visible()) {
349                                     pp->setVisible(val);
350                                     emit dataChanged(index, index, { VisibleRole });
351                                     }
352                               return true;
353                               }
354                         return false;
355                   case EditableRole:
356                         if (value.canConvert<bool>()) {
357                               const bool val = value.toBool();
358                               if (val != pp->editable()) {
359                                     pp->setEditable(val);
360                                     emit dataChanged(index, index, { EditableRole });
361                                     // notify cells editability changed too
362                                     const QModelIndex childFirstIndex = PaletteTreeModel::index(0, 0, index);
363                                     const int rows = rowCount(index);
364                                     const QModelIndex childLastIndex = PaletteTreeModel::index(rows - 1, 0, index);
365                                     emit dataChanged(childFirstIndex, childLastIndex, { EditableRole });
366                               }
367                               return true;
368                         }
369                         return false;
370                   case PaletteExpandedRole:
371                         if (value.canConvert<bool>()) {
372                               const bool val = value.toBool();
373                               if (val != pp->expanded()) {
374                                     const bool singlePalette = preferences.getBool(PREF_APP_USESINGLEPALETTE);
375 
376                                     if (singlePalette && val) {
377                                           for (auto& palette : palettes())
378                                                 palette->setExpanded(false);
379                                           pp->setExpanded(val);
380 
381                                           const QModelIndex parent = index.parent();
382                                           const int rows = rowCount(parent);
383                                           const QModelIndex first = PaletteTreeModel::index(0, 0, parent);
384                                           const QModelIndex last = PaletteTreeModel::index(rows - 1, 0, parent);
385                                           emit dataChanged(first, last, { PaletteExpandedRole });
386                                           }
387                                     else {
388                                           pp->setExpanded(val);
389                                           emit dataChanged(index, index, { PaletteExpandedRole });
390                                           }
391                                     }
392                               return true;
393                               }
394                         return false;
395                   case Qt::DisplayRole:
396                         pp->setName(value.toString());
397                         emit dataChanged(index, index, { Qt::DisplayRole, Qt::AccessibleTextRole });
398                         return true;
399 //                   case CustomRole:
400 //                         if (value.canConvert<bool>()) {
401 //                               const bool val = value.toBool();
402 //                               if (val != pp->custom()) {
403 //                                     pp->setCustom(val);
404 //                                     emit dataChanged(index, index, { CustomRole });
405 //                                     }
406 //                               return true;
407 //                               }
408 //                         return false;
409 //                   case gridSizeRole:
410 //                         return pp->gridSize();
411 //                   case drawGridRole:
412 //                         return pp->drawGrid();
413                   default:
414                         break;
415                   }
416             return false;
417             }
418 
419       if (PaletteCellPtr cell = findCell(index)) {
420             switch (role) {
421 //                   case Qt::DisplayRole:
422 //                   case Qt::ToolTipRole:
423                   // or EditRole?
424 //                         return cell->name;
425                   case PaletteCellRole: {
426                         PaletteCell* newCell = value.value<PaletteCell*>();
427                         if (!newCell)
428                               return false;
429                         *cell = std::move(*newCell);
430                         emit dataChanged(index, index);
431                         return true;
432                         };
433                   case VisibleRole:
434                         if (value.canConvert<bool>()) {
435                               const bool val = value.toBool();
436                               if (val != cell->visible) {
437                                     cell->visible = val;
438                                     emit dataChanged(index, index, { VisibleRole });
439                                     }
440                               return true;
441                               }
442                         return false;
443                   case CustomRole:
444                         if (value.canConvert<bool>()) {
445                               const bool val = value.toBool();
446                               if (val != cell->custom) {
447                                     cell->custom = val;
448                                     emit dataChanged(index, index, { CustomRole });
449                                     }
450                               return true;
451                               }
452                         return false;
453                   case MimeDataRole: {
454                         const QVariantMap map = value.toMap();
455 
456                         if (map.contains(PaletteCell::mimeDataFormat)) {
457                               const QByteArray cellMimeData = map[PaletteCell::mimeDataFormat].toByteArray();
458                               PaletteCellPtr newCell(PaletteCell::readMimeData(cellMimeData));
459                               if (!newCell)
460                                     return false;
461                               *cell = std::move(*newCell);
462                               }
463                         else if (map.contains(mimeSymbolFormat)) {
464                               const QByteArray elementMimeData = map[mimeSymbolFormat].toByteArray();
465                               *cell = std::move(*PaletteCell::readElementMimeData(elementMimeData));
466                               cell->custom = true; // mark the updated cell custom
467                               }
468                         else {
469                               return false;
470                               }
471 
472                         emit dataChanged(index, index);
473                         return true;
474                         }
475                   default:
476                         break;
477                   }
478             return false;
479             }
480 
481       return false;
482       }
483 
484 //---------------------------------------------------------
485 //   PaletteTreeModel::roleNames
486 //---------------------------------------------------------
487 
roleNames() const488 QHash<int, QByteArray> PaletteTreeModel::roleNames() const
489       {
490       QHash<int, QByteArray> roles(QAbstractItemModel::roleNames());
491       roles[MimeDataRole] = "mimeData";
492       roles[GridSizeRole] = "gridSize";
493       roles[DrawGridRole] = "drawGrid";
494       roles[CustomRole] = "custom";
495       roles[EditableRole] = "editable";
496       roles[PaletteExpandedRole] = "expanded";
497       roles[CellActiveRole] = "cellActive";
498       roles[Qt::AccessibleTextRole] = "accessibleText";
499       return roles;
500       }
501 
502 //---------------------------------------------------------
503 //   PaletteTreeModel::flags
504 //---------------------------------------------------------
505 
flags(const QModelIndex & index) const506 Qt::ItemFlags PaletteTreeModel::flags(const QModelIndex& index) const
507       {
508       return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled;
509       }
510 
511 //---------------------------------------------------------
512 //   PaletteTreeModel::supportedDropActions
513 //---------------------------------------------------------
514 
supportedDropActions() const515 Qt::DropActions PaletteTreeModel::supportedDropActions() const
516       {
517       return Qt::DropActions(Qt::CopyAction | Qt::MoveAction);
518       }
519 
520 //---------------------------------------------------------
521 //   PaletteTreeModel::mimeData
522 //---------------------------------------------------------
523 
mimeData(const QModelIndexList & indexes) const524 QMimeData* PaletteTreeModel::mimeData(const QModelIndexList& indexes) const
525       {
526       QMimeData* mime = QAbstractItemModel::mimeData(indexes); // TODO: needed or use only "our" MIME data?
527 
528       if (indexes.empty() || indexes.size() > 1)
529             return mime;
530 
531       if (const PalettePanel* pp = findPalettePanel(indexes[0])) {
532             mime->setData(PalettePanel::mimeDataFormat, pp->mimeData());
533             }
534       else if (PaletteCellConstPtr cell = findCell(indexes[0])) {
535             mime->setData(mimeSymbolFormat, cell->element->mimeData(QPointF()));
536             }
537 
538       return mime;
539       }
540 
541 //---------------------------------------------------------
542 //   PaletteTreeModel::mimeTypes
543 //---------------------------------------------------------
544 
mimeTypes() const545 QStringList PaletteTreeModel::mimeTypes() const
546       {
547       QStringList types = QAbstractItemModel::mimeTypes();
548       types << mimeSymbolFormat;
549       return types;
550       }
551 
552 //---------------------------------------------------------
553 //   PaletteTreeModel::canDropMimeData
554 //---------------------------------------------------------
555 
canDropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent) const556 bool PaletteTreeModel::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
557       {
558       Q_UNUSED(column);
559 
560       if (!parent.isValid())
561             return (action == Qt::CopyAction) && data->hasFormat(PalettePanel::mimeDataFormat);
562 
563       if (const PalettePanel* pp = findPalettePanel(parent)) {
564             if (row < 0 || row > pp->ncells())
565                   return false;
566 
567             if (data->hasFormat(PaletteCell::mimeDataFormat))
568                   return action & (Qt::CopyAction | Qt::MoveAction);
569             else if (data->hasFormat(mimeSymbolFormat))
570                   return action == Qt::CopyAction;
571             }
572       return false;
573       }
574 
575 //---------------------------------------------------------
576 //   PaletteTreeModel::dropMimeData
577 //---------------------------------------------------------
578 
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent)579 bool PaletteTreeModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
580       {
581       Q_UNUSED(column); // when dropping at the end of palette, column == -1. Probably an effect of proxy models
582 
583       if (!parent.isValid()) {
584             if (action != Qt::CopyAction || !data->hasFormat(PalettePanel::mimeDataFormat))
585                   return false;
586             if (row < 0 || row > int(palettes().size()))
587                   return false;
588 
589             auto panel = PalettePanel::readMimeData(data->data(PalettePanel::mimeDataFormat));
590             if (!panel)
591                   return false;
592 
593             beginInsertRows(parent, row, row);
594             palettes().insert(palettes().begin() + row, std::move(panel));
595             endInsertRows();
596             return true;
597             }
598 
599       if (PalettePanel* pp = findPalettePanel(parent)) {
600             if (row < 0 || row > pp->ncells())
601                   return false;
602 
603             PaletteCellPtr cell;
604 
605             if (data->hasFormat(PaletteCell::mimeDataFormat)) {
606                   cell = PaletteCell::readMimeData(data->data(PaletteCell::mimeDataFormat));
607                   if (action == Qt::CopyAction)
608                         cell->custom = true;
609                   }
610             else if (data->hasFormat(mimeSymbolFormat)) {
611                   cell = PaletteCell::readElementMimeData(data->data(mimeSymbolFormat));
612                   cell->custom = true; // the cell is created by dropping an element so it is custom
613                   }
614 
615             if (!cell)
616                   return false;
617 
618             beginInsertRows(parent, row, row);
619             pp->insertCell(row, std::move(cell));
620             endInsertRows();
621             return true;
622             }
623       return false;
624       }
625 
626 //---------------------------------------------------------
627 //   PaletteTreeModel::moveRows
628 //---------------------------------------------------------
629 
moveRows(const QModelIndex & sourceParent,int sourceRow,int count,const QModelIndex & destinationParent,int destinationChild)630 bool PaletteTreeModel::moveRows(const QModelIndex& sourceParent, int sourceRow, int count, const QModelIndex& destinationParent, int destinationChild)
631       {
632       const bool sameParent = sourceParent == destinationParent;
633 
634       if (!sourceParent.isValid()) {
635             // moving palette panels
636             if (sourceRow + count > int(palettes().size()) || destinationChild >= int(palettes().size()))
637                   return false;
638 
639             if (!sameParent) {
640                   // Cannot move between different parents, at least for now
641                   // TODO: reconsider?
642                   return false;
643                   }
644 
645             // The moved rows are considered to be inserted *before* destinationRow,
646             // so if we want to move a row down in the list within the same parent
647             // then we should increment the destinationChild index.
648             const int destinationRow = (destinationChild >= sourceRow) ? destinationChild + 1 : destinationChild;
649 
650             if (sameParent && sourceRow == destinationRow)
651                 return false;
652 
653             std::vector<std::unique_ptr<PalettePanel>> movedPanels;
654 
655             auto srcBegin = palettes().begin() + sourceRow;
656             auto srcEnd = srcBegin + count;
657 
658             // As of Qt 5.12, Qt proxy models rebuild the entire index mapping if
659             // layoutChanged() gets emitted (i.e. if begin/endMoveRows() gets called).
660             // Performance is much better when doing remove + insert rows instead.
661             beginRemoveRows(sourceParent, sourceRow, sourceRow + count - 1);
662             movedPanels.reserve(count);
663             movedPanels.insert(movedPanels.end(), std::make_move_iterator(srcBegin), std::make_move_iterator(srcEnd));
664             palettes().erase(srcBegin, srcEnd);
665             endRemoveRows();
666 
667             const int destIdx = (destinationRow < sourceRow) ? destinationRow : (destinationRow - count);
668             auto dest = palettes().begin() + destIdx;
669 
670             beginInsertRows(destinationParent, destIdx, destIdx + count - 1);
671             palettes().insert(dest, std::make_move_iterator(movedPanels.begin()), std::make_move_iterator(movedPanels.end()));
672             endInsertRows();
673 
674             return true;
675             }
676 
677       PalettePanel* sourcePanel = findPalettePanel(sourceParent);
678       PalettePanel* destPanel = sameParent ? sourcePanel : findPalettePanel(destinationParent);
679       if (sourcePanel && destPanel) {
680             // moving palette cells
681             if (sourceRow + count > sourcePanel->ncells() || destinationChild > destPanel->ncells())
682                   return false;
683 
684             // The moved rows are considered to be inserted *before* destinationRow,
685             // so if we want to move a row down in the list within the same parent
686             // then we should increment the destinationChild index.
687             const int destinationRow = (sameParent && destinationChild >= sourceRow) ? destinationChild + 1 : destinationChild;
688 
689             if (sameParent && sourceRow == destinationRow)
690                 return false;
691 
692             // As of Qt 5.12, Qt proxy models rebuild the entire index mapping if
693             // layoutChanged() gets emitted (i.e. if begin/endMoveRows() gets called).
694             // Performance is much better when doing remove + insert rows instead.
695             beginRemoveRows(sourceParent, sourceRow, sourceRow + count - 1);
696             auto movedCells(sourcePanel->takeCells(sourceRow, count));
697             endRemoveRows();
698 
699             const int destIdx = (sameParent && destinationRow >= sourceRow) ? (destinationRow - count) : destinationRow;
700 
701             beginInsertRows(destinationParent, destIdx, destIdx + count - 1);
702             destPanel->insertCells(destIdx, std::move(movedCells));
703             endInsertRows();
704 
705             return true;
706             }
707 
708       return false;
709       }
710 
711 //---------------------------------------------------------
712 //   PaletteTreeModel::removeRows
713 //---------------------------------------------------------
714 
removeRows(int row,int count,const QModelIndex & parent)715 bool PaletteTreeModel::removeRows(int row, int count, const QModelIndex& parent)
716       {
717       if (!parent.isValid()) {
718             // removing palette panels
719             if (row < 0 || row + count > int(palettes().size()))
720                   return false;
721 
722             beginRemoveRows(parent, row, row + count - 1);
723             auto rangeBegin = palettes().begin() + row;
724             auto rangeEnd = rangeBegin + count;
725             palettes().erase(rangeBegin, rangeEnd);
726             endRemoveRows();
727             return true;
728             }
729 
730       if (PalettePanel* panel = findPalettePanel(parent)) {
731             // removing palette cells
732             if (row < 0 || row + count > panel->ncells())
733                   return false;
734 
735             beginRemoveRows(parent, row, row + count - 1);
736             panel->takeCells(row, count);
737             endRemoveRows();
738             return true;
739             }
740 
741       return false;
742       }
743 
744 //---------------------------------------------------------
745 //   PaletteTreeModel::insertRows
746 //---------------------------------------------------------
747 
insertRows(int row,int count,const QModelIndex & parent)748 bool PaletteTreeModel::insertRows(int row, int count, const QModelIndex& parent)
749       {
750       if (!parent.isValid()) {
751             // inserting palette panels
752             if (row < 0 || row > int(palettes().size()))
753                   return false;
754 
755             beginInsertRows(parent, row, row + count - 1);
756             for (int i = 0; i < count; ++i) {
757                   std::unique_ptr<PalettePanel> p(new PalettePanel(PalettePanel::Type::Custom));
758                   p->setName(QT_TRANSLATE_NOOP("Palette", "Custom"));
759                   p->setGrid(QSize(48, 48));
760                   p->setExpanded(true);
761                   palettes().insert(palettes().begin() + row, std::move(p));
762                   }
763             endInsertRows();
764             return true;
765             }
766 
767       if (PalettePanel* panel = findPalettePanel(parent)) {
768             // inserting palette cells
769             if (row < 0 || row > panel->ncells())
770                   return false;
771 
772             beginInsertRows(parent, row, row + count - 1);
773             for (int i = 0; i < count; ++i)
774                   panel->insertCell(row, PaletteCellPtr(new PaletteCell));
775             endInsertRows();
776             return true;
777             }
778 
779       return false;
780       }
781 
782 //---------------------------------------------------------
783 //   PaletteTreeModel::insertPalettePanel
784 //---------------------------------------------------------
785 
insertPalettePanel(std::unique_ptr<PalettePanel> pp,int row,const QModelIndex & parent)786 bool PaletteTreeModel::insertPalettePanel(std::unique_ptr<PalettePanel> pp, int row, const QModelIndex& parent)
787       {
788       if (row < 0 || row > int(palettes().size()) || parent != QModelIndex())
789             return false;
790       beginInsertRows(parent, row, row);
791       palettes().insert(palettes().begin() + row, std::move(pp));
792       endInsertRows();
793       return true;
794       }
795 
796 //---------------------------------------------------------
797 //   PaletteTreeModel::updateCellsState
798 //---------------------------------------------------------
799 
updateCellsState(const Selection & sel)800 void PaletteTreeModel::updateCellsState(const Selection& sel)
801       {
802       const ChordRest* cr = sel.firstChordRest();
803       const Beam::Mode bm = cr ? cr->beamMode() : Beam::Mode::NONE;
804       const IconType beamIconType = Beam::iconType(bm);
805       bool deactivateAll = !cr;
806 
807       for (Element* e : sel.elements()) {
808             if (e->isNote())
809                   e = e->parent();
810             if (e->isChordRest()) {
811                   if (toChordRest(e)->beamMode() != bm)
812                         deactivateAll = true;
813                   }
814             }
815 
816       const size_t npalettes = palettes().size();
817       for (size_t row = 0; row < npalettes; ++row) {
818             PalettePanel* palette = palettes()[row].get();
819             // TODO: should this be turned on for all palettes?
820             if (palette->type() != PalettePanel::Type::Beam)
821                   continue;
822 
823             for (int ci = 0; ci < palette->ncells(); ++ci) {
824                   PaletteCellPtr cell = palette->cell(ci);
825                   if (deactivateAll)
826                         cell->active = false;
827                   else if (cell->element && cell->element->isIcon()) {
828                         const Icon* icon = toIcon(cell->element.get());
829                         cell->active = (icon->iconType() == beamIconType);
830                         }
831                   }
832 
833             const QModelIndex parent = index(int(row), 0, QModelIndex());
834             const QModelIndex first = index(0, 0, parent);
835             const QModelIndex last = index(palette->ncells() - 1, 0, parent);
836             emit dataChanged(first, last, { CellActiveRole });
837             }
838       }
839 
840 //---------------------------------------------------------
841 //   PaletteTreeModel::retranslate
842 //---------------------------------------------------------
843 
retranslate()844 void PaletteTreeModel::retranslate()
845       {
846       _paletteTree->retranslate();
847       }
848 
849 //---------------------------------------------------------
850 //   PaletteTreeModel::findPaletteCell
851 //---------------------------------------------------------
852 
findPaletteCell(const PaletteCell & cell,const QModelIndex & parent) const853 QModelIndex PaletteTreeModel::findPaletteCell(const PaletteCell& cell, const QModelIndex& parent) const
854       {
855       if (const PalettePanel* pp = findPalettePanel(parent)) {
856             const int idx = pp->findPaletteCell(cell);
857             if (idx == -1)
858                   return QModelIndex();
859             return index(idx, /* column */ 0, parent);
860             }
861       return QModelIndex();
862       }
863 
864 //---------------------------------------------------------
865 //   PaletteTreeModel::match
866 ///   Currently only searching for a given palette cell is
867 ///   implemented, for anything else the corresponding
868 ///   member function of QAbstractItemModel is used.
869 //---------------------------------------------------------
870 
match(const QModelIndex & start,int role,const QVariant & value,int hits,Qt::MatchFlags flags) const871 QModelIndexList PaletteTreeModel::match(const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags) const
872       {
873       if (role != PaletteCellRole || flags != Qt::MatchExactly || hits != 1
874          || !value.canConvert<const PaletteCell*>()
875          || !findPalettePanel(start.parent())
876          )
877             return QAbstractItemModel::match(start, role, value, hits, flags);
878 
879       const PaletteCell* cell = value.value<const PaletteCell*>();
880       if (!cell)
881             return QModelIndexList();
882 
883       return QModelIndexList({ findPaletteCell(*cell, start.parent()) });
884       }
885 
886 //---------------------------------------------------------
887 //   PaletteTreeModel::itemDataChanged
888 //---------------------------------------------------------
889 
itemDataChanged(const QModelIndex & idx)890 void PaletteTreeModel::itemDataChanged(const QModelIndex& idx)
891       {
892       emit dataChanged(idx, idx);
893       if (findPalettePanel(idx)) {
894             // palette cells appearance depends on palette settings
895             const QModelIndex childFirstIndex = index(0, 0, idx);
896             const int rows = rowCount(idx);
897             const QModelIndex childLastIndex = index(rows - 1, 0, idx);
898             emit dataChanged(childFirstIndex, childLastIndex, { Qt::DecorationRole });
899             }
900       }
901 
902 //---------------------------------------------------------
903 //   PaletteCellFilter::addChainedFilter
904 ///   Ownership over the added filter is passed to this
905 ///   filter.
906 //---------------------------------------------------------
907 
addChainedFilter(PaletteCellFilter * newFilter)908 void PaletteCellFilter::addChainedFilter(PaletteCellFilter* newFilter)
909       {
910       PaletteCellFilter* f = this;
911       while (f->chainedFilter)
912             f = f->chainedFilter;
913 
914       newFilter->setParent(f);
915       f->chainedFilter = newFilter;
916       connect(newFilter, &PaletteCellFilter::filterChanged, f, &PaletteCellFilter::filterChanged);
917       }
918 
919 //---------------------------------------------------------
920 //   PaletteCellFilter::accept
921 //---------------------------------------------------------
922 
accept(const PaletteCell & cell) const923 bool PaletteCellFilter::accept(const PaletteCell& cell) const
924       {
925       const PaletteCellFilter* f = this;
926       while (f) {
927             if (!f->acceptCell(cell))
928                   return false;
929             f = f->chainedFilter;
930             }
931       return true;
932       }
933 
934 //---------------------------------------------------------
935 //   PaletteCellFilter::connectToModel
936 //---------------------------------------------------------
937 
connectToModel(const QAbstractItemModel * model)938 void PaletteCellFilter::connectToModel(const QAbstractItemModel* model)
939       {
940 //       TODO: are all these needed?
941 //       columnsInserted(const QModelIndex &parent, int first, int last)
942 //       columnsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int column)
943 //       columnsRemoved(const QModelIndex &parent, int first, int last)
944       connect(model, &QAbstractItemModel::dataChanged, this, &PaletteCellFilter::filterChanged);
945 //       dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = ...)
946       connect(model, &QAbstractItemModel::layoutChanged, this, &PaletteCellFilter::filterChanged);
947 //       layoutChanged(const QList<QPersistentModelIndex> &parents = ..., QAbstractItemModel::LayoutChangeHint hint = ...)
948       connect(model, &QAbstractItemModel::modelReset, this, &PaletteCellFilter::filterChanged);
949 //       modelReset()
950       connect(model, &QAbstractItemModel::rowsInserted, this, &PaletteCellFilter::filterChanged);
951 //       rowsInserted(const QModelIndex &parent, int first, int last)
952       connect(model, &QAbstractItemModel::rowsMoved, this, &PaletteCellFilter::filterChanged);
953 //       rowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
954       connect(model, &QAbstractItemModel::rowsRemoved, this, &PaletteCellFilter::filterChanged);
955 //       rowsRemoved(const QModelIndex &parent, int first, int last)
956       }
957 
958 //---------------------------------------------------------
959 //   ExcludePaletteCellFilter
960 //---------------------------------------------------------
961 
962 class ExcludePaletteCellFilter : public PaletteCellFilter {
963       const PalettePanel* excludePanel;
964       const QPersistentModelIndex panelIndex; // filter is valid as long as this index is valid too
965 
966    public:
ExcludePaletteCellFilter(const PalettePanel * p,QPersistentModelIndex index,QObject * parent=nullptr)967       ExcludePaletteCellFilter(const PalettePanel* p, QPersistentModelIndex index, QObject* parent = nullptr)
968          : PaletteCellFilter(parent), excludePanel(p), panelIndex(std::move(index)) {}
969 
acceptCell(const PaletteCell & cell) const970       bool acceptCell(const PaletteCell& cell) const override
971             {
972             return panelIndex.isValid() && -1 == excludePanel->findPaletteCell(cell, /* matchName */ false);
973             }
974       };
975 
976 //---------------------------------------------------------
977 //   PaletteTreeModel::getFilter
978 ///   The ownership of the returned filter is passed to a
979 ///   caller.
980 //---------------------------------------------------------
981 
getFilter(const QModelIndex & index) const982 PaletteCellFilter* PaletteTreeModel::getFilter(const QModelIndex& index) const
983       {
984       if (const PalettePanel* pp = findPalettePanel(index)) {
985             ExcludePaletteCellFilter* filter = new ExcludePaletteCellFilter(pp, index);
986             filter->connectToModel(this);
987             return filter;
988             }
989 
990       // TODO: make a filter for a single cell?
991 
992       return nullptr;
993       }
994 
995 //---------------------------------------------------------
996 //   FilterPaletteTreeModel::FilterPaletteTreeModel
997 //---------------------------------------------------------
998 
FilterPaletteTreeModel(PaletteCellFilter * filter,PaletteTreeModel * model,QObject * parent)999 FilterPaletteTreeModel::FilterPaletteTreeModel(PaletteCellFilter* filter, PaletteTreeModel* model, QObject* parent)
1000    : QSortFilterProxyModel(parent), cellFilter(filter)
1001       {
1002       cellFilter->setParent(this);
1003 //       connect(cellFilter, &PaletteCellFilter::filterChanged, this, &QSortFilterProxyModel::invalidate);
1004       connect(cellFilter, &PaletteCellFilter::filterChanged, this, &FilterPaletteTreeModel::invalidateFilter);
1005 
1006       setSourceModel(model);
1007       }
1008 
1009 //---------------------------------------------------------
1010 //   FilterPaletteTreeModel::filterAcceptsRow
1011 //---------------------------------------------------------
1012 
filterAcceptsRow(int sourceRow,const QModelIndex & sourceParent) const1013 bool FilterPaletteTreeModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
1014       {
1015       const QModelIndex& cellIndex = sourceModel()->index(sourceRow, /* column */ 0, sourceParent);
1016       const QVariant cellData = sourceModel()->data(cellIndex, PaletteTreeModel::PaletteCellRole);
1017       const PaletteCell* cell = cellData.value<const PaletteCell*>();
1018       if (!cell) // a palette panel or just an unrelated model
1019             return true;
1020       return cellFilter->accept(*cell);
1021       }
1022 
1023 //---------------------------------------------------------
1024 //   PaletteCellFilterProxyModel::PaletteCellFilterProxyModel
1025 //---------------------------------------------------------
1026 
PaletteCellFilterProxyModel(QObject * parent)1027 PaletteCellFilterProxyModel::PaletteCellFilterProxyModel(QObject* parent)
1028       : QSortFilterProxyModel(parent)
1029       {
1030       setFilterRole(Qt::ToolTipRole); // palette cells have no data for DisplayRole
1031       }
1032 
1033 //---------------------------------------------------------
1034 //   PaletteCellFilterProxyModel::filterAcceptsRow
1035 //---------------------------------------------------------
1036 
filterAcceptsRow(int sourceRow,const QModelIndex & sourceParent) const1037 bool PaletteCellFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
1038       {
1039       const QAbstractItemModel* model = sourceModel();
1040       const QModelIndex rowIndex = model->index(sourceRow, 0, sourceParent);
1041       const int rowCount = model->rowCount(rowIndex);
1042 
1043       if (rowCount == 0) {
1044             if (QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent))
1045                   return true;
1046             // accept row if its parent is accepted by filter: necessary to be able to search by palette name
1047             if (sourceParent.isValid() && QSortFilterProxyModel::filterAcceptsRow(sourceParent.row(), sourceParent.parent()))
1048                   return true;
1049             return false;
1050             }
1051 
1052       for (int i = 0; i < rowCount; ++i) {
1053             if (filterAcceptsRow(i, rowIndex))
1054                   return true;
1055             }
1056 
1057       return false;
1058       }
1059 } // namespace Ms
1060