/* * PatternEditorDialogs.cpp * ------------------------ * Purpose: Code for various dialogs that are used in the pattern editor. * Notes : (currently none) * Authors: Olivier Lapicque * OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "stdafx.h" #include "Mptrack.h" #include "Mainfrm.h" #include "InputHandler.h" #include "Moddoc.h" #include "View_pat.h" #include "PatternEditorDialogs.h" #include "TempoSwingDialog.h" #include "../soundlib/mod_specifications.h" #include "../common/mptStringBuffer.h" OPENMPT_NAMESPACE_BEGIN static constexpr EffectCommand ExtendedCommands[] = {CMD_OFFSET, CMD_PATTERNBREAK, CMD_POSITIONJUMP, CMD_TEMPO, CMD_FINETUNE, CMD_FINETUNE_SMOOTH}; // For a given pattern cell, check if it contains a command supported by the X-Param mechanism. // If so, calculate the multipler for this cell and the value of all the other cells belonging to this param. void getXParam(ModCommand::COMMAND command, PATTERNINDEX nPat, ROWINDEX nRow, CHANNELINDEX nChannel, const CSoundFile &sndFile, UINT &xparam, UINT &multiplier) { UINT xp = 0, mult = 1; int cmdRow = static_cast(nRow); const auto &pattern = sndFile.Patterns[nPat]; if(command == CMD_XPARAM) { // current command is a parameter extension command cmdRow--; // Try to find previous command parameter to be extended while(cmdRow >= 0) { const ModCommand &m = *pattern.GetpModCommand(cmdRow, nChannel); if(mpt::contains(ExtendedCommands, m.command)) break; if(m.command != CMD_XPARAM) { cmdRow = -1; break; } cmdRow--; } } else if(!mpt::contains(ExtendedCommands, command)) { // If current row do not own any satisfying command parameter to extend, set return state cmdRow = -1; } if(cmdRow >= 0) { // An 'extendable' command parameter has been found const ModCommand &m = *pattern.GetpModCommand(cmdRow, nChannel); // Find extension resolution (8 to 24 bits) uint32 n = 1; while(n < 4 && cmdRow + n < pattern.GetNumRows()) { if(pattern.GetpModCommand(cmdRow + n, nChannel)->command != CMD_XPARAM) break; n++; } // Parameter extension found (above 8 bits non-standard parameters) if(n > 1) { // Limit offset command to 24 bits, other commands to 16 bits n = m.command == CMD_OFFSET ? n : (n > 2 ? 2 : n); // Compute extended value WITHOUT current row parameter value : this parameter // is being currently edited (this is why this function is being called) so we // only need to compute a multiplier so that we can add its contribution while // its value is changed by user for(uint32 j = 0; j < n; j++) { const ModCommand &mx = *pattern.GetpModCommand(cmdRow + j, nChannel); uint32 k = 8 * (n - j - 1); if(cmdRow + j == nRow) mult = 1 << k; else xp += (mx.param << k); } } else if(m.command == CMD_OFFSET || m.command == CMD_FINETUNE || m.command == CMD_FINETUNE_SMOOTH) { // No parameter extension to perform (8 bits standard parameter), // just care about offset command special case (16 bits, fake) mult <<= 8; } const auto modDoc = sndFile.GetpModDoc(); if(m.command == CMD_OFFSET && m.volcmd == VOLCMD_OFFSET && modDoc != nullptr) { SAMPLEINDEX smp = modDoc->GetSampleIndex(m); if(m.vol == 0 && smp != 0) { xp = Util::muldivr_unsigned(sndFile.GetSample(smp).nLength, pattern.GetpModCommand(nRow, nChannel)->param * mult + xp, 256u << (8u * (std::max(uint32(2), n) - 1u))); mult = 0; } else if(m.vol > 0 && smp != 0) { xp += sndFile.GetSample(smp).cues[m.vol - 1]; } } } // Return x-parameter multiplier = mult; xparam = xp; } ///////////////////////////////////////////////////////////////////////////////////////////// // CPatternPropertiesDlg BEGIN_MESSAGE_MAP(CPatternPropertiesDlg, CDialog) ON_COMMAND(IDC_BUTTON_HALF, &CPatternPropertiesDlg::OnHalfRowNumber) ON_COMMAND(IDC_BUTTON_DOUBLE, &CPatternPropertiesDlg::OnDoubleRowNumber) ON_COMMAND(IDC_CHECK1, &CPatternPropertiesDlg::OnOverrideSignature) ON_COMMAND(IDC_BUTTON1, &CPatternPropertiesDlg::OnTempoSwing) END_MESSAGE_MAP() BOOL CPatternPropertiesDlg::OnInitDialog() { CComboBox *combo; CDialog::OnInitDialog(); combo = (CComboBox *)GetDlgItem(IDC_COMBO1); const CSoundFile &sndFile = modDoc.GetSoundFile(); if(m_nPattern < sndFile.Patterns.Size() && combo) { CString s; const CPattern &pattern = sndFile.Patterns[m_nPattern]; ROWINDEX nrows = pattern.GetNumRows(); const CModSpecifications &specs = sndFile.GetModSpecifications(); combo->SetRedraw(FALSE); for(UINT irow = specs.patternRowsMin; irow <= specs.patternRowsMax; irow++) { combo->AddString(mpt::cfmt::dec(irow)); } combo->SetCurSel(nrows - specs.patternRowsMin); combo->SetRedraw(TRUE); CheckRadioButton(IDC_RADIO1, IDC_RADIO2, IDC_RADIO2); s = MPT_CFORMAT("Pattern #{}: {} row{} ({}K)")( m_nPattern, pattern.GetNumRows(), (pattern.GetNumRows() == 1) ? CString(_T("")) : CString(_T("s")), static_cast((pattern.GetNumRows() * sndFile.GetNumChannels() * sizeof(ModCommand)) / 1024)); SetDlgItemText(IDC_TEXT1, s); // Window title const CString patternName = mpt::ToCString(sndFile.GetCharsetInternal(), pattern.GetName()); s = MPT_CFORMAT("Pattern Properties for Pattern #{}")(m_nPattern); if(!patternName.IsEmpty()) { s += _T(" ("); s += patternName; s += _T(")"); } SetWindowText(s); // Pattern time signature const bool bOverride = pattern.GetOverrideSignature(); ROWINDEX nRPB = pattern.GetRowsPerBeat(), nRPM = pattern.GetRowsPerMeasure(); if(nRPB == 0 || !bOverride) nRPB = sndFile.m_nDefaultRowsPerBeat; if(nRPM == 0 || !bOverride) nRPM = sndFile.m_nDefaultRowsPerMeasure; m_tempoSwing = pattern.HasTempoSwing() ? pattern.GetTempoSwing() : sndFile.m_tempoSwing; GetDlgItem(IDC_CHECK1)->EnableWindow(sndFile.GetModSpecifications().hasPatternSignatures ? TRUE : FALSE); CheckDlgButton(IDC_CHECK1, bOverride ? BST_CHECKED : BST_UNCHECKED); SetDlgItemInt(IDC_ROWSPERBEAT, nRPB, FALSE); SetDlgItemInt(IDC_ROWSPERMEASURE, nRPM, FALSE); OnOverrideSignature(); } return TRUE; } void CPatternPropertiesDlg::OnHalfRowNumber() { const CSoundFile &sndFile = modDoc.GetSoundFile(); UINT nRows = GetDlgItemInt(IDC_COMBO1, NULL, FALSE); nRows /= 2; if(nRows < sndFile.GetModSpecifications().patternRowsMin) nRows = sndFile.GetModSpecifications().patternRowsMin; SetDlgItemInt(IDC_COMBO1, nRows, FALSE); } void CPatternPropertiesDlg::OnDoubleRowNumber() { const CSoundFile &sndFile = modDoc.GetSoundFile(); UINT nRows = GetDlgItemInt(IDC_COMBO1, NULL, FALSE); nRows *= 2; if(nRows > sndFile.GetModSpecifications().patternRowsMax) nRows = sndFile.GetModSpecifications().patternRowsMax; SetDlgItemInt(IDC_COMBO1, nRows, FALSE); } void CPatternPropertiesDlg::OnOverrideSignature() { GetDlgItem(IDC_ROWSPERBEAT)->EnableWindow(IsDlgButtonChecked(IDC_CHECK1)); GetDlgItem(IDC_ROWSPERMEASURE)->EnableWindow(IsDlgButtonChecked(IDC_CHECK1)); GetDlgItem(IDC_BUTTON1)->EnableWindow(IsDlgButtonChecked(IDC_CHECK1) && modDoc.GetSoundFile().m_nTempoMode == TempoMode::Modern); } void CPatternPropertiesDlg::OnTempoSwing() { CPattern &pat = modDoc.GetSoundFile().Patterns[m_nPattern]; const ROWINDEX oldRPB = pat.GetRowsPerBeat(); const ROWINDEX oldRPM = pat.GetRowsPerMeasure(); // Temporarily apply new tempo signature for preview const ROWINDEX newRPB = std::clamp(static_cast(GetDlgItemInt(IDC_ROWSPERBEAT)), ROWINDEX(1), MAX_ROWS_PER_BEAT); const ROWINDEX newRPM = std::clamp(static_cast(GetDlgItemInt(IDC_ROWSPERMEASURE)), newRPB, MAX_ROWS_PER_BEAT); pat.SetSignature(newRPB, newRPM); m_tempoSwing.resize(newRPB, TempoSwing::Unity); CTempoSwingDlg dlg(this, m_tempoSwing, modDoc.GetSoundFile(), m_nPattern); if(dlg.DoModal() == IDOK) { m_tempoSwing = dlg.m_tempoSwing; } pat.SetSignature(oldRPB, oldRPM); } void CPatternPropertiesDlg::OnOK() { CSoundFile &sndFile = modDoc.GetSoundFile(); CPattern &pattern = sndFile.Patterns[m_nPattern]; // Update pattern signature if necessary if(sndFile.GetModSpecifications().hasPatternSignatures) { if(IsDlgButtonChecked(IDC_CHECK1)) { // Enable signature const ROWINDEX newRPB = std::min(static_cast(GetDlgItemInt(IDC_ROWSPERBEAT, NULL, FALSE)), MAX_ROWS_PER_BEAT); const ROWINDEX newRPM = std::min(static_cast(GetDlgItemInt(IDC_ROWSPERMEASURE, NULL, FALSE)), MAX_ROWS_PER_BEAT); if(newRPB != pattern.GetRowsPerBeat() || newRPM != pattern.GetRowsPerMeasure() || m_tempoSwing != pattern.GetTempoSwing()) { if(!pattern.SetSignature(newRPB, newRPM)) { Reporting::Error("Invalid time signature!", "Pattern Properties"); GetDlgItem(IDC_ROWSPERBEAT)->SetFocus(); return; } m_tempoSwing.resize(newRPB, TempoSwing::Unity); pattern.SetTempoSwing(m_tempoSwing); modDoc.SetModified(); } } else { // Disable signature if(pattern.GetOverrideSignature() || pattern.HasTempoSwing()) { pattern.RemoveSignature(); pattern.RemoveTempoSwing(); modDoc.SetModified(); } } } const ROWINDEX newSize = (ROWINDEX)GetDlgItemInt(IDC_COMBO1, NULL, FALSE); // Check if any pattern data would be removed. bool resize = (newSize != sndFile.Patterns[m_nPattern].GetNumRows()); bool resizeAtEnd = IsDlgButtonChecked(IDC_RADIO2) != BST_UNCHECKED; if(newSize < sndFile.Patterns[m_nPattern].GetNumRows()) { ROWINDEX firstRow = resizeAtEnd ? newSize : 0; ROWINDEX lastRow = resizeAtEnd ? sndFile.Patterns[m_nPattern].GetNumRows() : sndFile.Patterns[m_nPattern].GetNumRows() - newSize; for(ROWINDEX row = firstRow; row < lastRow; row++) { if(!sndFile.Patterns[m_nPattern].IsEmptyRow(row)) { resize = (Reporting::Confirm(MPT_AFORMAT("Data at the {} of the pattern will be lost.\nDo you want to continue?")(resizeAtEnd ? "end" : "start"), "Shrink Pattern") == cnfYes); break; } } } if(resize) { modDoc.BeginWaitCursor(); modDoc.GetPatternUndo().PrepareUndo(m_nPattern, 0, 0, sndFile.Patterns[m_nPattern].GetNumChannels(), sndFile.Patterns[m_nPattern].GetNumRows(), "Resize"); if(sndFile.Patterns[m_nPattern].Resize(newSize, true, resizeAtEnd)) { modDoc.SetModified(); } modDoc.EndWaitCursor(); } CDialog::OnOK(); } //////////////////////////////////////////////////////////////////////////////////////////// // CEditCommand BEGIN_MESSAGE_MAP(CEditCommand, CDialog) ON_WM_ACTIVATE() ON_WM_CLOSE() ON_CBN_SELCHANGE(IDC_COMBO1, &CEditCommand::OnNoteChanged) ON_CBN_SELCHANGE(IDC_COMBO2, &CEditCommand::OnNoteChanged) ON_CBN_SELCHANGE(IDC_COMBO3, &CEditCommand::OnVolCmdChanged) ON_CBN_SELCHANGE(IDC_COMBO4, &CEditCommand::OnCommandChanged) ON_CBN_SELCHANGE(IDC_COMBO5, &CEditCommand::OnPlugParamChanged) ON_WM_HSCROLL() END_MESSAGE_MAP() void CEditCommand::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CSplitKeyboadSettings) DDX_Control(pDX, IDC_COMBO1, cbnNote); DDX_Control(pDX, IDC_COMBO2, cbnInstr); DDX_Control(pDX, IDC_COMBO3, cbnVolCmd); DDX_Control(pDX, IDC_COMBO4, cbnCommand); DDX_Control(pDX, IDC_COMBO5, cbnPlugParam); DDX_Control(pDX, IDC_SLIDER1, sldVolParam); DDX_Control(pDX, IDC_SLIDER2, sldParam); //}}AFX_DATA_MAP } CEditCommand::CEditCommand(CSoundFile &sndFile) : sndFile(sndFile), effectInfo(sndFile) { CDialog::Create(IDD_PATTERN_EDITCOMMAND); } BOOL CEditCommand::PreTranslateMessage(MSG *pMsg) { if((pMsg) && (pMsg->message == WM_KEYDOWN)) { if((pMsg->wParam == VK_ESCAPE) || (pMsg->wParam == VK_RETURN) || (pMsg->wParam == VK_APPS)) { OnClose(); return TRUE; } } return CDialog::PreTranslateMessage(pMsg); } bool CEditCommand::ShowEditWindow(PATTERNINDEX pat, const PatternCursor &cursor, CWnd *parent) { editPattern = pat; const ROWINDEX row = editRow = cursor.GetRow(); const CHANNELINDEX chn = editChannel = cursor.GetChannel(); if(!sndFile.Patterns.IsValidPat(pat) || !sndFile.Patterns[pat].IsValidRow(row) || chn >= sndFile.GetNumChannels()) { ShowWindow(SW_HIDE); return false; } m = sndFile.Patterns[pat].GetpModCommand(row, chn); modified = false; InitAll(); switch(cursor.GetColumnType()) { case PatternCursor::noteColumn: cbnNote.SetFocus(); break; case PatternCursor::instrColumn: cbnInstr.SetFocus(); break; case PatternCursor::volumeColumn: if(m->IsPcNote()) cbnPlugParam.SetFocus(); else cbnVolCmd.SetFocus(); break; case PatternCursor::effectColumn: if(m->IsPcNote()) sldParam.SetFocus(); else cbnCommand.SetFocus(); break; case PatternCursor::paramColumn: sldParam.SetFocus(); break; } // Update Window Title SetWindowText(MPT_CFORMAT("Note Properties - Row {}, Channel {}")(row, chn + 1)); CRect rectParent, rectWnd; parent->GetWindowRect(&rectParent); GetClientRect(&rectWnd); SetWindowPos(CMainFrame::GetMainFrame(), rectParent.left + (rectParent.Width() - rectWnd.right) / 2, rectParent.top + (rectParent.Height() - rectWnd.bottom) / 2, -1, -1, SWP_NOSIZE | SWP_NOACTIVATE); ShowWindow(SW_RESTORE); return true; } void CEditCommand::InitNote() { // Note cbnNote.SetRedraw(FALSE); if(oldSpecs != &sndFile.GetModSpecifications()) { cbnNote.ResetContent(); cbnNote.SetItemData(cbnNote.AddString(_T("No Note")), 0); AppendNotesToControlEx(cbnNote, sndFile, m->instr); oldSpecs = &sndFile.GetModSpecifications(); } if(m->IsNote()) { // Normal note / no note const ModCommand::NOTE noteStart = sndFile.GetModSpecifications().noteMin; cbnNote.SetCurSel(m->note - (noteStart - 1)); } else if(m->note == NOTE_NONE) { cbnNote.SetCurSel(0); } else { // Special notes for(int i = cbnNote.GetCount() - 1; i >= 0; --i) { if(cbnNote.GetItemData(i) == m->note) { cbnNote.SetCurSel(i); break; } } } cbnNote.SetRedraw(TRUE); // Instrument cbnInstr.SetRedraw(FALSE); cbnInstr.ResetContent(); if(m->IsPcNote()) { // control plugin param note cbnInstr.SetItemData(cbnInstr.AddString(_T("No Effect")), 0); AddPluginNamesToCombobox(cbnInstr, sndFile.m_MixPlugins, false); } else { // instrument / sample cbnInstr.SetItemData(cbnInstr.AddString(_T("No Instrument")), 0); const uint32 nmax = sndFile.GetNumInstruments() ? sndFile.GetNumInstruments() : sndFile.GetNumSamples(); for(uint32 i = 1; i <= nmax; i++) { CString s = mpt::cfmt::val(i) + _T(": "); // instrument / sample if(sndFile.GetNumInstruments()) { if(sndFile.Instruments[i]) s += mpt::ToCString(sndFile.GetCharsetInternal(), sndFile.Instruments[i]->name); } else s += mpt::ToCString(sndFile.GetCharsetInternal(), sndFile.m_szNames[i]); cbnInstr.SetItemData(cbnInstr.AddString(s), i); } } cbnInstr.SetCurSel(m->instr); cbnInstr.SetRedraw(TRUE); } void CEditCommand::InitVolume() { cbnVolCmd.SetRedraw(FALSE); cbnVolCmd.ResetContent(); if(sndFile.GetType() == MOD_TYPE_MOD || m->IsPcNote()) { cbnVolCmd.EnableWindow(FALSE); sldVolParam.EnableWindow(FALSE); } else { // Normal volume column effect cbnVolCmd.EnableWindow(TRUE); sldVolParam.EnableWindow(TRUE); uint32 count = effectInfo.GetNumVolCmds(); cbnVolCmd.SetItemData(cbnVolCmd.AddString(_T(" None")), (DWORD_PTR)-1); cbnVolCmd.SetCurSel(0); UINT fxndx = effectInfo.GetIndexFromVolCmd(m->volcmd); for(uint32 i = 0; i < count; i++) { CString s; if(effectInfo.GetVolCmdInfo(i, &s)) { int k = cbnVolCmd.AddString(s); cbnVolCmd.SetItemData(k, i); if(i == fxndx) cbnVolCmd.SetCurSel(k); } } UpdateVolCmdRange(); } cbnVolCmd.SetRedraw(TRUE); } void CEditCommand::InitEffect() { if(m->IsPcNote()) { cbnCommand.ShowWindow(SW_HIDE); return; } cbnCommand.ShowWindow(SW_SHOW); xParam = 0; xMultiplier = 1; getXParam(m->command, editPattern, editRow, editChannel, sndFile, xParam, xMultiplier); cbnCommand.SetRedraw(FALSE); cbnCommand.ResetContent(); uint32 numfx = effectInfo.GetNumEffects(); uint32 fxndx = effectInfo.GetIndexFromEffect(m->command, m->param); cbnCommand.SetItemData(cbnCommand.AddString(_T(" None")), (DWORD_PTR)-1); if(m->command == CMD_NONE) cbnCommand.SetCurSel(0); CString s; for(uint32 i = 0; i < numfx; i++) { if(effectInfo.GetEffectInfo(i, &s, true)) { int k = cbnCommand.AddString(s); cbnCommand.SetItemData(k, i); if(i == fxndx) cbnCommand.SetCurSel(k); } } UpdateEffectRange(false); cbnCommand.SetRedraw(TRUE); cbnCommand.Invalidate(); } void CEditCommand::InitPlugParam() { if(!m->IsPcNote()) { cbnPlugParam.ShowWindow(SW_HIDE); return; } cbnPlugParam.ShowWindow(SW_SHOW); cbnPlugParam.SetRedraw(FALSE); cbnPlugParam.ResetContent(); if(m->instr > 0 && m->instr <= MAX_MIXPLUGINS) { AddPluginParameternamesToCombobox(cbnPlugParam, sndFile.m_MixPlugins[m->instr - 1]); cbnPlugParam.SetCurSel(m->GetValueVolCol()); } UpdateEffectRange(false); cbnPlugParam.SetRedraw(TRUE); cbnPlugParam.Invalidate(); } void CEditCommand::UpdateVolCmdRange() { ModCommand::VOL rangeMin = 0, rangeMax = 0; LONG fxndx = effectInfo.GetIndexFromVolCmd(m->volcmd); bool ok = effectInfo.GetVolCmdInfo(fxndx, NULL, &rangeMin, &rangeMax); if(ok && rangeMax > rangeMin) { sldVolParam.EnableWindow(TRUE); sldVolParam.SetRange(rangeMin, rangeMax); Limit(m->vol, rangeMin, rangeMax); sldVolParam.SetPos(m->vol); } else { // Why does this not update the display at all? sldVolParam.SetRange(0, 0); sldVolParam.SetPos(0); sldVolParam.EnableWindow(FALSE); } UpdateVolCmdValue(); } void CEditCommand::UpdateEffectRange(bool set) { DWORD pos; bool enable = true; if(m->IsPcNote()) { // plugin param control note sldParam.SetRange(0, ModCommand::maxColumnValue); pos = m->GetValueEffectCol(); } else { // process as effect ModCommand::PARAM rangeMin = 0, rangeMax = 0; LONG fxndx = effectInfo.GetIndexFromEffect(m->command, m->param); enable = ((fxndx >= 0) && (effectInfo.GetEffectInfo(fxndx, NULL, false, &rangeMin, &rangeMax))); pos = effectInfo.MapValueToPos(fxndx, m->param); if(pos > rangeMax) pos = rangeMin | (pos & 0x0F); Limit(pos, rangeMin, rangeMax); sldParam.SetRange(rangeMin, rangeMax); } if(enable) { sldParam.EnableWindow(TRUE); sldParam.SetPageSize(1); sldParam.SetPos(pos); } else { // Why does this not update the display at all? sldParam.SetRange(0, 0); sldParam.SetPos(0); sldParam.EnableWindow(FALSE); } UpdateEffectValue(set); } void CEditCommand::OnNoteChanged() { const bool wasParamControl = m->IsPcNote(); ModCommand::NOTE newNote = m->note; ModCommand::INSTR newInstr = m->instr; int n = cbnNote.GetCurSel(); if(n >= 0) newNote = static_cast(cbnNote.GetItemData(n)); n = cbnInstr.GetCurSel(); if(n >= 0) newInstr = static_cast(cbnInstr.GetItemData(n)); if(m->note != newNote || m->instr != newInstr) { PrepareUndo("Note Entry"); CModDoc *modDoc = sndFile.GetpModDoc(); m->note = newNote; m->instr = newInstr; modDoc->UpdateAllViews(nullptr, RowHint(editRow), nullptr); if(wasParamControl != m->IsPcNote()) { InitAll(); } else if(!m->IsPcNote() && m->instr <= sndFile.GetNumInstruments() && newInstr <= sndFile.GetNumInstruments() && sndFile.Instruments[m->instr] != nullptr && sndFile.Instruments[newInstr] != nullptr && sndFile.Instruments[newInstr]->pTuning != sndFile.Instruments[m->instr]->pTuning) { //Checking whether note names should be recreated. InitNote(); } else if(m->IsPcNote()) { // Update parameter list InitPlugParam(); } } } void CEditCommand::OnVolCmdChanged() { ModCommand::VOLCMD newVolCmd = m->volcmd; ModCommand::VOL newVol = m->vol; int n = cbnVolCmd.GetCurSel(); if(n >= 0) { newVolCmd = effectInfo.GetVolCmdFromIndex(static_cast(cbnVolCmd.GetItemData(n))); } newVol = static_cast(sldVolParam.GetPos()); const bool volCmdChanged = m->volcmd != newVolCmd; if(volCmdChanged || m->vol != newVol) { PrepareUndo("Volume Entry"); CModDoc *modDoc = sndFile.GetpModDoc(); m->volcmd = newVolCmd; m->vol = newVol; modDoc->UpdateAllViews(nullptr, RowHint(editRow), nullptr); if(volCmdChanged) UpdateVolCmdRange(); else UpdateVolCmdValue(); } } void CEditCommand::OnCommandChanged() { ModCommand::COMMAND newCommand = m->command; ModCommand::PARAM newParam = m->param; int n = cbnCommand.GetCurSel(); if(n >= 0) { int ndx = static_cast(cbnCommand.GetItemData(n)); newCommand = static_cast((ndx >= 0) ? effectInfo.GetEffectFromIndex(ndx, newParam) : CMD_NONE); } if(m->command != newCommand || m->param != newParam) { PrepareUndo("Effect Entry"); m->command = newCommand; if(newCommand != CMD_NONE) { m->param = newParam; } xParam = 0; xMultiplier = 1; if(newCommand == CMD_XPARAM || mpt::contains(ExtendedCommands, newCommand)) { getXParam(newCommand, editPattern, editRow, editChannel, sndFile, xParam, xMultiplier); } UpdateEffectRange(true); sndFile.GetpModDoc()->UpdateAllViews(nullptr, RowHint(editRow), nullptr); } } void CEditCommand::OnPlugParamChanged() { uint16 newPlugParam = m->GetValueVolCol(); int n = cbnPlugParam.GetCurSel(); if(n >= 0) { newPlugParam = static_cast(cbnPlugParam.GetItemData(n)); } if(m->GetValueVolCol() != newPlugParam) { PrepareUndo("Effect Entry"); m->SetValueVolCol(newPlugParam); sndFile.GetpModDoc()->UpdateAllViews(nullptr, RowHint(editRow), nullptr); } } void CEditCommand::UpdateVolCmdValue() { CString s; if(m->IsPcNote()) { // plugin param control note uint16 plugParam = static_cast(sldVolParam.GetPos()); s.Format(_T("Value: %u"), plugParam); } else { // process as effect effectInfo.GetVolCmdParamInfo(*m, &s); } SetDlgItemText(IDC_TEXT2, s); } void CEditCommand::UpdateEffectValue(bool set) { CString s; uint16 newPlugParam = 0; ModCommand::PARAM newParam = 0; if(m->IsPcNote()) { // plugin param control note newPlugParam = static_cast(sldParam.GetPos()); s.Format(_T("Value: %u"), newPlugParam); } else { // process as effect LONG fxndx = effectInfo.GetIndexFromEffect(m->command, m->param); if(fxndx >= 0) { newParam = static_cast(effectInfo.MapPosToValue(fxndx, sldParam.GetPos())); effectInfo.GetEffectNameEx(s, *m, newParam * xMultiplier + xParam, editChannel); } } SetDlgItemText(IDC_TEXT1, s); if(set) { if((!m->IsPcNote() && m->param != newParam) || (m->IsPcNote() && m->GetValueVolCol() != newPlugParam)) { PrepareUndo("Effect Entry"); CModDoc *modDoc = sndFile.GetpModDoc(); if(m->IsPcNote()) { m->SetValueEffectCol(newPlugParam); } else { m->param = newParam; } modDoc->UpdateAllViews(nullptr, RowHint(editRow), nullptr); } } } void CEditCommand::PrepareUndo(const char *description) { CModDoc *modDoc = sndFile.GetpModDoc(); if(!modified) { // Let's create just one undo step. modDoc->GetPatternUndo().PrepareUndo(editPattern, editChannel, editRow, 1, 1, description); modified = true; } modDoc->SetModified(); } void CEditCommand::OnHScroll(UINT, UINT, CScrollBar *bar) { if(bar == static_cast(&sldVolParam)) { OnVolCmdChanged(); } else if(bar == static_cast(&sldParam)) { UpdateEffectValue(true); } } void CEditCommand::OnActivate(UINT nState, CWnd *pWndOther, BOOL bMinimized) { CDialog::OnActivate(nState, pWndOther, bMinimized); if(nState == WA_INACTIVE) ShowWindow(SW_HIDE); } //////////////////////////////////////////////////////////////////////////////////////////// // Chord Editor BEGIN_MESSAGE_MAP(CChordEditor, ResizableDialog) ON_MESSAGE(WM_MOD_KBDNOTIFY, &CChordEditor::OnKeyboardNotify) ON_CBN_SELCHANGE(IDC_COMBO1, &CChordEditor::OnChordChanged) ON_CBN_SELCHANGE(IDC_COMBO2, &CChordEditor::OnBaseNoteChanged) ON_CBN_SELCHANGE(IDC_COMBO3, &CChordEditor::OnNote1Changed) ON_CBN_SELCHANGE(IDC_COMBO4, &CChordEditor::OnNote2Changed) ON_CBN_SELCHANGE(IDC_COMBO5, &CChordEditor::OnNote3Changed) END_MESSAGE_MAP() void CChordEditor::DoDataExchange(CDataExchange *pDX) { ResizableDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CChordEditor) DDX_Control(pDX, IDC_KEYBOARD1, m_Keyboard); DDX_Control(pDX, IDC_COMBO1, m_CbnShortcut); DDX_Control(pDX, IDC_COMBO2, m_CbnBaseNote); DDX_Control(pDX, IDC_COMBO3, m_CbnNote[0]); DDX_Control(pDX, IDC_COMBO4, m_CbnNote[1]); DDX_Control(pDX, IDC_COMBO5, m_CbnNote[2]); static_assert(mpt::array_size::size == 3); //}}AFX_DATA_MAP } CChordEditor::CChordEditor(CWnd *parent) : ResizableDialog(IDD_CHORDEDIT, parent) { m_chords = TrackerSettings::GetChords(); } BOOL CChordEditor::OnInitDialog() { ResizableDialog::OnInitDialog(); m_Keyboard.Init(this, (CHORD_MAX - CHORD_MIN) / 12, true); m_CbnShortcut.SetRedraw(FALSE); m_CbnBaseNote.SetRedraw(FALSE); for(auto &combo : m_CbnNote) combo.SetRedraw(FALSE); // Shortcut key combo box AppendNotesToControl(m_CbnShortcut, NOTE_MIN, NOTE_MIN + static_cast(kcVPEndChords) - static_cast(kcVPStartChords)); m_CbnShortcut.SetCurSel(0); // Base Note combo box m_CbnBaseNote.SetItemData(m_CbnBaseNote.AddString(_T("Relative")), MPTChord::relativeMode); AppendNotesToControl(m_CbnBaseNote, NOTE_MIN, NOTE_MIN + 3 * 12 - 1); // Chord Note combo boxes CString s; for(int note = CHORD_MIN - 1; note < CHORD_MAX; note++) { int noteVal = note; if(note == CHORD_MIN - 1) { s = _T("--"); noteVal = MPTChord::noNote; } else { s = mpt::ToCString(CSoundFile::GetDefaultNoteName(mpt::wrapping_modulo(note, 12))); const int octave = mpt::wrapping_divide(note, 12); if(octave > 0) s.AppendFormat(_T(" (+%d)"), octave); else if(octave < 0) s.AppendFormat(_T(" (%d)"), octave); } for(auto &combo : m_CbnNote) combo.SetItemData(combo.AddString(s), noteVal); } m_CbnShortcut.SetRedraw(TRUE); m_CbnBaseNote.SetRedraw(TRUE); for(auto &combo : m_CbnNote) combo.SetRedraw(TRUE); // Update Dialog OnChordChanged(); return TRUE; } void CChordEditor::OnOK() { TrackerSettings::GetChords() = m_chords; ResizableDialog::OnOK(); } MPTChord &CChordEditor::GetChord() { int chord = m_CbnShortcut.GetCurSel(); if(chord >= 0) chord = static_cast(m_CbnShortcut.GetItemData(chord)) - NOTE_MIN; if(chord < 0 || chord >= static_cast(m_chords.size())) chord = 0; return m_chords[chord]; } LRESULT CChordEditor::OnKeyboardNotify(WPARAM cmd, LPARAM nKey) { const bool outside = static_cast(nKey) == -1; if(cmd == KBDNOTIFY_LBUTTONUP && outside) { // Stopped dragging ouside of keyboard area m_mouseDownKey = m_dragKey = MPTChord::noNote; return 0; } else if (cmd == KBDNOTIFY_MOUSEMOVE || outside) { return 0; } MPTChord &chord = GetChord(); const MPTChord::NoteType key = static_cast(nKey) + CHORD_MIN; bool update = false; if(cmd == KBDNOTIFY_LBUTTONDOWN && m_mouseDownKey == MPTChord::noNote) { // Initial mouse down m_mouseDownKey = key; m_dragKey = MPTChord::noNote; return 0; } if(cmd == KBDNOTIFY_LBUTTONDOWN && m_dragKey == MPTChord::noNote && key != m_mouseDownKey) { // Start dragging m_dragKey = m_mouseDownKey; } // Remove dragged note or toggle bool noteIsSet = false; for(auto ¬e : chord.notes) { if((m_dragKey != MPTChord::noNote && note == m_dragKey) || (m_dragKey == MPTChord::noNote && note == m_mouseDownKey)) { note = MPTChord::noNote; noteIsSet = update = true; break; } } // Move or toggle note if(cmd != KBDNOTIFY_LBUTTONUP || m_dragKey != MPTChord::noNote || !noteIsSet) { for(auto ¬e : chord.notes) { if(note == MPTChord::noNote) { note = key; update = true; break; } } } if(cmd == KBDNOTIFY_LBUTTONUP) m_mouseDownKey = m_dragKey = MPTChord::noNote; else m_dragKey = key; if(update) { std::sort(chord.notes.begin(), chord.notes.end(), [](MPTChord::NoteType left, MPTChord::NoteType right) { return (left == MPTChord::noNote) ? false : (left < right); }); OnChordChanged(); } return 0; } void CChordEditor::OnChordChanged() { const MPTChord &chord = GetChord(); if(chord.key != MPTChord::relativeMode) m_CbnBaseNote.SetCurSel(chord.key + 1); else m_CbnBaseNote.SetCurSel(0); for(int i = 0; i < MPTChord::notesPerChord - 1; i++) { int note = chord.notes[i]; if(note == MPTChord::noNote) note = 0; else note += 1 - CHORD_MIN; m_CbnNote[i].SetCurSel(note); } UpdateKeyboard(); } void CChordEditor::UpdateKeyboard() { MPTChord &chord = GetChord(); const int baseNote = (chord.key == MPTChord::relativeMode) ? 0 : (chord.key % 12); for(int i = CHORD_MIN; i < CHORD_MAX; i++) { uint8 b = CKeyboardControl::KEYFLAG_NORMAL; for(const auto note : chord.notes) { if(i == note) b |= CKeyboardControl::KEYFLAG_REDDOT; } if(i == baseNote) b |= CKeyboardControl::KEYFLAG_BRIGHTDOT; m_Keyboard.SetFlags(i - CHORD_MIN, b); } m_Keyboard.InvalidateRect(nullptr, FALSE); } void CChordEditor::OnBaseNoteChanged() { MPTChord &chord = GetChord(); int basenote = static_cast(m_CbnBaseNote.GetItemData(m_CbnBaseNote.GetCurSel())); if(basenote != MPTChord::relativeMode) basenote -= NOTE_MIN; chord.key = (uint8)basenote; UpdateKeyboard(); } void CChordEditor::OnNoteChanged(int noteIndex) { MPTChord &chord = GetChord(); int note = m_CbnNote[noteIndex].GetCurSel(); if(note < 0) return; chord.notes[noteIndex] = static_cast(m_CbnNote[noteIndex].GetItemData(note)); UpdateKeyboard(); } //////////////////////////////////////////////////////////////////////////////////////////// // Keyboard Split Settings (pattern editor) BEGIN_MESSAGE_MAP(CSplitKeyboardSettings, CDialog) ON_CBN_SELCHANGE(IDC_COMBO_OCTAVEMODIFIER, &CSplitKeyboardSettings::OnOctaveModifierChanged) END_MESSAGE_MAP() void CSplitKeyboardSettings::DoDataExchange(CDataExchange *pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CSplitKeyboadSettings) DDX_Control(pDX, IDC_COMBO_SPLITINSTRUMENT, m_CbnSplitInstrument); DDX_Control(pDX, IDC_COMBO_SPLITNOTE, m_CbnSplitNote); DDX_Control(pDX, IDC_COMBO_OCTAVEMODIFIER, m_CbnOctaveModifier); DDX_Control(pDX, IDC_COMBO_SPLITVOLUME, m_CbnSplitVolume); //}}AFX_DATA_MAP } BOOL CSplitKeyboardSettings::OnInitDialog() { if(sndFile.GetpModDoc() == nullptr) return TRUE; CDialog::OnInitDialog(); CString s; // Split Notes AppendNotesToControl(m_CbnSplitNote, sndFile.GetModSpecifications().noteMin, sndFile.GetModSpecifications().noteMax); m_CbnSplitNote.SetCurSel(m_Settings.splitNote - (sndFile.GetModSpecifications().noteMin - NOTE_MIN)); // Octave modifier m_CbnOctaveModifier.SetRedraw(FALSE); m_CbnSplitVolume.InitStorage(SplitKeyboardSettings::splitOctaveRange * 2 + 1, 9); for(int i = -SplitKeyboardSettings::splitOctaveRange; i < SplitKeyboardSettings::splitOctaveRange + 1; i++) { s.Format(i < 0 ? _T("Octave -%d") : i > 0 ? _T("Octave +%d") : _T("No Change"), std::abs(i)); int n = m_CbnOctaveModifier.AddString(s); m_CbnOctaveModifier.SetItemData(n, i); } m_CbnOctaveModifier.SetRedraw(TRUE); m_CbnOctaveModifier.SetCurSel(m_Settings.octaveModifier + SplitKeyboardSettings::splitOctaveRange); CheckDlgButton(IDC_PATTERN_OCTAVELINK, (m_Settings.octaveLink && m_Settings.octaveModifier != 0) ? BST_CHECKED : BST_UNCHECKED); // Volume m_CbnSplitVolume.SetRedraw(FALSE); m_CbnSplitVolume.InitStorage(65, 4); m_CbnSplitVolume.AddString(_T("No Change")); m_CbnSplitVolume.SetItemData(0, 0); for(int i = 1; i <= 64; i++) { s.Format(_T("%d"), i); int n = m_CbnSplitVolume.AddString(s); m_CbnSplitVolume.SetItemData(n, i); } m_CbnSplitVolume.SetRedraw(TRUE); m_CbnSplitVolume.SetCurSel(m_Settings.splitVolume); // Instruments m_CbnSplitInstrument.SetRedraw(FALSE); m_CbnSplitInstrument.InitStorage(1 + (sndFile.GetNumInstruments() ? sndFile.GetNumInstruments() : sndFile.GetNumSamples()), 16); m_CbnSplitInstrument.SetItemData(m_CbnSplitInstrument.AddString(_T("No Change")), 0); if(sndFile.GetNumInstruments()) { for(INSTRUMENTINDEX nIns = 1; nIns <= sndFile.GetNumInstruments(); nIns++) { if(sndFile.Instruments[nIns] == nullptr) continue; CString displayName = sndFile.GetpModDoc()->GetPatternViewInstrumentName(nIns); int n = m_CbnSplitInstrument.AddString(displayName); m_CbnSplitInstrument.SetItemData(n, nIns); } } else { for(SAMPLEINDEX nSmp = 1; nSmp <= sndFile.GetNumSamples(); nSmp++) { if(sndFile.GetSample(nSmp).HasSampleData()) { s.Format(_T("%02d: "), nSmp); s += mpt::ToCString(sndFile.GetCharsetInternal(), sndFile.m_szNames[nSmp]); int n = m_CbnSplitInstrument.AddString(s); m_CbnSplitInstrument.SetItemData(n, nSmp); } } } m_CbnSplitInstrument.SetRedraw(TRUE); m_CbnSplitInstrument.SetCurSel(m_Settings.splitInstrument); return TRUE; } void CSplitKeyboardSettings::OnOK() { CDialog::OnOK(); m_Settings.splitNote = static_cast(m_CbnSplitNote.GetItemData(m_CbnSplitNote.GetCurSel()) - 1); m_Settings.octaveModifier = m_CbnOctaveModifier.GetCurSel() - SplitKeyboardSettings::splitOctaveRange; m_Settings.octaveLink = (IsDlgButtonChecked(IDC_PATTERN_OCTAVELINK) != BST_UNCHECKED); m_Settings.splitVolume = static_cast(m_CbnSplitVolume.GetCurSel()); m_Settings.splitInstrument = static_cast(m_CbnSplitInstrument.GetItemData(m_CbnSplitInstrument.GetCurSel())); } void CSplitKeyboardSettings::OnCancel() { CDialog::OnCancel(); } void CSplitKeyboardSettings::OnOctaveModifierChanged() { CheckDlgButton(IDC_PATTERN_OCTAVELINK, (m_CbnOctaveModifier.GetCurSel() != 9) ? BST_CHECKED : BST_UNCHECKED); } ///////////////////////////////////////////////////////////////////////// // Show channel properties from pattern editor BEGIN_MESSAGE_MAP(QuickChannelProperties, CDialog) ON_WM_HSCROLL() // Sliders ON_WM_ACTIVATE() // Catch Window focus change ON_EN_UPDATE(IDC_EDIT1, &QuickChannelProperties::OnVolChanged) ON_EN_UPDATE(IDC_EDIT2, &QuickChannelProperties::OnPanChanged) ON_EN_UPDATE(IDC_EDIT3, &QuickChannelProperties::OnNameChanged) ON_COMMAND(IDC_CHECK1, &QuickChannelProperties::OnMuteChanged) ON_COMMAND(IDC_CHECK2, &QuickChannelProperties::OnSurroundChanged) ON_COMMAND(IDC_BUTTON1, &QuickChannelProperties::OnPrevChannel) ON_COMMAND(IDC_BUTTON2, &QuickChannelProperties::OnNextChannel) ON_COMMAND(IDC_BUTTON3, &QuickChannelProperties::OnChangeColor) ON_COMMAND(IDC_BUTTON4, &QuickChannelProperties::OnChangeColor) ON_COMMAND(IDC_BUTTON5, &QuickChannelProperties::OnPickPrevColor) ON_COMMAND(IDC_BUTTON6, &QuickChannelProperties::OnPickNextColor) ON_MESSAGE(WM_MOD_KEYCOMMAND, &QuickChannelProperties::OnCustomKeyMsg) ON_NOTIFY_EX_RANGE(TTN_NEEDTEXT, 0, 0xFFFF, &QuickChannelProperties::OnToolTipText) END_MESSAGE_MAP() void QuickChannelProperties::DoDataExchange(CDataExchange *pDX) { DDX_Control(pDX, IDC_SLIDER1, m_volSlider); DDX_Control(pDX, IDC_SLIDER2, m_panSlider); DDX_Control(pDX, IDC_SPIN1, m_volSpin); DDX_Control(pDX, IDC_SPIN2, m_panSpin); DDX_Control(pDX, IDC_EDIT3, m_nameEdit); } QuickChannelProperties::~QuickChannelProperties() { DestroyWindow(); } void QuickChannelProperties::OnActivate(UINT nState, CWnd *, BOOL) { if(nState == WA_INACTIVE && !m_settingColor) { // Hide window when changing focus to another window. m_visible = false; ShowWindow(SW_HIDE); } } // Show channel properties for a given channel at a given screen position. void QuickChannelProperties::Show(CModDoc *modDoc, CHANNELINDEX chn, CPoint position) { if(!m_hWnd) { Create(IDD_CHANNELSETTINGS, nullptr); EnableToolTips(); m_colorBtn.SubclassDlgItem(IDC_BUTTON4, this); m_colorBtnPrev.SubclassDlgItem(IDC_BUTTON5, this); m_colorBtnNext.SubclassDlgItem(IDC_BUTTON6, this); m_volSlider.SetRange(0, 64); m_volSlider.SetTicFreq(8); m_volSpin.SetRange(0, 64); m_panSlider.SetRange(0, 64); m_panSlider.SetTicFreq(8); m_panSpin.SetRange(0, 256); m_nameEdit.SetFocus(); } m_document = modDoc; m_channel = chn; SetParent(nullptr); // Center window around point where user clicked. CRect rect, screenRect; GetWindowRect(rect); ::GetWindowRect(::GetDesktopWindow(), &screenRect); rect.MoveToXY( Clamp(static_cast(position.x) - rect.Width() / 2, 0, static_cast(screenRect.right) - rect.Width()), Clamp(static_cast(position.y) - rect.Height() / 2, 0, static_cast(screenRect.bottom) - rect.Height())); MoveWindow(rect); SetWindowText(MPT_TFORMAT("Settings for Channel {}")(chn + 1).c_str()); UpdateDisplay(); const BOOL enablePan = (m_document->GetModType() & (MOD_TYPE_XM | MOD_TYPE_MOD)) ? FALSE : TRUE; const BOOL itOnly = (m_document->GetModType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) ? TRUE : FALSE; // Volume controls m_volSlider.EnableWindow(itOnly); m_volSpin.EnableWindow(itOnly); ::EnableWindow(::GetDlgItem(m_hWnd, IDC_EDIT1), itOnly); // Pan controls m_panSlider.EnableWindow(enablePan); m_panSpin.EnableWindow(enablePan); ::EnableWindow(::GetDlgItem(m_hWnd, IDC_EDIT2), enablePan); ::EnableWindow(::GetDlgItem(m_hWnd, IDC_CHECK2), itOnly); // Channel name m_nameEdit.EnableWindow((m_document->GetModType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_XM)) ? TRUE : FALSE); ShowWindow(SW_SHOW); m_visible = true; } void QuickChannelProperties::UpdateDisplay() { // Set up channel properties m_visible = false; const ModChannelSettings &settings = m_document->GetSoundFile().ChnSettings[m_channel]; SetDlgItemInt(IDC_EDIT1, settings.nVolume, FALSE); SetDlgItemInt(IDC_EDIT2, settings.nPan, FALSE); m_volSlider.SetPos(settings.nVolume); m_panSlider.SetPos(settings.nPan / 4u); CheckDlgButton(IDC_CHECK1, (settings.dwFlags[CHN_MUTE]) ? TRUE : FALSE); CheckDlgButton(IDC_CHECK2, (settings.dwFlags[CHN_SURROUND]) ? TRUE : FALSE); TCHAR description[16]; wsprintf(description, _T("Channel %d:"), m_channel + 1); SetDlgItemText(IDC_STATIC_CHANNEL_NAME, description); m_nameEdit.LimitText(MAX_CHANNELNAME - 1); m_nameEdit.SetWindowText(mpt::ToCString(m_document->GetSoundFile().GetCharsetInternal(), settings.szName)); const bool isFirst = (m_channel <= 0), isLast = (m_channel >= m_document->GetNumChannels() - 1); m_colorBtn.SetColor(settings.color); m_colorBtnPrev.EnableWindow(isFirst ? FALSE : TRUE); if(!isFirst) m_colorBtnPrev.SetColor(m_document->GetSoundFile().ChnSettings[m_channel - 1].color); m_colorBtnNext.EnableWindow(isLast ? FALSE : TRUE); if(!isLast) m_colorBtnNext.SetColor(m_document->GetSoundFile().ChnSettings[m_channel + 1].color); m_settingsChanged = false; m_visible = true; ::EnableWindow(::GetDlgItem(m_hWnd, IDC_BUTTON1), isFirst ? FALSE : TRUE); ::EnableWindow(::GetDlgItem(m_hWnd, IDC_BUTTON2), isLast ? FALSE : TRUE); } void QuickChannelProperties::PrepareUndo() { if(!m_settingsChanged) { // Backup old channel settings through pattern undo. m_settingsChanged = true; m_document->GetPatternUndo().PrepareChannelUndo(m_channel, 1, "Channel Settings"); } } void QuickChannelProperties::OnVolChanged() { if(!m_visible) { return; } uint16 volume = static_cast(GetDlgItemInt(IDC_EDIT1)); if(volume >= 0 && volume <= 64) { PrepareUndo(); m_document->SetChannelGlobalVolume(m_channel, volume); m_volSlider.SetPos(volume); m_document->UpdateAllViews(nullptr, GeneralHint(m_channel).Channels(), this); } } void QuickChannelProperties::OnPanChanged() { if(!m_visible) { return; } uint16 panning = static_cast(GetDlgItemInt(IDC_EDIT2)); if(panning >= 0 && panning <= 256) { PrepareUndo(); m_document->SetChannelDefaultPan(m_channel, panning); m_panSlider.SetPos(panning / 4u); m_document->UpdateAllViews(nullptr, GeneralHint(m_channel).Channels(), this); // Surround is forced off when changing pan, so uncheck the checkbox. CheckDlgButton(IDC_CHECK2, BST_UNCHECKED); } } void QuickChannelProperties::OnHScroll(UINT, UINT, CScrollBar *bar) { if(!m_visible) { return; } bool update = false; // Volume slider if(bar == reinterpret_cast(&m_volSlider)) { uint16 pos = static_cast(m_volSlider.GetPos()); PrepareUndo(); if(m_document->SetChannelGlobalVolume(m_channel, pos)) { SetDlgItemInt(IDC_EDIT1, pos); update = true; } } // Pan slider if(bar == reinterpret_cast(&m_panSlider)) { uint16 pos = static_cast(m_panSlider.GetPos()); PrepareUndo(); if(m_document->SetChannelDefaultPan(m_channel, pos * 4u)) { SetDlgItemInt(IDC_EDIT2, pos * 4u); CheckDlgButton(IDC_CHECK2, BST_UNCHECKED); update = true; } } if(update) { m_document->UpdateAllViews(nullptr, GeneralHint(m_channel).Channels(), this); } } void QuickChannelProperties::OnMuteChanged() { if(!m_visible) { return; } m_document->MuteChannel(m_channel, IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED); m_document->UpdateAllViews(nullptr, GeneralHint(m_channel).Channels(), this); } void QuickChannelProperties::OnSurroundChanged() { if(!m_visible) { return; } PrepareUndo(); m_document->SurroundChannel(m_channel, IsDlgButtonChecked(IDC_CHECK2) != BST_UNCHECKED); m_document->UpdateAllViews(nullptr, GeneralHint(m_channel).Channels(), this); UpdateDisplay(); } void QuickChannelProperties::OnNameChanged() { if(!m_visible) { return; } ModChannelSettings &settings = m_document->GetSoundFile().ChnSettings[m_channel]; CString newNameTmp; m_nameEdit.GetWindowText(newNameTmp); std::string newName = mpt::ToCharset(m_document->GetSoundFile().GetCharsetInternal(), newNameTmp); if(newName != settings.szName) { PrepareUndo(); settings.szName = newName; m_document->SetModified(); m_document->UpdateAllViews(nullptr, GeneralHint(m_channel).Channels(), this); } } void QuickChannelProperties::OnChangeColor() { m_settingColor = true; if(auto color = m_colorBtn.PickColor(m_document->GetSoundFile(), m_channel); color.has_value()) { PrepareUndo(); m_document->GetSoundFile().ChnSettings[m_channel].color = *color; if(m_document->SupportsChannelColors()) m_document->SetModified(); m_document->UpdateAllViews(nullptr, GeneralHint(m_channel).Channels(), this); } m_settingColor = false; } void QuickChannelProperties::OnPickPrevColor() { if(m_channel > 0) PickColorFromChannel(m_channel - 1); } void QuickChannelProperties::OnPickNextColor() { if(m_channel < m_document->GetNumChannels() - 1) PickColorFromChannel(m_channel + 1); } void QuickChannelProperties::PickColorFromChannel(CHANNELINDEX channel) { auto &channels = m_document->GetSoundFile().ChnSettings; if(channels[channel].color != channels[m_channel].color) { PrepareUndo(); channels[m_channel].color = channels[channel].color; m_colorBtn.SetColor(channels[m_channel].color); if(m_document->SupportsChannelColors()) m_document->SetModified(); m_document->UpdateAllViews(nullptr, GeneralHint(m_channel).Channels(), this); } } void QuickChannelProperties::OnPrevChannel() { if(m_channel > 0) { m_channel--; UpdateDisplay(); } } void QuickChannelProperties::OnNextChannel() { if(m_channel < m_document->GetNumChannels() - 1) { m_channel++; UpdateDisplay(); } } BOOL QuickChannelProperties::PreTranslateMessage(MSG *pMsg) { if(pMsg) { //We handle keypresses before Windows has a chance to handle them (for alt etc..) if((pMsg->message == WM_SYSKEYUP) || (pMsg->message == WM_KEYUP) || (pMsg->message == WM_SYSKEYDOWN) || (pMsg->message == WM_KEYDOWN)) { CInputHandler *ih = CMainFrame::GetInputHandler(); //Translate message manually UINT nChar = static_cast(pMsg->wParam); UINT nRepCnt = LOWORD(pMsg->lParam); UINT nFlags = HIWORD(pMsg->lParam); KeyEventType kT = ih->GetKeyEventType(nFlags); if(ih->KeyEvent(kCtxChannelSettings, nChar, nRepCnt, nFlags, kT, this) != kcNull) { return TRUE; // Mapped to a command, no need to pass message on. } } } return CDialog::PreTranslateMessage(pMsg); } LRESULT QuickChannelProperties::OnCustomKeyMsg(WPARAM wParam, LPARAM) { switch(wParam) { case kcChnSettingsPrev: OnPrevChannel(); return wParam; case kcChnSettingsNext: OnNextChannel(); return wParam; case kcChnColorFromPrev: OnPickPrevColor(); return wParam; case kcChnColorFromNext: OnPickNextColor(); return wParam; case kcChnSettingsClose: OnActivate(WA_INACTIVE, nullptr, FALSE); return wParam; } return kcNull; } BOOL QuickChannelProperties::OnToolTipText(UINT, NMHDR *pNMHDR, LRESULT *pResult) { auto pTTT = reinterpret_cast(pNMHDR); UINT_PTR id = pNMHDR->idFrom; if(pTTT->uFlags & TTF_IDISHWND) { // idFrom is actually the HWND of the tool id = static_cast(::GetDlgCtrlID(reinterpret_cast(id))); } mpt::tstring text; CommandID cmd = kcNull; switch (id) { case IDC_EDIT1: case IDC_SLIDER1: text = CModDoc::LinearToDecibels(m_document->GetSoundFile().ChnSettings[m_channel].nVolume, 64.0); break; case IDC_EDIT2: case IDC_SLIDER2: text = CModDoc::PanningToString(m_document->GetSoundFile().ChnSettings[m_channel].nPan, 128); break; case IDC_BUTTON1: text = _T("Previous Channel"); cmd = kcChnSettingsPrev; break; case IDC_BUTTON2: text = _T("Next Channel"); cmd = kcChnSettingsNext; break; case IDC_BUTTON5: text = _T("Take color from previous channel"); cmd = kcChnColorFromPrev; break; case IDC_BUTTON6: text = _T("Take color from next channel"); cmd = kcChnColorFromNext; break; default: return FALSE; } if(cmd != kcNull) { auto keyText = CMainFrame::GetInputHandler()->m_activeCommandSet->GetKeyTextFromCommand(cmd, 0); if(!keyText.IsEmpty()) text += MPT_TFORMAT(" ({})")(keyText); } mpt::String::WriteWinBuf(pTTT->szText) = text; *pResult = 0; // bring the tooltip window above other popup windows ::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_NOOWNERZORDER); return TRUE; // message was handled } OPENMPT_NAMESPACE_END