winamp/Src/external_dependencies/openmpt-trunk/mptrack/PatternEditorDialogs.cpp

1696 lines
45 KiB
C++
Raw Normal View History

2024-09-24 13:54:57 +01:00
/*
* 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<int>(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<int>((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<ROWINDEX>(GetDlgItemInt(IDC_ROWSPERBEAT)), ROWINDEX(1), MAX_ROWS_PER_BEAT);
const ROWINDEX newRPM = std::clamp(static_cast<ROWINDEX>(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<ROWINDEX>(GetDlgItemInt(IDC_ROWSPERBEAT, NULL, FALSE)), MAX_ROWS_PER_BEAT);
const ROWINDEX newRPM = std::min(static_cast<ROWINDEX>(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<ModCommand::NOTE>(cbnNote.GetItemData(n));
n = cbnInstr.GetCurSel();
if(n >= 0)
newInstr = static_cast<ModCommand::INSTR>(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<UINT>(cbnVolCmd.GetItemData(n)));
}
newVol = static_cast<ModCommand::VOL>(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<int>(cbnCommand.GetItemData(n));
newCommand = static_cast<ModCommand::COMMAND>((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<uint16>(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<uint16>(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<uint16>(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<ModCommand::PARAM>(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<CWnd *>(&sldVolParam))
{
OnVolCmdChanged();
} else if(bar == static_cast<CWnd *>(&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<decltype(m_CbnNote)>::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<int>(kcVPEndChords) - static_cast<int>(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<int>(m_CbnShortcut.GetItemData(chord)) - NOTE_MIN;
if(chord < 0 || chord >= static_cast<int>(m_chords.size()))
chord = 0;
return m_chords[chord];
}
LRESULT CChordEditor::OnKeyboardNotify(WPARAM cmd, LPARAM nKey)
{
const bool outside = static_cast<int>(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<MPTChord::NoteType>(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 &note : 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 &note : 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<int>(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<int8>(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<ModCommand::NOTE>(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<ModCommand::VOL>(m_CbnSplitVolume.GetCurSel());
m_Settings.splitInstrument = static_cast<ModCommand::INSTR>(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<int>(position.x) - rect.Width() / 2, 0, static_cast<int>(screenRect.right) - rect.Width()),
Clamp(static_cast<int>(position.y) - rect.Height() / 2, 0, static_cast<int>(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<uint16>(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<uint16>(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<CScrollBar *>(&m_volSlider))
{
uint16 pos = static_cast<uint16>(m_volSlider.GetPos());
PrepareUndo();
if(m_document->SetChannelGlobalVolume(m_channel, pos))
{
SetDlgItemInt(IDC_EDIT1, pos);
update = true;
}
}
// Pan slider
if(bar == reinterpret_cast<CScrollBar *>(&m_panSlider))
{
uint16 pos = static_cast<uint16>(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<UINT>(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<TOOLTIPTEXT *>(pNMHDR);
UINT_PTR id = pNMHDR->idFrom;
if(pTTT->uFlags & TTF_IDISHWND)
{
// idFrom is actually the HWND of the tool
id = static_cast<UINT_PTR>(::GetDlgCtrlID(reinterpret_cast<HWND>(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