Skip to content
Merged
Show file tree
Hide file tree
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
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ Written by D.P.C.M.

Version 0.5.3.9

Last updated: 2026-05-30
Last updated: 2026-05-31

---

## Unreleased - 2026-05-30
## Unreleased - 2026-05-31

### Breaking changes

Expand All @@ -22,6 +22,7 @@ Last updated: 2026-05-30

### Improvements

- Allow copying entire channel row at cursor with no selection (@henrikvilhelmberglund #385)
- Add color and font appearance setting for channel header (@Nemo55aa #413 #409)

### Bug fixes
Expand Down
19 changes: 16 additions & 3 deletions Source/MainFrm.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
** Dn-FamiTracker - NES/Famicom sound tracker
** Copyright (C) 2020-2025 D.P.C.M.
** Copyright (C) 2020-2026 D.P.C.M.
** FamiTracker Copyright (C) 2005-2020 Jonathan Liss
** 0CC-FamiTracker Copyright (C) 2014-2018 HertzDevil
**
Expand Down Expand Up @@ -2496,8 +2496,21 @@ void CMainFrame::OnUpdateEditCut(CCmdUI *pCmdUI)

void CMainFrame::OnUpdateEditCopy(CCmdUI *pCmdUI)
{
CFamiTrackerView *pView = static_cast<CFamiTrackerView*>(GetActiveView());
pCmdUI->Enable((pView->IsSelecting() || GetFocus() == m_pFrameEditor) ? 1 : 0);
CFamiTrackerView *pView = static_cast<CFamiTrackerView *>(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<CFamiTrackerDoc *>(GetActiveDocument())->GetAvailableChannels();
cursorValid = cursor.IsValid(frameLen, channelCount);
}

pCmdUI->Enable(hasSelection || cursorValid);
}

void CMainFrame::OnUpdatePatternEditorSelected(CCmdUI *pCmdUI) // // //
Expand Down
2 changes: 1 addition & 1 deletion Source/PatternAction.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
** Dn-FamiTracker - NES/Famicom sound tracker
** Copyright (C) 2020-2025 D.P.C.M.
** Copyright (C) 2020-2026 D.P.C.M.
** FamiTracker Copyright (C) 2005-2020 Jonathan Liss
** 0CC-FamiTracker Copyright (C) 2014-2018 HertzDevil
**
Expand Down
65 changes: 42 additions & 23 deletions Source/PatternEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3550,32 +3550,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,
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
// 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;
}
++it;
return pClipData;
}

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 = COLUMN_NOTE;
pSingleNote->ClipInfo.EndColumn = COLUMN_EFF4;

m_pDocument->GetNoteData(Track, Frame, Channel, Row, pSingleNote->GetPattern(0, 0));
return pSingleNote;
}

CPatternClipData *CPatternEditor::CopyRaw() const // // //
Expand Down