Skip to content

Tuning plugin: UI refinements #33512

Open
Ash-86 wants to merge 1 commit into
musescore:mainfrom
Ash-86:Tuning-plugin-–-UI-refinements

Hidden character warning

The head ref may contain hidden characters: "Tuning-plugin-\u2013-UI-refinements"
Open

Tuning plugin: UI refinements #33512
Ash-86 wants to merge 1 commit into
musescore:mainfrom
Ash-86:Tuning-plugin-–-UI-refinements

Conversation

@Ash-86
Copy link
Copy Markdown
Contributor

@Ash-86 Ash-86 commented May 20, 2026

Resolves: #31366

Some changes to dynamically add/remove/rename custom temperaments.

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR removes predefined custom tunings and makes custom entries runtime-managed. It adds root functions addCustomTuning(), customCount(), and deleteCustomTuning(), updates reset/compare logic to treat ids starting with "c" as custom, recalculates pure-tone offsets from the active tuningModel, forces the Flickable model to refresh and recenters the view on apply, adds inline name editing for custom items, introduces Add/Delete buttons in the bottom controls, and changes restoreSavedValues() to deep-copy the live model, append saved custom entries, merge other saved entries by id (preserving existing names), and reserialize options.data.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete; it lacks the required template sections (CLA signature, commit message validation, testing information) and provides minimal detail about the changes beyond referencing the issue. Complete the PR description using the repository template by filling in all required checkboxes, confirming code quality checks, testing verification, and providing more detailed explanation of the implemented changes.
Title check ❓ Inconclusive The title 'Tuning plugin: UI refinements' is vague and overly broad, using generic terms like 'refinements' that do not convey the specific changes (dynamic custom tuning management). Revise the title to be more specific, such as 'Tuning plugin: Add dynamic custom tuning management' or similar, to clearly indicate the primary functional change.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements dynamic add/remove/rename functionality for custom tunings, directly addressing the UX improvements objective in issue #31366 regarding custom tuning management.
Out of Scope Changes check ✅ Passed All changes are focused on custom tuning management UI and persistence logic, directly related to issue #31366; copyright year update is a standard maintenance change and does not represent scope creep.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
share/plugins/tuning/tuning.qml (1)

434-450: 💤 Low value

Consider hiding radio button text during inline editing.

When editing, both the RoundedRadioButton (with its text label) and the TextInputField are visible simultaneously. Depending on component styling, this may cause visual overlap. Consider conditionally hiding the radio button's text or the entire button when tuningItem.editing is true.

🤖 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 434 - 450, The radio button's
label and the inline TextInputField overlap during editing; update the
MU.RoundedRadioButton (id: radioButton) to hide its text (or the whole button)
when tuningItem.editing is true—for example, replace text: modelData.name with a
conditional expression like text: tuningItem.editing ? "" : modelData.name or
change visible to visible: !tuningItem.isSeparatorLine && !tuningItem.editing so
the radio label/button is not shown while editing.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@share/plugins/tuning/tuning.qml`:
- Line 680: The button label currently uses a hardcoded literal "Add custom
tuning"; update the Button's text property (the element with text: "Add custom
tuning") to use the qsTr() translation wrapper so it follows the file's i18n
pattern—replace the literal with a qsTr-wrapped string and ensure any nearby
similar labels remain consistent.
- Around line 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.
- Around line 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.

---

Nitpick comments:
In `@share/plugins/tuning/tuning.qml`:
- Around line 434-450: The radio button's label and the inline TextInputField
overlap during editing; update the MU.RoundedRadioButton (id: radioButton) to
hide its text (or the whole button) when tuningItem.editing is true—for example,
replace text: modelData.name with a conditional expression like text:
tuningItem.editing ? "" : modelData.name or change visible to visible:
!tuningItem.isSeparatorLine && !tuningItem.editing so the radio label/button is
not shown while editing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e168649f-ea1c-46f4-b1a2-f142e1435bce

📥 Commits

Reviewing files that changed from the base of the PR and between d48fdd2 and 4026fa1.

📒 Files selected for processing (1)
  • share/plugins/tuning/tuning.qml

Comment on lines +356 to +381
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
}
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.

Comment on lines +444 to +449
onDoubleClicked: {
if (modelData.id.startsWith("c")) {
tuningItem.editing = true
textField.text = modelData.name
}
}
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.

Comment thread share/plugins/tuning/tuning.qml Outdated
MU.FlatButton {
id: addCustomButton
Layout.preferredWidth: temperamentBox.width - deleteButton.width - 5
text: "Add custom tuning"
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 | 🟡 Minor | ⚡ Quick win

Button text should be wrapped in qsTr() for translation.

Other strings in this file use qsTr() for internationalization. This button label should follow the same pattern.

Proposed fix
-                text: "Add custom tuning"                
+                text: qsTr("Add custom tuning")                
📝 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
text: "Add custom tuning"
text: qsTr("Add custom tuning")
🤖 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` at line 680, The button label currently uses
a hardcoded literal "Add custom tuning"; update the Button's text property (the
element with text: "Add custom tuning") to use the qsTr() translation wrapper so
it follows the file's i18n pattern—replace the literal with a qsTr-wrapped
string and ensure any nearby similar labels remain consistent.

Comment on lines 171 to +173
// 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
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.

Comment thread share/plugins/tuning/tuning.qml Outdated

property bool chromatic: true
property int currentTemperament: 1
property int currentTemperament: 1
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.

Also unwanted

Comment thread share/plugins/tuning/tuning.qml Outdated
if (tuningModel[temperamentIndex].id == "separatorLine" || temperamentIndex == currentTemperament) {
return
}
}
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.

Also unwanted

temperamentClicked(tuningModel.length - 1)
}

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!

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

Unwanted

Comment thread share/plugins/tuning/tuning.qml Outdated
Comment on lines +712 to +814
var data = JSON.parse(options.data)
for (var i in data) {
if (data[i].id == "separatorLine") {
continue
}
for (var j in newValues) {
if (newValues[j].id == data[i].id) {
var name = newValues[j].name
newValues[j] = data[i]
newValues[j]["name"] = name
}
}
}
newValues = JSON.parse(options.data)
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.

Can you explain why this change works?

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.

Iterating over and comparing each item is not wanted here, specially since custom temperaments are not built in the default array.

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.

The reason for this is to automatically update the old saved model if changes are made (by future plugin authors) to the non-custom tunings. How can this still be guaranteed?

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.

I see. I'll push another fix for that shortly.

@Ash-86 Ash-86 force-pushed the Tuning-plugin-–-UI-refinements branch from 4026fa1 to 6c7e2e5 Compare May 21, 2026 14:33
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
share/plugins/tuning/tuning.qml (2)

358-359: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Generate custom IDs from the max existing suffix, not the current count.

After a delete, customCount() + 1 can reuse an existing cN, which breaks restore and selection by id. Please keep the numeric suffix monotonic or derive it from the current max.

🤖 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 358 - 359, The id generation
currently uses "id": "c" + (customCount() + 1) which can collide after
deletions; change this to compute the next suffix from the maximum numeric
suffix among existing custom items (scan existing items' ids like /^c(\d+)$/,
parse digits, take max, then use max+1) so the generated id is monotonic; update
the creation logic in tuning.qml (the place that builds the "id" and "name") to
call a helper that returns nextCustomSuffix instead of using customCount(),
ensuring restore/selection by id remains stable.

439-444: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Rename mode is still wired to a non-existent doubleClicked signal.

Unless MU.RoundedRadioButton explicitly re-emits doubleClicked, this handler never runs, so custom tunings cannot enter inline rename mode.

#!/bin/bash
set -e
fd -i 'RoundedRadioButton.qml' --exec sh -c 'printf "\n== %s ==\n" "$1"; sed -n "1,220p" "$1"' _ {}
rg -n --type qml 'signal\s+doubleClicked|onDoubleClicked|doubleClicked\s*:'

Expected result: RoundedRadioButton.qml does not declare or re-emit doubleClicked, and this file is the new call site relying on it.

🤖 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 439 - 444, The onDoubleClicked
handler is attached to MU.RoundedRadioButton which does not emit doubleClicked,
so the inline rename never triggers; replace the nonexistent signal usage by
adding a MouseArea over the radio button (or parent row) and move the logic into
its onDoubleClicked handler: create a MouseArea with anchors.fill: parent (or
appropriate bounds), implement onDoubleClicked { if
(modelData.id.startsWith("c")) { tuningItem.editing = true; textField.text =
modelData.name } }, or if you intended a single click use the radio button's
supported clicked/checkedChanged signal instead of onDoubleClicked.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@share/plugins/tuning/tuning.qml`:
- Around line 357-363: The new custom tuning currently initializes "offsets" to
twelve zeros, losing the user's current temperament; update the newCustomTuning
creation to copy the current temperament offsets instead of hardcoded zeros.
Locate the newCustomTuning object (variable name newCustomTuning) and replace
the "offsets": [0,...] entry with a clone of the current offsets array (e.g.,
use whatever current offsets variable/property is used elsewhere such as
currentOffsets or offsetsFromCurrentState and copy it via
slice()/concat()/Array.from to avoid referencing by pointer). Keep the other
fields (id, name, root, pure, globalOffset) unchanged.

---

Duplicate comments:
In `@share/plugins/tuning/tuning.qml`:
- Around line 358-359: The id generation currently uses "id": "c" +
(customCount() + 1) which can collide after deletions; change this to compute
the next suffix from the maximum numeric suffix among existing custom items
(scan existing items' ids like /^c(\d+)$/, parse digits, take max, then use
max+1) so the generated id is monotonic; update the creation logic in tuning.qml
(the place that builds the "id" and "name") to call a helper that returns
nextCustomSuffix instead of using customCount(), ensuring restore/selection by
id remains stable.
- Around line 439-444: The onDoubleClicked handler is attached to
MU.RoundedRadioButton which does not emit doubleClicked, so the inline rename
never triggers; replace the nonexistent signal usage by adding a MouseArea over
the radio button (or parent row) and move the logic into its onDoubleClicked
handler: create a MouseArea with anchors.fill: parent (or appropriate bounds),
implement onDoubleClicked { if (modelData.id.startsWith("c")) {
tuningItem.editing = true; textField.text = modelData.name } }, or if you
intended a single click use the radio button's supported clicked/checkedChanged
signal instead of onDoubleClicked.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 778e63d0-3e3d-4ac2-a14e-e415370c9caa

📥 Commits

Reviewing files that changed from the base of the PR and between 4026fa1 and 6c7e2e5.

📒 Files selected for processing (1)
  • share/plugins/tuning/tuning.qml

Comment on lines +357 to +363
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
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

Clone the current temperament offsets when adding a custom tuning.

This flow is meant to preserve the tuning the user is looking at, but the new entry starts with twelve zero offsets. That drops the current temperament state immediately.

Suggested fix
 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],
+        "offsets": tuningModel[currentTemperament].offsets.slice(),
         "root": currentRoot,
         "pure": currentPureTone,
         "globalOffset": currentGlobalOffset
     }
📝 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
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
var newCustomTuning = {
"id": "c" + (customCount() + 1),
"name": "Custom " + (customCount() + 1),
"offsets": tuningModel[currentTemperament].offsets.slice(),
"root": currentRoot,
"pure": currentPureTone,
"globalOffset": currentGlobalOffset
}
🤖 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 357 - 363, The new custom
tuning currently initializes "offsets" to twelve zeros, losing the user's
current temperament; update the newCustomTuning creation to copy the current
temperament offsets instead of hardcoded zeros. Locate the newCustomTuning
object (variable name newCustomTuning) and replace the "offsets": [0,...] entry
with a clone of the current offsets array (e.g., use whatever current offsets
variable/property is used elsewhere such as currentOffsets or
offsetsFromCurrentState and copy it via slice()/concat()/Array.from to avoid
referencing by pointer). Keep the other fields (id, name, root, pure,
globalOffset) unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve UX of saving custom tunings in 'Tunings & temperaments' plugin

3 participants