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 "paletteworkspace.h"
21 
22 #include "libmscore/keysig.h"
23 #include "libmscore/timesig.h"
24 
25 #include "createpalettedialog.h"
26 #include "keyedit.h"
27 #include "musescore.h"
28 #include "palette.h" // applyPaletteElement
29 #include "palettedialog.h"
30 #include "palettecelldialog.h"
31 #include "palettewidget.h"
32 #include "timedialog.h"
33 
34 namespace Ms {
35 
36 //---------------------------------------------------------
37 //   PaletteElementEditor::valid
38 //---------------------------------------------------------
39 
valid() const40 bool PaletteElementEditor::valid() const
41       {
42       using Type = PalettePanel::Type;
43       switch (_type) {
44             case Type::KeySig:
45             case Type::TimeSig:
46                   return true;
47             default:
48                   break;
49             }
50       return false;
51       }
52 
53 //---------------------------------------------------------
54 //   PaletteElementEditor::actionName
55 //---------------------------------------------------------
56 
actionName() const57 QString PaletteElementEditor::actionName() const
58       {
59       using Type = PalettePanel::Type;
60       switch (_type) {
61             case Type::KeySig:
62                   return tr("Create Key Signature");
63             case Type::TimeSig:
64                   return tr("Create Time Signature");
65             default:
66                   break;
67             }
68       return QString();
69       }
70 
71 //---------------------------------------------------------
72 //   PaletteElementEditor::onElementAdded
73 //---------------------------------------------------------
74 
onElementAdded(const Element * el)75 void PaletteElementEditor::onElementAdded(const Element* el)
76       {
77       if (!_paletteIndex.isValid()
78          || !_paletteIndex.data(PaletteTreeModel::VisibleRole).toBool()) {
79             QMessageBox::information(mscore, "", tr("The palette was hidden or changed"));
80             return;
81             }
82       QVariantMap mimeData;
83       mimeData[mimeSymbolFormat] = el->mimeData(QPointF());
84       _controller->insert(_paletteIndex, -1, mimeData, Qt::CopyAction);
85       }
86 
87 //---------------------------------------------------------
88 //   PaletteElementEditor::open
89 //---------------------------------------------------------
90 
open()91 void PaletteElementEditor::open()
92       {
93       if (!_paletteIndex.isValid())
94             return;
95 
96       QWidget* editor = nullptr;
97 
98       using Type = PalettePanel::Type;
99       switch (_type) {
100             case Type::KeySig: {
101                   KeyEditor* keyEditor = new KeyEditor(mscore);
102                   keyEditor->showKeyPalette(false);
103                   connect(keyEditor, &KeyEditor::keySigAdded, this, &PaletteElementEditor::onElementAdded);
104                   editor = keyEditor;
105                   }
106                   break;
107             case Type::TimeSig: {
108                   TimeDialog* timeEditor = new TimeDialog(mscore);
109                   timeEditor->showTimePalette(false);
110                   connect(timeEditor, &TimeDialog::timeSigAdded, this, &PaletteElementEditor::onElementAdded);
111                   editor = timeEditor;
112                   }
113                   break;
114             default:
115                   break;
116             }
117 
118       if (!editor)
119             return;
120 
121       mscore->stackUnder(editor);
122       editor->setAttribute(Qt::WA_DeleteOnClose);
123 
124       editor->show();
125       }
126 
127 //---------------------------------------------------------
128 //   findPaletteIndex
129 //---------------------------------------------------------
130 
findPaletteIndex(const QAbstractItemModel * model,PalettePanel::Type type)131 static QModelIndex findPaletteIndex(const QAbstractItemModel* model, PalettePanel::Type type)
132       {
133       constexpr int role = PaletteTreeModel::PaletteTypeRole;
134       const QModelIndex start = model->index(0, 0);
135       const QModelIndexList foundIndexList = model->match(start, role, QVariant::fromValue(type));
136 
137       if (!foundIndexList.empty())
138             return foundIndexList[0];
139       return QModelIndex();
140       }
141 
142 //---------------------------------------------------------
143 //   convertIndex
144 //---------------------------------------------------------
145 
convertIndex(const QModelIndex & index,const QAbstractItemModel * targetModel)146 static QModelIndex convertIndex(const QModelIndex& index, const QAbstractItemModel* targetModel)
147       {
148       if (index.model() == targetModel || !index.isValid())
149             return index;
150 
151       constexpr int typeRole = PaletteTreeModel::PaletteTypeRole;
152       const auto type = index.model()->data(index, typeRole).value<PalettePanel::Type>();
153 
154       return findPaletteIndex(targetModel, type);
155       }
156 
157 //---------------------------------------------------------
158 //   convertProxyIndex
159 //---------------------------------------------------------
160 
convertProxyIndex(const QModelIndex & srcIndex,const QAbstractItemModel * targetModel)161 static QModelIndex convertProxyIndex(const QModelIndex& srcIndex, const QAbstractItemModel* targetModel)
162       {
163       QModelIndex index = srcIndex;
164       while (index.model() != targetModel) {
165             if (auto m = qobject_cast<const QAbstractProxyModel*>(index.model()))
166                   index = m->mapToSource(index);
167             else
168                   break;
169             }
170 
171       if (targetModel && index.model() != targetModel)
172             return QModelIndex();
173       return index;
174       }
175 
176 //---------------------------------------------------------
177 //   AbstractPaletteController::elementEditor
178 //---------------------------------------------------------
179 
elementEditor(const QModelIndex & paletteIndex)180 PaletteElementEditor* AbstractPaletteController::elementEditor(const QModelIndex& paletteIndex)
181       {
182       PaletteElementEditor* ed = new PaletteElementEditor(this, paletteIndex, paletteIndex.data(PaletteTreeModel::PaletteTypeRole).value<PalettePanel::Type>(), this);
183       QQmlEngine::setObjectOwnership(ed, QQmlEngine::JavaScriptOwnership);
184       return ed;
185       }
186 
187 //---------------------------------------------------------
188 //   UserPaletteController::dropAction
189 //---------------------------------------------------------
190 
dropAction(const QVariantMap & mimeData,Qt::DropAction proposedAction,const QModelIndex & parent,bool internal) const191 Qt::DropAction UserPaletteController::dropAction(const QVariantMap& mimeData, Qt::DropAction proposedAction, const QModelIndex& parent, bool internal) const
192       {
193       if (internal && !userEditable())
194             return Qt::IgnoreAction;
195       const bool parentEditingEnabled = model()->data(parent, PaletteTreeModel::EditableRole).toBool();
196       if (!parentEditingEnabled)
197             return Qt::IgnoreAction;
198 
199       if (mimeData.contains(PaletteCell::mimeDataFormat) && proposedAction == Qt::MoveAction) {
200             const auto cell = PaletteCell::readMimeData(mimeData[PaletteCell::mimeDataFormat].toByteArray());
201             if (!cell)
202                   return Qt::IgnoreAction;
203             if (_filterCustom && cell->custom != _custom)
204                   return Qt::IgnoreAction;
205             return Qt::MoveAction;
206             }
207       if (mimeData.contains(mimeSymbolFormat) && proposedAction == Qt::CopyAction) {
208             if (_filterCustom && !_custom)
209                   return Qt::IgnoreAction;
210             return Qt::CopyAction;
211             }
212       return Qt::IgnoreAction;
213       }
214 
215 //---------------------------------------------------------
216 //   UserPaletteController::insert
217 //---------------------------------------------------------
218 
insert(const QModelIndex & parent,int row,const QVariantMap & mimeData,Qt::DropAction action)219 bool UserPaletteController::insert(const QModelIndex& parent, int row, const QVariantMap& mimeData, Qt::DropAction action)
220       {
221       if (dropAction(mimeData, action, parent, false) == Qt::IgnoreAction)
222             return false;
223 
224       if (row < 0)
225             row = parent.model()->rowCount(parent);
226 
227       PaletteCellPtr cell;
228 
229       if (mimeData.contains(PaletteCell::mimeDataFormat)) {
230             cell = PaletteCell::readMimeData(mimeData[PaletteCell::mimeDataFormat].toByteArray());
231 
232             if (!cell)
233                   return false;
234             if (_filterCustom && cell->custom != _custom)
235                   return false;
236 
237             if (action == Qt::MoveAction) {
238                   const QModelIndex visiblePaletteParentIndex = convertIndex(parent, _userPalette);
239                   const QModelIndex foundIndex(_userPalette->findPaletteCell(*cell, visiblePaletteParentIndex));
240                   if (foundIndex.isValid())
241                         return _userPalette->setData(foundIndex, _visible, PaletteTreeModel::VisibleRole);
242                   else if (!userEditable())
243                         return false;
244                   }
245             }
246       else if (mimeData.contains(mimeSymbolFormat) && (action == Qt::CopyAction))
247             cell = PaletteCell::readElementMimeData(mimeData[mimeSymbolFormat].toByteArray());
248 
249       if (!cell)
250             return false;
251 
252       if (_filterCustom) {
253             if (!_custom)
254                   return false; // can only move non-custom cells
255             cell->custom = _custom;
256             }
257       cell->visible = _visible;
258 
259       QMimeData data;
260       data.setData(PaletteCell::mimeDataFormat, cell->mimeData());
261       constexpr int column = 0;
262       return model()->dropMimeData(&data, action, row, column, parent);
263       }
264 
265 //---------------------------------------------------------
266 //   UserPaletteController::insertNewItem
267 //---------------------------------------------------------
268 
insertNewItem(const QModelIndex & parent,int row)269 bool UserPaletteController::insertNewItem(const QModelIndex& parent, int row)
270       {
271       if (!canEdit(parent))
272             return false;
273 
274       const bool newItemIsPalette = !parent.isValid();
275 
276       if (newItemIsPalette) {
277             CreatePaletteDialog dlg(mscore);
278             const int result = dlg.exec();
279             if (result == QDialog::Rejected || dlg.paletteName().isEmpty())
280                   return false;
281 
282             if (!model()->insertRow(row, parent))
283                   return false;
284             model()->setData(model()->index(row, 0, parent), dlg.paletteName(), Qt::DisplayRole);
285             return true;
286             }
287 
288       return model()->insertRow(row, parent);
289       }
290 
291 //---------------------------------------------------------
292 //   UserPaletteController::move
293 //---------------------------------------------------------
294 
move(const QModelIndex & sourceParent,int sourceRow,const QModelIndex & destinationParent,int destinationChild)295 bool UserPaletteController::move(const QModelIndex& sourceParent, int sourceRow, const QModelIndex& destinationParent, int destinationChild)
296       {
297       if (!canEdit(sourceParent) || !canEdit(destinationParent))
298             return false;
299       if (sourceParent == destinationParent && (sourceParent.model() == model() || !sourceParent.isValid())) {
300             const QModelIndex srcIndex = convertProxyIndex(model()->index(sourceRow, 0, sourceParent), _userPalette);
301             const QModelIndex destIndex = convertProxyIndex(model()->index(destinationChild, 0, destinationParent), _userPalette);
302             return _userPalette->moveRow(srcIndex.parent(), srcIndex.row(), destIndex.parent(), destIndex.row());
303             }
304       return false;
305       }
306 
307 //---------------------------------------------------------
308 //   UserPaletteController::showHideOrDeleteDialog
309 //---------------------------------------------------------
310 
showHideOrDeleteDialog(const QString & question,std::function<void (AbstractPaletteController::RemoveAction)> resultHandler) const311 void UserPaletteController::showHideOrDeleteDialog(const QString& question, std::function<void(AbstractPaletteController::RemoveAction)> resultHandler) const
312       {
313       QMessageBox* msg = new QMessageBox(mscore);
314       msg->setIcon(QMessageBox::Question);
315       msg->setText(question);
316       msg->setTextFormat(Qt::PlainText);
317       QPushButton* deleteButton = msg->addButton(tr("Delete permanently"), QMessageBox::DestructiveRole);
318       QPushButton* hideButton = msg->addButton(tr("Hide"), QMessageBox::AcceptRole);
319       msg->addButton(QMessageBox::Cancel);
320       msg->setDefaultButton(hideButton);
321 
322       connect(msg, &QDialog::finished, this, [=]() {
323             RemoveAction action = RemoveAction::NoAction;
324 
325             const QAbstractButton* btn = msg->clickedButton();
326             if (btn == deleteButton)
327                   action = RemoveAction::DeletePermanently;
328             else if (btn == hideButton)
329                   action = RemoveAction::Hide;
330 
331             resultHandler(action);
332             });
333 
334       msg->setWindowModality(Qt::ApplicationModal);
335       msg->setAttribute(Qt::WA_DeleteOnClose);
336       msg->open();
337       }
338 
339 //---------------------------------------------------------
340 //   UserPaletteController::queryRemove
341 //---------------------------------------------------------
342 
queryRemove(const QModelIndexList & removeIndices,int customCount)343 void UserPaletteController::queryRemove(const QModelIndexList& removeIndices, int customCount)
344       {
345       using RemoveAction = AbstractPaletteController::RemoveAction;
346 
347       if (removeIndices.empty() || !canEdit(removeIndices[0].parent()))
348             return;
349 
350       if (!customCount) {
351             remove(removeIndices, RemoveAction::AutoAction);
352             return;
353             }
354 
355       // these parameters should not depend on exact index as all they should have the same parent in palette tree
356       const bool visible = removeIndices[0].data(PaletteTreeModel::VisibleRole).toBool();
357       const bool isCell = bool(removeIndices[0].data(PaletteTreeModel::PaletteCellRole).value<const PaletteCell*>());
358 
359       RemoveAction action = RemoveAction::AutoAction;
360 
361       if (isCell) {
362             if (visible) {
363                   showHideOrDeleteDialog(
364                      customCount == 1 ? tr("Do you want to hide this custom palette cell or permanently delete it?") : tr("Do you want to hide these custom palette cells or permanently delete them?"),
365                      [=](RemoveAction action) { remove(removeIndices, action); }
366                      );
367                   return;
368                   }
369             else {
370                   QMessageBox* msg = new QMessageBox(
371                      QMessageBox::Question,
372                      "",
373                      customCount == 1 ? tr("Do you want to permanently delete this custom palette cell?") : tr("Do you want to permanently delete these custom palette cells?"),
374                      QMessageBox::Yes | QMessageBox::No,
375                      mscore
376                      );
377 
378                   connect(msg, &QDialog::finished, this, [=]() {
379                         const auto result = msg->standardButton(msg->clickedButton());
380                         if (result == QMessageBox::Yes)
381                               remove(removeIndices, RemoveAction::DeletePermanently);
382                         });
383 
384                   msg->setWindowModality(Qt::ApplicationModal);
385                   msg->setAttribute(Qt::WA_DeleteOnClose);
386                   msg->open();
387                   return;
388                   }
389             }
390       else {
391             if (visible) {
392                   showHideOrDeleteDialog(
393                      customCount == 1 ? tr("Do you want to hide this custom palette or permanently delete it?") : tr("Do you want to hide these custom palettes or permanently delete them?"),
394                      [=](RemoveAction action) { remove(removeIndices, action); }
395                      );
396                   return;
397                   }
398             else
399                   action = RemoveAction::Hide;
400             }
401 
402       remove(removeIndices, action);
403       }
404 
405 //---------------------------------------------------------
406 //   UserPaletteController::remove
407 //---------------------------------------------------------
408 
remove(const QModelIndexList & unsortedRemoveIndices,AbstractPaletteController::RemoveAction action)409 void UserPaletteController::remove(const QModelIndexList& unsortedRemoveIndices, AbstractPaletteController::RemoveAction action)
410       {
411       using RemoveAction = AbstractPaletteController::RemoveAction;
412 
413       if (action == RemoveAction::NoAction)
414             return;
415 
416       QModelIndexList removeIndices = unsortedRemoveIndices;
417       std::sort(removeIndices.begin(), removeIndices.end(), [](const QModelIndex& a, const QModelIndex& b) { return a.row() < b.row(); });
418 
419       // remove in reversed order to leave the previous model indices in the list valid
420       for (auto i = removeIndices.rbegin(); i != removeIndices.rend(); ++i) {
421             const QModelIndex& index = *i;
422 
423             RemoveAction indexAction = action;
424             const bool custom = model()->data(index, PaletteTreeModel::CustomRole).toBool();
425 
426             if (!custom || indexAction == RemoveAction::AutoAction) {
427                   const bool isCell = bool(model()->data(index, PaletteTreeModel::PaletteCellRole).value<const PaletteCell*>());
428                   indexAction = custom ? RemoveAction::NoAction : (isCell ? RemoveAction::DeletePermanently : RemoveAction::Hide);
429                   }
430 
431             switch (indexAction) {
432                   case RemoveAction::NoAction:
433                         break;
434                   case RemoveAction::Hide:
435                         model()->setData(index, false, PaletteTreeModel::VisibleRole);
436                         break;
437                   case RemoveAction::DeletePermanently:
438                         model()->removeRow(index.row(), index.parent());
439                         break;
440                   case RemoveAction::AutoAction:
441                         // impossible, we have just assigned another action for that case
442                         Q_ASSERT(false);
443                         break;
444                   }
445             }
446       }
447 
448 //---------------------------------------------------------
449 //   UserPaletteController::remove
450 //---------------------------------------------------------
451 
remove(const QModelIndex & index)452 void UserPaletteController::remove(const QModelIndex& index)
453       {
454       if (!canEdit(index.parent()))
455             return;
456 
457       const bool customItem = index.data(PaletteTreeModel::CustomRole).toBool();
458       queryRemove({ index }, customItem ? 1 : 0);
459       }
460 
461 //---------------------------------------------------------
462 //   UserPaletteController::removeSelection
463 //---------------------------------------------------------
464 
removeSelection(const QModelIndexList & selectedIndexes,const QModelIndex & parent)465 void UserPaletteController::removeSelection(const QModelIndexList& selectedIndexes, const QModelIndex& parent)
466       {
467       if (!canEdit(parent))
468             return;
469 
470       QModelIndexList removeIndices;
471       int customItemsCount = 0;
472 
473       for (const QModelIndex& idx : selectedIndexes) {
474             if (idx.parent() == parent) {
475                   removeIndices.push_back(idx);
476                   const bool custom = idx.data(PaletteTreeModel::CustomRole).toBool();
477                   if (custom)
478                         ++customItemsCount;
479                   }
480             }
481 
482       queryRemove(removeIndices, customItemsCount);
483       }
484 
485 //---------------------------------------------------------
486 //   UserPaletteController::editPaletteProperties
487 //---------------------------------------------------------
488 
editPaletteProperties(const QModelIndex & index)489 void UserPaletteController::editPaletteProperties(const QModelIndex& index)
490       {
491       if (!canEdit(index))
492             return;
493 
494       const QModelIndex srcIndex = convertProxyIndex(index, _userPalette);
495       PalettePanel* p = _userPalette->findPalettePanel(srcIndex);
496       if (!p)
497             return;
498 
499       PaletteTreeModel* m = _userPalette;
500       PalettePropertiesDialog* d = new PalettePropertiesDialog(p, mscore);
501 
502       const bool treeChangedWasBlocked = m->blockTreeChanged(true);
503       const bool paletteChangedState = m->paletteTreeChanged();
504 
505       connect(d, &QDialog::accepted, m, [m, treeChangedWasBlocked]() {
506             m->blockTreeChanged(treeChangedWasBlocked);
507       });
508       connect(d, &QDialog::rejected, m, [m, srcIndex, paletteChangedState, treeChangedWasBlocked]() {
509             m->itemDataChanged(srcIndex);
510             paletteChangedState ? m->setTreeChanged() : m->setTreeUnchanged();
511             m->blockTreeChanged(treeChangedWasBlocked);
512       });
513       connect(d, &PalettePropertiesDialog::changed, m, [m, srcIndex]() {
514             m->itemDataChanged(srcIndex);
515       });
516 
517       d->setModal(true);
518       d->setAttribute(Qt::WA_DeleteOnClose);
519       d->open();
520       }
521 
522 //---------------------------------------------------------
523 //   UserPaletteController::editCellProperties
524 //---------------------------------------------------------
525 
editCellProperties(const QModelIndex & index)526 void UserPaletteController::editCellProperties(const QModelIndex& index)
527       {
528       if (!canEdit(index))
529             return;
530 
531       const QModelIndex srcIndex = convertProxyIndex(index, _userPalette);
532       PaletteCellPtr cell = _userPalette->findCell(srcIndex);
533       if (!cell)
534             return;
535 
536       PaletteCellPropertiesDialog* d = new PaletteCellPropertiesDialog(cell.get(), mscore);
537       PaletteTreeModel* m = _userPalette;
538 
539       const bool treeChangedWasBlocked = m->blockTreeChanged(true);
540       const bool paletteChangedState = m->paletteTreeChanged();
541 
542       connect(d, &QDialog::accepted, m, [m, treeChangedWasBlocked]() {
543             m->blockTreeChanged(treeChangedWasBlocked);
544       });
545       connect(d, &QDialog::rejected, m, [m, srcIndex, paletteChangedState, treeChangedWasBlocked]() {
546             m->itemDataChanged(srcIndex);
547             paletteChangedState ? m->setTreeChanged() : m->setTreeUnchanged();
548             m->blockTreeChanged(treeChangedWasBlocked);
549       });
550       connect(d, &PaletteCellPropertiesDialog::changed, m, [m, srcIndex]() {
551             m->itemDataChanged(srcIndex);
552       });
553 
554       d->setModal(true);
555       d->setAttribute(Qt::WA_DeleteOnClose);
556       d->open();
557       }
558 
559 //---------------------------------------------------------
560 //   UserPaletteController::canEdit
561 //---------------------------------------------------------
562 
canEdit(const QModelIndex & index) const563 bool UserPaletteController::canEdit(const QModelIndex& index) const
564       {
565       if (!userEditable())
566             return false;
567 
568       return model()->data(index, PaletteTreeModel::EditableRole).toBool();
569       }
570 
571 //---------------------------------------------------------
572 //   UserPaletteController::applyPaletteElement
573 //---------------------------------------------------------
574 
applyPaletteElement(const QModelIndex & index,Qt::KeyboardModifiers modifiers)575 bool UserPaletteController::applyPaletteElement(const QModelIndex& index, Qt::KeyboardModifiers modifiers)
576       {
577       const PaletteCell* cell = model()->data(index, PaletteTreeModel::PaletteCellRole).value<const PaletteCell*>();
578       if (cell && cell->element)
579             return Palette::applyPaletteElement(cell->element.get(), modifiers);
580       return false;
581       }
582 
583 //---------------------------------------------------------
584 //   PaletteWorkspace
585 //---------------------------------------------------------
586 
PaletteWorkspace(PaletteTreeModel * user,PaletteTreeModel * master,QObject * parent)587 PaletteWorkspace::PaletteWorkspace(PaletteTreeModel* user, PaletteTreeModel* master, QObject* parent)
588    : QObject(parent), userPalette(user), masterPalette(master), defaultPalette(nullptr)
589       {
590       if (userPalette)
591             connect(userPalette, &PaletteTreeModel::treeChanged, this, &PaletteWorkspace::userPaletteChanged);
592 
593       preferencesListenerID = preferences.addOnSetListener([this](const QString& key, const QVariant& /*value*/) {
594             if (key == PREF_APP_USESINGLEPALETTE)
595                   emit singlePaletteChanged();
596             });
597       }
598 
599 //---------------------------------------------------------
600 //   ~PaletteWorkspace
601 //---------------------------------------------------------
602 
~PaletteWorkspace()603 PaletteWorkspace::~PaletteWorkspace()
604       {
605       preferences.removeOnSetListener(preferencesListenerID);
606       }
607 
608 //---------------------------------------------------------
609 //   PaletteWorkspace::singlePalette
610 //---------------------------------------------------------
611 
singlePalette() const612 bool PaletteWorkspace::singlePalette() const
613       {
614       return preferences.getBool(PREF_APP_USESINGLEPALETTE);
615       }
616 
setSinglePalette(bool val)617 void PaletteWorkspace::setSinglePalette(bool val) {
618       if (val != singlePalette())
619             preferences.setPreference(PREF_APP_USESINGLEPALETTE, val);
620             // No need to emit `singlePaletteChanged()` here;
621             // that's already done by the `onSetListener` lambda.
622             // That's better, because the signal will also be emitted
623             // when this preference is changed from the Preferences Dialog.
624       }
625 
626 //---------------------------------------------------------
627 //   PaletteWorkspace::masterPaletteModel
628 //---------------------------------------------------------
629 
mainPaletteModel()630 QAbstractItemModel* PaletteWorkspace::mainPaletteModel()
631       {
632       if (!mainPalette) {
633 //             PaletteCellFilter* filter = new VisibilityCellFilter(/* acceptedValue */ true);
634 //             mainPalette = new FilterPaletteTreeModel(filter, userPalette, this);
635             QSortFilterProxyModel* visFilterModel = new QSortFilterProxyModel(this);
636             visFilterModel->setFilterRole(PaletteTreeModel::VisibleRole);
637             visFilterModel->setFilterFixedString("true");
638             visFilterModel->setSourceModel(userPalette);
639 
640             // Wrap it into another proxy model to enable filtering by palette cell name
641             QSortFilterProxyModel* textFilterModel = new PaletteCellFilterProxyModel(this);
642             textFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
643             textFilterModel->setSourceModel(visFilterModel);
644             visFilterModel->setParent(textFilterModel);
645 
646             mainPalette = textFilterModel;
647             }
648       return mainPalette;
649       }
650 
651 //---------------------------------------------------------
652 //   PaletteWorkspace::masterPaletteModel
653 //---------------------------------------------------------
654 
getMainPaletteController()655 AbstractPaletteController* PaletteWorkspace::getMainPaletteController()
656       {
657       if (!mainPaletteController)
658             mainPaletteController = new UserPaletteController(mainPaletteModel(), userPalette, this);
659 //             mainPaletteController = new PaletteController(mainPaletteModel(), this, this);
660       return mainPaletteController;
661       }
662 
663 //---------------------------------------------------------
664 //   PaletteWorkspace::customPaletteIndex
665 //---------------------------------------------------------
666 
customElementsPaletteIndex(const QModelIndex & index)667 QModelIndex PaletteWorkspace::customElementsPaletteIndex(const QModelIndex& index)
668       {
669       const QAbstractItemModel* model = customElementsPaletteModel();
670       if (index.model() == mainPalette && index.parent() == QModelIndex()) {
671             const int row = convertProxyIndex(index, userPalette).row();
672             return model->index(row, 0);
673             }
674       return convertIndex(index, model);
675       }
676 
677 //---------------------------------------------------------
678 //   PaletteWorkspace::customElementsPaletteModel
679 //---------------------------------------------------------
680 
customElementsPaletteModel()681 FilterPaletteTreeModel* PaletteWorkspace::customElementsPaletteModel()
682       {
683       if (!customPoolPalette) {
684             PaletteCellFilter* filter = new VisibilityCellFilter(/* acceptedValue */ false);
685             filter->addChainedFilter(new CustomizedCellFilter(/* acceptedValue */ true));
686             customPoolPalette = new FilterPaletteTreeModel(filter, userPalette, this);
687             }
688       return customPoolPalette;
689       }
690 
691 //---------------------------------------------------------
692 //   PaletteWorkspace::getCustomElementsPaletteController
693 //---------------------------------------------------------
694 
getCustomElementsPaletteController()695 AbstractPaletteController* PaletteWorkspace::getCustomElementsPaletteController()
696       {
697       if (!customElementsPaletteController) {
698             customElementsPaletteController = new UserPaletteController(customElementsPaletteModel(), userPalette, this);
699             customElementsPaletteController->setVisible(false);
700             customElementsPaletteController->setCustom(true);
701             }
702 //             customElementsPaletteController = new CustomPaletteController(customElementsPaletteModel(), this, this);
703       return customElementsPaletteController;
704       }
705 
706 //---------------------------------------------------------
707 //   PaletteWorkspace::poolPaletteIndex
708 //---------------------------------------------------------
709 
poolPaletteIndex(const QModelIndex & index,Ms::FilterPaletteTreeModel * poolPalette)710 QModelIndex PaletteWorkspace::poolPaletteIndex(const QModelIndex& index, Ms::FilterPaletteTreeModel* poolPalette)
711       {
712       const QModelIndex poolPaletteIndex = convertIndex(index, poolPalette);
713       if (poolPaletteIndex.isValid())
714             return poolPaletteIndex;
715       const auto contentType = index.data(PaletteTreeModel::PaletteContentTypeRole).value<PalettePanel::Type>();
716       return findPaletteIndex(poolPalette, contentType);
717       }
718 
719 //---------------------------------------------------------
720 //   PaletteWorkspace::masterPaletteModel
721 //---------------------------------------------------------
722 
poolPaletteModel(const QModelIndex & index)723 FilterPaletteTreeModel* PaletteWorkspace::poolPaletteModel(const QModelIndex& index)
724       {
725       const QModelIndex filterIndex = convertProxyIndex(index, userPalette);
726       PaletteCellFilter* filter = userPalette->getFilter(filterIndex); // TODO: or mainPalette?
727       if (!filter)
728             return nullptr;
729 
730       FilterPaletteTreeModel* m = new FilterPaletteTreeModel(filter, masterPalette);
731       QQmlEngine::setObjectOwnership(m, QQmlEngine::JavaScriptOwnership);
732       return m;
733       }
734 
735 //---------------------------------------------------------
736 //   PaletteWorkspace::masterPaletteController
737 //---------------------------------------------------------
738 
poolPaletteController(FilterPaletteTreeModel * poolPaletteModel,const QModelIndex & rootIndex)739 AbstractPaletteController* PaletteWorkspace::poolPaletteController(FilterPaletteTreeModel* poolPaletteModel, const QModelIndex& rootIndex)
740       {
741       Q_UNUSED(rootIndex);
742       UserPaletteController* c = new UserPaletteController(poolPaletteModel, userPalette);
743       c->setVisible(false);
744       c->setCustom(false);
745       c->setUserEditable(false);
746 //       AbstractPaletteController* c = new MasterPaletteController(poolPaletteModel, userPalette, this);
747       QQmlEngine::setObjectOwnership(c, QQmlEngine::JavaScriptOwnership);
748       return c;
749       }
750 
751 //---------------------------------------------------------
752 //   PaletteWorkspace::availableExtraPalettePanelsModel
753 //---------------------------------------------------------
754 
availableExtraPalettesModel()755 QAbstractItemModel* PaletteWorkspace::availableExtraPalettesModel()
756       {
757       QStandardItemModel* m = new QStandardItemModel;
758 
759       {
760       auto roleNames = m->roleNames();
761       roleNames[CustomRole] = "custom";
762       roleNames[PaletteIndexRole] = "paletteIndex";
763       m->setItemRoleNames(roleNames);
764       }
765 
766       QStandardItem* root = m->invisibleRootItem();
767 
768       const int masterRows = masterPalette->rowCount();
769       for (int row = 0; row < masterRows; ++row) {
770             const QModelIndex idx = masterPalette->index(row, 0);
771             // add everything that cannot be found in user palette
772             if (!convertIndex(idx, userPalette).isValid()) {
773                   const QString name = masterPalette->data(idx, Qt::DisplayRole).toString();
774                   QStandardItem* item = new QStandardItem(name);
775                   item->setData(false, CustomRole); // this palette is from master palette, hence not custom
776                   item->setData(QPersistentModelIndex(idx), PaletteIndexRole);
777                   root->appendRow(item);
778                   }
779             }
780 
781       const int userRows = userPalette->rowCount();
782       for (int row = 0; row < userRows; ++row) {
783             const QModelIndex idx = userPalette->index(row, 0);
784             // add invisible custom palettes
785             const bool visible = userPalette->data(idx, PaletteTreeModel::VisibleRole).toBool();
786             const bool custom = userPalette->data(idx, PaletteTreeModel::CustomRole).toBool();
787             if (!visible) {
788                   const QString name = userPalette->data(idx, Qt::DisplayRole).toString();
789                   QStandardItem* item = new QStandardItem(name);
790                   item->setData(custom, CustomRole);
791                   item->setData(QPersistentModelIndex(idx), PaletteIndexRole);
792                   root->appendRow(item);
793                   }
794             }
795 
796       QQmlEngine::setObjectOwnership(m, QQmlEngine::JavaScriptOwnership);
797       return m;
798       }
799 
800 //---------------------------------------------------------
801 //   PaletteWorkspace::addPalette
802 //---------------------------------------------------------
803 
addPalette(const QPersistentModelIndex & index)804 bool PaletteWorkspace::addPalette(const QPersistentModelIndex& index)
805       {
806       if (!index.isValid())
807             return false;
808 
809       if (index.model() == userPalette) {
810             const bool ok = userPalette->setData(index, true, PaletteTreeModel::VisibleRole);
811             if (!ok)
812                   return false;
813             const QModelIndex parent = index.parent();
814             userPalette->moveRow(parent, index.row(), parent, 0);
815             return true;
816             }
817 
818       if (index.model() == masterPalette) {
819             QMimeData* data = masterPalette->mimeData({ QModelIndex(index) });
820             const bool success = userPalette->dropMimeData(data, Qt::CopyAction, 0, 0, QModelIndex());
821             data->deleteLater();
822             return success;
823             }
824 
825       return false;
826       }
827 
828 //---------------------------------------------------------
829 //   PaletteWorkspace::removeCustomPalette
830 //---------------------------------------------------------
831 
removeCustomPalette(const QPersistentModelIndex & index)832 bool PaletteWorkspace::removeCustomPalette(const QPersistentModelIndex& index)
833       {
834       if (!index.isValid())
835             return false;
836 
837 
838       if (index.model() == userPalette) {
839             const bool custom = index.data(PaletteTreeModel::CustomRole).toBool();
840             if (!custom)
841                   return false;
842 
843             const auto answer = QMessageBox::question(
844                   nullptr,
845                   "",
846                   tr("Do you want to permanently delete this custom palette?"),
847                   QMessageBox::Yes | QMessageBox::No
848                   );
849 
850             if (answer == QMessageBox::Yes)
851                   return userPalette->removeRow(index.row(), index.parent());
852             return false;
853             }
854 
855       return false;
856       }
857 
858 //---------------------------------------------------------
859 //   PaletteWorkspace::resetPalette
860 //---------------------------------------------------------
861 
resetPalette(const QModelIndex & index)862 bool PaletteWorkspace::resetPalette(const QModelIndex& index)
863       {
864       if (!index.isValid())
865             return false;
866 
867       const auto answer =
868          (MScore::noGui && MScore::testMode)
869          ? QMessageBox::Yes
870          : QMessageBox::question(
871             nullptr,
872             "",
873             tr("Do you want to restore this palette to its default state? All changes to this palette will be lost."),
874             QMessageBox::Yes | QMessageBox::No
875             );
876 
877       if (answer != QMessageBox::Yes)
878             return false;
879 
880       Q_ASSERT(defaultPalette != userPalette);
881 
882       QAbstractItemModel* resetModel = nullptr;
883       QModelIndex resetIndex;
884 
885       if (defaultPalette) {
886             resetModel = defaultPalette;
887             resetIndex = convertIndex(index, defaultPalette);
888             }
889 
890       if (!resetIndex.isValid()) {
891             resetModel = masterPalette;
892             resetIndex = convertIndex(index, masterPalette);
893             }
894 
895       const QModelIndex userPaletteIndex = convertProxyIndex(index, userPalette);
896       const QModelIndex parent = userPaletteIndex.parent();
897       const int row = userPaletteIndex.row();
898       const int column = userPaletteIndex.column();
899 
900       // restore visibility and expanded state of the palette after restoring its state
901       const bool wasVisible = index.data(PaletteTreeModel::VisibleRole).toBool();
902       const bool wasExpanded = index.data(PaletteTreeModel::PaletteExpandedRole).toBool();
903 
904       if (!userPalette->removeRow(row, parent))
905             return false;
906 
907       if (resetIndex.isValid()) {
908             QMimeData* data = resetModel->mimeData({ resetIndex });
909             userPalette->dropMimeData(data, Qt::CopyAction, row, column, parent);
910             data->deleteLater();
911             }
912       else
913             userPalette->insertRow(row, parent);
914 
915       const QModelIndex newIndex = userPalette->index(row, column, parent);
916       userPalette->setData(newIndex, wasVisible, PaletteTreeModel::VisibleRole);
917       userPalette->setData(newIndex, wasExpanded, PaletteTreeModel::PaletteExpandedRole);
918 
919       return true;
920       }
921 
922 //---------------------------------------------------------
923 //   PaletteWorkspace::savePalette
924 //---------------------------------------------------------
925 
savePalette(const QModelIndex & index)926 bool PaletteWorkspace::savePalette(const QModelIndex& index)
927       {
928       const QModelIndex srcIndex = convertProxyIndex(index, userPalette);
929       const PalettePanel* pp = userPalette->findPalettePanel(srcIndex);
930       if (!pp)
931             return false;
932 
933       const QString path = mscore->getPaletteFilename(/* load? */ false, pp->translatedName());
934 
935       if (path.isEmpty())
936             return false;
937       return pp->writeToFile(path);
938       }
939 
940 //---------------------------------------------------------
941 //   PaletteWorkspace::loadPalette
942 //---------------------------------------------------------
943 
loadPalette(const QModelIndex & index)944 bool PaletteWorkspace::loadPalette(const QModelIndex& index)
945       {
946       const QString path = mscore->getPaletteFilename(/* load? */ true);
947       if (path.isEmpty())
948             return false;
949 
950       std::unique_ptr<PalettePanel> pp(new PalettePanel);
951       if (!pp->readFromFile(path))
952             return false;
953       pp->setType(PalettePanel::Type::Custom); // mark the loaded palette custom
954 
955       const QModelIndex srcIndex = convertProxyIndex(index, userPalette);
956 
957       const int row = srcIndex.row();
958       const QModelIndex parent = srcIndex.parent();
959 
960       return userPalette->insertPalettePanel(std::move(pp), row, parent);
961       }
962 
963 //---------------------------------------------------------
964 //   PaletteWorkspace::setUserPaletteTree
965 //---------------------------------------------------------
966 
setUserPaletteTree(std::unique_ptr<PaletteTree> tree)967 void PaletteWorkspace::setUserPaletteTree(std::unique_ptr<PaletteTree> tree)
968       {
969       if (userPalette) {
970             disconnect(userPalette, &PaletteTreeModel::treeChanged, this, &PaletteWorkspace::userPaletteChanged);
971             userPalette->setPaletteTree(std::move(tree));
972             connect(userPalette, &PaletteTreeModel::treeChanged, this, &PaletteWorkspace::userPaletteChanged);
973             }
974       else {
975             userPalette = new PaletteTreeModel(std::move(tree), /* parent */ this);
976             connect(userPalette, &PaletteTreeModel::treeChanged, this, &PaletteWorkspace::userPaletteChanged);
977             }
978       }
979 
setDefaultPaletteTree(std::unique_ptr<PaletteTree> tree)980 void PaletteWorkspace::setDefaultPaletteTree(std::unique_ptr<PaletteTree> tree)
981       {
982       if (defaultPalette)
983             defaultPalette->setPaletteTree(std::move(tree));
984       else
985             defaultPalette = new PaletteTreeModel(std::move(tree), /* parent */ this);
986       }
987 
988 //---------------------------------------------------------
989 //   PaletteWorkspace::write
990 //---------------------------------------------------------
991 
write(XmlWriter & xml) const992 void PaletteWorkspace::write(XmlWriter& xml) const
993       {
994       if (!userPalette)
995             return;
996       if (const PaletteTree* tree = userPalette->paletteTree())
997             tree->write(xml);
998       }
999 
1000 //---------------------------------------------------------
1001 //   PaletteWorkspace::read
1002 //---------------------------------------------------------
1003 
read(XmlReader & e)1004 bool PaletteWorkspace::read(XmlReader& e)
1005       {
1006       std::unique_ptr<PaletteTree> tree(new PaletteTree);
1007       if (!tree->read(e))
1008             return false;
1009 
1010       setUserPaletteTree(std::move(tree));
1011 
1012       return true;
1013       }
1014 
1015 //---------------------------------------------------------
1016 //   PaletteWorkspace::needsItemDestructionAccessibilityWorkaround
1017 ///   Checks whether workaround for palette search crash
1018 ///   with NVDA is needed, see issue #298899.
1019 //---------------------------------------------------------
1020 
needsItemDestructionAccessibilityWorkaround() const1021 bool PaletteWorkspace::needsItemDestructionAccessibilityWorkaround() const
1022       {
1023 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
1024       // Qt switched to a new accessibility backend since 5.11
1025       // and no crashes are reported for 5.12 so presumably
1026       // the workaround is not needed since Qt 5.11.
1027       return false;
1028 #else
1029       return QAccessible::isActive();
1030 #endif
1031       }
1032 } // namespace Ms
1033