From fc3353436288530b13ffdb445011a62048d90afd Mon Sep 17 00:00:00 2001 From: henrikvilhelmberglund Date: Thu, 16 Oct 2025 15:26:31 +0200 Subject: [PATCH 1/2] feat: allow for copying without a selection (copies single line) --- Source/MainFrm.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Source/MainFrm.cpp b/Source/MainFrm.cpp index f1df202b..682ccbcf 100644 --- a/Source/MainFrm.cpp +++ b/Source/MainFrm.cpp @@ -2496,8 +2496,21 @@ void CMainFrame::OnUpdateEditCut(CCmdUI *pCmdUI) void CMainFrame::OnUpdateEditCopy(CCmdUI *pCmdUI) { - CFamiTrackerView *pView = static_cast(GetActiveView()); - pCmdUI->Enable((pView->IsSelecting() || GetFocus() == m_pFrameEditor) ? 1 : 0); + CFamiTrackerView *pView = static_cast(GetActiveView()); + bool patternEditorFocused = (GetFocus() == pView); + bool hasSelection = pView->IsSelecting(); + + // Check if the cursor is on a valid cell in the pattern editor + bool cursorValid = false; + if (patternEditorFocused) { + auto *pEditor = pView->GetPatternEditor(); + const auto &cursor = pEditor->GetCursor(); + int frameLen = pEditor->GetCurrentPatternLength(cursor.m_iFrame); + int channelCount = static_cast(GetActiveDocument())->GetAvailableChannels(); + cursorValid = cursor.IsValid(frameLen, channelCount); + } + + pCmdUI->Enable(hasSelection || cursorValid); } void CMainFrame::OnUpdatePatternEditorSelected(CCmdUI *pCmdUI) // // // From 36ff7db6a3117910132b48704f82f69721a8cb40 Mon Sep 17 00:00:00 2001 From: henrikvilhelmberglund Date: Thu, 16 Oct 2025 15:28:02 +0200 Subject: [PATCH 2/2] feat: copy single row when not selecting anything --- Source/PatternEditor.cpp | 69 +++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/Source/PatternEditor.cpp b/Source/PatternEditor.cpp index 2b9eb474..5c43e994 100644 --- a/Source/PatternEditor.cpp +++ b/Source/PatternEditor.cpp @@ -3537,32 +3537,51 @@ CPatternClipData *CPatternEditor::CopyEntire() const CPatternClipData *CPatternEditor::Copy() const { - // Copy selection - CPatternIterator it = GetIterators().first; // // // - const int Channels = m_selection.GetChanEnd() - m_selection.GetChanStart() + 1; - const int Rows = GetSelectionSize(); // // // - stChanNote NoteData; - - CPatternClipData *pClipData = new CPatternClipData(Channels, Rows); - pClipData->ClipInfo.Channels = Channels; // // // - pClipData->ClipInfo.Rows = Rows; - pClipData->ClipInfo.StartColumn = GetSelectColumn(m_selection.GetColStart()); // // // - pClipData->ClipInfo.EndColumn = GetSelectColumn(m_selection.GetColEnd()); // // // - - for (int r = 0; r < Rows; r++) { // // // - for (int i = 0; i < Channels; ++i) { - stChanNote *Target = pClipData->GetPattern(i, r); - it.Get(i + m_selection.GetChanStart(), &NoteData); - /*CopyNoteSection(Target, &NoteData, PASTE_DEFAULT, + // Copy selection if selecting + if (m_bSelecting && GetSelectionSize() > 0) { + CPatternIterator it = GetIterators().first; // // // + const int Channels = m_selection.GetChanEnd() - m_selection.GetChanStart() + 1; + const int Rows = GetSelectionSize(); // // // + stChanNote NoteData; + + CPatternClipData *pClipData = new CPatternClipData(Channels, Rows); + pClipData->ClipInfo.Channels = Channels; // // // + pClipData->ClipInfo.Rows = Rows; + pClipData->ClipInfo.StartColumn = GetSelectColumn(m_selection.GetColStart()); // // // + pClipData->ClipInfo.EndColumn = GetSelectColumn(m_selection.GetColEnd()); // // // + + for (int r = 0; r < Rows; r++) { // // // + for (int i = 0; i < Channels; ++i) { + stChanNote *Target = pClipData->GetPattern(i, r); + it.Get(i + m_selection.GetChanStart(), &NoteData); + /*CopyNoteSection(Target, &NoteData, PASTE_DEFAULT, i == 0 ? ColStart : COLUMN_NOTE, i == Channels - 1 ? ColEnd : COLUMN_EFF4);*/ - memcpy(Target, &NoteData, sizeof(stChanNote)); - // the clip data should store the entire field; - // other methods should check ClipInfo.StartColumn and ClipInfo.EndColumn before operating - } - ++it; - } - - return pClipData; + memcpy(Target, &NoteData, sizeof(stChanNote)); + // the clip data should store the entire field; + // other methods should check ClipInfo.StartColumn and ClipInfo.EndColumn before operating + } + ++it; + } + return pClipData; + } + + // If nothing is selected, copy the row under the cursor + const int Track = GetSelectedTrack(); + const int Channel = m_cpCursorPos.m_iChannel; + const int Row = m_cpCursorPos.m_iRow; + const int Frame = m_cpCursorPos.m_iFrame; + const cursor_column_t Col = m_cpCursorPos.m_iColumn; + + CPatternClipData *pSingleNote = new CPatternClipData(1, 1); + pSingleNote->ClipInfo.Channels = 1; + pSingleNote->ClipInfo.Rows = 1; + + // Always copy note, instrument, volume, and all 4 effects + pSingleNote->ClipInfo.StartColumn = static_cast(C_NOTE); + pSingleNote->ClipInfo.EndColumn = static_cast(C_EFF4_PARAM2); + + m_pDocument->GetNoteData(Track, Frame, Channel, Row, pSingleNote->GetPattern(0, 0)); + return pSingleNote; } CPatternClipData *CPatternEditor::CopyRaw() const // // //