Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 171 additions & 16 deletions share/plugins/tuning/tuning.qml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Apply a choice of tempraments and tunings.
// Copyright (C) 2018-2019 Bill Hails
// Copyright (C) 2025 XiaoMigros
// Copyright (C) 2026 Ashraf El Droubi
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -44,7 +45,7 @@ MuseScore {
* Each row is ordered in the cycle of fifths, so C, G, D, A, E, B, F#, C#, G#/Ab, Eb, Bb, F.
* Values are adjusted for root and 'pure' note before being applied to the score.
*
* Arabic tunings and most of western ones were provided by Fernando Martins.
* Arabic tunings and most of western ones were provided by Fernando Martins.
*/
function readDefaults() {
return [
Expand Down Expand Up @@ -96,7 +97,7 @@ MuseScore {
{ "id": "w45", "name": "C Cm D♭ E♭ Em F Fm G Gm A♭ Am Bm", "offsets": [0, 2, 3.9, -15.6, -13.7, -11.7, -9.8, 11.7, 13.7, 15.6, 17.6, -2], "root": 0, "pure": 0, "globalOffset": 0 },
{ "id": "w46", "name": "C Cm D Dm E♭ Em F♯m G Gm A♭ B♭ Bm", "offsets": [0, 2, 3.9, 5.9, -13.7, -11.7, -9.8, -7.8, 13.7, 15.6, 17.6, 19.6], "root": 0, "pure": 0, "globalOffset": 0 },
{ "id": "w47", "name": "C D E♭m E Em F♯ F♯m G G♯m B♭m B Bm", "offsets": [0, 2, 3.9, 5.9, -13.7, -11.7, -9.8, -7.8, -27.4, -25.4, -23.5, -21.5], "root": 0, "pure": 0, "globalOffset": 0 },

{ "id": "separatorLine", "name": qsTr("Arabic modal systems") },
{ "id": "a01", "name": "Melodic 1♯ 2♭", "offsets": [0, 2, 3.9, 5.9, 7.8, 9.8, -9.8, -7.8, -7.8, -5.9, -3.9, -2], "root": 0, "pure": 0, "globalOffset": 0 },
{ "id": "a02", "name": "Harmonic 1♯ 2♭", "offsets": [0, 2, 3.9, -15.6, -13.7, -11.7, -9.8, 11.7, 13.7, 15.6, 17.6, -2], "root": 0, "pure": 0, "globalOffset": 0 },
Expand All @@ -122,13 +123,8 @@ MuseScore {
{ "id": "a22", "name": "Rakb", "offsets": [0, 2, 3.9, -15.6, -13.7, -33.2, 58.9, 11.7, 13.7, 43.3, -3.9, -2], "root": 0, "pure": 0, "globalOffset": 0 },
{ "id": "a23", "name": "Sikah Baladi", "offsets": [0, 2, 3.9, 5.9, -35.2, -33.2, -9.8, 39.4, 41.4, -25.4, -3.9, -2], "root": 0, "pure": 0, "globalOffset": 0 },
{ "id": "a24", "name": "Iraq (Cadence)", "offsets": [0, 2, 3.9, 5.9, -56.7, -33.2, -31.3, -7.8, 13.7, -5.9, -23.5, -2], "root": 0, "pure": 0, "globalOffset": 0 },

{ "id": "separatorLine", "name": qsTr("Custom tunings") },
{ "id": "c01", "name": qsTr("Custom 1"), "offsets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "root": 0, "pure": 0, "globalOffset": 0 },
{ "id": "c02", "name": qsTr("Custom 2"), "offsets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "root": 0, "pure": 0, "globalOffset": 0 },
{ "id": "c03", "name": qsTr("Custom 3"), "offsets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "root": 0, "pure": 0, "globalOffset": 0 },
{ "id": "c04", "name": qsTr("Custom 4"), "offsets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "root": 0, "pure": 0, "globalOffset": 0 },
{ "id": "c05", "name": qsTr("Custom 5"), "offsets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "root": 0, "pure": 0, "globalOffset": 0 }

{ "id": "separatorLine", "name": qsTr("Custom tunings") }
]
}

Expand Down Expand Up @@ -167,23 +163,25 @@ MuseScore {

onRefresh: {
saveIsAvailable = formatCurrentValues() != options.data // modifying tuningModel here is intended and necessary
resetIsAvailable = JSON.stringify(defaultTuning[currentTemperament]) != JSON.stringify(tuningModel[currentTemperament])
resetIsAvailable = !tuningModel[currentTemperament].id.startsWith("c") && JSON.stringify(defaultTuning[currentTemperament]) != JSON.stringify(tuningModel[currentTemperament])
refreshTextFields()
}

function calculateTuningFromIndex(index) {
// index here is modulo pitch
var rootOffset = tuningModel[currentTemperament].offsets[(fifthsOffsets[index] - currentRoot + 12) % 12]
var pureToneOffset = defaultTuning[currentTemperament].offsets[(currentPureTone - currentRoot + 12) % 12] - defaultTuning[currentTemperament].offsets[0] // 0 because definitions are aligned to root C / pure C
var pureToneOffset = tuningModel[currentTemperament].offsets[(currentPureTone - currentRoot + 12) % 12] - tuningModel[currentTemperament].offsets[0] // 0 because definitions are aligned to root C / pure C
Comment on lines 171 to +173
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have been over this - changing this causes unwanted behaviour

Copy link
Copy Markdown
Contributor Author

@Ash-86 Ash-86 May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont know where this has been discussed. This change is necessary so that custom tuning can also be rotated.

return roundValue(rootOffset - pureToneOffset + currentGlobalOffset)
}

onRun: {
tuningModel = restoreSavedValues()
tuningsFlickable.model = tuningModel // retrigger the ListView with the loaded model
currentRoot = tuningModel[currentTemperament].root
currentPureTone = tuningModel[currentTemperament].pure
currentGlobalOffset = tuningModel[currentTemperament].globalOffset
root.refresh()
tuningsFlickable.positionViewAtIndex(currentTemperament, tuningsFlickable.Center)
}

function applyTemperament() {
Expand Down Expand Up @@ -355,12 +353,107 @@ MuseScore {
return Number(value.toFixed(root.decimals))
}

function addCustomTuning() {
var newCustomTuning = {
"id": "c" + (customCount() + 1),
"name": "Custom " + (customCount() + 1),
"offsets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"root": 0,
"pure": 0,
"globalOffset": 0
}

var oldTemperamentIndex = currentTemperament

commandHistory.add(
function () {
tuningModel.pop()
tuningsFlickable.model = tuningModel
tuningsFlickable.positionViewAtIndex(oldTemperamentIndex, ListView.Center)
currentTemperament = oldTemperamentIndex
currentRoot = tuningModel[oldTemperamentIndex].root
currentPureTone = tuningModel[oldTemperamentIndex].pure
currentGlobalOffset = tuningModel[oldTemperamentIndex].globalOffset
},
function() {
tuningModel.push(newCustomTuning)
tuningsFlickable.model = tuningModel
tuningsFlickable.positionViewAtIndex(tuningModel.length - 1, ListView.Center)

var newIndex = tuningModel.length - 1
currentTemperament = newIndex
currentRoot = tuningModel[newIndex].root
currentPureTone = tuningModel[newIndex].pure
currentGlobalOffset = tuningModel[newIndex].globalOffset
},
"Add custom tuning"
)
}

function customCount() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would tuningModel.length - readDefaults().length work instead? Likely faster

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'll make that change!

var count = tuningModel.length - readDefaults().length
return count
}
Comment on lines +356 to +396
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Custom tuning ID generation can produce duplicates after deletions.

The current approach uses customCount() + 1 for the new ID. If a user creates c1, c2, deletes c1, then adds another tuning, customCount() returns 1 and the new ID becomes "c2" — a duplicate.

Consider tracking a monotonically increasing counter or finding the maximum existing ID:

Proposed fix using max ID approach
 function addCustomTuning() {
     var newCustomTuning = {
-        "id": "c" + (customCount() + 1),
-        "name": "Custom " + (customCount() + 1), 
+        "id": "c" + (nextCustomId()),
+        "name": "Custom " + (customCount() + 1),  // Display name can still use count
         "offsets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
         ...
     }
     ...
 }

+function nextCustomId() {
+    var maxId = 0
+    for (var i in tuningModel) {
+        if (tuningModel[i].id.startsWith("c")) {
+            var num = parseInt(tuningModel[i].id.substring(1), 10)
+            if (!isNaN(num) && num > maxId) {
+                maxId = num
+            }
+        }
+    }
+    return maxId + 1
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function addCustomTuning() {
var newCustomTuning = {
"id": "c" + (customCount() + 1),
"name": "Custom " + (customCount() + 1),
"offsets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"root": currentRoot,
"pure": currentPureTone,
"globalOffset": currentGlobalOffset
}
tuningModel.push(newCustomTuning)
tuningsFlickable.model = tuningModel // necessary to trigger the ListView update
tuningsFlickable.positionViewAtEnd()
// Select the new temperament
temperamentClicked(tuningModel.length - 1)
}
function customCount() {
var count = 0
for (var i in tuningModel) {
if (tuningModel[i].id.startsWith("c")) {
count++
}
}
return count
}
function addCustomTuning() {
var newCustomTuning = {
"id": "c" + (nextCustomId()),
"name": "Custom " + (customCount() + 1), // Display name can still use count
"offsets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"root": currentRoot,
"pure": currentPureTone,
"globalOffset": currentGlobalOffset
}
tuningModel.push(newCustomTuning)
tuningsFlickable.model = tuningModel // necessary to trigger the ListView update
tuningsFlickable.positionViewAtEnd()
// Select the new temperament
temperamentClicked(tuningModel.length - 1)
}
function nextCustomId() {
var maxId = 0
for (var i in tuningModel) {
if (tuningModel[i].id.startsWith("c")) {
var num = parseInt(tuningModel[i].id.substring(1), 10)
if (!isNaN(num) && num > maxId) {
maxId = num
}
}
}
return maxId + 1
}
function customCount() {
var count = 0
for (var i in tuningModel) {
if (tuningModel[i].id.startsWith("c")) {
count++
}
}
return count
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@share/plugins/tuning/tuning.qml` around lines 356 - 381, The ID generation in
addCustomTuning using customCount() + 1 can create duplicates after deletions;
change it to compute a new unique numeric suffix by scanning existing
tuningModel entries for ids starting with "c", parse their numeric parts (or
track a persistent monotonic counter), take the maximum found and add 1, then
use that value for the new "id" and "name"; update/add logic in customCount() or
replace it with a helper (referencing addCustomTuning, customCount, tuningModel
and the id property) so new IDs are always increasing and never reused.


function deleteCustomTuning(index) {
var oldCustomTuning = tuningModel[index]
var oldTemperamentIndex = currentTemperament
var targetIndex = index - 1

if (tuningModel[targetIndex].id == "separatorLine") {
targetIndex--
}

commandHistory.add(
function () {
currentTemperament = oldTemperamentIndex
currentRoot = oldCustomTuning.root
currentPureTone = oldCustomTuning.pure
currentGlobalOffset = oldCustomTuning.globalOffset
tuningModel.splice(index, 0, oldCustomTuning)
tuningsFlickable.model = tuningModel
tuningsFlickable.positionViewAtIndex(index, tuningsFlickable.Center)

},
function() {
currentTemperament = targetIndex
currentRoot = tuningModel[targetIndex].root
currentPureTone = tuningModel[targetIndex].pure
currentGlobalOffset = tuningModel[targetIndex].globalOffset
tuningModel.splice(index, 1)
tuningsFlickable.model = tuningModel // necessary to trigger the ListView update
tuningsFlickable.positionViewAtIndex(targetIndex, tuningsFlickable.Center)

},
"Delete custom tuning"
)
}

function renameCustomTuning(index, currentText){
var oldName = tuningModel[index].name

commandHistory.add(
function () {
tuningModel[index].name = oldName
tuningsFlickable.model = tuningModel
tuningsFlickable.positionViewAtIndex(index, tuningsFlickable.Center)
},
function() {
tuningModel[index].name = currentText
tuningsFlickable.model = tuningModel
tuningsFlickable.positionViewAtIndex(index, tuningsFlickable.Center)
},
"Rename custom tuning"
)
}

ColumnLayout {
anchors.centerIn: parent
spacing: defaultSpacing
Row {
spacing: defaultSpacing
MU.StyledGroupBox {
id: temperamentBox
title: qsTr("Tuning systems and temperaments")
width: tuningsFlickable.width + defaultSpacing + tuningsFlickable.visualScrollBarInset - tuningsFlickable.scrollBarThickness
height: configureRow.height
Expand All @@ -377,14 +470,15 @@ MuseScore {
delegate: Item {
id: tuningItem
readonly property bool isSeparatorLine: modelData.id == "separatorLine"
property bool editing: false
anchors.left: parent ? parent.left : undefined
anchors.right: parent ? parent.right : undefined
anchors.rightMargin: defaultSpacing + tuningsFlickable.visualScrollBarInset
height: isSeparatorLine ? tuningLabel.height : radioButton.implicitHeight

MU.StyledTextLabel {
id: tuningLabel
visible: tuningItem.isSeparatorLine
visible: tuningItem.isSeparatorLine && index != tuningModel.length - 1 // don't show separator line for custom tunings if there are none
text: modelData.name
font: ui.theme.bodyBoldFont
width: parent.width
Expand All @@ -403,6 +497,33 @@ MuseScore {
onToggled: {
temperamentClicked(index)
}
onDoubleClicked: {
if (modelData.id.startsWith("c")) {
tuningItem.editing = true
textField.text = modelData.name
}
}
Comment on lines +500 to +505
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for RoundedRadioButton definition to verify doubleClicked signal support
fd -i "RoundedRadioButton.qml" --exec cat {}

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

rg "RoundedRadioButton" --type qml -A 5 -B 5 | head -100

Repository: musescore/MuseScore

Length of output: 9271


🏁 Script executed:

# Look for the MU module definition and find where RoundedRadioButton might be
fd "\.qml$" -x rg -l "RoundedRadioButton" {} | head -20

Repository: musescore/MuseScore

Length of output: 1536


🏁 Script executed:

# Check the tuning.qml file to understand the context and imports
cat -n share/plugins/tuning/tuning.qml | head -50

Repository: musescore/MuseScore

Length of output: 2351


🏁 Script executed:

fd "RoundedRadioButton" --type f

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Search in UiComponents directory
fd -type f "\.qml$" | xargs rg -l "RoundedRadioButton" | grep -i uicomponents

Repository: musescore/MuseScore

Length of output: 234


🏁 Script executed:

# Look for the component definition
rg "component RoundedRadioButton" --type qml -A 10

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Check what the component extends from
rg "RoundedRadioButton\s*:" --type qml -A 5

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

find . -type d -name "UiComponents" 2>/dev/null

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Search for qml files that define RoundedRadioButton
rg "RoundedRadioButton" --type qml -B 3 | grep -E "^[./]+.*\.qml:" | head -20

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Search in common framework paths
find . -path "*/framework/*" -name "*.qml" | xargs rg -l "RoundedRadioButton" 2>/dev/null

Repository: musescore/MuseScore

Length of output: 2174


🏁 Script executed:

# Look for any qml file that contains the definition
rg "id:\s*RoundedRadioButton|component.*RoundedRadioButton" --type qml -A 20

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Look for qml in src/framework
find src -type d -name "framework" 2>/dev/null

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Check full path structure
find src -type d | grep -i "ui" | head -20

Repository: musescore/MuseScore

Length of output: 666


🏁 Script executed:

# Read the specific lines around 444-449 in tuning.qml
cat -n share/plugins/tuning/tuning.qml | sed -n '430,480p'

Repository: musescore/MuseScore

Length of output: 2989


🏁 Script executed:

# Look for RadioButton definitions in the codebase
rg "^.*RadioButton\s*{" --type qml -B 5 -A 10 | head -80

Repository: musescore/MuseScore

Length of output: 163


🏁 Script executed:

# Search for qmldir files that define modules
find . -name "qmldir" | head -20

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Look for RoundedRadioButton in framework directories
find src -type d -name "*Components*" 2>/dev/null

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Search for RadioButton or Button base class definitions
rg "RadioButton\s*:\s*" --type qml -A 3 | head -50

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Check if there's a Controls module that defines RadioButton
rg "import.*Controls" share/plugins/tuning/tuning.qml

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Search all qml files for RadioButton or Control definitions
find . -name "*.qml" -exec grep -l "RadioButton" {} \; | grep -v node_modules | head -30

Repository: musescore/MuseScore

Length of output: 2344


🏁 Script executed:

# Check the entire src/framework directory structure
find src/framework -type d 2>/dev/null | head -20

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Look for any qml that might define the component
find . -name "*.qml" -exec grep -l "onDoubleClicked" {} \; | head -10

Repository: musescore/MuseScore

Length of output: 850


🏁 Script executed:

# Search for component or Item definitions that might be RoundedRadioButton
rg "component.*Button|Button\s*{" --type qml -B 2 -A 5 | grep -A 5 "Rounded" | head -40

Repository: musescore/MuseScore

Length of output: 183


🏁 Script executed:

# Search for CMakeLists to understand module structure
find . -name "CMakeLists.txt" -exec grep -l "UiComponents\|Muse" {} \; | head -10

Repository: musescore/MuseScore

Length of output: 4138


🏁 Script executed:

# Look for the qml module registration
find . -name "*.qml" -path "*/Muse/*" -o -path "*/muse/*" | head -20

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Check src/framework for any UI or Components related files
find src/framework -name "*.qml" 2>/dev/null | head -20

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Search for where Muse.UiComponents might be defined
rg "UiComponents" --type qml -B 2 -A 2 | head -40

Repository: musescore/MuseScore

Length of output: 2097


🌐 Web query:

Qt Quick RadioButton doubleClicked signal

💡 Result:

The Qt Quick RadioButton type does not have a doubleClicked signal [1][2][3]. RadioButton inherits from AbstractButton [1][2], which provides basic signals such as clicked(), pressed(), and released(), but it does not include native support for double-click events [1][2]. If you need to detect a double-click on a RadioButton in QML, you can use a MouseArea to wrap the RadioButton (or place it on top) and handle the onDoubleClicked signal [4]. Note that using a MouseArea may intercept mouse events intended for the RadioButton itself; you may need to explicitly forward those events or adjust the propagateComposedEvents property if interaction issues arise. Alternatively, if you are working with a standard Button type in some versions of Qt Quick, it may provide a doubleClicked() signal [4], but this is not available for the RadioButton component [1][2][3].

Citations:


Remove onDoubleClicked handler from MU.RoundedRadioButton—the signal doesn't exist.

Qt Quick's RadioButton and AbstractButton do not expose a doubleClicked signal. The handler at lines 444–449 will never be triggered. To enable double-click editing, wrap the RoundedRadioButton in a MouseArea with onDoubleClicked, or use an alternative approach like onClicked with a timer to detect double-clicks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@share/plugins/tuning/tuning.qml` around lines 444 - 449, Remove the
non-existent onDoubleClicked handler from the MU.RoundedRadioButton (the block
referencing modelData.id, tuningItem.editing and textField.text) and implement
double-click handling by either wrapping the MU.RoundedRadioButton in a
MouseArea that uses onDoubleClicked to set tuningItem.editing and
textField.text, or replace it with an onClicked + timer-based double-click
detection that checks modelData.id.startsWith("c") before toggling editing and
copying modelData.name into textField.text.

}

MU.TextInputField {
id: textField
anchors.left: radioButton.left
anchors.leftMargin: 25
anchors.right: radioButton.right
anchors.verticalCenter: radioButton.verticalCenter
visible: tuningItem.editing
currentText: modelData.name
focus: true

onVisibleChanged: {
if (visible) {
ensureActiveFocus()
}
}
onTextEdited: function (newText) { currentText = newText }
onAccepted: renameCustomTuning(index, currentText)
onTextEditingFinished: tuningItem.editing = false
Keys.onEscapePressed: tuningItem.editing = false
}
}
}
Expand Down Expand Up @@ -562,7 +683,7 @@ MuseScore {
spacing: 8
MU.FlatButton {
id: saveButton
text: qsTranslate("PrefsDialogBase", "Save")
text: qsTranslate("PrefsDialogBase", "Save")
Layout.preferredWidth: 100
enabled: root.saveIsAvailable
onClicked: {
Expand Down Expand Up @@ -600,17 +721,47 @@ MuseScore {
}
}
}

RowLayout {
spacing: 8
spacing: 0

MU.FlatButton {
id: addCustomButton
Layout.preferredWidth: temperamentBox.width - deleteButton.width - 5
text: "Add custom tuning"
onClicked: {
addCustomTuning()
}
}
Item {
width: 5
}
MU.FlatButton {
id: deleteButton
enabled: tuningModel[currentTemperament].id.startsWith("c")
icon: IconCode.DELETE_TANK
onClicked: {
deleteCustomTuning(currentTemperament)
}
}
Item {
width: defaultSpacing
}
MU.CheckBox {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
id: annotateBox
text: qsTr("Annotate tunings in score")
checked: false
onClicked: {
checked = !checked
}
}
}

RowLayout {
spacing: 8
Layout.alignment: Qt.AlignRight

MU.FlatButton {
text: curScore ? qsTranslate("PrefsDialogBase", "Cancel") : qsTranslate("PrefsDialogBase", "Quit")
onClicked: {
Expand Down Expand Up @@ -707,13 +858,17 @@ MuseScore {
}

function restoreSavedValues() {
var newValues = tuningModel
var newValues = JSON.parse(JSON.stringify(tuningModel))
try {
var data = JSON.parse(options.data)
for (var i in data) {
if (data[i].id == "separatorLine") {
continue
}
if (data[i].id.startsWith("c")) {
newValues.push(data[i])
continue
}
for (var j in newValues) {
if (newValues[j].id == data[i].id) {
var name = newValues[j].name
Expand Down
Loading