Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "paletteprovider.h"
24 :
25 : #include <QFileDialog>
26 : #include <QStandardItemModel>
27 : #include <QJsonDocument>
28 : #include <QQmlEngine>
29 : #include <QMimeData>
30 : #include <QStandardPaths>
31 :
32 : #include "engraving/dom/mscore.h"
33 :
34 : #include "notation/inotation.h"
35 : #include "notation/inotationinteraction.h" // IWYU pragma: keep
36 :
37 : #include "palettecreator.h"
38 :
39 : #include "io/path.h"
40 :
41 : #include "translation.h"
42 : #include "types/uri.h"
43 :
44 : #include "app_config.h"
45 :
46 : using namespace muse;
47 : using namespace mu::palette;
48 : using namespace mu::engraving;
49 :
50 : // ========================================================
51 : // PaletteElementEditor
52 : // ========================================================
53 :
54 0 : bool PaletteElementEditor::valid() const
55 : {
56 : using Type = Palette::Type;
57 0 : switch (_type) {
58 0 : case Type::KeySig:
59 : case Type::TimeSig:
60 0 : return true;
61 0 : default:
62 0 : break;
63 : }
64 0 : return false;
65 : }
66 :
67 0 : QString PaletteElementEditor::actionName() const
68 : {
69 : using Type = Palette::Type;
70 0 : switch (_type) {
71 0 : case Type::KeySig:
72 0 : return muse::qtrc("palette", "Create key signature");
73 0 : case Type::TimeSig:
74 0 : return muse::qtrc("palette", "Create time signature");
75 0 : default:
76 0 : break;
77 : }
78 0 : return QString();
79 : }
80 :
81 0 : void PaletteElementEditor::setPaletteIndex(QPersistentModelIndex paletteIndex)
82 : {
83 0 : _paletteIndex = paletteIndex;
84 0 : }
85 :
86 0 : void PaletteElementEditor::onElementAdded(const ElementPtr element)
87 : {
88 0 : if (!element) {
89 0 : return;
90 : }
91 :
92 : static const QMap<ElementType, Palette::Type> elementTypeToPaletteType {
93 : { ElementType::TIMESIG, Palette::Type::TimeSig },
94 : { ElementType::KEYSIG, Palette::Type::KeySig }
95 0 : };
96 :
97 0 : Palette::Type paletteType = elementTypeToPaletteType.value(element->type(), Palette::Type::Unknown);
98 :
99 0 : if (paletteType != _type) {
100 0 : return;
101 : }
102 :
103 0 : if (!_paletteIndex.isValid()
104 0 : || !_paletteIndex.data(PaletteTreeModel::VisibleRole).toBool()) {
105 0 : interactive()->info("", muse::trc("palette", "The palette was hidden or changed"));
106 0 : return;
107 : }
108 :
109 0 : QVariantMap mimeData;
110 0 : mimeData[mimeSymbolFormat] = element->mimeData().toQByteArray();
111 :
112 0 : _controller->insert(_paletteIndex, -1, mimeData, Qt::CopyAction);
113 0 : }
114 :
115 0 : void PaletteElementEditor::open()
116 : {
117 0 : if (!_paletteIndex.isValid()) {
118 0 : return;
119 : }
120 :
121 0 : muse::UriQuery uri;
122 :
123 : using Type = Palette::Type;
124 0 : switch (_type) {
125 0 : case Type::KeySig: {
126 0 : uri = muse::UriQuery("musescore://notation/keysignatures");
127 0 : uri.addParam("showKeyPalette", Val(false));
128 : }
129 0 : break;
130 0 : case Type::TimeSig: {
131 0 : uri = muse::UriQuery("musescore://notation/timesignatures");
132 0 : uri.addParam("showTimePalette", Val(false));
133 : }
134 0 : break;
135 0 : default:
136 0 : break;
137 : }
138 :
139 0 : if (uri.isValid()) {
140 0 : uri.addParam("sync", Val(false));
141 :
142 0 : paletteProvider()->addCustomItemRequested().onReceive(this, [this](ElementPtr item) {
143 0 : onElementAdded(item);
144 0 : }, async::Asyncable::Mode::SetReplace);
145 :
146 0 : if (interactive()->isOpened(uri).val) {
147 0 : interactive()->raise(uri);
148 : } else {
149 0 : interactive()->open(uri);
150 : }
151 : }
152 0 : }
153 :
154 : // ========================================================
155 : // Model indices
156 : // ========================================================
157 :
158 0 : static QModelIndex findPaletteIndex(const QAbstractItemModel* model, Palette::Type type)
159 : {
160 0 : constexpr int role = PaletteTreeModel::PaletteTypeRole;
161 0 : const QModelIndex start = model->index(0, 0);
162 0 : const QModelIndexList foundIndexList = model->match(start, role, QVariant::fromValue(type));
163 :
164 0 : if (!foundIndexList.empty()) {
165 0 : return foundIndexList[0];
166 : }
167 0 : return QModelIndex();
168 0 : }
169 :
170 0 : static QModelIndex convertIndex(const QModelIndex& index, const QAbstractItemModel* targetModel)
171 : {
172 0 : if (index.model() == targetModel || !index.isValid()) {
173 0 : return index;
174 : }
175 :
176 0 : constexpr int typeRole = PaletteTreeModel::PaletteTypeRole;
177 0 : const auto type = index.model()->data(index, typeRole).value<Palette::Type>();
178 :
179 0 : return findPaletteIndex(targetModel, type);
180 : }
181 :
182 0 : static QModelIndex convertProxyIndex(const QModelIndex& srcIndex, const QAbstractItemModel* targetModel)
183 : {
184 0 : QModelIndex index = srcIndex;
185 0 : while (index.model() != targetModel) {
186 0 : if (auto m = qobject_cast<const QAbstractProxyModel*>(index.model())) {
187 0 : index = m->mapToSource(index);
188 : } else {
189 0 : break;
190 : }
191 : }
192 :
193 0 : if (targetModel && index.model() != targetModel) {
194 0 : return QModelIndex();
195 : }
196 0 : return index;
197 : }
198 :
199 : // ========================================================
200 : // AbstractPaletteController
201 : // ========================================================
202 0 : PaletteElementEditor* AbstractPaletteController::elementEditor(const QModelIndex& paletteIndex)
203 : {
204 0 : Palette::Type paletteType = paletteIndex.data(PaletteTreeModel::PaletteTypeRole).value<Palette::Type>();
205 :
206 0 : if (m_paletteElementEditorMap.contains(paletteType)) {
207 0 : PaletteElementEditor* ed = m_paletteElementEditorMap[paletteType];
208 0 : ed->setPaletteIndex(paletteIndex);
209 0 : return ed;
210 : }
211 :
212 : PaletteElementEditor* ed = new PaletteElementEditor(
213 0 : this, paletteIndex, paletteIndex.data(PaletteTreeModel::PaletteTypeRole).value<Palette::Type>(),
214 0 : iocContext(), this);
215 :
216 0 : m_paletteElementEditorMap.insert(paletteType, ed);
217 0 : return ed;
218 : }
219 :
220 : // ========================================================
221 : // UserPaletteController
222 : // ========================================================
223 :
224 0 : Qt::DropAction UserPaletteController::dropAction(const QVariantMap& mimeData, Qt::DropAction proposedAction,
225 : const QModelIndex& parent, bool internal) const
226 : {
227 0 : if (internal && !userEditable()) {
228 0 : return Qt::IgnoreAction;
229 : }
230 0 : const bool parentEditingEnabled = model()->data(parent, PaletteTreeModel::EditableRole).toBool();
231 0 : if (!parentEditingEnabled) {
232 0 : return Qt::IgnoreAction;
233 : }
234 :
235 0 : if (mimeData.contains(PaletteCell::mimeDataFormat) && proposedAction == Qt::MoveAction) {
236 0 : const auto cell = PaletteCell::fromMimeData(mimeData[PaletteCell::mimeDataFormat].toByteArray(), iocContext());
237 0 : if (!cell) {
238 0 : return Qt::IgnoreAction;
239 : }
240 0 : if (_filterCustom && cell->custom != _custom) {
241 0 : return Qt::IgnoreAction;
242 : }
243 0 : return Qt::MoveAction;
244 0 : }
245 0 : if (mimeData.contains(mimeSymbolFormat) && proposedAction == Qt::CopyAction) {
246 0 : if (_filterCustom && !_custom) {
247 0 : return Qt::IgnoreAction;
248 : }
249 0 : return Qt::CopyAction;
250 : }
251 0 : return Qt::IgnoreAction;
252 : }
253 :
254 0 : bool UserPaletteController::insert(const QModelIndex& parent, int row, const QVariantMap& mimeData,
255 : Qt::DropAction action)
256 : {
257 0 : if (dropAction(mimeData, action, parent, false) == Qt::IgnoreAction) {
258 0 : return false;
259 : }
260 :
261 0 : if (row < 0) {
262 0 : row = parent.model()->rowCount(parent);
263 : }
264 :
265 0 : PaletteCellPtr cell;
266 :
267 0 : if (mimeData.contains(PaletteCell::mimeDataFormat)) {
268 0 : cell = PaletteCell::fromMimeData(mimeData[PaletteCell::mimeDataFormat].toByteArray(), iocContext());
269 :
270 0 : if (!cell) {
271 0 : return false;
272 : }
273 0 : if (_filterCustom && cell->custom != _custom) {
274 0 : return false;
275 : }
276 :
277 0 : if (action == Qt::MoveAction) {
278 0 : const QModelIndex visiblePaletteParentIndex = convertIndex(parent, _userPalette);
279 0 : const QModelIndex foundIndex(_userPalette->findPaletteCell(*cell, visiblePaletteParentIndex));
280 0 : if (foundIndex.isValid()) {
281 0 : return _userPalette->setData(foundIndex, _visible, PaletteTreeModel::VisibleRole);
282 0 : } else if (!userEditable()) {
283 0 : return false;
284 : }
285 : }
286 0 : } else if (mimeData.contains(mimeSymbolFormat) && (action == Qt::CopyAction)) {
287 0 : cell = PaletteCell::fromElementMimeData(mimeData[mimeSymbolFormat].toByteArray(), iocContext());
288 : }
289 :
290 0 : if (!cell) {
291 0 : return false;
292 : }
293 :
294 0 : if (_filterCustom) {
295 0 : if (!_custom) {
296 0 : return false; // can only move non-custom cells
297 : }
298 0 : cell->custom = _custom;
299 : }
300 0 : cell->visible = _visible;
301 :
302 0 : QMimeData data;
303 0 : data.setData(PaletteCell::mimeDataFormat, cell->toMimeData());
304 0 : constexpr int column = 0;
305 0 : return model()->dropMimeData(&data, action, row, column, parent);
306 0 : }
307 :
308 0 : bool UserPaletteController::insertNewItem(const QModelIndex& parent, int row, const QString& name)
309 : {
310 0 : if (!canEdit(parent)) {
311 0 : return false;
312 : }
313 :
314 0 : const bool newItemIsPalette = !parent.isValid();
315 :
316 0 : if (newItemIsPalette) {
317 0 : if (!model()->insertRow(row, parent)) {
318 0 : return false;
319 : }
320 0 : model()->setData(model()->index(row, 0, parent), name, Qt::DisplayRole);
321 0 : return true;
322 : }
323 :
324 0 : return model()->insertRow(row, parent);
325 : }
326 :
327 0 : bool UserPaletteController::move(const QModelIndex& sourceParent, int sourceRow, const QModelIndex& destinationParent,
328 : int destinationChild)
329 : {
330 0 : if (!canEdit(sourceParent) || !canEdit(destinationParent)) {
331 0 : return false;
332 : }
333 0 : if (sourceParent == destinationParent && (sourceParent.model() == model() || !sourceParent.isValid())) {
334 0 : const QModelIndex srcIndex = convertProxyIndex(model()->index(sourceRow, 0, sourceParent), _userPalette);
335 0 : const QModelIndex destIndex = convertProxyIndex(model()->index(destinationChild, 0,
336 0 : destinationParent), _userPalette);
337 0 : return _userPalette->moveRow(srcIndex.parent(), srcIndex.row(), destIndex.parent(), destIndex.row());
338 : }
339 0 : return false;
340 : }
341 :
342 0 : async::Promise<UserPaletteController::RemoveAction> UserPaletteController::showHideOrDeleteDialog(const std::string& question) const
343 : {
344 0 : int hideButton = int(IInteractive::Button::CustomButton) + 1;
345 0 : int deleteButton = hideButton + 1;
346 :
347 0 : return interactive()->question(std::string(), question, {
348 0 : IInteractive::ButtonData(hideButton, muse::trc("palette", "Hide")),
349 0 : IInteractive::ButtonData(deleteButton, muse::trc("palette", "Delete permanently")),
350 0 : interactive()->buttonData(IInteractive::Button::Cancel)
351 : })
352 0 : .then<RemoveAction>(this, [deleteButton, hideButton](const IInteractive::Result& res, auto resolve) {
353 0 : RemoveAction action = RemoveAction::NoAction;
354 0 : if (res.isButton(deleteButton)) {
355 0 : action = RemoveAction::DeletePermanently;
356 0 : } else if (res.isButton(hideButton)) {
357 0 : action = RemoveAction::Hide;
358 : }
359 0 : return resolve(action);
360 0 : });
361 0 : }
362 :
363 0 : void UserPaletteController::queryRemove(const QModelIndexList& removeIndices, int customCount)
364 : {
365 0 : if (removeIndices.empty() || !canEdit(removeIndices[0].parent())) {
366 0 : return;
367 : }
368 :
369 0 : if (!customCount) {
370 0 : remove(removeIndices, RemoveAction::AutoAction);
371 0 : return;
372 : }
373 :
374 : // these parameters should not depend on exact index as all they should have the same parent in palette tree
375 0 : const bool visible = removeIndices[0].data(PaletteTreeModel::VisibleRole).toBool();
376 0 : const bool isCell = bool(removeIndices[0].data(PaletteTreeModel::PaletteCellRole).value<const PaletteCell*>());
377 :
378 0 : RemoveAction action = RemoveAction::AutoAction;
379 :
380 0 : if (isCell) {
381 0 : if (visible) {
382 : std::string question = customCount == 1
383 0 : ? muse::trc("palette", "Do you want to hide this custom palette cell or permanently delete it?")
384 0 : : muse::trc("palette", "Do you want to hide these custom palette cells or permanently delete them?");
385 :
386 0 : showHideOrDeleteDialog(question)
387 0 : .onResolve(this, [this, removeIndices](RemoveAction action) {
388 0 : remove(removeIndices, action);
389 0 : });
390 0 : return;
391 0 : } else {
392 : std::string question = customCount == 1
393 0 : ? muse::trc("palette", "Do you want to permanently delete this custom palette cell?")
394 0 : : muse::trc("palette", "Do you want to permanently delete these custom palette cells?");
395 :
396 0 : interactive()->question(std::string(), question, {
397 : IInteractive::Button::Yes,
398 : IInteractive::Button::No
399 : })
400 0 : .onResolve(this, [this, removeIndices](const IInteractive::Result& res) {
401 0 : if (res.isButton(IInteractive::Button::Yes)) {
402 0 : remove(removeIndices, RemoveAction::DeletePermanently);
403 : }
404 0 : });
405 0 : }
406 : } else {
407 0 : if (visible) {
408 : std::string question = customCount == 1
409 0 : ? muse::trc("palette", "Do you want to hide this custom palette or permanently delete it?")
410 0 : : muse::trc("palette", "Do you want to hide these custom palettes or permanently delete them?");
411 :
412 0 : showHideOrDeleteDialog(question)
413 0 : .onResolve(this, [this, removeIndices](RemoveAction action) { remove(removeIndices, action); });
414 0 : return;
415 0 : } else {
416 0 : action = RemoveAction::Hide;
417 : }
418 :
419 0 : remove(removeIndices, action);
420 : }
421 : }
422 :
423 0 : void UserPaletteController::remove(const QModelIndexList& unsortedRemoveIndices,
424 : AbstractPaletteController::RemoveAction action)
425 : {
426 0 : if (action == RemoveAction::NoAction) {
427 0 : return;
428 : }
429 :
430 0 : QModelIndexList removeIndices = unsortedRemoveIndices;
431 0 : std::sort(removeIndices.begin(), removeIndices.end(), [](const QModelIndex& a, const QModelIndex& b) {
432 0 : return a.row() < b.row();
433 : });
434 :
435 : // remove in reversed order to leave the previous model indices in the list valid
436 0 : for (auto i = removeIndices.rbegin(); i != removeIndices.rend(); ++i) {
437 0 : const QModelIndex& index = *i;
438 :
439 0 : RemoveAction indexAction = action;
440 0 : const bool custom = model()->data(index, PaletteTreeModel::CustomRole).toBool();
441 :
442 0 : if (!custom || indexAction == RemoveAction::AutoAction) {
443 : const bool isCell
444 0 : = bool(model()->data(index, PaletteTreeModel::PaletteCellRole).value<const PaletteCell*>());
445 : indexAction
446 0 : = custom ? RemoveAction::NoAction : (isCell ? RemoveAction::DeletePermanently : RemoveAction::Hide);
447 : }
448 :
449 0 : switch (indexAction) {
450 0 : case RemoveAction::NoAction:
451 0 : break;
452 0 : case RemoveAction::Hide:
453 0 : model()->setData(index, false, PaletteTreeModel::VisibleRole);
454 0 : break;
455 0 : case RemoveAction::DeletePermanently:
456 0 : model()->removeRow(index.row(), index.parent());
457 0 : break;
458 0 : case RemoveAction::AutoAction:
459 : // impossible, we have just assigned another action for that case
460 0 : Q_ASSERT(false);
461 : break;
462 : }
463 : }
464 0 : }
465 :
466 0 : void UserPaletteController::remove(const QModelIndex& index)
467 : {
468 0 : if (!canEdit(index.parent())) {
469 0 : return;
470 : }
471 :
472 0 : const bool customItem = index.data(PaletteTreeModel::CustomRole).toBool();
473 0 : queryRemove({ index }, customItem ? 1 : 0);
474 : }
475 :
476 0 : void UserPaletteController::removeSelection(const QModelIndexList& selectedIndexes, const QModelIndex& parent)
477 : {
478 0 : if (!canEdit(parent)) {
479 0 : return;
480 : }
481 :
482 0 : QModelIndexList removeIndices;
483 0 : int customItemsCount = 0;
484 :
485 0 : for (const QModelIndex& idx : selectedIndexes) {
486 0 : if (idx.parent() == parent || !parent.isValid()) {
487 0 : removeIndices.push_back(idx);
488 0 : const bool custom = idx.data(PaletteTreeModel::CustomRole).toBool();
489 0 : if (custom) {
490 0 : ++customItemsCount;
491 : }
492 : }
493 : }
494 :
495 0 : queryRemove(removeIndices, customItemsCount);
496 0 : }
497 :
498 0 : void UserPaletteController::editPaletteProperties(const QModelIndex& index)
499 : {
500 0 : if (!canEdit(index)) {
501 0 : return;
502 : }
503 :
504 0 : QModelIndex srcIndex = convertProxyIndex(index, _userPalette);
505 0 : Palette* palette = _userPalette->findPalette(srcIndex);
506 0 : if (!palette) {
507 0 : return;
508 : }
509 :
510 0 : configuration()->paletteConfig(palette->id()).ch.onReceive(this,
511 0 : [this, srcIndex, palette](
512 : const IPaletteConfiguration::PaletteConfig& config) {
513 0 : palette->setName(config.name);
514 0 : palette->setGridSize(config.size);
515 0 : palette->setMag(config.scale);
516 0 : palette->setYOffset(config.elementOffset);
517 0 : palette->setDrawGrid(config.showGrid);
518 0 : _userPalette->itemDataChanged(srcIndex);
519 0 : });
520 :
521 0 : QVariantMap properties;
522 0 : properties["paletteId"] = palette->id();
523 0 : properties["name"] = QString(palette->translatedName().toUtf8().toPercentEncoding());
524 0 : properties["cellWidth"] = palette->gridSize().width();
525 0 : properties["cellHeight"] = palette->gridSize().height();
526 0 : properties["scale"] = palette->mag();
527 0 : properties["elementOffset"] = palette->yOffset();
528 0 : properties["showGrid"] = palette->drawGrid();
529 :
530 0 : QJsonDocument document = QJsonDocument::fromVariant(properties);
531 0 : QString uri = QString("musescore://palette/properties?properties=%1")
532 0 : .arg(QString(document.toJson()));
533 :
534 0 : interactive()->open(uri.toStdString());
535 0 : }
536 :
537 0 : void UserPaletteController::editCellProperties(const QModelIndex& index)
538 : {
539 0 : if (!canEdit(index)) {
540 0 : return;
541 : }
542 :
543 0 : QModelIndex srcIndex = convertProxyIndex(index, _userPalette);
544 0 : PaletteCellPtr cell = _userPalette->findCell(srcIndex);
545 0 : if (!cell) {
546 0 : return;
547 : }
548 :
549 0 : configuration()->paletteCellConfig(cell->id).ch.onReceive(this,
550 0 : [this, srcIndex, cell](
551 : const IPaletteConfiguration::PaletteCellConfig& config) {
552 0 : cell->name = config.name;
553 0 : cell->mag = config.scale;
554 0 : cell->drawStaff = config.drawStaff;
555 0 : cell->xoffset = config.xOffset;
556 0 : cell->yoffset = config.yOffset;
557 0 : _userPalette->itemDataChanged(srcIndex);
558 0 : });
559 :
560 0 : QVariantMap properties;
561 0 : properties["cellId"] = cell->id;
562 0 : properties["name"] = cell->translatedName();
563 0 : properties["xOffset"] = cell->xoffset;
564 0 : properties["yOffset"] = cell->yoffset;
565 0 : properties["scale"] = cell->mag;
566 0 : properties["drawStaff"] = cell->drawStaff;
567 :
568 0 : QJsonDocument document = QJsonDocument::fromVariant(properties);
569 0 : QString uri = QString("musescore://palette/cellproperties?properties=%1")
570 0 : .arg(QString(document.toJson()));
571 :
572 0 : interactive()->open(uri.toStdString());
573 0 : }
574 :
575 0 : bool UserPaletteController::canEdit(const QModelIndex& index) const
576 : {
577 0 : if (!userEditable()) {
578 0 : return false;
579 : }
580 :
581 0 : return model()->data(index, PaletteTreeModel::EditableRole).toBool();
582 : }
583 :
584 0 : bool UserPaletteController::applyPaletteElement(const QModelIndex& index, Qt::KeyboardModifiers modifiers)
585 : {
586 0 : const PaletteCell* cell = model()->data(index, PaletteTreeModel::PaletteCellRole).value<const PaletteCell*>();
587 0 : if (!cell || !cell->element) {
588 0 : return false;
589 : }
590 :
591 0 : auto notation = projectSession()->currentNotation();
592 0 : if (!notation) {
593 0 : return false;
594 : }
595 :
596 0 : return notation->interaction()->applyPaletteElement(cell->element.get(), modifiers);
597 0 : }
598 :
599 : // ========================================================
600 : // PaletteProvider
601 : // ========================================================
602 :
603 0 : void PaletteProvider::init()
604 : {
605 0 : m_userPaletteModel = new PaletteTreeModel(std::make_shared<PaletteTree>(), iocContext(), this);
606 0 : connect(m_userPaletteModel, &PaletteTreeModel::treeChanged, this, &PaletteProvider::notifyAboutUserPaletteChanged);
607 :
608 0 : m_masterPaletteModel = new PaletteTreeModel(PaletteCreator(iocContext()).newMasterPaletteTree(), iocContext(), this);
609 :
610 0 : m_searchFilterModel = new PaletteCellFilterProxyModel(this);
611 0 : m_searchFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
612 :
613 0 : m_visibilityFilterModel = new QSortFilterProxyModel(this);
614 0 : m_visibilityFilterModel->setFilterRole(PaletteTreeModel::VisibleRole);
615 0 : m_visibilityFilterModel->setFilterFixedString("true");
616 0 : m_visibilityFilterModel->setSourceModel(m_userPaletteModel);
617 :
618 0 : configuration()->isSinglePalette().ch.onReceive(this, [this](bool) {
619 0 : emit isSinglePaletteChanged();
620 0 : });
621 :
622 0 : configuration()->isSingleClickToOpenPalette().ch.onReceive(this, [this](bool) {
623 0 : emit isSingleClickToOpenPaletteChanged();
624 0 : });
625 :
626 0 : configuration()->isPaletteDragEnabled().ch.onReceive(this, [this](bool) {
627 0 : emit isPaletteDragEnabledChanged();
628 0 : });
629 0 : }
630 :
631 0 : void PaletteProvider::setFilter(const QString& filter)
632 : {
633 : // Unbind the model when there is no search text so as to return no results
634 : // and thus speed up the opening of the palette search. Rebind the model
635 : // as soon as the search text is non-empty.
636 : // Doing this *trick* also when the search text is non-empty helps
637 : // speed up the search in certain other scenarios,
638 : // e.g. when deleting search characters (going from fewer to more search results).
639 0 : m_searchFilterModel->setSourceModel(nullptr);
640 0 : m_searchFilterModel->setFilterFixedString(filter);
641 0 : if (!filter.isEmpty()) {
642 0 : m_searchFilterModel->setSourceModel(m_masterPaletteModel);
643 : }
644 0 : }
645 :
646 0 : void PaletteProvider::setSearching(bool searching)
647 : {
648 0 : if (m_isSearching == searching) {
649 0 : return;
650 : }
651 :
652 0 : m_isSearching = searching;
653 :
654 0 : m_mainPalette = nullptr;
655 0 : m_mainPaletteController = nullptr;
656 0 : emit mainPaletteChanged();
657 : }
658 :
659 0 : bool PaletteProvider::isSinglePalette() const
660 : {
661 0 : return configuration()->isSinglePalette().val;
662 : }
663 :
664 0 : bool PaletteProvider::isSingleClickToOpenPalette() const
665 : {
666 0 : return configuration()->isSingleClickToOpenPalette().val;
667 : }
668 :
669 0 : bool PaletteProvider::isPaletteDragEnabled() const
670 : {
671 0 : return configuration()->isPaletteDragEnabled().val;
672 : }
673 :
674 0 : QAbstractItemModel* PaletteProvider::mainPaletteModel()
675 : {
676 0 : if (m_isSearching) {
677 0 : m_mainPalette = m_searchFilterModel;
678 : } else {
679 0 : m_mainPalette = m_visibilityFilterModel;
680 : }
681 :
682 0 : return m_mainPalette;
683 : }
684 :
685 0 : AbstractPaletteController* PaletteProvider::mainPaletteController()
686 : {
687 0 : if (!m_mainPaletteController) {
688 0 : m_mainPaletteController = new UserPaletteController(mainPaletteModel(), m_userPaletteModel, iocContext(), this);
689 : }
690 0 : return m_mainPaletteController;
691 : }
692 :
693 0 : QModelIndex PaletteProvider::customElementsPaletteIndex(const QModelIndex& index)
694 : {
695 0 : const QAbstractItemModel* model = customElementsPaletteModel();
696 0 : if (index.model() == m_mainPalette && index.parent() == QModelIndex()) {
697 0 : const int row = convertProxyIndex(index, m_userPaletteModel).row();
698 0 : return model->index(row, 0);
699 : }
700 0 : return convertIndex(index, model);
701 : }
702 :
703 0 : FilterPaletteTreeModel* PaletteProvider::customElementsPaletteModel()
704 : {
705 0 : if (!m_customPoolPalette) {
706 0 : PaletteCellFilter* filter = new VisibilityCellFilter(/* acceptedValue */ false);
707 0 : filter->addChainedFilter(new CustomizedCellFilter(/* acceptedValue */ true));
708 0 : m_customPoolPalette = new FilterPaletteTreeModel(filter, m_userPaletteModel, this);
709 : }
710 0 : return m_customPoolPalette;
711 : }
712 :
713 0 : AbstractPaletteController* PaletteProvider::customElementsPaletteController()
714 : {
715 0 : if (!m_customElementsPaletteController) {
716 0 : m_customElementsPaletteController = new UserPaletteController(customElementsPaletteModel(), m_userPaletteModel, iocContext(), this);
717 0 : m_customElementsPaletteController->setCustom(true);
718 : }
719 :
720 0 : return m_customElementsPaletteController;
721 : }
722 :
723 0 : QModelIndex PaletteProvider::poolPaletteIndex(const QModelIndex& index, FilterPaletteTreeModel* poolPalette) const
724 : {
725 0 : const QModelIndex poolPaletteIndex = convertIndex(index, poolPalette);
726 0 : if (poolPaletteIndex.isValid()) {
727 0 : return poolPaletteIndex;
728 : }
729 0 : const auto contentType = index.data(PaletteTreeModel::PaletteContentTypeRole).value<Palette::Type>();
730 0 : return findPaletteIndex(poolPalette, contentType);
731 : }
732 :
733 0 : FilterPaletteTreeModel* PaletteProvider::poolPaletteModel(const QModelIndex& index) const
734 : {
735 0 : const QModelIndex filterIndex = convertProxyIndex(index, m_userPaletteModel);
736 0 : PaletteCellFilter* filter = m_userPaletteModel->getFilter(filterIndex);
737 0 : if (!filter) {
738 0 : return nullptr;
739 : }
740 :
741 0 : FilterPaletteTreeModel* m = new FilterPaletteTreeModel(filter, m_masterPaletteModel);
742 0 : QQmlEngine::setObjectOwnership(m, QQmlEngine::JavaScriptOwnership);
743 0 : return m;
744 : }
745 :
746 0 : AbstractPaletteController* PaletteProvider::poolPaletteController(FilterPaletteTreeModel* poolPaletteModel,
747 : const QModelIndex& rootIndex) const
748 : {
749 : Q_UNUSED(rootIndex);
750 0 : UserPaletteController* c = new UserPaletteController(poolPaletteModel, m_userPaletteModel, iocContext());
751 0 : c->setVisible(false);
752 0 : c->setCustom(false);
753 0 : c->setUserEditable(false);
754 : // AbstractPaletteController* c = new MasterPaletteController(poolPaletteModel, userPalette, this);
755 0 : QQmlEngine::setObjectOwnership(c, QQmlEngine::JavaScriptOwnership);
756 0 : return c;
757 : }
758 :
759 0 : QAbstractItemModel* PaletteProvider::availableExtraPalettesModel() const
760 : {
761 0 : QStandardItemModel* m = new QStandardItemModel;
762 :
763 : {
764 0 : auto roleNames = m->roleNames();
765 0 : roleNames[CustomRole] = "custom";
766 0 : roleNames[PaletteIndexRole] = "paletteIndex";
767 0 : m->setItemRoleNames(roleNames);
768 0 : }
769 :
770 0 : QStandardItem* root = m->invisibleRootItem();
771 :
772 0 : const int masterRows = m_masterPaletteModel->rowCount();
773 0 : for (int row = 0; row < masterRows; ++row) {
774 0 : const QModelIndex idx = m_masterPaletteModel->index(row, 0);
775 : // add everything that cannot be found in user palette
776 0 : if (!convertIndex(idx, m_userPaletteModel).isValid()) {
777 0 : const QString name = m_masterPaletteModel->data(idx, Qt::DisplayRole).toString();
778 0 : QStandardItem* item = new QStandardItem(name);
779 0 : item->setData(false, CustomRole); // this palette is from master palette, hence not custom
780 0 : item->setData(QPersistentModelIndex(idx), PaletteIndexRole);
781 0 : root->appendRow(item);
782 0 : }
783 : }
784 :
785 0 : const int userRows = m_userPaletteModel->rowCount();
786 0 : for (int row = 0; row < userRows; ++row) {
787 0 : const QModelIndex idx = m_userPaletteModel->index(row, 0);
788 : // add invisible custom palettes
789 0 : const bool visible = m_userPaletteModel->data(idx, PaletteTreeModel::VisibleRole).toBool();
790 0 : const bool custom = m_userPaletteModel->data(idx, PaletteTreeModel::CustomRole).toBool();
791 0 : if (!visible) {
792 0 : const QString name = m_userPaletteModel->data(idx, Qt::DisplayRole).toString();
793 0 : QStandardItem* item = new QStandardItem(name);
794 0 : item->setData(custom, CustomRole);
795 0 : item->setData(QPersistentModelIndex(idx), PaletteIndexRole);
796 0 : root->appendRow(item);
797 0 : }
798 : }
799 :
800 0 : QQmlEngine::setObjectOwnership(m, QQmlEngine::JavaScriptOwnership);
801 0 : return m;
802 : }
803 :
804 0 : bool PaletteProvider::addPalette(const QPersistentModelIndex& index)
805 : {
806 0 : if (!index.isValid()) {
807 0 : return false;
808 : }
809 :
810 0 : if (index.model() == m_userPaletteModel) {
811 0 : const bool ok = m_userPaletteModel->setData(index, true, PaletteTreeModel::VisibleRole);
812 0 : if (!ok) {
813 0 : return false;
814 : }
815 0 : const QModelIndex parent = index.parent();
816 0 : m_userPaletteModel->moveRow(parent, index.row(), parent, 0);
817 0 : return true;
818 : }
819 :
820 0 : if (index.model() == m_masterPaletteModel) {
821 0 : QMimeData* data = m_masterPaletteModel->mimeData({ QModelIndex(index) });
822 0 : QModelIndex dropIndex = m_userPaletteModel->index(0, 0);
823 :
824 0 : const bool success = m_userPaletteModel->dropMimeData(data, Qt::CopyAction, dropIndex.row(), dropIndex.column(), QModelIndex());
825 0 : if (success) {
826 0 : m_userPaletteModel->setData(dropIndex, true, PaletteTreeModel::VisibleRole);
827 : }
828 :
829 0 : data->deleteLater();
830 0 : return success;
831 : }
832 :
833 0 : return false;
834 : }
835 :
836 0 : void PaletteProvider::resetPalette(const QModelIndex& index)
837 : {
838 0 : if (!index.isValid()) {
839 0 : return;
840 : }
841 :
842 : std::string title = muse::trc("palette",
843 0 : "Do you want to restore this palette to its default state? All changes to this palette will be lost.");
844 :
845 0 : interactive()->question("", title, {
846 : IInteractive::Button::No, IInteractive::Button::Yes
847 : })
848 0 : .onResolve(this, [this, index](const IInteractive::Result& res) {
849 0 : if (res.isButton(IInteractive::Button::Yes)) {
850 0 : doResetPalette(index);
851 : }
852 0 : });
853 0 : }
854 :
855 0 : void PaletteProvider::doResetPalette(const QModelIndex& index)
856 : {
857 0 : Q_ASSERT(m_defaultPaletteModel != m_userPaletteModel);
858 :
859 0 : QAbstractItemModel* resetModel = nullptr;
860 0 : QModelIndex resetIndex;
861 :
862 0 : if (m_defaultPaletteModel) {
863 0 : resetModel = m_defaultPaletteModel;
864 0 : resetIndex = convertIndex(index, m_defaultPaletteModel);
865 : }
866 :
867 0 : if (!resetIndex.isValid()) {
868 0 : resetModel = m_masterPaletteModel;
869 0 : resetIndex = convertIndex(index, m_masterPaletteModel);
870 : }
871 :
872 0 : const QModelIndex userPaletteIndex = convertProxyIndex(index, m_userPaletteModel);
873 0 : const QModelIndex parent = userPaletteIndex.parent();
874 0 : const int row = userPaletteIndex.row();
875 0 : const int column = userPaletteIndex.column();
876 :
877 : // restore visibility and expanded state of the palette after restoring its state
878 0 : const bool wasVisible = index.data(PaletteTreeModel::VisibleRole).toBool();
879 0 : const bool wasExpanded = index.data(PaletteTreeModel::PaletteExpandedRole).toBool();
880 :
881 0 : if (!m_userPaletteModel->removeRow(row, parent)) {
882 0 : return;
883 : }
884 :
885 0 : if (resetIndex.isValid()) {
886 0 : QMimeData* data = resetModel->mimeData({ resetIndex });
887 0 : m_userPaletteModel->dropMimeData(data, Qt::CopyAction, row, column, parent);
888 0 : data->deleteLater();
889 : } else {
890 0 : m_userPaletteModel->insertRow(row, parent);
891 : }
892 :
893 0 : const QModelIndex newIndex = m_userPaletteModel->index(row, column, parent);
894 0 : m_userPaletteModel->setData(newIndex, wasVisible, PaletteTreeModel::VisibleRole);
895 0 : m_userPaletteModel->setData(newIndex, wasExpanded, PaletteTreeModel::PaletteExpandedRole);
896 : }
897 :
898 0 : QString PaletteProvider::getPaletteFilename(bool open, const QString& name) const
899 : {
900 0 : std::string title;
901 0 : std::vector<std::string> filter;
902 : #ifdef WIN_PORTABLE
903 : QString wd = QDir::cleanPath(QString("%1/../../../Data/settings").arg(QCoreApplication::applicationDirPath()));
904 : #else
905 0 : QString wd = QString("%1/%2").arg(QStandardPaths::writableLocation(QStandardPaths::HomeLocation))
906 0 : .arg(QCoreApplication::applicationName());
907 : #endif
908 0 : if (open) {
909 0 : title = muse::trc("palette", "Load palette");
910 0 : filter = { muse::trc("palette", "MuseScore Studio palette") + " (*.mpal)" };
911 : } else {
912 0 : title = muse::trc("palette", "Save palette");
913 0 : filter = { muse::trc("palette", "MuseScore Studio palette") + " (*.mpal)" };
914 : }
915 :
916 0 : QFileInfo myPalettes(wd);
917 0 : QString defaultPath = myPalettes.absoluteFilePath();
918 0 : if (!name.isEmpty()) {
919 0 : QString fname = muse::io::escapeFileName(name).toQString();
920 0 : QFileInfo myName(fname);
921 0 : if (myName.isRelative()) {
922 0 : myName.setFile(defaultPath, fname);
923 : }
924 0 : defaultPath = myName.absoluteFilePath();
925 0 : }
926 :
927 0 : muse::io::path_t fn;
928 0 : if (open) {
929 0 : fn = interactive()->selectOpeningFileSync(title, defaultPath, filter);
930 : } else {
931 0 : fn = interactive()->selectSavingFileSync(title, defaultPath, filter);
932 : }
933 0 : return fn.toQString();
934 0 : }
935 :
936 0 : bool PaletteProvider::savePalette(const QModelIndex& index)
937 : {
938 0 : const QModelIndex srcIndex = convertProxyIndex(index, m_userPaletteModel);
939 0 : const Palette* pp = m_userPaletteModel->findPalette(srcIndex);
940 0 : if (!pp) {
941 0 : return false;
942 : }
943 :
944 0 : const QString path = getPaletteFilename(/* load? */ false, pp->translatedName());
945 :
946 0 : if (path.isEmpty()) {
947 0 : return false;
948 : }
949 0 : return pp->writeToFile(path);
950 0 : }
951 :
952 0 : bool PaletteProvider::loadPalette(const QModelIndex& index)
953 : {
954 0 : const QString path = getPaletteFilename(/* load? */ true);
955 0 : if (path.isEmpty()) {
956 0 : return false;
957 : }
958 :
959 0 : PalettePtr pp = std::make_shared<Palette>(iocContext());
960 0 : if (!pp->readFromFile(path)) {
961 0 : return false;
962 : }
963 0 : pp->setType(Palette::Type::Custom); // mark the loaded palette custom
964 :
965 0 : const QModelIndex srcIndex = convertProxyIndex(index, m_userPaletteModel);
966 :
967 0 : const int row = srcIndex.row();
968 0 : const QModelIndex parent = srcIndex.parent();
969 :
970 0 : return m_userPaletteModel->insertPalette(pp, row, parent);
971 0 : }
972 :
973 0 : void PaletteProvider::setUserPaletteTree(PaletteTreePtr tree)
974 : {
975 0 : if (m_userPaletteModel) {
976 0 : disconnect(m_userPaletteModel, &PaletteTreeModel::treeChanged, this, &PaletteProvider::notifyAboutUserPaletteChanged);
977 0 : m_userPaletteModel->setPaletteTree(tree);
978 0 : connect(m_userPaletteModel, &PaletteTreeModel::treeChanged, this, &PaletteProvider::notifyAboutUserPaletteChanged);
979 : } else {
980 0 : m_userPaletteModel = new PaletteTreeModel(tree, iocContext(), /* parent */ this);
981 0 : connect(m_userPaletteModel, &PaletteTreeModel::treeChanged, this, &PaletteProvider::notifyAboutUserPaletteChanged);
982 : }
983 0 : }
984 :
985 0 : void PaletteProvider::setDefaultPaletteTree(PaletteTreePtr tree)
986 : {
987 0 : if (m_defaultPaletteModel) {
988 0 : m_defaultPaletteModel->setPaletteTree(tree);
989 : } else {
990 0 : m_defaultPaletteModel = new PaletteTreeModel(tree, iocContext(), /* parent */ this);
991 : }
992 0 : }
993 :
994 0 : muse::async::Channel<ElementPtr> PaletteProvider::addCustomItemRequested() const
995 : {
996 0 : return m_addCustomItemRequested;
997 : }
998 :
999 0 : void PaletteProvider::write(XmlWriter& xml, bool pasteMode) const
1000 : {
1001 0 : if (!m_userPaletteModel) {
1002 0 : return;
1003 : }
1004 0 : if (const PaletteTree* tree = m_userPaletteModel->paletteTree()) {
1005 0 : tree->write(xml, pasteMode);
1006 : }
1007 : }
1008 :
1009 0 : bool PaletteProvider::read(XmlReader& e, bool pasteMode)
1010 : {
1011 0 : PaletteTreePtr tree = std::make_shared<PaletteTree>();
1012 0 : if (!tree->read(e, pasteMode, iocContext())) {
1013 0 : return false;
1014 : }
1015 :
1016 0 : setUserPaletteTree(tree);
1017 :
1018 0 : return true;
1019 0 : }
|