LCOV - code coverage report
Current view: top level - src/palette/widgets - customizekitdialog.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 365 0
Test Date: 2026-11-05 04:07:02

            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 : }
        

Generated by: LCOV version 2.0-1