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 "customizekitdialog.h"
24 :
25 : #include "global/io/buffer.h"
26 : #include "global/io/file.h"
27 :
28 : #include "engraving/infrastructure/smufl.h"
29 :
30 : #include "engraving/types/constants.h"
31 : #include "engraving/types/symnames.h"
32 : #include "engraving/types/typesconv.h"
33 :
34 : #include "engraving/rw/xmlreader.h"
35 : #include "engraving/rw/xmlwriter.h"
36 :
37 : #include "engraving/dom/chord.h"
38 : #include "engraving/dom/factory.h"
39 : #include "engraving/dom/masterscore.h"
40 : #include "engraving/dom/note.h"
41 : #include "engraving/dom/score.h"
42 : #include "engraving/dom/utils.h"
43 :
44 : #include "notation/inotation.h"
45 : #include "notation/inotationinteraction.h"
46 : #include "notation/inotationnoteinput.h" // IWYU pragma: keep
47 : #include "notation/inotationparts.h" // IWYU pragma: keep
48 :
49 : #include "modularity/ioc.h"
50 : #include "notationscene/utilities/percussionutilities.h"
51 :
52 : #include "draw/types/geometry.h"
53 :
54 : #include <QMessageBox>
55 :
56 : using namespace mu::palette;
57 : using namespace muse::io;
58 : using namespace mu::notation;
59 : using namespace mu::engraving;
60 :
61 : enum Column : char {
62 : PITCH, NOTE, SHORTCUT, NAME
63 : };
64 :
65 : class CustomizeKitTreeWidgetItem : public QTreeWidgetItem
66 : {
67 : public:
68 0 : CustomizeKitTreeWidgetItem(QTreeWidget* parent)
69 0 : : QTreeWidgetItem(parent) {}
70 :
71 0 : bool operator<(const QTreeWidgetItem& other) const override
72 : {
73 0 : if (treeWidget()->sortColumn() == Column::PITCH) {
74 0 : return data(Column::PITCH, Qt::UserRole).toInt() < other.data(Column::PITCH, Qt::UserRole).toInt();
75 : }
76 :
77 0 : return QTreeWidgetItem::operator<(other);
78 : }
79 : };
80 :
81 : //---------------------------------------------------------
82 : // noteHeadNames
83 : // "Sol" and "Alt. Brevis" omitted,
84 : // as not being useful for drums
85 : //---------------------------------------------------------
86 :
87 : NoteHeadGroup noteHeadNames[] = {
88 : NoteHeadGroup::HEAD_NORMAL,
89 : NoteHeadGroup::HEAD_CROSS,
90 : NoteHeadGroup::HEAD_PLUS,
91 : NoteHeadGroup::HEAD_XCIRCLE,
92 : NoteHeadGroup::HEAD_WITHX,
93 : NoteHeadGroup::HEAD_TRIANGLE_UP,
94 : NoteHeadGroup::HEAD_TRIANGLE_DOWN,
95 : NoteHeadGroup::HEAD_SLASH,
96 : NoteHeadGroup::HEAD_SLASHED1,
97 : NoteHeadGroup::HEAD_SLASHED2,
98 : NoteHeadGroup::HEAD_DIAMOND,
99 : NoteHeadGroup::HEAD_DIAMOND_OLD,
100 : NoteHeadGroup::HEAD_CIRCLED,
101 : NoteHeadGroup::HEAD_CIRCLED_LARGE,
102 : NoteHeadGroup::HEAD_LARGE_ARROW,
103 : NoteHeadGroup::HEAD_DO,
104 : NoteHeadGroup::HEAD_RE,
105 : NoteHeadGroup::HEAD_MI,
106 : NoteHeadGroup::HEAD_FA,
107 : NoteHeadGroup::HEAD_LA,
108 : NoteHeadGroup::HEAD_TI,
109 : NoteHeadGroup::HEAD_SWISS_RUDIMENTS_FLAM,
110 : NoteHeadGroup::HEAD_SWISS_RUDIMENTS_DOUBLE,
111 : NoteHeadGroup::HEAD_CUSTOM
112 : };
113 :
114 : //---------------------------------------------------------
115 : // CustomizeKitDialog
116 : //---------------------------------------------------------
117 :
118 : struct SymbolIcon {
119 : SymId id;
120 : QIcon icon;
121 0 : SymbolIcon(SymId i, QIcon j)
122 0 : : id(i), icon(j)
123 0 : {}
124 :
125 0 : static SymbolIcon generateIcon(const SymId& id, double w, double h, double defaultScale)
126 : {
127 0 : QIcon icon;
128 0 : QPixmap image(w, h);
129 0 : image.fill(Qt::transparent);
130 0 : muse::draw::Painter painter(&image, "generateicon");
131 :
132 0 : const muse::RectF& bbox = CustomizeKitDialog::engravingFonts()->fallbackFont()->bbox(id, 1);
133 0 : const qreal actualSymbolScale = std::min(w / bbox.width(), h / bbox.height());
134 0 : qreal mag = std::min(defaultScale, actualSymbolScale);
135 0 : const qreal& xStShift = (w - mag * bbox.width()) / 2 - mag * bbox.left();
136 0 : const qreal& yStShift = (h - mag * bbox.height()) / 2 - mag * bbox.top();
137 0 : const muse::PointF& stPtPos = muse::PointF(xStShift, yStShift);
138 :
139 0 : painter.setPen(Color(CustomizeKitDialog::uiConfiguration()->currentTheme().values[muse::ui::FONT_PRIMARY_COLOR].toString()));
140 0 : painter.setBrush(Color::transparent);
141 :
142 0 : CustomizeKitDialog::engravingFonts()->fallbackFont()->draw(id, &painter, mag, stPtPos);
143 :
144 0 : icon.addPixmap(image);
145 :
146 0 : return SymbolIcon(id, icon);
147 0 : }
148 : };
149 :
150 0 : CustomizeKitDialog::CustomizeKitDialog(QWidget* parent)
151 0 : : muse::ui::WidgetDialog(parent)
152 : {
153 0 : setObjectName(QStringLiteral("CustomizeKitDialog"));
154 :
155 0 : setupUi(this);
156 0 : setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
157 :
158 0 : drumNote->setGridSize(70, 80);
159 0 : drumNote->setDrawGrid(false);
160 0 : drumNote->setReadOnly(true);
161 0 : drumNote->setIgnoreInputEvents(true);
162 :
163 0 : for (auto g : noteHeadNames) {
164 0 : noteHead->addItem(TConv::translatedUserName(g), int(g));
165 : }
166 :
167 0 : connect(pitchList, &QTreeWidget::currentItemChanged, this, &CustomizeKitDialog::itemChanged);
168 0 : connect(buttonBox, &QDialogButtonBox::clicked, this, &CustomizeKitDialog::bboxClicked);
169 0 : connect(name, &QLineEdit::textChanged, this, &CustomizeKitDialog::nameChanged);
170 0 : connect(noteHead, &QComboBox::currentIndexChanged, this, &CustomizeKitDialog::valueChanged);
171 0 : connect(staffLine, &QSpinBox::valueChanged, this, &CustomizeKitDialog::valueChanged);
172 0 : connect(voice, &QComboBox::currentIndexChanged, this, &CustomizeKitDialog::valueChanged);
173 0 : connect(stemDirection, &QComboBox::currentIndexChanged, this, &CustomizeKitDialog::valueChanged);
174 0 : connect(shortcut, &QPushButton::clicked, this, &CustomizeKitDialog::defineShortcut);
175 0 : connect(loadButton, &QPushButton::clicked, this, &CustomizeKitDialog::load);
176 0 : connect(saveButton, &QPushButton::clicked, this, &CustomizeKitDialog::save);
177 0 : pitchList->setColumnWidth(0, 40);
178 0 : pitchList->setColumnWidth(1, 60);
179 0 : pitchList->setColumnWidth(2, 30);
180 :
181 : StringList validNoteheadRanges = {
182 : u"Noteheads",
183 : u"Round and square noteheads",
184 : u"Slash noteheads",
185 : u"Shape note noteheads",
186 : u"Shape note noteheads supplement",
187 : u"Techniques noteheads"
188 0 : };
189 : QSet excludedSyms = {
190 : SymId::noteheadParenthesisLeft,
191 : SymId::noteheadParenthesisRight,
192 : SymId::noteheadParenthesis,
193 : SymId::noteheadNull,
194 0 : };
195 : QList primaryNoteheads = {
196 : SymId::noteheadXOrnate,
197 : SymId::noteheadXBlack,
198 : SymId::noteheadXHalf,
199 : SymId::noteheadXWhole,
200 : SymId::noteheadXDoubleWhole,
201 : SymId::noteheadSlashedBlack1,
202 : SymId::noteheadSlashedHalf1,
203 : SymId::noteheadSlashedWhole1,
204 : SymId::noteheadSlashedDoubleWhole1,
205 : SymId::noteheadSlashedBlack2,
206 : SymId::noteheadSlashedHalf2,
207 : SymId::noteheadSlashedWhole2,
208 : SymId::noteheadSlashedDoubleWhole2,
209 : SymId::noteheadSquareBlack,
210 : SymId::noteheadMoonBlack,
211 : SymId::noteheadTriangleUpRightBlack,
212 : SymId::noteheadTriangleDownBlack,
213 : SymId::noteheadTriangleUpBlack,
214 : SymId::noteheadTriangleLeftBlack,
215 : SymId::noteheadTriangleRoundDownBlack,
216 : SymId::noteheadDiamondBlack,
217 : SymId::noteheadDiamondHalf,
218 : SymId::noteheadDiamondWhole,
219 : SymId::noteheadDiamondDoubleWhole,
220 : SymId::noteheadRoundWhiteWithDot,
221 : SymId::noteheadVoidWithX,
222 : SymId::noteheadHalfWithX,
223 : SymId::noteheadWholeWithX,
224 : SymId::noteheadDoubleWholeWithX,
225 : SymId::noteheadLargeArrowUpBlack,
226 : SymId::noteheadLargeArrowUpHalf,
227 : SymId::noteheadLargeArrowUpWhole,
228 : SymId::noteheadLargeArrowUpDoubleWhole
229 0 : };
230 :
231 0 : int w = quarterCmb->iconSize().width() * qApp->devicePixelRatio();
232 0 : int h = quarterCmb->iconSize().height() * qApp->devicePixelRatio();
233 : //default scale is 0.3, will use smaller scale for large noteheads symbols
234 0 : const qreal defaultScale = 0.3 * qApp->devicePixelRatio();
235 :
236 0 : QList<SymbolIcon> resNoteheads;
237 0 : for (SymId symId : primaryNoteheads) {
238 0 : resNoteheads.append(SymbolIcon::generateIcon(symId, w, h, defaultScale));
239 : }
240 :
241 0 : for (const String& range : validNoteheadRanges) {
242 0 : for (SymId symId : Smufl::smuflRanges().at(range)) {
243 0 : if (!excludedSyms.contains(symId) && !primaryNoteheads.contains(symId)) {
244 0 : resNoteheads.append(SymbolIcon::generateIcon(symId, w, h, defaultScale));
245 : }
246 : }
247 : }
248 :
249 0 : QComboBox* combos[] = { wholeCmb, halfCmb, quarterCmb, doubleWholeCmb };
250 0 : for (QComboBox* combo : combos) {
251 0 : for (const SymbolIcon& si : resNoteheads) {
252 0 : SymId id = si.id;
253 0 : QIcon icon = si.icon;
254 0 : combo->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
255 0 : combo->addItem(icon, SymNames::translatedUserNameForSymId(id), static_cast<int>(id));
256 0 : }
257 : }
258 0 : wholeCmb->setCurrentIndex(quarterCmb->findData(static_cast<int>(SymId::noteheadWhole)));
259 0 : halfCmb->setCurrentIndex(quarterCmb->findData(static_cast<int>(SymId::noteheadHalf)));
260 0 : quarterCmb->setCurrentIndex(quarterCmb->findData(static_cast<int>(SymId::noteheadBlack)));
261 0 : doubleWholeCmb->setCurrentIndex(quarterCmb->findData(static_cast<int>(SymId::noteheadDoubleWhole)));
262 :
263 0 : connect(customGbox, &QGroupBox::toggled, this, &CustomizeKitDialog::customGboxToggled);
264 0 : connect(quarterCmb, &QComboBox::currentIndexChanged, this, &CustomizeKitDialog::customQuarterChanged);
265 :
266 0 : quarterCmb->setAccessibleName(quarterLbl->text() + " " + quarterCmb->currentText());
267 0 : halfCmb->setAccessibleName(halfLbl->text() + " " + halfCmb->currentText());
268 0 : wholeCmb->setAccessibleName(wholeLbl->text() + " " + wholeCmb->currentText());
269 0 : doubleWholeCmb->setAccessibleName(doubleWholeLbl->text() + " " + doubleWholeCmb->currentText());
270 :
271 : //! NOTE: It is necessary for the correct start of navigation in the dialog
272 0 : setFocus();
273 0 : }
274 :
275 0 : void CustomizeKitDialog::componentComplete()
276 : {
277 0 : m_notation = projectSession()->currentNotation();
278 0 : if (!m_notation) {
279 0 : return;
280 : }
281 :
282 0 : initDrumsetAndKey();
283 :
284 0 : QTreeWidgetItem* itemToSelect = loadPitchesList();
285 0 : itemToSelect = itemToSelect ? itemToSelect : pitchList->topLevelItem(0);
286 :
287 0 : if (itemToSelect) {
288 0 : pitchList->setCurrentItem(itemToSelect);
289 : }
290 : }
291 :
292 : //---------------------------------------------------------
293 : // customGboxToggled
294 : //---------------------------------------------------------
295 :
296 0 : void CustomizeKitDialog::customGboxToggled(bool checked)
297 : {
298 0 : noteHead->setEnabled(!checked);
299 0 : if (checked) {
300 0 : noteHead->setCurrentIndex(noteHead->findData(int(NoteHeadGroup::HEAD_CUSTOM)));
301 : } else {
302 0 : noteHead->setCurrentIndex(noteHead->findData(int(NoteHeadGroup::HEAD_NORMAL)));
303 : }
304 0 : }
305 :
306 0 : QTreeWidgetItem* CustomizeKitDialog::loadPitchesList()
307 : {
308 0 : const INotationInteractionPtr interaction = m_notation->interaction();
309 0 : const mu::engraving::Note* note = dynamic_cast<mu::engraving::Note*>(interaction->contextItem());
310 0 : const int originPitch = note ? note->pitch() : -1;
311 :
312 0 : pitchList->blockSignals(true);
313 0 : pitchList->clear();
314 0 : pitchList->blockSignals(false);
315 :
316 0 : QTreeWidgetItem* itemToSelect = nullptr;
317 :
318 0 : for (int i = 0; i < DRUM_INSTRUMENTS; ++i) {
319 0 : QTreeWidgetItem* item = new CustomizeKitTreeWidgetItem(pitchList);
320 0 : item->setText(Column::PITCH, QString("%1").arg(i));
321 0 : item->setText(Column::NOTE, pitch2string(i));
322 0 : item->setText(Column::SHORTCUT, m_editedDrumset.shortcut(i));
323 0 : item->setText(Column::NAME, m_editedDrumset.translatedName(i));
324 0 : item->setData(Column::PITCH, Qt::UserRole, i);
325 0 : if (i == originPitch) {
326 0 : itemToSelect = item;
327 : }
328 : }
329 :
330 0 : pitchList->sortItems(3, Qt::SortOrder::DescendingOrder);
331 :
332 0 : return itemToSelect;
333 0 : }
334 :
335 0 : void CustomizeKitDialog::setEnabledPitchControls(bool enable)
336 : {
337 0 : customGbox->setEnabled(enable);
338 0 : noteHead->setEnabled(enable);
339 0 : voice->setEnabled(enable);
340 0 : shortcut->setEnabled(enable);
341 0 : staffLine->setEnabled(enable);
342 0 : stemDirection->setEnabled(enable);
343 0 : drumNote->setEnabled(enable);
344 0 : label_2->setEnabled(enable);
345 0 : label_3->setEnabled(enable);
346 0 : label_4->setEnabled(enable);
347 0 : label_5->setEnabled(enable);
348 0 : label_6->setEnabled(enable);
349 0 : }
350 :
351 : //---------------------------------------------------------
352 : // nameChanged
353 : //---------------------------------------------------------
354 :
355 0 : void CustomizeKitDialog::nameChanged(const QString& n)
356 : {
357 0 : QTreeWidgetItem* item = pitchList->currentItem();
358 0 : if (item) {
359 0 : item->setText(Column::NAME, n);
360 0 : int pitch = item->data(Column::PITCH, Qt::UserRole).toInt();
361 0 : if (!n.isEmpty()) {
362 0 : if (!m_editedDrumset.isValid(pitch)) {
363 0 : noteHead->setCurrentIndex(0);
364 : }
365 : } else {
366 0 : m_editedDrumset.drum(pitch).name.clear();
367 : }
368 : }
369 0 : setEnabledPitchControls(!n.isEmpty());
370 0 : }
371 :
372 : //---------------------------------------------------------
373 : // bboxClicked
374 : //---------------------------------------------------------
375 0 : void CustomizeKitDialog::bboxClicked(QAbstractButton* button)
376 : {
377 0 : switch (buttonBox->buttonRole(button)) {
378 0 : case QDialogButtonBox::ApplyRole:
379 : case QDialogButtonBox::AcceptRole:
380 0 : apply();
381 0 : break;
382 0 : case QDialogButtonBox::RejectRole:
383 0 : cancel();
384 0 : break;
385 0 : default:
386 0 : break;
387 : }
388 0 : }
389 :
390 0 : void CustomizeKitDialog::initDrumsetAndKey()
391 : {
392 0 : const INotationInteractionPtr interaction = m_notation->interaction();
393 0 : const INotationInteraction::HitElementContext context = interaction->hitElementContext();
394 0 : const NoteInputState& state = interaction->noteInput()->state();
395 :
396 : // Prefer hit context, fall back to note input...
397 0 : const bool hitContextValid = context.staff && context.element;
398 0 : const bool noteInputValid = state.staff() && state.segment();
399 0 : IF_ASSERT_FAILED(hitContextValid || noteInputValid) {
400 0 : return;
401 : }
402 :
403 0 : mu::engraving::Part* part = hitContextValid ? context.staff->part() : state.staff()->part();
404 0 : mu::engraving::Fraction tick = hitContextValid ? context.element->tick() : state.segment()->tick();
405 0 : mu::engraving::Instrument* inst = part ? part->instrument(tick) : nullptr;
406 0 : IF_ASSERT_FAILED(inst && inst->useDrumset()) {
407 0 : return;
408 : }
409 :
410 0 : m_instrumentKey = { inst->id(), part->id(), tick };
411 :
412 0 : m_originDrumset = *inst->drumset();
413 0 : m_editedDrumset = m_originDrumset;
414 0 : }
415 :
416 0 : void CustomizeKitDialog::apply()
417 : {
418 0 : valueChanged(); //save last changes in name
419 :
420 : //! NOTE: The note input state changes inside valueChanged,
421 : //! so to update the state of the drumset panel view, need to notify the change
422 0 : notifyAboutNoteInputStateChanged();
423 0 : }
424 :
425 0 : void CustomizeKitDialog::notifyAboutNoteInputStateChanged()
426 : {
427 0 : m_notation->interaction()->noteInput()->stateChanged().notify();
428 0 : }
429 :
430 0 : void CustomizeKitDialog::cancel()
431 : {
432 0 : m_notation->parts()->replaceDrumset(m_instrumentKey, m_originDrumset);
433 0 : }
434 :
435 : //---------------------------------------------------------
436 : // fillCustomNoteheadsDataFromComboboxes
437 : //---------------------------------------------------------
438 0 : void CustomizeKitDialog::fillCustomNoteheadsDataFromComboboxes(int pitch)
439 : {
440 0 : m_editedDrumset.drum(pitch).notehead = NoteHeadGroup::HEAD_CUSTOM;
441 0 : m_editedDrumset.drum(pitch).noteheads[int(NoteHeadType::HEAD_WHOLE)] = static_cast<SymId>(wholeCmb->currentData().toInt());
442 0 : m_editedDrumset.drum(pitch).noteheads[int(NoteHeadType::HEAD_QUARTER)] = static_cast<SymId>(quarterCmb->currentData().toInt());
443 0 : m_editedDrumset.drum(pitch).noteheads[int(NoteHeadType::HEAD_HALF)] = static_cast<SymId>(halfCmb->currentData().toInt());
444 0 : m_editedDrumset.drum(pitch).noteheads[int(NoteHeadType::HEAD_BREVIS)] = static_cast<SymId>(doubleWholeCmb->currentData().toInt());
445 0 : }
446 :
447 0 : void CustomizeKitDialog::fillNoteheadsComboboxes(bool customGroup, int pitch)
448 : {
449 0 : if (customGroup) {
450 0 : wholeCmb->setCurrentIndex(quarterCmb->findData(static_cast<int>(m_editedDrumset.noteHeads(pitch, NoteHeadType::HEAD_WHOLE))));
451 0 : halfCmb->setCurrentIndex(quarterCmb->findData(static_cast<int>(m_editedDrumset.noteHeads(pitch, NoteHeadType::HEAD_HALF))));
452 0 : quarterCmb->setCurrentIndex(quarterCmb->findData(static_cast<int>(m_editedDrumset.noteHeads(pitch, NoteHeadType::HEAD_QUARTER))));
453 0 : doubleWholeCmb->setCurrentIndex(quarterCmb->findData(static_cast<int>(m_editedDrumset.noteHeads(pitch,
454 : NoteHeadType::HEAD_BREVIS))));
455 : } else {
456 0 : const auto group = m_editedDrumset.drum(pitch).notehead;
457 0 : if (group == NoteHeadGroup::HEAD_INVALID) {
458 0 : return;
459 : }
460 :
461 0 : wholeCmb->setCurrentIndex(quarterCmb->findData(static_cast<int>(Note::noteHead(0, group, NoteHeadType::HEAD_WHOLE))));
462 0 : halfCmb->setCurrentIndex(quarterCmb->findData(static_cast<int>(Note::noteHead(0, group, NoteHeadType::HEAD_HALF))));
463 0 : quarterCmb->setCurrentIndex(quarterCmb->findData(static_cast<int>(Note::noteHead(0, group, NoteHeadType::HEAD_QUARTER))));
464 0 : doubleWholeCmb->setCurrentIndex(quarterCmb->findData(static_cast<int>(Note::noteHead(0, group, NoteHeadType::HEAD_BREVIS))));
465 : }
466 : }
467 :
468 : //---------------------------------------------------------
469 : // itemChanged
470 : //---------------------------------------------------------
471 0 : void CustomizeKitDialog::itemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
472 : {
473 0 : if (previous) {
474 0 : int pitch = previous->data(0, Qt::UserRole).toInt();
475 0 : m_editedDrumset.drum(pitch).name = name->text();
476 0 : if (customGbox->isChecked()) {
477 0 : fillCustomNoteheadsDataFromComboboxes(pitch);
478 : } else {
479 0 : const QVariant currData = noteHead->currentData();
480 0 : if (currData.isValid()) {
481 0 : m_editedDrumset.drum(pitch).notehead = NoteHeadGroup(currData.toInt());
482 : }
483 0 : }
484 :
485 0 : m_editedDrumset.drum(pitch).line = staffLine->value();
486 0 : m_editedDrumset.drum(pitch).voice = voice->currentIndex();
487 :
488 0 : m_editedDrumset.drum(pitch).line = staffLine->value();
489 0 : m_editedDrumset.drum(pitch).voice = voice->currentIndex();
490 0 : m_editedDrumset.drum(pitch).stemDirection = DirectionV(stemDirection->currentIndex());
491 :
492 0 : previous->setText(Column::NAME, m_editedDrumset.translatedName(pitch));
493 : }
494 0 : if (current == 0) {
495 0 : return;
496 : }
497 :
498 0 : staffLine->blockSignals(true);
499 0 : voice->blockSignals(true);
500 0 : stemDirection->blockSignals(true);
501 0 : noteHead->blockSignals(true);
502 :
503 0 : int pitch = current->data(0, Qt::UserRole).toInt();
504 0 : name->setText(m_editedDrumset.translatedName(pitch));
505 0 : staffLine->setValue(m_editedDrumset.line(pitch));
506 0 : voice->setCurrentIndex(m_editedDrumset.voice(pitch));
507 0 : stemDirection->setCurrentIndex(int(m_editedDrumset.stemDirection(pitch)));
508 :
509 0 : NoteHeadGroup nh = m_editedDrumset.noteHead(pitch);
510 0 : bool isCustomGroup = (nh == NoteHeadGroup::HEAD_CUSTOM);
511 0 : if (m_editedDrumset.isValid(pitch)) {
512 0 : setCustomNoteheadsGUIEnabled(isCustomGroup);
513 : }
514 0 : noteHead->setCurrentIndex(noteHead->findData(int(nh)));
515 0 : fillNoteheadsComboboxes(isCustomGroup, pitch);
516 :
517 0 : const QString shortcutText = m_editedDrumset.shortcut(pitch);
518 0 : shortcut->setText(!shortcutText.isEmpty() ? shortcutText : muse::qtrc("shortcuts", "None"));
519 :
520 0 : staffLine->blockSignals(false);
521 0 : voice->blockSignals(false);
522 0 : stemDirection->blockSignals(false);
523 0 : noteHead->blockSignals(false);
524 :
525 0 : updateExample();
526 0 : }
527 :
528 : //---------------------------------------------------------
529 : // setCustomNoteheadsGUIEnabled
530 : //---------------------------------------------------------
531 0 : void CustomizeKitDialog::setCustomNoteheadsGUIEnabled(bool enabled)
532 : {
533 0 : customGbox->setChecked(enabled);
534 0 : noteHead->setEnabled(!enabled);
535 0 : if (enabled) {
536 0 : noteHead->setCurrentIndex(noteHead->findData(int(NoteHeadGroup::HEAD_CUSTOM)));
537 : }
538 0 : }
539 :
540 : //---------------------------------------------------------
541 : // valueChanged
542 : //---------------------------------------------------------
543 0 : void CustomizeKitDialog::valueChanged()
544 : {
545 0 : if (!pitchList->currentItem()) {
546 0 : return;
547 : }
548 0 : int pitch = pitchList->currentItem()->data(Column::PITCH, Qt::UserRole).toInt();
549 0 : m_editedDrumset.drum(pitch).name = name->text();
550 0 : if (customGbox->isChecked() || noteHead->currentIndex() == noteHead->findData(int(NoteHeadGroup::HEAD_CUSTOM))) {
551 0 : fillCustomNoteheadsDataFromComboboxes(pitch);
552 0 : setCustomNoteheadsGUIEnabled(true);
553 : } else {
554 0 : m_editedDrumset.drum(pitch).notehead = NoteHeadGroup(noteHead->currentData().toInt());
555 0 : fillNoteheadsComboboxes(false, pitch);
556 0 : setCustomNoteheadsGUIEnabled(false);
557 : }
558 :
559 0 : m_editedDrumset.drum(pitch).line = staffLine->value();
560 0 : m_editedDrumset.drum(pitch).voice = voice->currentIndex();
561 0 : m_editedDrumset.drum(pitch).stemDirection = DirectionV(stemDirection->currentIndex());
562 :
563 0 : updateExample();
564 :
565 0 : m_notation->parts()->replaceDrumset(m_instrumentKey, m_editedDrumset);
566 : }
567 :
568 : //---------------------------------------------------------
569 : // updateExample
570 : //---------------------------------------------------------
571 0 : void CustomizeKitDialog::updateExample()
572 : {
573 0 : drumNote->clear();
574 0 : int pitch = pitchList->currentItem()->data(0, Qt::UserRole).toInt();
575 0 : if (!m_editedDrumset.isValid(pitch)) {
576 0 : return;
577 : }
578 0 : int line = m_editedDrumset.line(pitch);
579 0 : NoteHeadGroup nh = m_editedDrumset.noteHead(pitch);
580 0 : int v = m_editedDrumset.voice(pitch);
581 0 : DirectionV dir = m_editedDrumset.stemDirection(pitch);
582 0 : bool up = (DirectionV::UP == dir) || (DirectionV::AUTO == dir && line > 4);
583 0 : std::shared_ptr<Chord> chord = Factory::makeChord(paletteScoreProvider()->paletteScore()->dummy());
584 0 : chord->setDurationType(DurationType::V_QUARTER);
585 0 : chord->setStemDirection(dir);
586 0 : chord->setTrack(v);
587 0 : chord->setIsUiItem(true);
588 0 : Note* note = Factory::createNote(chord.get());
589 0 : note->setTrack(v);
590 0 : note->setPitch(pitch);
591 0 : note->setTpcFromPitch();
592 0 : note->setLine(line);
593 0 : note->setPos(0.0, paletteScoreProvider()->paletteScore()->style().spatium() * .5 * line);
594 0 : note->setHeadType(NoteHeadType::HEAD_QUARTER);
595 0 : note->setHeadGroup(nh);
596 0 : note->mutldata()->cachedNoteheadSym.set_value(static_cast<SymId>(quarterCmb->currentData().toInt()));
597 0 : chord->add(note);
598 0 : Stem* stem = Factory::createStem(chord.get());
599 0 : stem->setBaseLength(up ? -3.0_sp : 3.0_sp);
600 0 : engravingRenderer()->layoutItem(stem);
601 0 : chord->add(stem);
602 0 : drumNote->appendElement(chord, m_editedDrumset.translatedName(pitch));
603 0 : }
604 :
605 : //---------------------------------------------------------
606 : // loadDrum
607 : //---------------------------------------------------------
608 :
609 0 : void CustomizeKitDialog::load()
610 : {
611 0 : std::vector<std::string> filter = { muse::trc("palette", "MuseScore drumset file") + " (*.drm)" };
612 0 : muse::io::path_t dir = notationConfiguration()->userStylesPath();
613 0 : muse::io::path_t fname = interactive()->selectOpeningFileSync(muse::trc("palette", "Load drumset"), dir, filter);
614 :
615 0 : if (fname.empty()) {
616 0 : return;
617 : }
618 :
619 0 : File fp(fname.toQString());
620 0 : if (!fp.open(IODevice::ReadOnly)) {
621 0 : return;
622 : }
623 :
624 0 : XmlReader e(&fp);
625 0 : m_editedDrumset.clear();
626 0 : while (e.readNextStartElement()) {
627 0 : if (e.name() == "museScore") {
628 0 : if (e.attribute("version") != Constants::MSC_VERSION_STR) {
629 0 : auto result = interactive()->warningSync(
630 0 : muse::trc("palette", "Drumset file too old"),
631 0 : muse::trc("palette", "MuseScore Studio may not be able to load this drumset file."), {
632 : muse::IInteractive::Button::Cancel,
633 : muse::IInteractive::Button::Ignore
634 0 : }, muse::IInteractive::Button::Cancel);
635 :
636 0 : if (result.standardButton() != muse::IInteractive::Button::Ignore) { // covers Cancel and Esc
637 0 : return;
638 : }
639 : }
640 0 : while (e.readNextStartElement()) {
641 0 : if (e.name() == "Drum") {
642 0 : m_editedDrumset.loadDrum(e);
643 0 : } else if (e.name() == "percussionPanelColumns") {
644 0 : m_editedDrumset.setPercussionPanelColumns(e.readInt());
645 : } else {
646 0 : e.unknown();
647 : }
648 : }
649 : }
650 : }
651 0 : fp.close();
652 0 : loadPitchesList();
653 0 : pitchList->setCurrentItem(pitchList->topLevelItem(0));
654 0 : }
655 :
656 : //---------------------------------------------------------
657 : // save
658 : //---------------------------------------------------------
659 :
660 0 : void CustomizeKitDialog::save()
661 : {
662 0 : std::vector<std::string> filter = { muse::trc("palette", "MuseScore drumset file") + " (*.drm)" };
663 0 : muse::io::path_t dir = notationConfiguration()->userStylesPath();
664 0 : muse::io::path_t fname = interactive()->selectSavingFileSync(muse::trc("palette", "Save drumset"), dir, filter);
665 :
666 0 : if (fname.empty()) {
667 0 : return;
668 : }
669 :
670 0 : auto outBuf = Buffer::opened(IODevice::WriteOnly);
671 0 : valueChanged(); //save last changes in name
672 0 : XmlWriter xml(&outBuf);
673 0 : xml.startDocument();
674 0 : xml.startElement("museScore", { { "version", Constants::MSC_VERSION_STR } });
675 0 : m_editedDrumset.save(xml);
676 0 : xml.endElement();
677 0 : xml.flush();
678 0 : if (outBuf.hasError()) {
679 0 : QString s = muse::qtrc("palette", "Writing file failed: %1").arg(QString::fromStdString(outBuf.errorString()));
680 0 : interactive()->error(muse::trc("palette", "Write drumset"), s.toStdString());
681 0 : }
682 :
683 0 : const muse::Ret ret = File::writeFile(fname, outBuf.data());
684 0 : if (!ret) {
685 0 : QString s = muse::qtrc("palette", "Writing file failed: %1").arg(ret.toString());
686 0 : interactive()->error(muse::trc("palette", "Write drumset"), s.toStdString());
687 0 : return;
688 0 : }
689 0 : }
690 :
691 : //---------------------------------------------------------
692 : // customQuarterChanged
693 : //---------------------------------------------------------
694 0 : void CustomizeKitDialog::customQuarterChanged(int)
695 : {
696 0 : updateExample();
697 0 : }
698 :
699 0 : void CustomizeKitDialog::defineShortcut()
700 : {
701 0 : QTreeWidgetItem* item = pitchList->currentItem();
702 0 : if (!item) {
703 0 : return;
704 : }
705 :
706 0 : const int originPitch = item->data(Column::PITCH, Qt::UserRole).toInt();
707 0 : if (!PercussionUtilities(iocContext()).editPercussionShortcut(m_editedDrumset, originPitch)) {
708 0 : return;
709 : }
710 :
711 0 : const QString editedShortcutText = m_editedDrumset.shortcut(originPitch);
712 0 : shortcut->setText(!editedShortcutText.isEmpty() ? editedShortcutText : muse::qtrc("shortcuts", "None"));
713 0 : item->setText(Column::SHORTCUT, !editedShortcutText.isEmpty() ? editedShortcutText : QString());
714 : // TODO: Update the item of the conflict shortcut too (#26226)
715 :
716 0 : valueChanged();
717 0 : }
|