/*
 * view_gen.cpp
 * ------------
 * Purpose: General tab, lower panel.
 * 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 "Childfrm.h"
#include "Moddoc.h"
#include "Globals.h"
#include "Ctrl_gen.h"
#include "View_gen.h"
#include "../soundlib/plugins/PlugInterface.h"
#include "EffectVis.h"
#include "MoveFXSlotDialog.h"
#include "ChannelManagerDlg.h"
#include "SelectPluginDialog.h"
#include "../soundlib/mod_specifications.h"
#include "../common/mptStringBuffer.h"
#include "AbstractVstEditor.h"

// This is used for retrieving the correct background colour for the
// frames on the general tab when using WinXP Luna or Vista/Win7 Aero.
#include <uxtheme.h>


OPENMPT_NAMESPACE_BEGIN


IMPLEMENT_SERIAL(CViewGlobals, CFormView, 0)

BEGIN_MESSAGE_MAP(CViewGlobals, CFormView)
	//{{AFX_MSG_MAP(CViewGlobals)
	ON_WM_SIZE()
	ON_WM_HSCROLL()
	ON_WM_VSCROLL()
	ON_WM_DESTROY()
	
	ON_MESSAGE(WM_MOD_MDIACTIVATE,   &CViewGlobals::OnMDIDeactivate)
	ON_MESSAGE(WM_MOD_MDIDEACTIVATE, &CViewGlobals::OnMDIDeactivate)

	ON_COMMAND(IDC_CHECK1,		&CViewGlobals::OnMute1)
	ON_COMMAND(IDC_CHECK3,		&CViewGlobals::OnMute2)
	ON_COMMAND(IDC_CHECK5,		&CViewGlobals::OnMute3)
	ON_COMMAND(IDC_CHECK7,		&CViewGlobals::OnMute4)
	ON_COMMAND(IDC_CHECK2,		&CViewGlobals::OnSurround1)
	ON_COMMAND(IDC_CHECK4,		&CViewGlobals::OnSurround2)
	ON_COMMAND(IDC_CHECK6,		&CViewGlobals::OnSurround3)
	ON_COMMAND(IDC_CHECK8,		&CViewGlobals::OnSurround4)

	ON_COMMAND(IDC_BUTTON9,		&CViewGlobals::OnEditColor1)
	ON_COMMAND(IDC_BUTTON10,	&CViewGlobals::OnEditColor2)
	ON_COMMAND(IDC_BUTTON11,	&CViewGlobals::OnEditColor3)
	ON_COMMAND(IDC_BUTTON12,	&CViewGlobals::OnEditColor4)

	ON_EN_UPDATE(IDC_EDIT1,		&CViewGlobals::OnEditVol1)
	ON_EN_UPDATE(IDC_EDIT3,		&CViewGlobals::OnEditVol2)
	ON_EN_UPDATE(IDC_EDIT5,		&CViewGlobals::OnEditVol3)
	ON_EN_UPDATE(IDC_EDIT7,		&CViewGlobals::OnEditVol4)
	ON_EN_UPDATE(IDC_EDIT2,		&CViewGlobals::OnEditPan1)
	ON_EN_UPDATE(IDC_EDIT4,		&CViewGlobals::OnEditPan2)
	ON_EN_UPDATE(IDC_EDIT6,		&CViewGlobals::OnEditPan3)
	ON_EN_UPDATE(IDC_EDIT8,		&CViewGlobals::OnEditPan4)
	ON_EN_UPDATE(IDC_EDIT9,		&CViewGlobals::OnEditName1)
	ON_EN_UPDATE(IDC_EDIT10,	&CViewGlobals::OnEditName2)
	ON_EN_UPDATE(IDC_EDIT11,	&CViewGlobals::OnEditName3)
	ON_EN_UPDATE(IDC_EDIT12,	&CViewGlobals::OnEditName4)

	ON_CBN_SELCHANGE(IDC_COMBO1, &CViewGlobals::OnFx1Changed)
	ON_CBN_SELCHANGE(IDC_COMBO2, &CViewGlobals::OnFx2Changed)
	ON_CBN_SELCHANGE(IDC_COMBO3, &CViewGlobals::OnFx3Changed)
	ON_CBN_SELCHANGE(IDC_COMBO4, &CViewGlobals::OnFx4Changed)

	// Plugins
	ON_COMMAND(IDC_CHECK9,		&CViewGlobals::OnMixModeChanged)
	ON_COMMAND(IDC_CHECK10,		&CViewGlobals::OnBypassChanged)
	ON_COMMAND(IDC_CHECK11,		&CViewGlobals::OnDryMixChanged)
	ON_COMMAND(IDC_BUTTON1,		&CViewGlobals::OnSelectPlugin)
	ON_COMMAND(IDC_DELPLUGIN,	&CViewGlobals::OnRemovePlugin)
	ON_COMMAND(IDC_BUTTON2,		&CViewGlobals::OnEditPlugin)
	ON_COMMAND(IDC_BUTTON4,		&CViewGlobals::OnNextPlugin)
	ON_COMMAND(IDC_BUTTON5,		&CViewGlobals::OnPrevPlugin)
	ON_COMMAND(IDC_MOVEFXSLOT,	&CViewGlobals::OnMovePlugToSlot)
	ON_COMMAND(IDC_INSERTFXSLOT,&CViewGlobals::OnInsertSlot)
	ON_COMMAND(IDC_CLONEPLUG,	&CViewGlobals::OnClonePlug)

	ON_COMMAND(IDC_BUTTON6,		&CViewGlobals::OnLoadParam)
	ON_COMMAND(IDC_BUTTON8,		&CViewGlobals::OnSaveParam)

	ON_EN_UPDATE(IDC_EDIT13,	&CViewGlobals::OnPluginNameChanged)
	ON_EN_UPDATE(IDC_EDIT14,	&CViewGlobals::OnSetParameter)
	ON_EN_SETFOCUS(IDC_EDIT14,	&CViewGlobals::OnFocusParam)
	ON_EN_KILLFOCUS(IDC_EDIT14,	&CViewGlobals::OnParamChanged)
	ON_CBN_SELCHANGE(IDC_COMBO5, &CViewGlobals::OnPluginChanged)

	ON_CBN_SELCHANGE(IDC_COMBO6, &CViewGlobals::OnParamChanged)
	ON_CBN_SETFOCUS(IDC_COMBO6, &CViewGlobals::OnFillParamCombo)

	ON_CBN_SELCHANGE(IDC_COMBO7, &CViewGlobals::OnOutputRoutingChanged)

	ON_CBN_SELCHANGE(IDC_COMBO8, &CViewGlobals::OnProgramChanged)
	ON_CBN_SETFOCUS(IDC_COMBO8, &CViewGlobals::OnFillProgramCombo)

	ON_COMMAND(IDC_CHECK12,		 &CViewGlobals::OnWetDryExpandChanged)
	ON_CBN_SELCHANGE(IDC_COMBO9, &CViewGlobals::OnSpecialMixProcessingChanged)

	ON_NOTIFY(TCN_SELCHANGE, IDC_TABCTRL1,	&CViewGlobals::OnTabSelchange)
	ON_MESSAGE(WM_MOD_UNLOCKCONTROLS,		&CViewGlobals::OnUnlockControls)
	ON_MESSAGE(WM_MOD_VIEWMSG,	&CViewGlobals::OnModViewMsg)
	ON_MESSAGE(WM_MOD_MIDIMSG,	&CViewGlobals::OnMidiMsg)
	ON_MESSAGE(WM_MOD_PLUGPARAMAUTOMATE,	&CViewGlobals::OnParamAutomated)
	ON_MESSAGE(WM_MOD_PLUGINDRYWETRATIOCHANGED, &CViewGlobals::OnDryWetRatioChangedFromPlayer)

	ON_COMMAND(ID_EDIT_UNDO, &CViewGlobals::OnEditUndo)
	ON_COMMAND(ID_EDIT_REDO, &CViewGlobals::OnEditRedo)
	ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, &CViewGlobals::OnUpdateUndo)
	ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, &CViewGlobals::OnUpdateRedo)
	ON_NOTIFY_EX_RANGE(TTN_NEEDTEXT, 0, 0xFFFF, &CViewGlobals::OnToolTipText)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


void CViewGlobals::DoDataExchange(CDataExchange* pDX)
{
	CFormView::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CViewGlobals)
	DDX_Control(pDX, IDC_TABCTRL1,	m_TabCtrl);
	DDX_Control(pDX, IDC_COMBO1,	m_CbnEffects[0]);
	DDX_Control(pDX, IDC_COMBO2,	m_CbnEffects[1]);
	DDX_Control(pDX, IDC_COMBO3,	m_CbnEffects[2]);
	DDX_Control(pDX, IDC_COMBO4,	m_CbnEffects[3]);
	DDX_Control(pDX, IDC_COMBO5,	m_CbnPlugin);
	DDX_Control(pDX, IDC_COMBO6,	m_CbnParam);
	DDX_Control(pDX, IDC_COMBO7,	m_CbnOutput);

	DDX_Control(pDX, IDC_COMBO8,	m_CbnPreset);
	DDX_Control(pDX, IDC_COMBO9,	m_CbnSpecialMixProcessing);
	DDX_Control(pDX, IDC_SPIN10,	m_SpinMixGain);

	DDX_Control(pDX, IDC_SLIDER1,	m_sbVolume[0]);
	DDX_Control(pDX, IDC_SLIDER2,	m_sbPan[0]);
	DDX_Control(pDX, IDC_SLIDER3,	m_sbVolume[1]);
	DDX_Control(pDX, IDC_SLIDER4,	m_sbPan[1]);
	DDX_Control(pDX, IDC_SLIDER5,	m_sbVolume[2]);
	DDX_Control(pDX, IDC_SLIDER6,	m_sbPan[2]);
	DDX_Control(pDX, IDC_SLIDER7,	m_sbVolume[3]);
	DDX_Control(pDX, IDC_SLIDER8,	m_sbPan[3]);
	DDX_Control(pDX, IDC_SLIDER9,	m_sbValue);
	DDX_Control(pDX, IDC_SLIDER10,  m_sbDryRatio);
	DDX_Control(pDX, IDC_SPIN1,		m_spinVolume[0]);
	DDX_Control(pDX, IDC_SPIN2,		m_spinPan[0]);
	DDX_Control(pDX, IDC_SPIN3,		m_spinVolume[1]);
	DDX_Control(pDX, IDC_SPIN4,		m_spinPan[1]);
	DDX_Control(pDX, IDC_SPIN5,		m_spinVolume[2]);
	DDX_Control(pDX, IDC_SPIN6,		m_spinPan[2]);
	DDX_Control(pDX, IDC_SPIN7,		m_spinVolume[3]);
	DDX_Control(pDX, IDC_SPIN8,		m_spinPan[3]);
	DDX_Control(pDX, IDC_BUTTON1,	m_BtnSelect);
	DDX_Control(pDX, IDC_BUTTON2,	m_BtnEdit);
	//}}AFX_DATA_MAP
}

void CViewGlobals::OnInitialUpdate()
{
	CChildFrame *pFrame = (CChildFrame *)GetParentFrame();
	int nMapMode = MM_TEXT;
	SIZE sizeTotal, sizePage, sizeLine;

	m_nActiveTab = CHANNELINDEX(-1);
	m_nCurrentPlugin = 0;
	m_nCurrentParam = 0;
	CFormView::OnInitialUpdate();
	EnableToolTips();

	if (pFrame)
	{
		GENERALVIEWSTATE &generalState = pFrame->GetGeneralViewState();
		if (generalState.initialized)
		{
			m_TabCtrl.SetCurSel(generalState.nTab);
			m_nActiveTab = generalState.nTab;
			m_nCurrentPlugin = generalState.nPlugin;
			m_nCurrentParam = generalState.nParam;
		}
	}
	GetDeviceScrollSizes(nMapMode, sizeTotal, sizePage, sizeLine);
	m_rcClient.SetRect(0, 0, sizeTotal.cx, sizeTotal.cy);
	RecalcLayout();

	// Initializing scroll ranges
	for(int ichn = 0; ichn < CHANNELS_IN_TAB; ichn++)
	{
		// Color select
		m_channelColor[ichn].SubclassDlgItem(IDC_BUTTON9 + ichn, this);
		// Volume Slider
		m_sbVolume[ichn].SetRange(0, 64);
		m_sbVolume[ichn].SetTicFreq(8);
		// Pan Slider
		m_sbPan[ichn].SetRange(0, 64);
		m_sbPan[ichn].SetTicFreq(8);
		// Volume Spin
		m_spinVolume[ichn].SetRange(0, 64);
		// Pan Spin
		m_spinPan[ichn].SetRange(0, 256);
	}
	m_sbValue.SetPos(0);
	m_sbValue.SetRange(0, 100);

	m_sbValue.SetPos(0);
	m_sbValue.SetRange(0, 100);

	m_CbnSpecialMixProcessing.AddString(_T("Default"));
	m_CbnSpecialMixProcessing.AddString(_T("Wet Subtract"));
	m_CbnSpecialMixProcessing.AddString(_T("Dry Subtract"));
	m_CbnSpecialMixProcessing.AddString(_T("Mix Subtract"));
	m_CbnSpecialMixProcessing.AddString(_T("Middle Subtract"));
	m_CbnSpecialMixProcessing.AddString(_T("LR Balance"));
	m_SpinMixGain.SetRange(0, 80);
	m_SpinMixGain.SetPos(10);
	SetDlgItemText(IDC_EDIT16, _T("Gain: x1.0"));

	UpdateView(UpdateHint().ModType());
	OnParamChanged();
	m_nLockCount = 0;

	// Use tab background color rather than regular dialog background color (required for Aero/etc. where they are not the same color)
	EnableThemeDialogTexture(m_hWnd, ETDT_ENABLETAB);
}


void CViewGlobals::OnDestroy()
{
	CChildFrame *pFrame = (CChildFrame *)GetParentFrame();
	if (pFrame)
	{
		GENERALVIEWSTATE &generalState = pFrame->GetGeneralViewState();
		generalState.initialized = true;
		generalState.nTab = m_nActiveTab;
		generalState.nPlugin = m_nCurrentPlugin;
		generalState.nParam = m_nCurrentParam;
	}
	CFormView::OnDestroy();
}


LRESULT CViewGlobals::OnMDIDeactivate(WPARAM, LPARAM)
{
	// Create new undo point if we switch to / from other window
	m_lastEdit = CHANNELINDEX_INVALID;
	return 0;
}


LRESULT CViewGlobals::OnMidiMsg(WPARAM midiData_, LPARAM)
{
	uint32 midiData = static_cast<uint32>(midiData_);
	// Handle MIDI messages assigned to shortcuts
	CInputHandler *ih = CMainFrame::GetInputHandler();
	ih->HandleMIDIMessage(kCtxViewGeneral, midiData) != kcNull
		|| ih->HandleMIDIMessage(kCtxAllContexts, midiData) != kcNull;
	return 1;
}


void CViewGlobals::RecalcLayout()
{
	if (m_TabCtrl.m_hWnd != NULL)
	{
		CRect rect;
		GetClientRect(&rect);
		if (rect.right < m_rcClient.right) rect.right = m_rcClient.right;
		if (rect.bottom < m_rcClient.bottom) rect.bottom = m_rcClient.bottom;
		m_TabCtrl.SetWindowPos(&CWnd::wndBottom, 0,0, rect.right, rect.bottom, SWP_NOMOVE);
	}
}


int CViewGlobals::GetDlgItemIntEx(UINT nID)
{
	CString s;
	GetDlgItemText(nID, s);
	if(s.GetLength() < 1 || s[0] < _T('0') || s[0] > _T('9')) return -1;
	return _ttoi(s);
}


void CViewGlobals::OnSize(UINT nType, int cx, int cy)
{
	CFormView::OnSize(nType, cx, cy);
	if (((nType == SIZE_RESTORED) || (nType == SIZE_MAXIMIZED)) && (cx > 0) && (cy > 0) && (m_hWnd))
	{
		RecalcLayout();
	}
}


void CViewGlobals::OnUpdate(CView *pView, LPARAM lHint, CObject *pHint)
{
	if (pView != this) UpdateView(UpdateHint::FromLPARAM(lHint), pHint);
}


void CViewGlobals::UpdateView(UpdateHint hint, CObject *pObject)
{
	const CModDoc *pModDoc = GetDocument();
	int nTabCount, nTabIndex;

	if (!pModDoc || pObject == this ) return;
	const CSoundFile &sndFile = pModDoc->GetSoundFile();
	const GeneralHint genHint = hint.ToType<GeneralHint>();
	const PluginHint plugHint = hint.ToType<PluginHint>();
	if (!genHint.GetType()[HINT_MODTYPE | HINT_MODCHANNELS]
		&& !plugHint.GetType()[HINT_MIXPLUGINS | HINT_PLUGINNAMES | HINT_PLUGINPARAM])
	{
		return;
	}
	FlagSet<HintType> hintType = hint.GetType();
	const bool updateAll = hintType[HINT_MODTYPE];
	const auto updateChannel = genHint.GetChannel();
	const int updateTab = (updateChannel < sndFile.GetNumChannels()) ? (updateChannel / CHANNELS_IN_TAB) : m_nActiveTab;
	if(genHint.GetType()[HINT_MODCHANNELS] && updateTab != m_nActiveTab)
		return;

	CString s;
	nTabCount = (sndFile.m_nChannels + (CHANNELS_IN_TAB - 1)) / CHANNELS_IN_TAB;
	if (nTabCount != m_TabCtrl.GetItemCount())
	{
		UINT nOldSel = m_TabCtrl.GetCurSel();
		if (!m_TabCtrl.GetItemCount()) nOldSel = m_nActiveTab;
		m_TabCtrl.SetRedraw(FALSE);
		m_TabCtrl.DeleteAllItems();
		for (int iItem=0; iItem<nTabCount; iItem++)
		{
			const int lastItem = std::min(iItem * CHANNELS_IN_TAB + CHANNELS_IN_TAB, static_cast<int>(MAX_BASECHANNELS));
			s = MPT_CFORMAT("{} - {}")(iItem * CHANNELS_IN_TAB + 1, lastItem);
			TC_ITEM tci;
			tci.mask = TCIF_TEXT | TCIF_PARAM;
			tci.pszText = const_cast<TCHAR *>(s.GetString());
			tci.lParam = iItem * CHANNELS_IN_TAB;
			m_TabCtrl.InsertItem(iItem, &tci);
		}
		if (nOldSel >= (UINT)nTabCount) nOldSel = 0;

		m_TabCtrl.SetRedraw(TRUE);
		m_TabCtrl.SetCurSel(nOldSel);

		InvalidateRect(NULL, FALSE);
	}
	nTabIndex = m_TabCtrl.GetCurSel();
	if ((nTabIndex < 0) || (nTabIndex >= nTabCount)) return; // ???

	if (m_nActiveTab != nTabIndex || genHint.GetType()[HINT_MODTYPE | HINT_MODCHANNELS])
	{
		LockControls();
		m_nActiveTab = static_cast<CHANNELINDEX>(nTabIndex);
		for (CHANNELINDEX ichn = 0; ichn < CHANNELS_IN_TAB; ichn++)
		{
			const CHANNELINDEX nChn = m_nActiveTab * CHANNELS_IN_TAB + ichn;
			const BOOL bEnable = (nChn < sndFile.GetNumChannels()) ? TRUE : FALSE;
			if(nChn < MAX_BASECHANNELS)
			{
				const auto &chnSettings = sndFile.ChnSettings[nChn];
				// Text
				if(bEnable)
					s = MPT_CFORMAT("Channel {}")(nChn + 1);
				else
					s = _T("");
				SetDlgItemText(IDC_TEXT1 + ichn, s);
				// Channel color
				m_channelColor[ichn].SetColor(chnSettings.color);
				m_channelColor[ichn].EnableWindow(bEnable);
				// Mute
				CheckDlgButton(IDC_CHECK1 + ichn * 2, chnSettings.dwFlags[CHN_MUTE] ? TRUE : FALSE);
				// Surround
				CheckDlgButton(IDC_CHECK2 + ichn * 2, chnSettings.dwFlags[CHN_SURROUND] ? TRUE : FALSE);
				// Volume
				int vol = chnSettings.nVolume;
				m_sbVolume[ichn].SetPos(vol);
				m_sbVolume[ichn].Invalidate(FALSE);
				SetDlgItemInt(IDC_EDIT1+ichn*2, vol);
				// Pan
				int pan = chnSettings.nPan;
				m_sbPan[ichn].SetPos(pan/4);
				m_sbPan[ichn].Invalidate(FALSE);
				SetDlgItemInt(IDC_EDIT2+ichn*2, pan);

				// Channel name
				s = mpt::ToCString(sndFile.GetCharsetInternal(), chnSettings.szName);
				SetDlgItemText(IDC_EDIT9 + ichn, s);
				((CEdit*)(GetDlgItem(IDC_EDIT9 + ichn)))->LimitText(MAX_CHANNELNAME - 1);
			} else
			{
				SetDlgItemText(IDC_TEXT1 + ichn, _T(""));
				SetDlgItemText(IDC_EDIT9 + ichn, _T(""));
				m_channelColor[ichn].EnableWindow(FALSE);
			}

			// Enable/Disable controls for this channel
			BOOL bIT = ((bEnable) && (sndFile.m_nType & (MOD_TYPE_IT|MOD_TYPE_MPT)));
			GetDlgItem(IDC_CHECK1 + ichn * 2)->EnableWindow(bEnable);
			GetDlgItem(IDC_CHECK2 + ichn * 2)->EnableWindow(bIT);

			m_sbVolume[ichn].EnableWindow(bIT);
			m_spinVolume[ichn].EnableWindow(bIT);

			m_sbPan[ichn].EnableWindow(bEnable && !(sndFile.GetType() & (MOD_TYPE_XM|MOD_TYPE_MOD)));
			m_spinPan[ichn].EnableWindow(bEnable && !(sndFile.GetType() & (MOD_TYPE_XM|MOD_TYPE_MOD)));
			GetDlgItem(IDC_EDIT1 + ichn * 2)->EnableWindow(bIT);	// channel vol
			GetDlgItem(IDC_EDIT2 + ichn * 2)->EnableWindow(bEnable && !(sndFile.GetType() & (MOD_TYPE_XM|MOD_TYPE_MOD)));	// channel pan
			GetDlgItem(IDC_EDIT9 + ichn)->EnableWindow(((bEnable) && (sndFile.m_nType & (MOD_TYPE_XM|MOD_TYPE_IT|MOD_TYPE_MPT))));	// channel name
			m_CbnEffects[ichn].EnableWindow(bEnable & (sndFile.GetModSpecifications().supportsPlugins ? TRUE : FALSE));
		}
		UnlockControls();
	}

	// Update plugin names
	if(genHint.GetType()[HINT_MODTYPE | HINT_MODCHANNELS] || plugHint.GetType()[HINT_PLUGINNAMES])
	{
		PopulateChannelPlugins(plugHint.GetPlugin() ? plugHint.GetPlugin() - 1 : PLUGINDEX_INVALID);
		SetDlgItemText(IDC_EDIT13, mpt::ToCString(sndFile.m_MixPlugins[m_nCurrentPlugin].GetName()));
	}
	// Update plugin info
	const SNDMIXPLUGIN &plugin = sndFile.m_MixPlugins[m_nCurrentPlugin];
	const bool updatePlug = (plugHint.GetPlugin() == 0 || plugHint.GetPlugin() == m_nCurrentPlugin + 1);
	const bool updateWholePluginView = updateAll || (plugHint.GetType()[HINT_MIXPLUGINS] && updatePlug);
	if(updateWholePluginView)
	{
		const PLUGINDEX plugIndex = (plugHint.GetPlugin() != 0) ? plugHint.GetPlugin() - 1 : PLUGINDEX_INVALID;
		m_CbnPlugin.SetRedraw(FALSE);
		if(plugIndex == PLUGINDEX_INVALID)
			m_CbnPlugin.ResetContent();
		AddPluginNamesToCombobox(m_CbnPlugin, sndFile.m_MixPlugins, true, plugIndex);
		m_CbnPlugin.SetRedraw(TRUE);
		m_CbnPlugin.SetCurSel(m_nCurrentPlugin);
		if (m_nCurrentPlugin >= MAX_MIXPLUGINS) m_nCurrentPlugin = 0;
		SetDlgItemText(IDC_EDIT13, mpt::ToCString(plugin.GetName()));
		CheckDlgButton(IDC_CHECK9, plugin.IsMasterEffect() ? BST_CHECKED : BST_UNCHECKED);
		CheckDlgButton(IDC_CHECK10, plugin.IsBypassed() ? BST_CHECKED : BST_UNCHECKED);
		CheckDlgButton(IDC_CHECK11, plugin.IsWetMix() ? BST_CHECKED : BST_UNCHECKED);
		IMixPlugin *pPlugin = plugin.pMixPlugin;
		m_BtnEdit.EnableWindow((pPlugin != nullptr && (pPlugin->HasEditor() || pPlugin->GetNumParameters())) ? TRUE : FALSE);
		GetDlgItem(IDC_MOVEFXSLOT)->EnableWindow((pPlugin) ? TRUE : FALSE);
		GetDlgItem(IDC_INSERTFXSLOT)->EnableWindow((pPlugin) ? TRUE : FALSE);
		GetDlgItem(IDC_CLONEPLUG)->EnableWindow((pPlugin) ? TRUE : FALSE);
		UpdateDryWetDisplay();

		if(pPlugin && pPlugin->IsInstrument())
		{
			m_CbnSpecialMixProcessing.EnableWindow(FALSE);
			GetDlgItem(IDC_CHECK12)->EnableWindow(FALSE);
		} else
		{
			m_CbnSpecialMixProcessing.EnableWindow(TRUE);
			GetDlgItem(IDC_CHECK12)->EnableWindow(TRUE);
			m_CbnSpecialMixProcessing.SetCurSel(plugin.GetMixMode());
			CheckDlgButton(IDC_CHECK12, plugin.IsExpandedMix() ? BST_CHECKED : BST_UNCHECKED);
		}
		int gain = plugin.GetGain();
		if(gain == 0) gain = 10;
		float value = 0.1f * (float)gain;
		s.Format(_T("Gain: x%1.1f"), value);
		SetDlgItemText(IDC_EDIT16, s);
		m_SpinMixGain.SetPos(gain);

		if (pPlugin)
		{
			const PlugParamIndex nParams = pPlugin->GetNumParameters();
			m_CbnParam.SetRedraw(FALSE);
			m_CbnParam.ResetContent();
			if (m_nCurrentParam >= nParams) m_nCurrentParam = 0;

			if(nParams)
			{
				m_CbnParam.SetItemData(m_CbnParam.AddString(pPlugin->GetFormattedParamName(m_nCurrentParam)), m_nCurrentParam);
			}

			m_CbnParam.SetCurSel(0);
			m_CbnParam.SetRedraw(TRUE);
			OnParamChanged();

			// Input / Output type
			int in = pPlugin->GetNumInputChannels(), out = pPlugin->GetNumOutputChannels();
			if (in < 1) s = _T("No input");
			else if (in == 1) s = _T("Mono-In");
			else s = _T("Stereo-In");
			s += _T(", ");
			if (out < 1) s += _T("No output");
			else if (out == 1) s += _T("Mono-Out");
			else s += _T("Stereo-Out");

			// For now, only display the "current" preset.
			// This prevents the program from hanging when switching between plugin slots or
			// switching to the general tab and the first plugin in the list has a lot of presets.
			// Some plugins like Synth1 have so many presets that this *does* indeed make a difference,
			// even on fairly modern CPUs. The rest of the presets are just added when the combo box
			// gets the focus, i.e. just when they're needed.
			int32 currentProg = pPlugin->GetCurrentProgram();
			FillPluginProgramBox(currentProg, currentProg);
			m_CbnPreset.SetCurSel(0);

			m_sbValue.EnableWindow(TRUE);
			m_sbDryRatio.EnableWindow(TRUE);
			GetDlgItem(IDC_EDIT14)->EnableWindow(TRUE);
		} else
		{
			s.Empty();
			if (m_CbnParam.GetCount() > 0) m_CbnParam.ResetContent();
			m_nCurrentParam = 0;

			m_CbnPreset.SetRedraw(FALSE);
			m_CbnPreset.ResetContent();
			m_CbnPreset.SetItemData(m_CbnPreset.AddString(_T("none")), 0);
			m_CbnPreset.SetRedraw(TRUE);
			m_CbnPreset.SetCurSel(0);
			m_sbValue.EnableWindow(FALSE);
			m_sbDryRatio.EnableWindow(FALSE);
			GetDlgItem(IDC_EDIT14)->EnableWindow(FALSE);
		}
		SetDlgItemText(IDC_TEXT6, s);
	}
	
	if(updateWholePluginView || plugHint.GetPlugin() > m_nCurrentPlugin)
	{
		int insertAt = 1;
		m_CbnOutput.SetRedraw(FALSE);
		if(updateWholePluginView)
		{
			m_CbnOutput.ResetContent();
			m_CbnOutput.SetItemData(m_CbnOutput.AddString(_T("Default")), 0);

			for(PLUGINDEX i = m_nCurrentPlugin + 1; i < MAX_MIXPLUGINS; i++)
			{
				if(!sndFile.m_MixPlugins[i].IsValidPlugin())
				{
					m_CbnOutput.SetItemData(m_CbnOutput.AddString(_T("New Plugin...")), 1);
					insertAt = 2;
					break;
				}
			}
		} else
		{
			const DWORD_PTR changedPlugin = 0x80 + (plugHint.GetPlugin() - 1);
			const int items = m_CbnOutput.GetCount();
			for(insertAt = 1; insertAt < items; insertAt++)
			{
				DWORD_PTR thisPlugin = m_CbnOutput.GetItemData(insertAt);
				if(thisPlugin == changedPlugin)
					m_CbnOutput.DeleteString(insertAt);
				if(thisPlugin >= changedPlugin)
					break;
			}
		}

		int outputSel = plugin.IsOutputToMaster() ? 0 : -1;
		for(PLUGINDEX iOut = m_nCurrentPlugin + 1; iOut < MAX_MIXPLUGINS; iOut++)
		{
			if(!updateWholePluginView && (iOut + 1) != plugHint.GetPlugin())
				continue;
			const SNDMIXPLUGIN &outPlug = sndFile.m_MixPlugins[iOut];
			if(outPlug.IsValidPlugin())
			{
				const auto name = outPlug.GetName(), libName = outPlug.GetLibraryName();
				s.Format(_T("FX%d: "), iOut + 1);
				s += mpt::ToCString(name.empty() ? libName : name);
				if(!name.empty() && libName != name)
				{
					s += _T(" (");
					s += mpt::ToCString(libName);
					s += _T(")");
				}

				insertAt = m_CbnOutput.InsertString(insertAt, s);
				m_CbnOutput.SetItemData(insertAt, 0x80 + iOut);
				if(!plugin.IsOutputToMaster() && (plugin.GetOutputPlugin() == iOut))
				{
					outputSel = insertAt;
				}
				insertAt++;
			}
		}
		m_CbnOutput.SetRedraw(TRUE);
		if(outputSel >= 0)
			m_CbnOutput.SetCurSel(outputSel);
	}
	if(plugHint.GetType()[HINT_PLUGINPARAM] && updatePlug)
	{
		OnParamChanged();
	}

	m_CbnPlugin.Invalidate(FALSE);
	m_CbnParam.Invalidate(FALSE);
	m_CbnPreset.Invalidate(FALSE);
	m_CbnSpecialMixProcessing.Invalidate(FALSE);
	m_CbnOutput.Invalidate(FALSE);
}


void CViewGlobals::PopulateChannelPlugins(PLUGINDEX plugin)
{
	// Channel effect lists
	const CSoundFile &sndFile = GetDocument()->GetSoundFile();
	CString s;
	for(CHANNELINDEX ichn = 0; ichn < CHANNELS_IN_TAB; ichn++)
	{
		const CHANNELINDEX nChn = m_nActiveTab * CHANNELS_IN_TAB + ichn;
		auto &comboBox = m_CbnEffects[ichn];
		if(nChn < MAX_BASECHANNELS)
		{
			comboBox.SetRedraw(FALSE);
			int insertAt = 1;
			if(plugin == PLUGINDEX_INVALID)
			{
				comboBox.ResetContent();
				comboBox.SetItemData(comboBox.AddString(_T("No plugin")), 0);
			} else
			{
				const int items = comboBox.GetCount();
				for(insertAt = 1; insertAt < items; insertAt++)
				{
					auto thisPlugin = static_cast<PLUGINDEX>(comboBox.GetItemData(insertAt));
					if(thisPlugin == (plugin + 1))
						comboBox.DeleteString(insertAt);
					if(thisPlugin >= (plugin + 1))
						break;
				}
			}
			int fxsel = 0;
			for(PLUGINDEX ifx = 0; ifx < MAX_MIXPLUGINS; ifx++)
			{
				if(plugin != PLUGINDEX_INVALID && ifx != plugin)
					continue;
				if(sndFile.m_MixPlugins[ifx].IsValidPlugin()
					|| (sndFile.m_MixPlugins[ifx].GetName() != U_(""))
					|| (sndFile.ChnSettings[nChn].nMixPlugin == ifx + 1))
				{
					s = MPT_CFORMAT("FX{}: ")(ifx + 1);
					s += mpt::ToCString(sndFile.m_MixPlugins[ifx].GetName());
					insertAt = comboBox.InsertString(insertAt, s);
					comboBox.SetItemData(insertAt, ifx + 1);
					if(sndFile.ChnSettings[nChn].nMixPlugin == ifx + 1)
						fxsel = insertAt;
					insertAt++;
				}
			}
			comboBox.SetRedraw(TRUE);
			if(plugin == PLUGINDEX_INVALID || fxsel > 0)
				comboBox.SetCurSel(fxsel);
			comboBox.Invalidate(FALSE);
		}
	}
}


IMixPlugin *CViewGlobals::GetCurrentPlugin() const
{
	if(GetDocument() == nullptr || m_nCurrentPlugin >= MAX_MIXPLUGINS)
	{
		return nullptr;
	}

	return GetDocument()->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].pMixPlugin;
}


void CViewGlobals::OnTabSelchange(NMHDR*, LRESULT* pResult)
{
	UpdateView(GeneralHint().Channels());
	if (pResult) *pResult = 0;
}


void CViewGlobals::OnUpdateUndo(CCmdUI *pCmdUI)
{
	CModDoc *pModDoc = GetDocument();
	if((pCmdUI) && (pModDoc))
	{
		pCmdUI->Enable(pModDoc->GetPatternUndo().CanUndoChannelSettings());
		pCmdUI->SetText(CMainFrame::GetInputHandler()->GetKeyTextFromCommand(kcEditUndo, _T("Undo ") + pModDoc->GetPatternUndo().GetUndoName()));
	}
}


void CViewGlobals::OnUpdateRedo(CCmdUI *pCmdUI)
{
	CModDoc *pModDoc = GetDocument();
	if((pCmdUI) && (pModDoc))
	{
		pCmdUI->Enable(pModDoc->GetPatternUndo().CanRedoChannelSettings());
		pCmdUI->SetText(CMainFrame::GetInputHandler()->GetKeyTextFromCommand(kcEditRedo, _T("Redo ") + pModDoc->GetPatternUndo().GetRedoName()));
	}
}


void CViewGlobals::OnEditUndo()
{
	UndoRedo(true);
}


void CViewGlobals::OnEditRedo()
{
	UndoRedo(false);
}


void CViewGlobals::UndoRedo(bool undo)
{
	CModDoc *pModDoc = GetDocument();
	if(!pModDoc)
		return;
	if(undo && pModDoc->GetPatternUndo().CanUndoChannelSettings())
		pModDoc->GetPatternUndo().Undo();
	else if(!undo && pModDoc->GetPatternUndo().CanRedoChannelSettings())
		pModDoc->GetPatternUndo().Redo();
}


void CViewGlobals::PrepareUndo(CHANNELINDEX chnMod4)
{
	if(m_lastEdit != chnMod4)
	{
		// Backup old channel settings through pattern undo.
		m_lastEdit = chnMod4;
		const CHANNELINDEX chn = static_cast<CHANNELINDEX>(m_nActiveTab * CHANNELS_IN_TAB) + chnMod4;
		GetDocument()->GetPatternUndo().PrepareChannelUndo(chn, 1, "Channel Settings");
	}
}


void CViewGlobals::OnEditColor(const CHANNELINDEX chnMod4)
{
	auto *modDoc = GetDocument();
	auto &sndFile = modDoc->GetSoundFile();
	const CHANNELINDEX chn = static_cast<CHANNELINDEX>(m_nActiveTab * CHANNELS_IN_TAB) + chnMod4;
	if(auto color = m_channelColor[chnMod4].PickColor(sndFile, chn); color.has_value())
	{
		PrepareUndo(chnMod4);
		sndFile.ChnSettings[chn].color = *color;
		if(modDoc->SupportsChannelColors())
			modDoc->SetModified();
		modDoc->UpdateAllViews(nullptr, GeneralHint(chn).Channels());
	}
}


void CViewGlobals::OnEditColor1() { OnEditColor(0); }
void CViewGlobals::OnEditColor2() { OnEditColor(1); }
void CViewGlobals::OnEditColor3() { OnEditColor(2); }
void CViewGlobals::OnEditColor4() { OnEditColor(3); }


void CViewGlobals::OnMute(const CHANNELINDEX chnMod4, const UINT itemID)
{
	CModDoc *pModDoc = GetDocument();

	if (pModDoc)
	{
		const bool b = (IsDlgButtonChecked(itemID) != FALSE);
		const CHANNELINDEX nChn = (CHANNELINDEX)(m_nActiveTab * CHANNELS_IN_TAB) + chnMod4;
		pModDoc->MuteChannel(nChn, b);
		pModDoc->UpdateAllViews(this, GeneralHint(nChn).Channels());
	}
}

void CViewGlobals::OnMute1() {OnMute(0, IDC_CHECK1);}
void CViewGlobals::OnMute2() {OnMute(1, IDC_CHECK3);}
void CViewGlobals::OnMute3() {OnMute(2, IDC_CHECK5);}
void CViewGlobals::OnMute4() {OnMute(3, IDC_CHECK7);}


void CViewGlobals::OnSurround(const CHANNELINDEX chnMod4, const UINT itemID)
{
	CModDoc *pModDoc = GetDocument();

	if (pModDoc)
	{
		PrepareUndo(chnMod4);
		const bool b = (IsDlgButtonChecked(itemID) != FALSE);
		const CHANNELINDEX nChn = (CHANNELINDEX)(m_nActiveTab * CHANNELS_IN_TAB) + chnMod4;
		pModDoc->SurroundChannel(nChn, b);
		pModDoc->UpdateAllViews(nullptr, GeneralHint(nChn).Channels());
	}
}

void CViewGlobals::OnSurround1() {OnSurround(0, IDC_CHECK2);}
void CViewGlobals::OnSurround2() {OnSurround(1, IDC_CHECK4);}
void CViewGlobals::OnSurround3() {OnSurround(2, IDC_CHECK6);}
void CViewGlobals::OnSurround4() {OnSurround(3, IDC_CHECK8);}

void CViewGlobals::OnEditVol(const CHANNELINDEX chnMod4, const UINT itemID)
{
	CModDoc *pModDoc = GetDocument();
	const CHANNELINDEX nChn = (CHANNELINDEX)(m_nActiveTab * CHANNELS_IN_TAB) + chnMod4;
	const int vol = GetDlgItemIntEx(itemID);
	if ((pModDoc) && (vol >= 0) && (vol <= 64) && (!m_nLockCount))
	{
		PrepareUndo(chnMod4);
		if (pModDoc->SetChannelGlobalVolume(nChn, static_cast<uint16>(vol)))
		{
			m_sbVolume[chnMod4].SetPos(vol);
			pModDoc->UpdateAllViews(this, GeneralHint(nChn).Channels());
		}
	}
}

void CViewGlobals::OnEditVol1() {OnEditVol(0, IDC_EDIT1);}
void CViewGlobals::OnEditVol2() {OnEditVol(1, IDC_EDIT3);}
void CViewGlobals::OnEditVol3() {OnEditVol(2, IDC_EDIT5);}
void CViewGlobals::OnEditVol4() {OnEditVol(3, IDC_EDIT7);}


void CViewGlobals::OnEditPan(const CHANNELINDEX chnMod4, const UINT itemID)
{
	CModDoc *pModDoc = GetDocument();
	const CHANNELINDEX nChn = (CHANNELINDEX)(m_nActiveTab * CHANNELS_IN_TAB) + chnMod4;
	const int pan = GetDlgItemIntEx(itemID);
	if ((pModDoc) && (pan >= 0) && (pan <= 256) && (!m_nLockCount))
	{
		PrepareUndo(chnMod4);
		if (pModDoc->SetChannelDefaultPan(nChn, static_cast<uint16>(pan)))
		{
			m_sbPan[chnMod4].SetPos(pan / 4);
			pModDoc->UpdateAllViews(this, GeneralHint(nChn).Channels());
			// Surround is forced off when changing pan, so uncheck the checkbox.
			CheckDlgButton(IDC_CHECK2 + chnMod4 * 2, BST_UNCHECKED);
		}
	}
}


void CViewGlobals::OnEditPan1() {OnEditPan(0, IDC_EDIT2);}
void CViewGlobals::OnEditPan2() {OnEditPan(1, IDC_EDIT4);}
void CViewGlobals::OnEditPan3() {OnEditPan(2, IDC_EDIT6);}
void CViewGlobals::OnEditPan4() {OnEditPan(3, IDC_EDIT8);}


void CViewGlobals::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	CModDoc *pModDoc;
	CHANNELINDEX nChn;

	CFormView::OnHScroll(nSBCode, nPos, pScrollBar);

	pModDoc = GetDocument();
	nChn = (CHANNELINDEX)(m_nActiveTab * CHANNELS_IN_TAB);
	if ((pModDoc) && (!IsLocked()) && (nChn < MAX_BASECHANNELS))
	{
		BOOL bUpdate = FALSE;
		short int pos;

		LockControls();
		const CHANNELINDEX nLoopLimit = std::min(static_cast<CHANNELINDEX>(CHANNELS_IN_TAB), static_cast<CHANNELINDEX>(pModDoc->GetSoundFile().GetNumChannels() - nChn));
		for (CHANNELINDEX iCh = 0; iCh < nLoopLimit; iCh++)
		{
			if(pScrollBar == (CScrollBar *) &m_sbVolume[iCh])
			{
				// Volume sliders
				pos = (short int)m_sbVolume[iCh].GetPos();
				if ((pos >= 0) && (pos <= 64))
				{
					PrepareUndo(iCh);
					if (pModDoc->SetChannelGlobalVolume(nChn + iCh, pos))
					{
						SetDlgItemInt(IDC_EDIT1 + iCh * 2, pos);
						bUpdate = TRUE;
					}
				}
			} else if(pScrollBar == (CScrollBar *) &m_sbPan[iCh])
			{
				// Pan sliders
				pos = (short int)m_sbPan[iCh].GetPos();
				if(pos >= 0 && pos <= 64 && (static_cast<uint16>(pos) != pModDoc->GetSoundFile().ChnSettings[nChn+iCh].nPan / 4u))
				{
					PrepareUndo(iCh);
					if (pModDoc->SetChannelDefaultPan(nChn + iCh, pos * 4))
					{
						SetDlgItemInt(IDC_EDIT2 + iCh * 2, pos * 4);
						CheckDlgButton(IDC_CHECK2 + iCh * 2, BST_UNCHECKED);
						bUpdate = TRUE;
					}
				}
			}
		}


		if ((pScrollBar) && (pScrollBar->m_hWnd == m_sbDryRatio.m_hWnd))
		{
			int n = 100 - m_sbDryRatio.GetPos();
			if ((n >= 0) && (n <= 100) && (m_nCurrentPlugin < MAX_MIXPLUGINS))
			{
				SNDMIXPLUGIN &plugin = pModDoc->GetSoundFile().m_MixPlugins[m_nCurrentPlugin];

				if(plugin.pMixPlugin)
				{
					plugin.fDryRatio = static_cast<float>(n) / 100.0f;
					SetPluginModified();
				}
				UpdateDryWetDisplay();
			}
		}

		if (bUpdate) pModDoc->UpdateAllViews(this, GeneralHint(nChn).Channels());
		UnlockControls();

		if ((pScrollBar) && (pScrollBar->m_hWnd == m_sbValue.m_hWnd))
		{
			int n = (short int)m_sbValue.GetPos();
			if ((n >= 0) && (n <= 100) && (m_nCurrentPlugin < MAX_MIXPLUGINS))
			{
				IMixPlugin *pPlugin = GetCurrentPlugin();
				if(pPlugin != nullptr)
				{
					const PlugParamIndex nParams = pPlugin->GetNumParameters();
					if(m_nCurrentParam < nParams)
					{
						if (nSBCode == SB_THUMBPOSITION || nSBCode == SB_THUMBTRACK || nSBCode == SB_ENDSCROLL)
						{
							pPlugin->SetScaledUIParam(m_nCurrentParam, 0.01f * n);
							OnParamChanged();
							SetPluginModified();
						}
					}
				}
			}
		}
	}
}


void CViewGlobals::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	CModDoc *pModDoc = GetDocument();

	if((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return;
	CSoundFile &sndFile = pModDoc->GetSoundFile();
	TCHAR s[32];

	if(nSBCode != SB_ENDSCROLL && pScrollBar && pScrollBar == (CScrollBar*)&m_SpinMixGain)
	{

		SNDMIXPLUGIN &plugin = sndFile.m_MixPlugins[m_nCurrentPlugin];

		if(plugin.pMixPlugin)
		{
			uint8 gain = (uint8)nPos;
			if(gain == 0) gain = 1;

			plugin.SetGain(gain);

			float fValue = 0.1f * (float)gain;
			_stprintf(s, _T("Gain: x%1.1f"), fValue);
			CEdit *gainEdit = (CEdit *)GetDlgItem(IDC_EDIT16);
			gainEdit->SetWindowText(s);

			SetPluginModified();
		}
	}

	CFormView::OnVScroll(nSBCode, nPos, pScrollBar);
}


void CViewGlobals::OnEditName(const CHANNELINDEX chnMod4, const UINT itemID)
{
	CModDoc *pModDoc = GetDocument();

	if ((pModDoc) && (!m_nLockCount))
	{
		CSoundFile &sndFile = pModDoc->GetSoundFile();
		const CHANNELINDEX nChn = m_nActiveTab * CHANNELS_IN_TAB + chnMod4;
		CString tmp;
		GetDlgItemText(itemID, tmp);
		const std::string s = mpt::ToCharset(sndFile.GetCharsetInternal(), tmp);
		if ((sndFile.GetType() & (MOD_TYPE_XM|MOD_TYPE_IT|MOD_TYPE_MPT)) && (nChn < sndFile.GetNumChannels()) && (s != sndFile.ChnSettings[nChn].szName))
		{
			PrepareUndo(chnMod4);
			sndFile.ChnSettings[nChn].szName = s;
			pModDoc->SetModified();
			pModDoc->UpdateAllViews(this, GeneralHint(nChn).Channels());
		}
	}
}
void CViewGlobals::OnEditName1() {OnEditName(0, IDC_EDIT9);}
void CViewGlobals::OnEditName2() {OnEditName(1, IDC_EDIT10);}
void CViewGlobals::OnEditName3() {OnEditName(2, IDC_EDIT11);}
void CViewGlobals::OnEditName4() {OnEditName(3, IDC_EDIT12);}


void CViewGlobals::OnFxChanged(const CHANNELINDEX chnMod4)
{
	CModDoc *pModDoc = GetDocument();

	if (pModDoc)
	{
		CSoundFile &sndFile = pModDoc->GetSoundFile();
		CHANNELINDEX nChn = m_nActiveTab * CHANNELS_IN_TAB + chnMod4;
		int nfx = static_cast<int>(m_CbnEffects[chnMod4].GetItemData(m_CbnEffects[chnMod4].GetCurSel()));
		if ((nfx >= 0) && (nfx <= MAX_MIXPLUGINS) && (nChn < sndFile.GetNumChannels())
		 && (sndFile.ChnSettings[nChn].nMixPlugin != (UINT)nfx))
		{
			PrepareUndo(chnMod4);
			sndFile.ChnSettings[nChn].nMixPlugin = (PLUGINDEX)nfx;
			if(sndFile.GetModSpecifications().supportsPlugins)
				pModDoc->SetModified();
			pModDoc->UpdateAllViews(this, GeneralHint(nChn).Channels());
		}
	}
}


void CViewGlobals::OnFx1Changed() {OnFxChanged(0);}
void CViewGlobals::OnFx2Changed() {OnFxChanged(1);}
void CViewGlobals::OnFx3Changed() {OnFxChanged(2);}
void CViewGlobals::OnFx4Changed() {OnFxChanged(3);}


void CViewGlobals::OnPluginNameChanged()
{
	CModDoc *pModDoc = GetDocument();

	if ((pModDoc) && (m_nCurrentPlugin < MAX_MIXPLUGINS))
	{
		CSoundFile &sndFile = pModDoc->GetSoundFile();
		SNDMIXPLUGIN &plugin = sndFile.m_MixPlugins[m_nCurrentPlugin];

		CString s;
		GetDlgItemText(IDC_EDIT13, s);
		if (s != mpt::ToCString(plugin.GetName()))
		{
			plugin.Info.szName = mpt::ToCharset(mpt::Charset::Locale, s);
			if(sndFile.GetModSpecifications().supportsPlugins)
				pModDoc->SetModified();
			pModDoc->UpdateAllViews(this, PluginHint(m_nCurrentPlugin + 1).Info().Names(), this);

			IMixPlugin *pPlugin = plugin.pMixPlugin;
			if(pPlugin != nullptr && pPlugin->GetEditor() != nullptr)
			{
				pPlugin->GetEditor()->SetTitle();
			}
			// Update channel plugin assignments
			PopulateChannelPlugins(m_nCurrentPlugin);

			m_CbnPlugin.SetRedraw(FALSE);
			AddPluginNamesToCombobox(m_CbnPlugin, sndFile.m_MixPlugins, true, m_nCurrentPlugin);
			m_CbnPlugin.SetCurSel(m_nCurrentPlugin);
			m_CbnPlugin.Invalidate(FALSE);
			m_CbnPlugin.SetRedraw(TRUE);
		}
	}
}


void CViewGlobals::OnPrevPlugin()
{
	CModDoc *pModDoc = GetDocument();
	if ((m_nCurrentPlugin > 0) && (pModDoc))
	{
		m_nCurrentPlugin--;
		UpdateView(PluginHint(m_nCurrentPlugin + 1).Info());
	}
}


void CViewGlobals::OnNextPlugin()
{
	CModDoc *pModDoc = GetDocument();
	if ((m_nCurrentPlugin < MAX_MIXPLUGINS-1) && (pModDoc))
	{
		m_nCurrentPlugin++;
		UpdateView(PluginHint(m_nCurrentPlugin + 1).Info());

	}
}


void CViewGlobals::OnPluginChanged()
{
	CModDoc *pModDoc = GetDocument();
	int nPlugin = m_CbnPlugin.GetCurSel();
	if ((pModDoc) && (nPlugin >= 0) && (nPlugin < MAX_MIXPLUGINS))
	{
		m_nCurrentPlugin = (PLUGINDEX)nPlugin;
		UpdateView(PluginHint(m_nCurrentPlugin + 1).Info());
	}
	m_CbnPreset.SetCurSel(0);
}


void CViewGlobals::OnSelectPlugin()
{
#ifndef NO_PLUGINS
	CModDoc *pModDoc = GetDocument();

	if ((pModDoc) && (m_nCurrentPlugin < MAX_MIXPLUGINS))
	{
		CSelectPluginDlg dlg(pModDoc, m_nCurrentPlugin, this);
		if (dlg.DoModal() == IDOK)
		{
			if(pModDoc->GetSoundFile().GetModSpecifications().supportsPlugins)
				pModDoc->SetModified();
		}
		OnPluginChanged();
		OnParamChanged();
	}
#endif // NO_PLUGINS
}


void CViewGlobals::OnRemovePlugin()
{
#ifndef NO_PLUGINS
	CModDoc *pModDoc = GetDocument();

	if(pModDoc && m_nCurrentPlugin < MAX_MIXPLUGINS && Reporting::Confirm(MPT_UFORMAT("Remove plugin FX{}: {}?")(m_nCurrentPlugin + 1, pModDoc->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].GetName()), false, true) == cnfYes)
	{
		if(pModDoc->RemovePlugin(m_nCurrentPlugin))
		{
			OnPluginChanged();
			OnParamChanged();
		}
	}
#endif  // NO_PLUGINS
}


LRESULT CViewGlobals::OnParamAutomated(WPARAM plugin, LPARAM param)
{
	if(plugin == m_nCurrentPlugin && static_cast<PlugParamIndex>(param) == m_nCurrentParam)
	{
		OnParamChanged();
	}
	return 0;
}


LRESULT CViewGlobals::OnDryWetRatioChangedFromPlayer(WPARAM plugin, LPARAM)
{
	if(plugin == m_nCurrentPlugin)
	{
		UpdateDryWetDisplay();
	}
	return 0;
}


void CViewGlobals::OnParamChanged()
{
	PlugParamIndex cursel = static_cast<PlugParamIndex>(m_CbnParam.GetItemData(m_CbnParam.GetCurSel()));

	IMixPlugin *pPlugin = GetCurrentPlugin();

	if(pPlugin != nullptr && cursel != static_cast<PlugParamIndex>(CB_ERR))
	{
		const PlugParamIndex nParams = pPlugin->GetNumParameters();
		if(cursel < nParams) m_nCurrentParam = cursel;
		if(m_nCurrentParam < nParams)
		{
			const auto value = pPlugin->GetScaledUIParam(m_nCurrentParam);
			int intValue = mpt::saturate_round<int>(value * 100.0f);
			LockControls();
			if(GetFocus() != GetDlgItem(IDC_EDIT14))
			{
				CString s = pPlugin->GetFormattedParamValue(m_nCurrentParam).Trim();
				if(s.IsEmpty())
				{
					s.Format(_T("%f"), value);
				}
				SetDlgItemText(IDC_EDIT14, s);
			}
			m_sbValue.SetPos(intValue);
			UnlockControls();
			return;
		}
	}
	SetDlgItemText(IDC_EDIT14, _T(""));
	m_sbValue.SetPos(0);
}


// When focussing the parameter value, show its real value to edit
void CViewGlobals::OnFocusParam()
{
	IMixPlugin *pPlugin = GetCurrentPlugin();
	if(pPlugin != nullptr)
	{
		const PlugParamIndex nParams = pPlugin->GetNumParameters();
		if(m_nCurrentParam < nParams)
		{
			TCHAR s[32];
			float fValue = pPlugin->GetScaledUIParam(m_nCurrentParam);
			_stprintf(s, _T("%f"), fValue);
			LockControls();
			SetDlgItemText(IDC_EDIT14, s);
			UnlockControls();
		}
	}
}


void CViewGlobals::SetPluginModified()
{
	CModDoc *pModDoc = GetDocument();
	if(pModDoc->GetSoundFile().GetModSpecifications().supportsPlugins)
		pModDoc->SetModified();
	pModDoc->UpdateAllViews(this, PluginHint(m_nCurrentPlugin + 1).Info());
}


void CViewGlobals::OnProgramChanged()
{
	int32 curProg = static_cast<int32>(m_CbnPreset.GetItemData(m_CbnPreset.GetCurSel()));

	IMixPlugin *pPlugin = GetCurrentPlugin();
	if(pPlugin != nullptr)
	{
		const int32 numProgs = pPlugin->GetNumPrograms();
		if(curProg <= numProgs)
		{
			pPlugin->SetCurrentProgram(curProg);
			// Update parameter display
			OnParamChanged();

			SetPluginModified();
		}
	}
}


void CViewGlobals::OnLoadParam()
{
	IMixPlugin *pPlugin = GetCurrentPlugin();
	if(pPlugin != nullptr && pPlugin->LoadProgram())
	{
		int32 currentProg = pPlugin->GetCurrentProgram();
		FillPluginProgramBox(currentProg, currentProg);
		m_CbnPreset.SetCurSel(0);
		SetPluginModified();
	}
}


void CViewGlobals::OnSaveParam()
{
	IMixPlugin *pPlugin = GetCurrentPlugin();
	if(pPlugin != nullptr)
	{
		pPlugin->SaveProgram();
	}
}


void CViewGlobals::OnSetParameter()
{
	if(m_nCurrentPlugin >= MAX_MIXPLUGINS || IsLocked()) return;
	IMixPlugin *pPlugin = GetCurrentPlugin();

	if(pPlugin != nullptr)
	{
		const PlugParamIndex nParams = pPlugin->GetNumParameters();
		TCHAR s[32];
		GetDlgItemText(IDC_EDIT14, s, mpt::saturate_cast<int>(std::size(s)));
		if ((m_nCurrentParam < nParams) && (s[0]))
		{
			float fValue = (float)_tstof(s);
			pPlugin->SetScaledUIParam(m_nCurrentParam, fValue);
			OnParamChanged();
			SetPluginModified();
		}
	}
}


void CViewGlobals::UpdateDryWetDisplay()
{
	SNDMIXPLUGIN &plugin = GetDocument()->GetSoundFile().m_MixPlugins[m_nCurrentPlugin];
	float wetRatio = 1.0f - plugin.fDryRatio, dryRatio = plugin.fDryRatio;
	m_sbDryRatio.SetPos(mpt::saturate_round<int>(wetRatio * 100));
	if(plugin.IsExpandedMix())
	{
		wetRatio = 2.0f * wetRatio - 1.0f;
		dryRatio = -wetRatio;
	}
	int wetInt = mpt::saturate_round<int>(wetRatio * 100), dryInt = mpt::saturate_round<int>(dryRatio * 100);
	SetDlgItemText(IDC_STATIC8, MPT_TFORMAT("{}% wet, {}% dry")(wetInt, dryInt).c_str());
}


void CViewGlobals::OnMixModeChanged()
{
	CModDoc *pModDoc = GetDocument();
	if ((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return;

	pModDoc->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].SetMasterEffect(IsDlgButtonChecked(IDC_CHECK9) != BST_UNCHECKED);
	SetPluginModified();
}


void CViewGlobals::OnBypassChanged()
{
	CModDoc *pModDoc = GetDocument();
	if ((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return;

	pModDoc->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].SetBypass(IsDlgButtonChecked(IDC_CHECK10) != BST_UNCHECKED);
	SetPluginModified();
}


void CViewGlobals::OnWetDryExpandChanged()
{
	CModDoc *pModDoc = GetDocument();
	if ((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return;

	pModDoc->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].SetExpandedMix(IsDlgButtonChecked(IDC_CHECK12) != BST_UNCHECKED);
	UpdateDryWetDisplay();
	SetPluginModified();
}


void CViewGlobals::OnSpecialMixProcessingChanged()
{
	CModDoc *pModDoc = GetDocument();
	if ((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return;

	pModDoc->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].SetMixMode((uint8)m_CbnSpecialMixProcessing.GetCurSel());
	SetPluginModified();
}


void CViewGlobals::OnDryMixChanged()
{
	CModDoc *pModDoc = GetDocument();
	if ((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return;

	pModDoc->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].SetWetMix(IsDlgButtonChecked(IDC_CHECK11) != BST_UNCHECKED);
	SetPluginModified();
}


void CViewGlobals::OnEditPlugin()
{
	CModDoc *pModDoc = GetDocument();
	if ((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return;
	pModDoc->TogglePluginEditor(m_nCurrentPlugin, CMainFrame::GetInputHandler()->ShiftPressed());
	return;
}


void CViewGlobals::OnOutputRoutingChanged()
{
	CModDoc *pModDoc = GetDocument();
	int nroute;

	if ((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return;
	CSoundFile &sndFile = pModDoc->GetSoundFile();
	SNDMIXPLUGIN &plugin = sndFile.m_MixPlugins[m_nCurrentPlugin];
	nroute = static_cast<int>(m_CbnOutput.GetItemData(m_CbnOutput.GetCurSel()));

	if(nroute == 1)
	{
		// Add new plugin
		for(PLUGINDEX i = m_nCurrentPlugin + 1; i < MAX_MIXPLUGINS; i++)
		{
			if(!sndFile.m_MixPlugins[i].IsValidPlugin())
			{
				CSelectPluginDlg dlg(pModDoc, i, this);
				if(dlg.DoModal() != IDOK)
					return;
				
				plugin.SetOutputPlugin(i);
				SetPluginModified();
				nroute = 0x80 + i;
				m_nCurrentPlugin = i;
				m_CbnPlugin.SetCurSel(i);
				OnPluginChanged();
				break;
			}
		}
		if(nroute == 1)
		{
			Reporting::Error("All following plugin slots are used.", "Unable to add new plugin");
			return;
		}
	}

	if(!nroute)
		plugin.SetOutputToMaster();
	else
		plugin.SetOutputPlugin(static_cast<PLUGINDEX>(nroute - 0x80));

	SetPluginModified();
}



LRESULT CViewGlobals::OnModViewMsg(WPARAM wParam, LPARAM /*lParam*/)
{
	switch(wParam)
	{
		case VIEWMSG_SETFOCUS:
		case VIEWMSG_SETACTIVE:
			GetParentFrame()->SetActiveView(this);
			SetFocus();
			return 0;
		default:
			return 0;
	}
}

void CViewGlobals::OnMovePlugToSlot()
{
	if(GetCurrentPlugin() == nullptr)
	{
		return;
	}

	// If any plugin routes its output to the current plugin, we shouldn't try to move it before that plugin...
	PLUGINDEX defaultIndex = 0;
	CSoundFile &sndFile = GetDocument()->GetSoundFile();
	for(PLUGINDEX i = 0; i < m_nCurrentPlugin; i++)
	{
		if(sndFile.m_MixPlugins[i].GetOutputPlugin() == m_nCurrentPlugin)
		{
			defaultIndex = i + 1;
		}
	}

	std::vector<PLUGINDEX> emptySlots;
	BuildEmptySlotList(emptySlots);

	CMoveFXSlotDialog dlg(this, m_nCurrentPlugin, emptySlots, defaultIndex, false, !sndFile.m_MixPlugins[m_nCurrentPlugin].IsOutputToMaster());

	if(dlg.DoModal() == IDOK)
	{
		size_t toIndex = dlg.GetSlotIndex();
		do
		{
			const SNDMIXPLUGIN &curPlugin = sndFile.m_MixPlugins[m_nCurrentPlugin];
			SNDMIXPLUGIN &newPlugin = sndFile.m_MixPlugins[emptySlots[toIndex]];
			const PLUGINDEX nextPlugin = curPlugin.GetOutputPlugin();

			MovePlug(m_nCurrentPlugin, emptySlots[toIndex]);

			if(nextPlugin == PLUGINDEX_INVALID || toIndex == emptySlots.size() - 1)
			{
				break;
			}

			m_nCurrentPlugin = nextPlugin;

			if(dlg.DoMoveChain())
			{
				toIndex++;
				newPlugin.SetOutputPlugin(emptySlots[toIndex]);
			}
		} while(dlg.DoMoveChain());

		m_CbnPlugin.SetCurSel(dlg.GetSlot());
		OnPluginChanged();
		GetDocument()->UpdateAllViews(nullptr, PluginHint().Names().Info());
	}
}


// Functor for adjusting plug indexes in modcommands. Adjusts all instrument column values in
// range [m_nInstrMin, m_nInstrMax] by m_nDiff.
struct PlugIndexModifier
{
	PlugIndexModifier(PLUGINDEX nMin, PLUGINDEX nMax, int nDiff) :
		m_nDiff(nDiff), m_nInstrMin(nMin), m_nInstrMax(nMax) {}
	void operator()(ModCommand& m)
	{
		if (m.IsInstrPlug() && m.instr >= m_nInstrMin && m.instr <= m_nInstrMax)
			m.instr = (ModCommand::INSTR)((int)m.instr + m_nDiff);
	}
	int m_nDiff;
	ModCommand::INSTR m_nInstrMin;
	ModCommand::INSTR m_nInstrMax;
};


bool CViewGlobals::MovePlug(PLUGINDEX src, PLUGINDEX dest, bool bAdjustPat)
{
	if (src == dest)
		return false;
	CModDoc *pModDoc = GetDocument();
	CSoundFile &sndFile = pModDoc->GetSoundFile();

	BeginWaitCursor();

	CriticalSection cs;

	// Move plug data
	sndFile.m_MixPlugins[dest] = std::move(sndFile.m_MixPlugins[src]);
	sndFile.m_MixPlugins[src] = SNDMIXPLUGIN();

	// Prevent plug from pointing backwards.
	if(!sndFile.m_MixPlugins[dest].IsOutputToMaster())
	{
		PLUGINDEX nOutput = sndFile.m_MixPlugins[dest].GetOutputPlugin();
		if (nOutput <= dest && nOutput != PLUGINDEX_INVALID)
		{
			sndFile.m_MixPlugins[dest].SetOutputToMaster();
		}
	}

	// Update current plug
	IMixPlugin *pPlugin = sndFile.m_MixPlugins[dest].pMixPlugin;
	if(pPlugin != nullptr)
	{
		pPlugin->SetSlot(dest);
		if(pPlugin->GetEditor() != nullptr)
		{
			pPlugin->GetEditor()->SetTitle();
		}
	}

	// Update all other plugs' outputs
	for (PLUGINDEX nPlug = 0; nPlug < src; nPlug++)
	{
		if(!sndFile.m_MixPlugins[nPlug].IsOutputToMaster())
		{
			if(sndFile.m_MixPlugins[nPlug].GetOutputPlugin() == src)
			{
				sndFile.m_MixPlugins[nPlug].SetOutputPlugin(dest);
			}
		}
	}
	// Update channels
	for (CHANNELINDEX nChn = 0; nChn < sndFile.GetNumChannels(); nChn++)
	{
		if (sndFile.ChnSettings[nChn].nMixPlugin == src + 1u)
		{
			sndFile.ChnSettings[nChn].nMixPlugin = dest + 1u;
		}
	}

	// Update instruments
	for (INSTRUMENTINDEX nIns = 1; nIns <= sndFile.GetNumInstruments(); nIns++)
	{
		if (sndFile.Instruments[nIns] && (sndFile.Instruments[nIns]->nMixPlug == src + 1))
		{
			sndFile.Instruments[nIns]->nMixPlug = dest + 1u;
		}
	}

	// Update MODCOMMANDs so that they won't be referring to old indexes (e.g. with NOTE_PC).
	if (bAdjustPat && sndFile.GetModSpecifications().HasNote(NOTE_PC))
		sndFile.Patterns.ForEachModCommand(PlugIndexModifier(src + 1, src + 1, int(dest) - int(src)));

	cs.Leave();

	SetPluginModified();

	EndWaitCursor();

	return true;
}


void CViewGlobals::BuildEmptySlotList(std::vector<PLUGINDEX> &emptySlots)
{
	const CSoundFile &sndFile = GetDocument()->GetSoundFile();

	emptySlots.clear();

	for(PLUGINDEX nSlot = 0; nSlot < MAX_MIXPLUGINS; nSlot++)
	{
		if(sndFile.m_MixPlugins[nSlot].pMixPlugin == nullptr)
		{
			emptySlots.push_back(nSlot);
		}
	}
	return;
}

void CViewGlobals::OnInsertSlot()
{
	CString prompt;
	CSoundFile &sndFile = GetDocument()->GetSoundFile();
	prompt.Format(_T("Insert empty slot before slot FX%d?"), m_nCurrentPlugin + 1);

	// If last plugin slot is occupied, move it so that the plugin is not lost.
	// This could certainly be improved...
	bool moveLastPlug = false;

	if(sndFile.m_MixPlugins[MAX_MIXPLUGINS - 1].pMixPlugin)
	{
		if(sndFile.m_MixPlugins[MAX_MIXPLUGINS - 2].pMixPlugin == nullptr)
		{
			moveLastPlug = true;
		} else
		{
			prompt += _T("\nWarning: plugin data in last slot will be lost.");
		}
	}
	if(Reporting::Confirm(prompt) == cnfYes)
	{

		// Delete last plug...
		if(sndFile.m_MixPlugins[MAX_MIXPLUGINS - 1].pMixPlugin)
		{
			if(moveLastPlug)
			{
				MovePlug(MAX_MIXPLUGINS - 1, MAX_MIXPLUGINS - 2, true);
			} else
			{
				sndFile.m_MixPlugins[MAX_MIXPLUGINS - 1].Destroy();
				MemsetZero(sndFile.m_MixPlugins[MAX_MIXPLUGINS - 1].Info);
			}
		}

		// Update MODCOMMANDs so that they won't be referring to old indexes (e.g. with NOTE_PC).
		if(sndFile.GetModSpecifications().HasNote(NOTE_PC))
			sndFile.Patterns.ForEachModCommand(PlugIndexModifier(m_nCurrentPlugin + 1, MAX_MIXPLUGINS - 1, 1));


		for(PLUGINDEX nSlot = MAX_MIXPLUGINS - 1; nSlot > m_nCurrentPlugin; nSlot--)
		{
			if(sndFile.m_MixPlugins[nSlot-1].pMixPlugin)
			{
				MovePlug(nSlot - 1, nSlot, NoPatternAdjust);
			}
		}

		m_CbnPlugin.SetCurSel(m_nCurrentPlugin + 1);
		OnPluginChanged();
		GetDocument()->UpdateAllViews(nullptr, PluginHint().Names().Info());

		SetPluginModified();
	}

}


void CViewGlobals::OnClonePlug()
{
	if(GetCurrentPlugin() == nullptr)
	{
		return;
	}

	CSoundFile &sndFile = GetDocument()->GetSoundFile();

	std::vector<PLUGINDEX> emptySlots;
	BuildEmptySlotList(emptySlots);

	CMoveFXSlotDialog dlg(this, m_nCurrentPlugin, emptySlots, 0, true, !sndFile.m_MixPlugins[m_nCurrentPlugin].IsOutputToMaster());

	if(dlg.DoModal() == IDOK)
	{
		size_t toIndex = dlg.GetSlotIndex();
		do
		{
			const SNDMIXPLUGIN &curPlugin = sndFile.m_MixPlugins[m_nCurrentPlugin];
			SNDMIXPLUGIN &newPlugin = sndFile.m_MixPlugins[emptySlots[toIndex]];

			GetDocument()->ClonePlugin(newPlugin, curPlugin);
			IMixPlugin *mixPlug = newPlugin.pMixPlugin;
			if(mixPlug != nullptr && mixPlug->IsInstrument() && GetDocument()->HasInstrumentForPlugin(emptySlots[toIndex]) == INSTRUMENTINDEX_INVALID)
			{
				GetDocument()->InsertInstrumentForPlugin(emptySlots[toIndex]);
			}

			if(curPlugin.IsOutputToMaster() || toIndex == emptySlots.size() - 1)
			{
				break;
			}

			m_nCurrentPlugin = curPlugin.GetOutputPlugin();

			if(dlg.DoMoveChain())
			{
				toIndex++;
				newPlugin.SetOutputPlugin(emptySlots[toIndex]);
			}
		} while(dlg.DoMoveChain());

		m_CbnPlugin.SetCurSel(dlg.GetSlot());
		OnPluginChanged();
		PopulateChannelPlugins();
		GetDocument()->UpdateAllViews(this, PluginHint().Names(), this);

		SetPluginModified();
	}
}


// The plugin param box is only filled when it gets the focus (done here).
void CViewGlobals::OnFillParamCombo()
{
	// no need to fill it again.
	if(m_CbnParam.GetCount() > 1)
		return;

	IMixPlugin *pPlugin = GetCurrentPlugin();
	if(pPlugin == nullptr) return;

	const PlugParamIndex nParams = pPlugin->GetNumParameters();
	m_CbnParam.SetRedraw(FALSE);
	m_CbnParam.ResetContent();

	AddPluginParameternamesToCombobox(m_CbnParam, *pPlugin);

	if (m_nCurrentParam >= nParams) m_nCurrentParam = 0;
	m_CbnParam.SetCurSel(m_nCurrentParam);
	m_CbnParam.SetRedraw(TRUE);
	m_CbnParam.Invalidate(FALSE);
}


// The preset box is only filled when it gets the focus (done here).
void CViewGlobals::OnFillProgramCombo()
{
	// no need to fill it again.
	if(m_CbnPreset.GetCount() > 1)
		return;

	IMixPlugin *pPlugin = GetCurrentPlugin();
	if(pPlugin == nullptr) return;

	FillPluginProgramBox(0, pPlugin->GetNumPrograms() - 1);
	m_CbnPreset.SetCurSel(pPlugin->GetCurrentProgram());
}


void CViewGlobals::FillPluginProgramBox(int32 firstProg, int32 lastProg)
{
	IMixPlugin *pPlugin = GetCurrentPlugin();

	m_CbnPreset.SetRedraw(FALSE);
	m_CbnPreset.ResetContent();

	pPlugin->CacheProgramNames(firstProg, lastProg + 1);
	for (int32 i = firstProg; i <= lastProg; i++)
	{
		m_CbnPreset.SetItemData(m_CbnPreset.AddString(pPlugin->GetFormattedProgramName(i)), i);
	}

	m_CbnPreset.SetRedraw(TRUE);
	m_CbnPreset.Invalidate(FALSE);
}


BOOL CViewGlobals::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;
	const auto &chnSettings = GetDocument()->GetSoundFile().ChnSettings;
	switch(id)
	{
		case IDC_EDIT1:
		case IDC_EDIT3:
		case IDC_EDIT5:
		case IDC_EDIT7:
			text = CModDoc::LinearToDecibels(chnSettings[m_nActiveTab * CHANNELS_IN_TAB + (id - IDC_EDIT1) / 2].nVolume, 64.0);
			break;
		case IDC_SLIDER1:
		case IDC_SLIDER3:
		case IDC_SLIDER5:
		case IDC_SLIDER7:
			text = CModDoc::LinearToDecibels(chnSettings[m_nActiveTab * CHANNELS_IN_TAB + (id - IDC_SLIDER1) / 2].nVolume, 64.0);
			break;
		case IDC_EDIT2:
		case IDC_EDIT4:
		case IDC_EDIT6:
		case IDC_EDIT8:
			text = CModDoc::PanningToString(chnSettings[m_nActiveTab * CHANNELS_IN_TAB + (id - IDC_EDIT2) / 2].nPan, 128);
			break;
		case IDC_SLIDER2:
		case IDC_SLIDER4:
		case IDC_SLIDER6:
		case IDC_SLIDER8:
			text = CModDoc::PanningToString(chnSettings[m_nActiveTab * CHANNELS_IN_TAB + (id - IDC_SLIDER2) / 2].nPan, 128);
			break;
		case IDC_EDIT16:
			{
				const auto gain = GetDocument()->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].GetGain();
				text = CModDoc::LinearToDecibels(gain ? gain : 10, 10.0);
			}
			break;
		case IDC_BUTTON5:
			text = _T("Previous Plugin");
			break;
		case IDC_BUTTON4:
			text = _T("Next Plugin");
			break;
		default:
			return FALSE;
	}

	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