Skip to content

Add a context-aware right-click menu and Cut/Copy/Paste - #29

Merged
denmase merged 3 commits into
mainfrom
claude/noteeditor-translate-english-fworkt
Sep 18, 2026
Merged

denmase merged 3 commits into
mainfrom
claude/noteeditor-translate-english-fworkt

Conversation

@denmase

@denmase denmase commented Sep 18, 2026

Copy link
Copy Markdown
Owner

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

What changed

  • 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).
  • README.md: documented both features.

Test plan

Verified via the same sandbox pipeline as every prior change this session:

  • dotnet build succeeds against the real project/package graph.
  • dotnet format --verify-no-changes passes.
  • The full regression suite passes under Mono + Xvfb against the rebuilt assembly — including
    three new integration tests added to 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).

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

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
@denmase
denmase merged commit f9ea271 into main Sep 18, 2026
1 check passed
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.

2 participants