Add a context-aware right-click menu and Cut/Copy/Paste - #29
Merged
Merged
Conversation
The score canvas's right-click menu previously only had "Move playback marker here". It's now context-aware: it hit-tests whatever's under the cursor (reusing the same ScoreHitType-based hit test that already drives left-click selection) and shows a different set of commands depending on whether you right-clicked a note, an empty gap, a tie, a chord marker, lyric text, or empty canvas -- syncing the selection to the right-click target first (without triggering a click handler's side effects, like deleting a chord marker or opening an inline editor) so the menu always acts on what you clicked, and so an existing multi-note selection isn't collapsed when you right-click inside it. Nearly every item on the menu reuses a command that already existed (wired to the ribbon/menu elsewhere) -- Shorten/Extend, octave/transpose, Split/Merge, Tie, Ornaments, Delete, Add/Duplicate Measure, Add Chord Marker. Cut/Copy/Paste are new: there was no clipboard concept anywhere in the app before this. - New `INoteClipboardService`/`NoteClipboardService`: a small in-app clipboard for melody notes (not the OS clipboard), registered as a singleton like `IMidiOutput` so cut/copy in one tab can be pasted into another. - `NoteEditorViewModel` gains `CopySelectedNotes()` (returns false if nothing was selected, so Cut knows not to fall through to Delete's own "nothing selected" behavior) and `PasteNotes()`, which inserts after the last selected note, at the selected gap, or at the end of the current measure -- wrapped in the same undo-snapshot command pattern already used for Split/Merge. - `ScoreCanvas` exposes a new `ContextMenuOpening` event, firing after the canvas has already synced the selection to the hit-test target; it still owns "Move playback marker here" itself, but the edit-command items come entirely from the event handler. - `MainForm` builds the context menu's items per hit type, and adds Cut/Copy/Paste to the Edit menu and Ctrl+X/C/V (guarded the same way Delete already is, so a focused text box gets native cut/copy/paste instead). Verified via the same sandbox pipeline as every prior change this session: dotnet build and dotnet format --verify-no-changes both pass, and the full regression suite passes under Mono + Xvfb against the rebuilt assembly -- including three new integration tests added to the existing MainFormHarness.cs covering copy+paste (clone inserted at the right position), cut (removes the note and populates the clipboard), and cut with nothing selected (must not fall through to deleting the last note in the measure). The context menu's actual on-screen appearance hasn't been visually confirmed on a real Windows machine, same caveat as every prior UI change this session. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pguj4XSScE141p1ScWoqEr
… arg CI caught this (PR #29, build check): the real xUnit test project constructs NoteEditorViewModel directly in three places, bypassing DI, so adding the required INoteClipboardService constructor parameter broke its build. This sandbox's Mono/Xvfb harness pipeline doesn't build or run this project (it targets net8.0-windows and needs the real WindowsDesktop workload), so it didn't catch this before the push. Reproduced the exact failure locally by temporarily adding EnableWindowsTargeting to JianpuEditor.Tests.csproj (reverted, not committed) to cross-compile it in this sandbox, confirmed it matches CI's error, and confirmed this fix builds clean with zero errors (same pre-existing CA1707 warnings CI already had). dotnet format --verify-no-changes also passes for both projects. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pguj4XSScE141p1ScWoqEr
…ntainers The previous fix only covered ViewModelTestHelper.cs's direct NoteEditorViewModel constructions. CI (PR #29) still failed: 14 tests in MainFormMultiTabTests.cs failed at runtime with the same "Unable to resolve service for type INoteClipboardService" error, because it builds its own ServiceCollection (substituting fakes for hardware-dependent singletons) rather than going through ViewModelTestHelper or AppBootstrapper, and hadn't registered the new service either. DocumentTabTests.cs does the same thing for a narrower DocumentTab-only container -- grepped for every "new ServiceCollection()" in the repo to make sure this was the complete list (three: these two test files, plus AppBootstrapper.cs which was already fixed and never broken). Reproduced this one for real: temporarily added EnableWindowsTargeting to JianpuEditor.Tests.csproj (reverted, not committed) to cross-compile it here, then ran `dotnet test` directly -- it does build and start, but can't actually execute (this sandbox has the compile-time Microsoft.WindowsDesktop.App reference assemblies cached, not the real runtime, so testhost can't launch), confirming this is a compile-and- launch-only environment for this project, same limit as every net8.0- windows check this session. dotnet build succeeds with zero errors (matching CI's pre-existing warnings exactly), and dotnet format --verify-no-changes passes for both projects. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pguj4XSScE141p1ScWoqEr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The score canvas's right-click menu previously only had "Move playback marker here". It's now
context-aware: it hit-tests whatever's under the cursor (reusing the same
ScoreHitType-basedhit test that already drives left-click selection) and shows a different set of commands
depending on whether you right-clicked a note, an empty gap, a tie, a chord marker, lyric text, or
empty canvas — syncing the selection to the right-click target first (without triggering a click
handler's side effects, like deleting a chord marker or opening an inline editor) so the menu
always acts on what you clicked, and so an existing multi-note selection isn't collapsed when you
right-click inside it.
Nearly every item on the menu reuses a command that already existed (wired to the ribbon/menu
elsewhere) — Shorten/Extend, octave/transpose, Split/Merge, Tie, Ornaments, Delete, Add/Duplicate
Measure, Add Chord Marker. Cut/Copy/Paste are new — there was no clipboard concept anywhere in
the app before this.
What changed
INoteClipboardService/NoteClipboardService: a small in-app clipboard for melody notes(not the OS clipboard), registered as a singleton like
IMidiOutputso cut/copy in one tab canbe pasted into another.
NoteEditorViewModelgainsCopySelectedNotes()(returnsfalseif nothing was selected, soCut knows not to fall through to Delete's own "nothing selected" behavior) and
PasteNotes(),which inserts after the last selected note, at the selected gap, or at the end of the current
measure — wrapped in the same undo-snapshot command pattern already used for Split/Merge.
ScoreCanvasexposes a newContextMenuOpeningevent, firing after the canvas has alreadysynced the selection to the hit-test target; it still owns "Move playback marker here" itself,
but the edit-command items come entirely from the event handler.
MainFormbuilds the context menu's items per hit type, and adds Cut/Copy/Paste to the Editmenu and Ctrl+X/C/V (guarded the same way Delete already is, so a focused text box gets native
cut/copy/paste instead).
README.md: documented both features.Test plan
Verified via the same sandbox pipeline as every prior change this session:
dotnet buildsucceeds against the real project/package graph.dotnet format --verify-no-changespasses.three new integration tests added to
MainFormHarness.cscovering copy+paste (clone inserted atthe right position), cut (removes the note and populates the clipboard), and cut with nothing
selected (must not fall through to deleting the last note in the measure).
Not verified in this sandbox: the context menu's actual on-screen appearance/layout hasn't been
visually confirmed on a real Windows machine, same caveat as every prior UI change this session.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Pguj4XSScE141p1ScWoqEr
Generated by Claude Code