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