Initial community commit

This commit is contained in:
Jef
2024-09-24 14:54:57 +02:00
parent 537bcbc862
commit 20d28e80a5
16810 changed files with 4640254 additions and 2 deletions
@@ -0,0 +1,589 @@
/*
* AboutDialog.cpp
* ---------------
* Purpose: About dialog with credits, system information and a fancy demo effect.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "resource.h"
#include "AboutDialog.h"
#include "Image.h"
#include "Mptrack.h"
#include "TrackerSettings.h"
#include "BuildVariants.h"
#include "../common/version.h"
#include "../misc/mptWine.h"
OPENMPT_NAMESPACE_BEGIN
CAboutDlg *CAboutDlg::instance = nullptr;
BEGIN_MESSAGE_MAP(CRippleBitmap, CWnd)
ON_WM_PAINT()
ON_WM_ERASEBKGND()
ON_WM_MOUSEMOVE()
ON_WM_MOUSEHOVER()
ON_WM_MOUSELEAVE()
END_MESSAGE_MAP()
CRippleBitmap::CRippleBitmap()
{
m_bitmapSrc = LoadPixelImage(GetResource(MAKEINTRESOURCE(IDB_MPTRACK), _T("PNG")));
m_bitmapTarget = std::make_unique<RawGDIDIB>(m_bitmapSrc->Width(), m_bitmapSrc->Height());
m_offset1.assign(m_bitmapSrc->Pixels().size(), 0);
m_offset2.assign(m_bitmapSrc->Pixels().size(), 0);
m_frontBuf = m_offset2.data();
m_backBuf = m_offset1.data();
// Pre-fill first and last row of output bitmap, since those won't be touched.
const RawGDIDIB::Pixel *in1 = m_bitmapSrc->Pixels().data(), *in2 = m_bitmapSrc->Pixels().data() + (m_bitmapSrc->Height() - 1) * m_bitmapSrc->Width();
RawGDIDIB::Pixel *out1 = m_bitmapTarget->Pixels().data(), *out2 = m_bitmapTarget->Pixels().data() + (m_bitmapSrc->Height() - 1) * m_bitmapSrc->Width();
for(uint32 i = 0; i < m_bitmapSrc->Width(); i++)
{
*(out1++) = *(in1++);
*(out2++) = *(in2++);
}
MemsetZero(m_bi);
m_bi.biSize = sizeof(BITMAPINFOHEADER);
m_bi.biWidth = m_bitmapSrc->Width();
m_bi.biHeight = -(int32)m_bitmapSrc->Height();
m_bi.biPlanes = 1;
m_bi.biBitCount = 32;
m_bi.biCompression = BI_RGB;
m_bi.biSizeImage = m_bitmapSrc->Width() * m_bitmapSrc->Height() * 4;
}
CRippleBitmap::~CRippleBitmap()
{
if(!m_showMouse)
{
ShowCursor(TRUE);
}
}
void CRippleBitmap::OnMouseMove(UINT nFlags, CPoint point)
{
// Rate limit in order to avoid too may ripples.
DWORD now = timeGetTime();
if(now - m_lastRipple < UPDATE_INTERVAL)
return;
m_lastRipple = now;
// Initiate ripples at cursor location
point.x = Util::ScalePixelsInv(point.x, m_hWnd);
point.y = Util::ScalePixelsInv(point.y, m_hWnd);
Limit(point.x, 1, int(m_bitmapSrc->Width()) - 2);
Limit(point.y, 2, int(m_bitmapSrc->Height()) - 3);
int32 *p = m_backBuf + point.x + point.y * m_bitmapSrc->Width();
p[0] += (nFlags & MK_LBUTTON) ? 50 : 150;
p[0] += (nFlags & MK_MBUTTON) ? 150 : 0;
int32 w = m_bitmapSrc->Width();
// Make the initial point of this ripple a bit "fatter".
p[-1] += p[0] / 2; p[1] += p[0] / 2;
p[-w] += p[0] / 2; p[w] += p[0] / 2;
p[-w - 1] += p[0] / 4; p[-w + 1] += p[0] / 4;
p[w - 1] += p[0] / 4; p[w + 1] += p[0] / 4;
m_damp = !(nFlags & MK_RBUTTON);
m_activity = true;
// Wine will only ever generate MouseLeave message when the message
// queue is completely empty and the hover timeout has expired.
// This results in a hidden mouse cursor long after it had already left the
// control.
// Avoid hiding the mouse cursor on Wine. Interferring with the users input
// methods is an absolute no-go.
if(mpt::OS::Windows::IsWine())
{
return;
}
TRACKMOUSEEVENT me;
me.cbSize = sizeof(TRACKMOUSEEVENT);
me.hwndTrack = m_hWnd;
me.dwFlags = TME_LEAVE | TME_HOVER;
me.dwHoverTime = 1500;
if(TrackMouseEvent(&me) && m_showMouse)
{
ShowCursor(FALSE);
m_showMouse = false;
}
}
void CRippleBitmap::OnMouseLeave()
{
if(!m_showMouse)
{
ShowCursor(TRUE);
m_showMouse = true;
}
}
void CRippleBitmap::OnPaint()
{
CPaintDC dc(this);
CRect rect;
GetClientRect(rect);
StretchDIBits(dc.m_hDC,
0, 0, rect.Width(), rect.Height(),
0, 0, m_bitmapTarget->Width(), m_bitmapTarget->Height(),
m_bitmapTarget->Pixels().data(),
reinterpret_cast<BITMAPINFO *>(&m_bi), DIB_RGB_COLORS, SRCCOPY);
}
bool CRippleBitmap::Animate()
{
// Were there any pixels being moved in the last frame?
if(!m_activity)
return false;
DWORD now = timeGetTime();
if(now - m_lastFrame < UPDATE_INTERVAL)
return true;
m_lastFrame = now;
m_activity = false;
m_frontBuf = (m_frame ? m_offset2 : m_offset1).data();
m_backBuf = (m_frame ? m_offset1 : m_offset2).data();
// Spread the ripples...
const int32 w = m_bitmapSrc->Width(), h = m_bitmapSrc->Height();
const int32 numPixels = w * (h - 2);
const int32 *back = m_backBuf + w;
int32 *front = m_frontBuf + w;
for(int32 i = numPixels; i != 0; i--, back++, front++)
{
(*front) = (back[-1] + back[1] + back[w] + back[-w]) / 2 - (*front);
if(m_damp) (*front) -= (*front) >> 5;
}
// ...and compute the final picture.
const int32 *offset = m_frontBuf + w;
const RawGDIDIB::Pixel *pixelIn = m_bitmapSrc->Pixels().data() + w;
RawGDIDIB::Pixel *pixelOut = m_bitmapTarget->Pixels().data() + w;
RawGDIDIB::Pixel *limitMin = m_bitmapSrc->Pixels().data(), *limitMax = m_bitmapSrc->Pixels().data() + m_bitmapSrc->Pixels().size() - 1;
for(int32 i = numPixels; i != 0; i--, pixelIn++, pixelOut++, offset++)
{
// Compute pixel displacement
const int32 xOff = offset[-1] - offset[1];
const int32 yOff = offset[-w] - offset[w];
if(xOff | yOff)
{
const RawGDIDIB::Pixel *p = pixelIn + xOff + yOff * w;
Limit(p, limitMin, limitMax);
// Add a bit of shading depending on how far we're displacing the pixel...
pixelOut->r = mpt::saturate_cast<uint8>(p->r + (p->r * xOff) / 32);
pixelOut->g = mpt::saturate_cast<uint8>(p->g + (p->g * xOff) / 32);
pixelOut->b = mpt::saturate_cast<uint8>(p->b + (p->b * xOff) / 32);
// ...and mix it with original picture
pixelOut->r = (pixelOut->r + pixelIn->r) / 2u;
pixelOut->g = (pixelOut->g + pixelIn->g) / 2u;
pixelOut->b = (pixelOut->b + pixelIn->b) / 2u;
// And now some cheap image smoothing...
pixelOut[-1].r = (pixelOut->r + pixelOut[-1].r) / 2u;
pixelOut[-1].g = (pixelOut->g + pixelOut[-1].g) / 2u;
pixelOut[-1].b = (pixelOut->b + pixelOut[-1].b) / 2u;
pixelOut[-w].r = (pixelOut->r + pixelOut[-w].r) / 2u;
pixelOut[-w].g = (pixelOut->g + pixelOut[-w].g) / 2u;
pixelOut[-w].b = (pixelOut->b + pixelOut[-w].b) / 2u;
m_activity = true; // Also use this to update activity status...
} else
{
*pixelOut = *pixelIn;
}
}
m_frame = !m_frame;
InvalidateRect(NULL, FALSE);
return true;
}
CAboutDlg::~CAboutDlg()
{
instance = nullptr;
}
void CAboutDlg::OnOK()
{
instance = nullptr;
if(m_TimerID != 0)
{
KillTimer(m_TimerID);
m_TimerID = 0;
}
DestroyWindow();
delete this;
}
void CAboutDlg::OnCancel()
{
OnOK();
}
BOOL CAboutDlg::OnInitDialog()
{
CDialog::OnInitDialog();
mpt::ustring app;
app += MPT_UFORMAT("OpenMPT{} ({} ({} bit))")(
BuildVariants().GetBuildVariantDescription(BuildVariants().GetBuildVariant()),
mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture()),
mpt::arch_bits)
+ U_("\n");
app += U_("Version ") + Build::GetVersionStringSimple() + U_("\n\n");
app += Build::GetURL(Build::Url::Website) + U_("\n");
SetDlgItemText(IDC_EDIT3, mpt::ToCString(mpt::String::Replace(app, U_("\n"), U_("\r\n"))));
m_bmp.SubclassDlgItem(IDC_BITMAP1, this);
m_Tab.InsertItem(TCIF_TEXT, 0, _T("OpenMPT"), 0, 0, 0, 0);
m_Tab.InsertItem(TCIF_TEXT, 1, _T("Components"), 0, 0, 0, 0);
m_Tab.InsertItem(TCIF_TEXT, 2, _T("Credits"), 0, 0, 0, 0);
m_Tab.InsertItem(TCIF_TEXT, 3, _T("License"), 0, 0, 0, 0);
m_Tab.InsertItem(TCIF_TEXT, 4, _T("Resources"), 0, 0, 0, 0);
if(mpt::OS::Windows::IsWine()) m_Tab.InsertItem(TCIF_TEXT, 5, _T("Wine"), 0, 0, 0, 0);
m_Tab.SetCurSel(0);
OnTabChange(nullptr, nullptr);
if(m_TimerID != 0)
{
KillTimer(m_TimerID);
m_TimerID = 0;
}
m_TimerID = SetTimer(TIMERID_ABOUT_DEFAULT, CRippleBitmap::UPDATE_INTERVAL, nullptr);
return TRUE;
}
void CAboutDlg::OnTimer(UINT_PTR nIDEvent)
{
if(nIDEvent == m_TimerID)
{
m_bmp.Animate();
}
}
void CAboutDlg::OnTabChange(NMHDR * /*pNMHDR*/ , LRESULT * /*pResult*/ )
{
m_TabEdit.SetWindowText(mpt::ToCString(mpt::String::Replace(GetTabText(m_Tab.GetCurSel()), U_("\n"), U_("\r\n"))));
}
#ifdef MPT_ENABLE_ARCH_INTRINSICS
static mpt::ustring ProcSupportToString(uint32 procSupport)
{
std::vector<mpt::ustring> features;
#if MPT_COMPILER_MSVC
#if defined(MPT_ENABLE_ARCH_X86)
features.push_back(U_("x86"));
#endif
#if defined(MPT_ENABLE_ARCH_AMD64)
features.push_back(U_("amd64"));
#endif
struct ProcFlag
{
decltype(procSupport) flag;
const char *name;
};
static constexpr ProcFlag flags[] =
{
{ 0, "" },
#if defined(MPT_ENABLE_ARCH_X86) || defined(MPT_ENABLE_ARCH_AMD64)
{ CPU::feature::mmx, "mmx" },
{ CPU::feature::sse, "sse" },
{ CPU::feature::sse2, "sse2" },
{ CPU::feature::sse3, "sse3" },
{ CPU::feature::ssse3, "ssse3" },
{ CPU::feature::sse4_1, "sse4.1" },
{ CPU::feature::sse4_2, "sse4.2" },
{ CPU::feature::avx, "avx" },
{ CPU::feature::avx2, "avx2" },
#endif
};
for(const auto &f : flags)
{
if(procSupport & f.flag) features.push_back(mpt::ToUnicode(mpt::Charset::ASCII, f.name));
}
#else
MPT_UNUSED_VARIABLE(procSupport);
#endif
return mpt::String::Combine(features, U_(" "));
}
#endif // MPT_ENABLE_ARCH_INTRINSICS
mpt::ustring CAboutDlg::GetTabText(int tab)
{
const mpt::ustring lf = U_("\n");
const mpt::ustring yes = U_("yes");
const mpt::ustring no = U_("no");
#ifdef MPT_ENABLE_ARCH_INTRINSICS
const CPU::Info CPUInfo = CPU::Info::Get();
#endif // MPT_ENABLE_ARCH_INTRINSICS
mpt::ustring text;
switch(tab)
{
case 0:
text = U_("OpenMPT - Open ModPlug Tracker\n\n")
+ MPT_UFORMAT("Version: {}\n")(Build::GetVersionStringExtended())
+ MPT_UFORMAT("Source Code: {}\n")(SourceInfo::Current().GetUrlWithRevision() + UL_(" ") + SourceInfo::Current().GetStateString())
+ MPT_UFORMAT("Build Date: {}\n")(Build::GetBuildDateString())
+ MPT_UFORMAT("Compiler: {}\n")(Build::GetBuildCompilerString())
+ MPT_UFORMAT("Architecture: {}\n")(mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture()))
+ MPT_UFORMAT("Required Windows Kernel Level: {}\n")(mpt::OS::Windows::Version::GetName(mpt::OS::Windows::Version::GetMinimumKernelLevel()))
+ MPT_UFORMAT("Required Windows API Level: {}\n")(mpt::OS::Windows::Version::GetName(mpt::OS::Windows::Version::GetMinimumAPILevel()));
{
text += U_("Required CPU features: ");
std::vector<mpt::ustring> features;
#if MPT_COMPILER_MSVC
#if defined(_M_X64)
features.push_back(U_("x86-64"));
if(CPU::GetMinimumFeatures() & CPU::feature::avx) features.push_back(U_("avx"));
if(CPU::GetMinimumFeatures() & CPU::feature::avx2) features.push_back(U_("avx2"));
#elif defined(_M_IX86)
if(CPU::GetMinimumFeatures() & CPU::feature::sse) features.push_back(U_("sse"));
if(CPU::GetMinimumFeatures() & CPU::feature::sse2) features.push_back(U_("sse2"));
if(CPU::GetMinimumFeatures() & CPU::feature::avx) features.push_back(U_("avx"));
if(CPU::GetMinimumFeatures() & CPU::feature::avx2) features.push_back(U_("avx2"));
#else
if(CPU::GetMinimumFeatures() & CPU::feature::sse) features.push_back(U_("sse"));
if(CPU::GetMinimumFeatures() & CPU::feature::sse2) features.push_back(U_("sse2"));
if(CPU::GetMinimumFeatures() & CPU::feature::avx) features.push_back(U_("avx"));
if(CPU::GetMinimumFeatures() & CPU::feature::avx2) features.push_back(U_("avx2"));
#endif
#endif
text += mpt::String::Combine(features, U_(" "));
text += lf;
}
#ifdef MPT_ENABLE_ARCH_INTRINSICS
text += MPT_UFORMAT("Optional CPU features used: {}\n")(ProcSupportToString(CPU::GetEnabledFeatures()));
#endif // MPT_ENABLE_ARCH_INTRINSICS
text += lf;
text += MPT_UFORMAT("System Architecture: {}\n")(mpt::OS::Windows::Name(mpt::OS::Windows::GetHostArchitecture()));
#ifdef MPT_ENABLE_ARCH_INTRINSICS
text += MPT_UFORMAT("CPU: {}, Family {}, Model {}, Stepping {}\n")
( mpt::ToUnicode(mpt::Charset::ASCII, (std::strlen(CPUInfo.VendorID) > 0) ? std::string(CPUInfo.VendorID) : std::string("Generic"))
, CPUInfo.Family
, CPUInfo.Model
, CPUInfo.Stepping
);
text += MPT_UFORMAT("CPU Name: {}\n")(mpt::ToUnicode(mpt::Charset::ASCII, (std::strlen(CPUInfo.BrandID) > 0) ? std::string(CPUInfo.BrandID) : std::string("")));
text += MPT_UFORMAT("Available CPU features: {}\n")(ProcSupportToString(CPUInfo.AvailableFeatures));
#endif // MPT_ENABLE_ARCH_INTRINSICS
text += MPT_UFORMAT("Operating System: {}\n\n")(mpt::OS::Windows::Version::GetName(mpt::OS::Windows::Version::Current()));
text += MPT_UFORMAT("OpenMPT Install Path{1}: {0}\n")(theApp.GetInstallPath(), theApp.IsPortableMode() ? U_(" (portable)") : U_(""));
text += MPT_UFORMAT("OpenMPT Executable Path{1}: {0}\n")(theApp.GetInstallBinArchPath(), theApp.IsPortableMode() ? U_(" (portable)") : U_(""));
text += MPT_UFORMAT("Settings{1}: {0}\n")(theApp.GetConfigFileName(), theApp.IsPortableMode() ? U_(" (portable)") : U_(""));
break;
case 1:
{
std::vector<std::string> components = ComponentManager::Instance()->GetRegisteredComponents();
if(!TrackerSettings::Instance().ComponentsKeepLoaded)
{
text += U_("Components are loaded and unloaded as needed.\n\n");
for(const auto &component : components)
{
ComponentInfo info = ComponentManager::Instance()->GetComponentInfo(component);
mpt::ustring name = mpt::ToUnicode(mpt::Charset::ASCII, (info.name.substr(0, 9) == "Component") ? info.name.substr(9) : info.name);
if(!info.settingsKey.empty())
{
name = mpt::ToUnicode(mpt::Charset::ASCII, info.settingsKey);
}
text += name + lf;
}
} else
{
for(int available = 1; available >= 0; --available)
{
if(available)
{
text += U_("Loaded Components:\n");
} else
{
text += U_("\nUnloaded Components:\n");
}
for(const auto &component : components)
{
ComponentInfo info = ComponentManager::Instance()->GetComponentInfo(component);
if(available && info.state != ComponentStateAvailable) continue;
if(!available && info.state == ComponentStateAvailable) continue;
mpt::ustring name = mpt::ToUnicode(mpt::Charset::ASCII, (info.name.substr(0, 9) == "Component") ? info.name.substr(9) : info.name);
if(!info.settingsKey.empty())
{
name = mpt::ToUnicode(mpt::Charset::ASCII, info.settingsKey);
}
text += MPT_UFORMAT("{}: {}")
( name
, info.state == ComponentStateAvailable ? U_("ok") :
info.state == ComponentStateUnavailable? U_("missing") :
info.state == ComponentStateUnintialized ? U_("not loaded") :
info.state == ComponentStateBlocked ? U_("blocked") :
info.state == ComponentStateUnregistered ? U_("unregistered") :
U_("unknown")
);
if(info.type != ComponentTypeUnknown)
{
text += MPT_UFORMAT(" ({})")
( info.type == ComponentTypeBuiltin ? U_("builtin") :
info.type == ComponentTypeSystem ? U_("system") :
info.type == ComponentTypeSystemInstallable ? U_("system, optional") :
info.type == ComponentTypeBundled ? U_("bundled") :
info.type == ComponentTypeForeign ? U_("foreign") :
U_("unknown")
);
}
text += lf;
}
}
}
}
break;
case 2:
text += Build::GetFullCreditsString();
break;
case 3:
text += Build::GetLicenseString();
break;
case 4:
text += U_("Website:\n") + Build::GetURL(Build::Url::Website);
text += U_("\n\nForum:\n") + Build::GetURL(Build::Url::Forum);
text += U_("\n\nBug Tracker:\n") + Build::GetURL(Build::Url::Bugtracker);
text += U_("\n\nUpdates:\n") + Build::GetURL(Build::Url::Updates);
break;
case 5:
try
{
if(!theApp.GetWine())
{
text += U_("Wine integration not available.\n");
} else
{
mpt::Wine::Context & wine = *theApp.GetWine();
text += MPT_UFORMAT("Windows: {}\n")
( mpt::OS::Windows::Version::Current().IsWindows() ? yes : no
);
text += MPT_UFORMAT("Windows version: {}\n")
(
mpt::OS::Windows::Version::Current().IsAtLeast(mpt::OS::Windows::Version::Win81) ? U_("Windows 8.1") :
mpt::OS::Windows::Version::Current().IsAtLeast(mpt::OS::Windows::Version::Win8) ? U_("Windows 8") :
mpt::OS::Windows::Version::Current().IsAtLeast(mpt::OS::Windows::Version::Win7) ? U_("Windows 7") :
mpt::OS::Windows::Version::Current().IsAtLeast(mpt::OS::Windows::Version::WinVista) ? U_("Windows Vista") :
mpt::OS::Windows::Version::Current().IsAtLeast(mpt::OS::Windows::Version::WinXP) ? U_("Windows XP") :
mpt::OS::Windows::Version::Current().IsAtLeast(mpt::OS::Windows::Version::Win2000) ? U_("Windows 2000") :
mpt::OS::Windows::Version::Current().IsAtLeast(mpt::OS::Windows::Version::WinNT4) ? U_("Windows NT4") :
U_("unknown")
);
text += MPT_UFORMAT("Windows original: {}\n")
( mpt::OS::Windows::IsOriginal() ? yes : no
);
text += U_("\n");
text += MPT_UFORMAT("Wine: {}\n")
( mpt::OS::Windows::IsWine() ? yes : no
);
text += MPT_UFORMAT("Wine Version: {}\n")
( mpt::ToUnicode(mpt::Charset::UTF8, wine.VersionContext().RawVersion())
);
text += MPT_UFORMAT("Wine Build ID: {}\n")
( mpt::ToUnicode(mpt::Charset::UTF8, wine.VersionContext().RawBuildID())
);
text += MPT_UFORMAT("Wine Host Sys Name: {}\n")
( mpt::ToUnicode(mpt::Charset::UTF8, wine.VersionContext().RawHostSysName())
);
text += MPT_UFORMAT("Wine Host Release: {}\n")
( mpt::ToUnicode(mpt::Charset::UTF8, wine.VersionContext().RawHostRelease())
);
text += U_("\n");
text += MPT_UFORMAT("uname -m: {}\n")
( mpt::ToUnicode(mpt::Charset::UTF8, wine.Uname_m())
);
text += MPT_UFORMAT("HOME: {}\n")
( mpt::ToUnicode(mpt::Charset::UTF8, wine.HOME())
);
text += MPT_UFORMAT("XDG_DATA_HOME: {}\n")
( mpt::ToUnicode(mpt::Charset::UTF8, wine.XDG_DATA_HOME())
);
text += MPT_UFORMAT("XDG_CACHE_HOME: {}\n")
( mpt::ToUnicode(mpt::Charset::UTF8, wine.XDG_CACHE_HOME())
);
text += MPT_UFORMAT("XDG_CONFIG_HOME: {}\n")
( mpt::ToUnicode(mpt::Charset::UTF8, wine.XDG_CONFIG_HOME())
);
text += U_("\n");
text += MPT_UFORMAT("OpenMPT folder: {}\n")
( theApp.GetInstallPath().ToUnicode()
);
text += MPT_UFORMAT("OpenMPT folder (host): {}\n")
( mpt::ToUnicode(mpt::Charset::UTF8, wine.PathToPosix(theApp.GetInstallPath()))
);
text += MPT_UFORMAT("OpenMPT config folder: {}\n")
( theApp.GetConfigPath().ToUnicode()
);
text += MPT_UFORMAT("OpenMPT config folder (host): {}\n")
( mpt::ToUnicode(mpt::Charset::UTF8, wine.PathToPosix(theApp.GetConfigPath()))
);
text += MPT_UFORMAT("Host root: {}\n")
( wine.PathToWindows("/").ToUnicode()
);
}
} catch(const mpt::Wine::Exception & e)
{
text += U_("Exception: ") + mpt::get_exception_text<mpt::ustring>(e) + U_("\n");
}
break;
}
return text;
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_TABABOUT, m_Tab);
DDX_Control(pDX, IDC_EDITABOUT, m_TabEdit);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
ON_WM_TIMER()
ON_NOTIFY(TCN_SELCHANGE, IDC_TABABOUT, &CAboutDlg::OnTabChange)
END_MESSAGE_MAP()
OPENMPT_NAMESPACE_END
@@ -0,0 +1,83 @@
/*
* AboutDialog.h
* -------------
* Purpose: About dialog with credits, system information and a fancy demo effect.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
OPENMPT_NAMESPACE_BEGIN
class RawGDIDIB;
class CRippleBitmap: public CWnd
{
public:
static constexpr DWORD UPDATE_INTERVAL = 15; // milliseconds
protected:
BITMAPINFOHEADER m_bi;
std::unique_ptr<RawGDIDIB> m_bitmapSrc, m_bitmapTarget;
std::vector<int32> m_offset1, m_offset2;
int32 *m_frontBuf, *m_backBuf;
DWORD m_lastFrame = 0; // Time of last frame
DWORD m_lastRipple = 0; // Time of last added ripple
bool m_frame = false; // Backbuffer toggle
bool m_damp = true; // Ripple damping status
bool m_activity = true; // There are actually some ripples
bool m_showMouse = true;
public:
CRippleBitmap();
~CRippleBitmap();
bool Animate();
protected:
void OnPaint();
BOOL OnEraseBkgnd(CDC *) { return TRUE; }
void OnMouseMove(UINT nFlags, CPoint point);
void OnMouseHover(UINT nFlags, CPoint point) { OnMouseMove(nFlags, point); }
void OnMouseLeave();
DECLARE_MESSAGE_MAP()
};
class CAboutDlg: public CDialog
{
protected:
CRippleBitmap m_bmp;
CTabCtrl m_Tab;
CEdit m_TabEdit;
UINT_PTR m_TimerID = 0;
static constexpr UINT_PTR TIMERID_ABOUT_DEFAULT = 3;
public:
static CAboutDlg *instance;
~CAboutDlg();
// Implementation
protected:
BOOL OnInitDialog() override;
void OnOK() override;
void OnCancel() override;
DECLARE_MESSAGE_MAP();
void DoDataExchange(CDataExchange* pDX) override;
afx_msg void OnTabChange(NMHDR *pNMHDR, LRESULT *pResult);
void OnTimer(UINT_PTR nIDEvent);
public:
static mpt::ustring GetTabText(int tab);
};
OPENMPT_NAMESPACE_END
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,140 @@
/*
* AbstractVstEditor.h
* -------------------
* Purpose: Common plugin editor interface class. This code is shared between custom and default plugin user interfaces.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#ifndef NO_PLUGINS
#include <vector>
#include "../soundlib/Snd_defs.h"
#include "Moddoc.h"
OPENMPT_NAMESPACE_BEGIN
class IMixPlugin;
struct UpdateHint;
class CAbstractVstEditor: public CDialog
{
protected:
CMenu m_Menu;
CMenu m_PresetMenu;
std::vector<std::unique_ptr<CMenu>> m_presetMenuGroup;
CMenu m_InputMenu;
CMenu m_OutputMenu;
CMenu m_MacroMenu;
CMenu m_OptionsMenu;
static UINT m_clipboardFormat;
int32 m_currentPresetMenu = 0;
int32 m_clientHeight;
int m_nLearnMacro = -1;
int m_nCurProg = -1;
INSTRUMENTINDEX m_nInstrument;
bool m_isMinimized = false;
bool m_updateDisplay = false;
CModDoc::NoteToChannelMap m_noteChannel; // Note -> Preview channel assignment
// Adjust window size if menu bar height changes
class WindowSizeAdjuster
{
CWnd &m_wnd;
int m_menuHeight = 0;
public:
WindowSizeAdjuster(CWnd &wnd);
~WindowSizeAdjuster();
};
public:
IMixPlugin &m_VstPlugin;
CAbstractVstEditor(IMixPlugin &plugin);
virtual ~CAbstractVstEditor();
void SetupMenu(bool force = false);
void SetTitle();
void SetLearnMacro(int inMacro);
int GetLearnMacro();
void SetPreset(int32 preset);
void UpdatePresetField();
afx_msg void OnNcLButtonDblClk(UINT nHitTest, CPoint point);
afx_msg void OnLoadPreset();
afx_msg void OnSavePreset();
afx_msg void OnCopyParameters();
afx_msg void OnPasteParameters();
afx_msg void OnRandomizePreset();
afx_msg void OnRenamePlugin();
afx_msg void OnSetPreset(UINT nID);
afx_msg void OnBypassPlug();
afx_msg void OnRecordAutomation();
afx_msg void OnRecordMIDIOut();
afx_msg void OnPassKeypressesToPlug();
afx_msg void OnSetPreviousVSTPreset();
afx_msg void OnSetNextVSTPreset();
afx_msg void OnVSTPresetBackwardJump();
afx_msg void OnVSTPresetForwardJump();
afx_msg void OnVSTPresetRename();
afx_msg void OnCreateInstrument();
afx_msg void OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hMenu);
afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM);
afx_msg LRESULT OnMidiMsg(WPARAM, LPARAM);
afx_msg void OnDropFiles(HDROP hDropInfo);
afx_msg void OnMove(int x, int y);
afx_msg void OnClose() { DoClose(); }
// Overridden methods:
void PostNcDestroy() override;
void OnOK() override { DoClose(); }
void OnCancel() override { DoClose(); }
virtual bool OpenEditor(CWnd *parent);
virtual void DoClose();
virtual void UpdateParamDisplays() { if(m_updateDisplay) { SetupMenu(true); m_updateDisplay = false; } }
virtual void UpdateParam(int32 /*param*/) { }
virtual void UpdateView(UpdateHint hint);
virtual bool IsResizable() const = 0;
virtual bool SetSize(int contentWidth, int contentHeight) = 0;
void UpdateDisplay() { m_updateDisplay = true; }
DECLARE_MESSAGE_MAP()
protected:
BOOL PreTranslateMessage(MSG *msg) override;
bool HandleKeyMessage(MSG &msg);
void UpdatePresetMenu(bool force = false);
void GeneratePresetMenu(int32 offset, CMenu &parent);
void UpdateInputMenu();
void UpdateOutputMenu();
void UpdateMacroMenu();
void UpdateOptionsMenu();
INSTRUMENTINDEX GetBestInstrumentCandidate() const;
bool CheckInstrument(INSTRUMENTINDEX ins) const;
bool ValidateCurrentInstrument();
void OnToggleEditor(UINT nID);
void OnSetInputInstrument(UINT nID);
afx_msg void OnInitMenu(CMenu* pMenu);
void PrepareToLearnMacro(UINT nID);
void OnActivate(UINT nState, CWnd *pWndOther, BOOL bMinimized);
void StoreWindowPos();
void RestoreWindowPos();
};
OPENMPT_NAMESPACE_END
#endif // NO_PLUGINS
@@ -0,0 +1,330 @@
/*
* AdvancedConfigDlg.cpp
* ---------------------
* Purpose: Implementation of the advanced settings dialog.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "Mainfrm.h"
#include "AdvancedConfigDlg.h"
#include "Settings.h"
#include "dlg_misc.h"
OPENMPT_NAMESPACE_BEGIN
BEGIN_MESSAGE_MAP(COptionsAdvanced, CPropertyPage)
ON_NOTIFY(NM_DBLCLK, IDC_LIST1, &COptionsAdvanced::OnOptionDblClick)
#ifndef MPT_MFC_FULL
ON_NOTIFY(NM_CUSTOMDRAW, IDC_LIST1, &COptionsAdvanced::OnCustomDrawList)
#endif
ON_EN_CHANGE(IDC_EDIT1, &COptionsAdvanced::OnFindStringChanged)
ON_COMMAND(IDC_BUTTON1, &COptionsAdvanced::OnSaveNow)
END_MESSAGE_MAP()
void COptionsAdvanced::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CModTypeDlg)
DDX_Control(pDX, IDC_LIST1, m_List);
//}}AFX_DATA_MAP
}
BOOL COptionsAdvanced::PreTranslateMessage(MSG *msg)
{
if(msg->message == WM_KEYDOWN && msg->wParam == VK_RETURN)
{
OnOptionDblClick(nullptr, nullptr);
return TRUE;
}
return FALSE;
}
BOOL COptionsAdvanced::OnInitDialog()
{
CPropertyPage::OnInitDialog();
m_List.SetExtendedStyle(m_List.GetExtendedStyle() | LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT);
ListView_EnableGroupView(m_List.m_hWnd, FALSE); // try to set known state
int enableGroupsResult1 = static_cast<int>(ListView_EnableGroupView(m_List.m_hWnd, TRUE));
int enableGroupsResult2 = static_cast<int>(ListView_EnableGroupView(m_List.m_hWnd, TRUE));
// Looks like we have to check enabling and check that a second enabling does
// not change anything.
// Just checking if enabling fails with -1 does not work for older control
// versions because they just do not know the window message at all and return
// 0, always. At least Wine does behave this way.
if(enableGroupsResult1 == 1 && enableGroupsResult2 == 0)
{
m_listGrouped = true;
} else
{
// Did not behave as documented or expected, the actual state of the
// control is unknown by now.
// Play safe and set and assume the traditional ungrouped mode again.
ListView_EnableGroupView(m_List.m_hWnd, FALSE);
m_listGrouped = false;
}
if(m_listGrouped)
{
static constexpr ListCtrl::Header headers[] =
{
{ _T("Setting"), 150, LVCFMT_LEFT },
{ _T("Type"), 40, LVCFMT_LEFT },
{ _T("Value"), 140, LVCFMT_LEFT },
{ _T("Default"), 62, LVCFMT_LEFT },
};
m_List.SetHeaders(headers);
} else
{
static constexpr ListCtrl::Header headers[] =
{
{ _T("Setting"), 200, LVCFMT_LEFT },
{ _T("Type"), 40, LVCFMT_LEFT },
{ _T("Value"), 100, LVCFMT_LEFT },
{ _T("Default"), 52, LVCFMT_LEFT },
};
m_List.SetHeaders(headers);
}
ReInit();
return TRUE;
}
void COptionsAdvanced::ReInit()
{
m_List.SetRedraw(FALSE);
m_List.DeleteAllItems();
if(m_listGrouped)
{
ListView_RemoveAllGroups(m_List.m_hWnd);
}
m_List.SetItemCount(static_cast<int>(theApp.GetSettings().size()));
m_indexToPath.clear();
m_indexToPath.reserve(theApp.GetSettings().size());
m_groups.clear();
int numGroups = 0;
mpt::ustring findStr = mpt::ToLowerCase(GetWindowTextUnicode(*GetDlgItem(IDC_EDIT1)));
LVITEMW lvi;
lvi.mask = LVIF_TEXT | LVIF_PARAM;
lvi.mask |= (m_listGrouped ? LVIF_GROUPID : 0);
lvi.iSubItem = 0;
lvi.state = 0;
lvi.stateMask = 0;
lvi.cchTextMax = 0;
lvi.iImage = 0;
lvi.iIndent = 0;
lvi.iGroupId = 0;
int i = 0;
for(const auto &[path, state] : theApp.GetSettings())
{
// In MPT_USTRING_MODE_WIDE mode,
// this loop is heavily optimized to avoid as much string copies as possible
// in order to perform ok-ish in debug builds.
// MPT_USTRING_MODE_UTF8 is not optimized as we (currently) do not build in
// this mode by default.
const mpt::ustring &section = path.GetRefSection();
const mpt::ustring &key = path.GetRefKey();
const SettingValue &value = state.GetRefValue();
const SettingValue &defaultValue = state.GetRefDefault();
if(!findStr.empty())
{
mpt::ustring str = path.FormatAsString() + U_("=") + value.FormatValueAsString();
str = mpt::ToLowerCase(str);
if(str.find(findStr) == mpt::ustring::npos)
{
continue;
}
}
int index;
lvi.iItem = i++;
lvi.lParam = m_indexToPath.size();
if(m_listGrouped)
{
auto gi = m_groups.find(section);
if(gi == m_groups.end())
{
LVGROUP group;
#if _WIN32_WINNT >= 0x0600
group.cbSize = LVGROUP_V5_SIZE;
#else
group.cbSize = sizeof(group);
#endif
group.mask = LVGF_HEADER | LVGF_GROUPID;
#if MPT_USTRING_MODE_WIDE
group.pszHeader = const_cast<wchar_t *>(section.c_str());
#else
const std::wstring wsection = mpt::ToWide(section);
group.pszHeader = const_cast<wchar_t *>(wsection.c_str());
#endif
group.cchHeader = 0;
group.pszFooter = nullptr;
group.cchFooter = 0;
group.iGroupId = lvi.iGroupId = numGroups++;
group.stateMask = LVGS_COLLAPSIBLE;
group.state = LVGS_COLLAPSIBLE;
group.uAlign = LVGA_HEADER_LEFT;
ListView_InsertGroup(m_List.m_hWnd, -1, &group);
m_groups.insert(std::make_pair(section, lvi.iGroupId));
} else
{
lvi.iGroupId = gi->second;
}
#if MPT_USTRING_MODE_WIDE
lvi.pszText = const_cast<wchar_t *>(key.c_str());
#else
const std::wstring wkey = mpt::ToWide(key);
lvi.pszText = const_cast<wchar_t *>(wkey.c_str());
#endif
index = static_cast<int>(m_List.SendMessage(LVM_INSERTITEMW, 0, (LPARAM)(&lvi)));
} else
{
const mpt::ustring sectionAndKey = path.FormatAsString();
#if MPT_USTRING_MODE_WIDE
lvi.pszText = const_cast<wchar_t *>(sectionAndKey.c_str());
#else
const std::wstring wsectionAndKey = mpt::ToWide(sectionAndKey);
lvi.pszText = const_cast<wchar_t *>(wsectionAndKey.c_str());
#endif
index = static_cast<int>(m_List.SendMessage(LVM_INSERTITEMW, 0, (LPARAM)(&lvi)));
}
#if MPT_USTRING_MODE_WIDE
m_List.SetItemText(index, 1, value.FormatTypeAsString().c_str());
m_List.SetItemText(index, 2, value.FormatValueAsString().c_str());
m_List.SetItemText(index, 3, defaultValue.FormatValueAsString().c_str());
#else
m_List.SetItemText(index, 1, mpt::ToCString(value.FormatTypeAsString()));
m_List.SetItemText(index, 2, mpt::ToCString(value.FormatValueAsString()));
m_List.SetItemText(index, 3, mpt::ToCString(defaultValue.FormatValueAsString()));
#endif
m_indexToPath.push_back(path);
}
m_List.SetItemCount(i);
m_List.SetRedraw(TRUE);
m_List.Invalidate(FALSE);
}
void COptionsAdvanced::OnOK()
{
CSoundFile::SetDefaultNoteNames();
CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
if (pMainFrm) pMainFrm->PostMessage(WM_MOD_INVALIDATEPATTERNS, HINT_MPTOPTIONS);
CPropertyPage::OnOK();
}
BOOL COptionsAdvanced::OnSetActive()
{
ReInit();
CMainFrame::m_nLastOptionsPage = OPTIONS_PAGE_ADVANCED;
return CPropertyPage::OnSetActive();
}
#ifdef MPT_MFC_FULL
COLORREF CAdvancedSettingsList::OnGetCellBkColor(int nRow, int /* nColumn */ )
{
const bool isDefault = theApp.GetSettings().GetMap().find(m_indexToPath[GetItemData(nRow)])->second.IsDefault();
COLORREF defColor = GetBkColor();
COLORREF txtColor = GetTextColor();
COLORREF modColor = RGB(GetRValue(defColor) * 0.9 + GetRValue(txtColor) * 0.1, GetGValue(defColor) * 0.9 + GetGValue(txtColor) * 0.1, GetBValue(defColor) * 0.9 + GetBValue(txtColor) * 0.1);
return isDefault ? defColor : modColor;
}
COLORREF CAdvancedSettingsList::OnGetCellTextColor(int nRow, int nColumn)
{
return CMFCListCtrlEx::OnGetCellTextColor(nRow, nColumn);
}
#else // !MPT_MFC_FULL
void COptionsAdvanced::OnCustomDrawList(NMHDR* pNMHDR, LRESULT* pResult)
{
NMLVCUSTOMDRAW *pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);
*pResult = CDRF_DODEFAULT;
switch(pLVCD->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
*pResult = CDRF_NOTIFYITEMDRAW;
break;
case CDDS_ITEMPREPAINT:
{
const bool isDefault = theApp.GetSettings().GetMap().find(m_indexToPath[pLVCD->nmcd.lItemlParam])->second.IsDefault();
COLORREF defColor = m_List.GetBkColor();
COLORREF txtColor = m_List.GetTextColor();
COLORREF modColor = RGB(GetRValue(defColor) * 0.9 + GetRValue(txtColor) * 0.1, GetGValue(defColor) * 0.9 + GetGValue(txtColor) * 0.1, GetBValue(defColor) * 0.9 + GetBValue(txtColor) * 0.1);
pLVCD->clrTextBk = isDefault ? defColor : modColor;
}
break;
}
}
#endif // MPT_MFC_FULL
void COptionsAdvanced::OnOptionDblClick(NMHDR *, LRESULT *)
{
const int index = m_List.GetSelectionMark();
if(index < 0)
return;
const SettingPath path = m_indexToPath[m_List.GetItemData(index)];
SettingValue val = theApp.GetSettings().GetMap().find(path)->second;
if(val.GetType() == SettingTypeBool)
{
val = !val.as<bool>();
} else
{
CInputDlg inputDlg(this, _T("Enter new value for ") + mpt::ToCString(path.FormatAsString()), mpt::ToCString(val.FormatValueAsString()));
if(inputDlg.DoModal() != IDOK)
{
return;
}
val.SetFromString(inputDlg.resultAsString);
}
theApp.GetSettings().Write(path, val);
#if MPT_USTRING_MODE_WIDE
m_List.SetItemText(index, 2, val.FormatValueAsString().c_str());
#else
m_List.SetItemText(index, 2, mpt::ToCString(val.FormatValueAsString()));
#endif
m_List.SetSelectionMark(index);
OnSettingsChanged();
}
void COptionsAdvanced::OnSaveNow()
{
TrackerSettings::Instance().SaveSettings();
theApp.GetSettings().WriteSettings();
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,86 @@
/*
* AdvancedConfigDlg.h
* -------------------
* Purpose: Implementation of the advanced settings dialog.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "CListCtrl.h"
#if MPT_USTRING_MODE_WIDE
#include <unordered_map>
#else
#include <map>
#endif
OPENMPT_NAMESPACE_BEGIN
#ifdef MPT_MFC_FULL
class CAdvancedSettingsList : public CMFCListCtrlEx
{
private:
std::vector<SettingPath> & m_indexToPath;
public:
CAdvancedSettingsList(std::vector<SettingPath> & indexToPath) : m_indexToPath(indexToPath) {}
COLORREF OnGetCellBkColor(int nRow, int nColumn) override;
COLORREF OnGetCellTextColor(int nRow, int nColumn) override;
};
#endif // MPT_MFC_FULL
class COptionsAdvanced: public CPropertyPage
{
#ifdef MPT_MFC_FULL
using ListCtrl = CAdvancedSettingsList;
#else // MPT_MFC_FULL
using ListCtrl = CListCtrlEx;
#endif // !MPT_MFC_FULL
protected:
ListCtrl m_List;
#if MPT_USTRING_MODE_WIDE
using GroupMap = std::unordered_map<mpt::ustring, int>;
#else
using GroupMap = std::map<mpt::ustring, int>;
#endif
std::vector<SettingPath> m_indexToPath;
GroupMap m_groups;
bool m_listGrouped = false;
public:
#ifdef MPT_MFC_FULL
COptionsAdvanced():CPropertyPage(IDD_OPTIONS_ADVANCED), m_List(m_indexToPath) {}
#else // !MPT_MFC_FULL
COptionsAdvanced():CPropertyPage(IDD_OPTIONS_ADVANCED) {}
#endif // MPT_MFC_FULL
protected:
BOOL OnInitDialog() override;
void OnOK() override;
BOOL OnSetActive() override;
void DoDataExchange(CDataExchange* pDX) override;
BOOL PreTranslateMessage(MSG *msg) override;
afx_msg void OnOptionDblClick(NMHDR *, LRESULT *);
afx_msg void OnSettingsChanged() { SetModified(TRUE); }
afx_msg void OnFindStringChanged() { ReInit(); }
afx_msg void OnSaveNow();
#ifndef MPT_MFC_FULL
afx_msg void OnCustomDrawList(NMHDR* pNMHDR, LRESULT* pResult);
#endif // !MPT_MFC_FULL
void ReInit();
DECLARE_MESSAGE_MAP();
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,316 @@
/*
* AppendModule.cpp
* ----------------
* Purpose: Appending one module to an existing module
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "Moddoc.h"
#include "../soundlib/mod_specifications.h"
OPENMPT_NAMESPACE_BEGIN
// Add samples, instruments, plugins and patterns from another module to the current module
void CModDoc::AppendModule(const CSoundFile &source)
{
const CModSpecifications &specs = m_SndFile.GetModSpecifications();
// Mappings between old and new indices
std::vector<PLUGINDEX> pluginMapping(MAX_MIXPLUGINS + 1, 0);
std::vector<INSTRUMENTINDEX> instrMapping((source.GetNumInstruments() ? source.GetNumInstruments() : source.GetNumSamples()) + 1, INSTRUMENTINDEX_INVALID);
std::vector<ORDERINDEX> orderMapping;
std::vector<PATTERNINDEX> patternMapping(source.Patterns.GetNumPatterns(), PATTERNINDEX_INVALID);
///////////////////////////////////////////////////////////////////////////
// Copy plugins
#ifndef NO_PLUGINS
if(specs.supportsPlugins)
{
PLUGINDEX plug = 0;
for(PLUGINDEX i = 0; i < MAX_MIXPLUGINS; i++)
{
if(!source.m_MixPlugins[i].IsValidPlugin())
{
continue;
}
while(plug < MAX_MIXPLUGINS && m_SndFile.m_MixPlugins[plug].IsValidPlugin())
{
plug++;
}
if(plug < MAX_MIXPLUGINS)
{
ClonePlugin(m_SndFile.m_MixPlugins[plug], source.m_MixPlugins[i]);
pluginMapping[i + 1] = plug + 1;
} else
{
AddToLog("Too many plugins!");
break;
}
}
// Fix up references between plugins
for(PLUGINDEX i = 0; i < MAX_MIXPLUGINS; i++)
{
if(pluginMapping[i + 1] != 0 && source.m_MixPlugins[i].GetOutputPlugin() < MAX_MIXPLUGINS)
{
m_SndFile.m_MixPlugins[pluginMapping[i + 1] - 1].SetOutputPlugin(pluginMapping[source.m_MixPlugins[i].GetOutputPlugin() + 1] - 1);
}
}
}
#endif // NO_PLUGINS
///////////////////////////////////////////////////////////////////////////
// Copy samples / instruments
if(source.GetNumInstruments() != 0 && m_SndFile.GetNumInstruments() == 0 && specs.instrumentsMax)
{
// Convert to instruments first
ConvertSamplesToInstruments();
}
// Check which samples / instruments are actually referenced.
for(const auto &pat : source.Patterns) if(pat.IsValid())
{
for(const auto &m : pat)
{
if(!m.IsPcNote() && m.instr < instrMapping.size()) instrMapping[m.instr] = 0;
}
}
if(m_SndFile.GetNumInstruments())
{
INSTRUMENTINDEX targetIns = 0;
if(source.GetNumInstruments())
{
for(INSTRUMENTINDEX i = 1; i <= source.GetNumInstruments(); i++) if(source.Instruments[i] != nullptr && !instrMapping[i])
{
targetIns = m_SndFile.GetNextFreeInstrument(targetIns + 1);
if(targetIns == INSTRUMENTINDEX_INVALID)
{
AddToLog("Too many instruments!");
break;
}
if(m_SndFile.ReadInstrumentFromSong(targetIns, source, i))
{
ModInstrument *ins = m_SndFile.Instruments[targetIns];
if(ins->nMixPlug <= MAX_MIXPLUGINS)
{
ins->nMixPlug = pluginMapping[ins->nMixPlug];
}
instrMapping[i] = targetIns;
}
}
} else
{
SAMPLEINDEX targetSmp = 0;
for(SAMPLEINDEX i = 1; i <= source.GetNumSamples(); i++) if(!instrMapping[i])
{
targetIns = m_SndFile.GetNextFreeInstrument(targetIns + 1);
targetSmp = m_SndFile.GetNextFreeSample(targetIns, targetSmp + 1);
if(targetIns == INSTRUMENTINDEX_INVALID)
{
AddToLog("Too many instruments!");
break;
} else if(targetSmp == SAMPLEINDEX_INVALID)
{
AddToLog("Too many samples!");
break;
}
if(m_SndFile.AllocateInstrument(targetIns, targetSmp) != nullptr)
{
m_SndFile.ReadSampleFromSong(targetSmp, source, i);
}
instrMapping[i] = targetIns;
}
}
} else
{
SAMPLEINDEX targetSmp = 0;
if(source.GetNumInstruments())
{
for(INSTRUMENTINDEX i = 1; i <= source.GetNumInstruments(); i++) if(source.Instruments[i] != nullptr && !instrMapping[i])
{
targetSmp = m_SndFile.GetNextFreeSample(INSTRUMENTINDEX_INVALID, targetSmp + 1);
if(targetSmp == SAMPLEINDEX_INVALID)
{
AddToLog("Too many samples!");
break;
}
m_SndFile.ReadSampleFromSong(targetSmp, source, source.Instruments[i]->Keyboard[NOTE_MIDDLEC - NOTE_MIN]);
instrMapping[i] = targetSmp;
}
} else
{
for(SAMPLEINDEX i = 1; i <= source.GetNumSamples(); i++) if(!instrMapping[i])
{
targetSmp = m_SndFile.GetNextFreeSample(INSTRUMENTINDEX_INVALID, targetSmp + 1);
if(targetSmp == SAMPLEINDEX_INVALID)
{
AddToLog("Too many samples!");
break;
}
m_SndFile.ReadSampleFromSong(targetSmp, source, i);
instrMapping[i] = targetSmp;
}
}
}
///////////////////////////////////////////////////////////////////////////
// Copy order lists
const bool useOrderMapping = source.Order.GetNumSequences() == 1;
for(auto &srcOrder : source.Order)
{
ORDERINDEX insertPos = 0;
if(m_SndFile.Order.GetNumSequences() < specs.sequencesMax)
{
m_SndFile.Order.AddSequence();
m_SndFile.Order().SetName(srcOrder.GetName());
} else
{
insertPos = m_SndFile.Order().GetLengthTailTrimmed();
if(specs.hasStopIndex)
insertPos++;
}
const ORDERINDEX ordLen = srcOrder.GetLengthTailTrimmed();
if(useOrderMapping) orderMapping.resize(ordLen, ORDERINDEX_INVALID);
for(ORDERINDEX ord = 0; ord < ordLen; ord++)
{
if(insertPos >= specs.ordersMax)
{
AddToLog("Too many order items!");
break;
}
PATTERNINDEX insertPat = PATTERNINDEX_INVALID;
PATTERNINDEX srcPat = srcOrder[ord];
if(source.Patterns.IsValidPat(srcPat) && srcPat < patternMapping.size())
{
if(patternMapping[srcPat] == PATTERNINDEX_INVALID && source.Patterns.IsValidPat(srcPat))
{
patternMapping[srcPat] = InsertPattern(Clamp(source.Patterns[srcPat].GetNumRows(), specs.patternRowsMin, specs.patternRowsMax));
if(patternMapping[srcPat] == PATTERNINDEX_INVALID)
{
AddToLog("Too many patterns!");
break;
}
}
if(patternMapping[srcPat] == PATTERNINDEX_INVALID)
{
continue;
}
insertPat = patternMapping[srcPat];
} else if(srcPat == srcOrder.GetIgnoreIndex() && specs.hasIgnoreIndex)
{
insertPat = m_SndFile.Order.GetIgnoreIndex();
} else if(srcPat == srcOrder.GetInvalidPatIndex() && specs.hasStopIndex)
{
insertPat = m_SndFile.Order.GetInvalidPatIndex();
} else
{
continue;
}
m_SndFile.Order().insert(insertPos, 1, insertPat);
if(useOrderMapping) orderMapping[ord] = insertPos++;
}
}
///////////////////////////////////////////////////////////////////////////
// Adjust number of channels
if(source.GetNumChannels() > m_SndFile.GetNumChannels())
{
CHANNELINDEX newChn = source.GetNumChannels();
if(newChn > specs.channelsMax)
{
AddToLog("Too many channels!");
newChn = specs.channelsMax;
}
if(newChn > m_SndFile.GetNumChannels())
{
ChangeNumChannels(newChn, false);
}
}
///////////////////////////////////////////////////////////////////////////
// Copy patterns
const bool tempoSwingDiffers = source.m_tempoSwing != m_SndFile.m_tempoSwing;
const bool timeSigDiffers = source.m_nDefaultRowsPerBeat != m_SndFile.m_nDefaultRowsPerBeat || source.m_nDefaultRowsPerMeasure != m_SndFile.m_nDefaultRowsPerMeasure;
const CHANNELINDEX copyChannels = std::min(m_SndFile.GetNumChannels(), source.GetNumChannels());
for(PATTERNINDEX pat = 0; pat < patternMapping.size(); pat++)
{
if(patternMapping[pat] == PATTERNINDEX_INVALID)
{
continue;
}
const CPattern &sourcePat = source.Patterns[pat];
CPattern &targetPat = m_SndFile.Patterns[patternMapping[pat]];
if(specs.hasPatternNames)
{
targetPat.SetName(sourcePat.GetName());
}
if(specs.hasPatternSignatures)
{
if(sourcePat.GetOverrideSignature())
{
targetPat.SetSignature(sourcePat.GetRowsPerBeat(), sourcePat.GetRowsPerMeasure());
} else if(timeSigDiffers)
{
// Try fixing differing signature settings by copying them to the newly created patterns
targetPat.SetSignature(source.m_nDefaultRowsPerBeat, source.m_nDefaultRowsPerMeasure);
}
}
if(m_SndFile.m_nTempoMode == TempoMode::Modern)
{
// Swing only works in modern tempo mode
if(sourcePat.HasTempoSwing())
{
targetPat.SetTempoSwing(sourcePat.GetTempoSwing());
} else if(tempoSwingDiffers)
{
// Try fixing differing swing settings by copying them to the newly created patterns
targetPat.SetSignature(source.m_nDefaultRowsPerBeat, source.m_nDefaultRowsPerMeasure);
targetPat.SetTempoSwing(source.m_tempoSwing);
}
}
const ROWINDEX copyRows = std::min(sourcePat.GetNumRows(), targetPat.GetNumRows());
for(ROWINDEX row = 0; row < copyRows; row++)
{
const ModCommand *src = sourcePat.GetRow(row);
ModCommand *m = targetPat.GetRow(row);
for(CHANNELINDEX chn = 0; chn < copyChannels; chn++, src++, m++)
{
*m = *src;
m->Convert(source.GetType(), m_SndFile.GetType(), source);
if(m->IsPcNote())
{
if(m->instr && m->instr < pluginMapping.size()) m->instr = static_cast<ModCommand::INSTR>(pluginMapping[m->instr]);
} else
{
if(m->instr && m->instr < instrMapping.size()) m->instr = static_cast<ModCommand::INSTR>(instrMapping[m->instr]);
if(m->command == CMD_POSITIONJUMP && m->param < orderMapping.size())
{
if(orderMapping[m->param] == ORDERINDEX_INVALID)
{
m->command = CMD_NONE;
} else
{
m->param = static_cast<ModCommand::PARAM>(orderMapping[m->param]);
}
}
}
}
}
if(copyRows < targetPat.GetNumRows())
{
// If source pattern was smaller, write pattern break effect.
targetPat.WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(copyRows - 1).RetryNextRow());
}
}
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,190 @@
/*
* AutoSaver.cpp
* -------------
* Purpose: Class for automatically saving open modules at a specified interval.
* Notes : (currently none)
* Authors: 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 "Moddoc.h"
#include "AutoSaver.h"
#include "FileDialog.h"
#include "FolderScanner.h"
#include "resource.h"
#include "../soundlib/mod_specifications.h"
#include <algorithm>
OPENMPT_NAMESPACE_BEGIN
CAutoSaver::CAutoSaver()
: m_lastSave(timeGetTime())
{
}
bool CAutoSaver::IsEnabled() const
{
return TrackerSettings::Instance().AutosaveEnabled;
}
bool CAutoSaver::GetUseOriginalPath() const
{
return TrackerSettings::Instance().AutosaveUseOriginalPath;
}
mpt::PathString CAutoSaver::GetPath() const
{
return TrackerSettings::Instance().AutosavePath.GetDefaultDir();
}
uint32 CAutoSaver::GetHistoryDepth() const
{
return TrackerSettings::Instance().AutosaveHistoryDepth;
}
uint32 CAutoSaver::GetSaveInterval() const
{
return TrackerSettings::Instance().AutosaveIntervalMinutes;
}
bool CAutoSaver::DoSave(DWORD curTime)
{
bool success = true;
//If time to save and not already having save in progress.
if (CheckTimer(curTime) && !m_saveInProgress)
{
m_saveInProgress = true;
theApp.BeginWaitCursor(); //display hour glass
for(auto &modDoc : theApp.GetOpenDocuments())
{
if(modDoc->ModifiedSinceLastAutosave())
{
if(SaveSingleFile(*modDoc))
{
CleanUpBackups(*modDoc);
} else
{
TrackerSettings::Instance().AutosaveEnabled = false;
Reporting::Warning("Warning: Auto Save failed and has been disabled. Please:\n- Review your Auto Save paths\n- Check available disk space and filesystem access rights");
success = false;
}
}
}
m_lastSave = timeGetTime();
theApp.EndWaitCursor(); // End display hour glass
m_saveInProgress = false;
}
return success;
}
bool CAutoSaver::CheckTimer(DWORD curTime) const
{
return (curTime - m_lastSave) >= GetSaveIntervalMilliseconds();
}
mpt::PathString CAutoSaver::GetBasePath(const CModDoc &modDoc, bool createPath) const
{
mpt::PathString path;
if(GetUseOriginalPath())
{
if(modDoc.m_bHasValidPath && !(path = modDoc.GetPathNameMpt()).empty())
{
// File has a user-chosen path - remove filename
path = path.GetPath();
} else
{
// if it doesn't, put it in settings dir
path = theApp.GetConfigPath() + P_("Autosave\\");
if(createPath && !CreateDirectory(path.AsNative().c_str(), nullptr) && GetLastError() == ERROR_PATH_NOT_FOUND)
path = theApp.GetConfigPath();
else if(!createPath && !path.IsDirectory())
path = theApp.GetConfigPath();
}
} else
{
path = GetPath();
}
return path.EnsureTrailingSlash();
}
mpt::PathString CAutoSaver::GetBaseName(const CModDoc &modDoc) const
{
return mpt::PathString::FromCString(modDoc.GetTitle()).SanitizeComponent();
}
mpt::PathString CAutoSaver::BuildFileName(const CModDoc &modDoc) const
{
mpt::PathString name = GetBasePath(modDoc, true) + GetBaseName(modDoc);
const CString timeStamp = CTime::GetCurrentTime().Format(_T(".AutoSave.%Y%m%d.%H%M%S."));
name += mpt::PathString::FromCString(timeStamp); //append backtup tag + timestamp
name += mpt::PathString::FromUTF8(modDoc.GetSoundFile().GetModSpecifications().fileExtension);
return name;
}
bool CAutoSaver::SaveSingleFile(CModDoc &modDoc)
{
// We do not call CModDoc::DoSave as this populates the Recent Files
// list with backups... hence we have duplicated code.. :(
CSoundFile &sndFile = modDoc.GetSoundFile();
mpt::PathString fileName = BuildFileName(modDoc);
// We are actually not going to show the log for autosaved files.
ScopedLogCapturer logcapturer(modDoc, _T(""), nullptr, false);
bool success = false;
mpt::ofstream f(fileName, std::ios::binary);
if(f)
{
switch(modDoc.GetSoundFile().GetBestSaveFormat())
{
case MOD_TYPE_MOD: success = sndFile.SaveMod(f); break;
case MOD_TYPE_S3M: success = sndFile.SaveS3M(f); break;
case MOD_TYPE_XM: success = sndFile.SaveXM(f); break;
case MOD_TYPE_IT: success = sndFile.SaveIT(f, fileName); break;
case MOD_TYPE_MPT: success = sndFile.SaveIT(f, fileName); break;
}
}
return success;
}
void CAutoSaver::CleanUpBackups(const CModDoc &modDoc) const
{
// Find all autosave files for this document, and delete the oldest ones if there are more than the user wants.
std::vector<mpt::PathString> foundfiles;
FolderScanner scanner(GetBasePath(modDoc, false), FolderScanner::kOnlyFiles, GetBaseName(modDoc) + P_(".AutoSave.*"));
mpt::PathString fileName;
while(scanner.Next(fileName))
{
foundfiles.push_back(std::move(fileName));
}
std::sort(foundfiles.begin(), foundfiles.end());
size_t filesToDelete = std::max(static_cast<size_t>(GetHistoryDepth()), foundfiles.size()) - GetHistoryDepth();
for(size_t i = 0; i < filesToDelete; i++)
{
DeleteFile(foundfiles[i].AsNative().c_str());
}
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,51 @@
/*
* AutoSaver.h
* -----------
* Purpose: Class for automatically saving open modules at a specified interval.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
OPENMPT_NAMESPACE_BEGIN
class CModDoc;
class CAutoSaver
{
public:
CAutoSaver();
bool DoSave(DWORD curTime);
bool IsEnabled() const;
bool GetUseOriginalPath() const;
mpt::PathString GetPath() const;
uint32 GetHistoryDepth() const;
uint32 GetSaveInterval() const;
uint32 GetSaveIntervalMilliseconds() const
{
return Clamp(GetSaveInterval(), 0u, (1u << 30) / 60u / 1000u) * 60 * 1000;
}
private:
bool SaveSingleFile(CModDoc &modDoc);
mpt::PathString GetBasePath(const CModDoc &modDoc, bool createPath) const;
mpt::PathString GetBaseName(const CModDoc &modDoc) const;
mpt::PathString BuildFileName(const CModDoc &modDoc) const;
void CleanUpBackups(const CModDoc &modDoc) const;
bool CheckTimer(DWORD curTime) const;
DWORD m_lastSave = 0;
//Flag to prevent autosave from starting new saving if previous is still in progress.
bool m_saveInProgress = false;
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,414 @@
/*
* Autotune.cpp
* ------------
* Purpose: Class for tuning a sample to a given base note automatically.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "Autotune.h"
#include <math.h>
#include "../common/misc_util.h"
#include "../soundlib/Sndfile.h"
#include <algorithm>
#include <execution>
#include <numeric>
#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2)
#include <emmintrin.h>
#endif
OPENMPT_NAMESPACE_BEGIN
// The more bins, the more autocorrelations are done and the more precise the result is.
#define BINS_PER_NOTE 32
#define MIN_SAMPLE_LENGTH 2
#define START_NOTE (24 * BINS_PER_NOTE) // C-2
#define END_NOTE (96 * BINS_PER_NOTE) // C-8
#define HISTORY_BINS (12 * BINS_PER_NOTE) // One octave
static double FrequencyToNote(double freq, double pitchReference)
{
return ((12.0 * (log(freq / (pitchReference / 2.0)) / log(2.0))) + 57.0);
}
static double NoteToFrequency(double note, double pitchReference)
{
return pitchReference * pow(2.0, (note - 69.0) / 12.0);
}
// Calculate the amount of samples for autocorrelation shifting for a given note
static SmpLength NoteToShift(uint32 sampleFreq, int note, double pitchReference)
{
const double fundamentalFrequency = NoteToFrequency((double)note / BINS_PER_NOTE, pitchReference);
return std::max(mpt::saturate_round<SmpLength>((double)sampleFreq / fundamentalFrequency), SmpLength(1));
}
// Create an 8-Bit sample buffer with loop unrolling and mono conversion for autocorrelation.
template <class T>
void Autotune::CopySamples(const T* origSample, SmpLength sampleLoopStart, SmpLength sampleLoopEnd)
{
const uint8 channels = m_sample.GetNumChannels();
sampleLoopStart *= channels;
sampleLoopEnd *= channels;
for(SmpLength i = 0, pos = 0; i < m_sampleLength; i++, pos += channels)
{
if(pos >= sampleLoopEnd)
{
pos = sampleLoopStart;
}
const T* smp = origSample + pos;
int32 data = 0; // More than enough for 256 channels... :)
for(uint8 chn = 0; chn < channels; chn++)
{
// We only want the MSB.
data += static_cast<int32>(smp[chn] >> ((sizeof(T) - 1) * 8));
}
data /= channels;
m_sampleData[i] = static_cast<int16>(data);
}
}
// Prepare a sample buffer for autocorrelation
bool Autotune::PrepareSample(SmpLength maxShift)
{
// Determine which parts of the sample should be examined.
SmpLength sampleOffset = 0, sampleLoopStart = 0, sampleLoopEnd = m_sample.nLength;
if(m_selectionEnd >= sampleLoopStart + MIN_SAMPLE_LENGTH)
{
// A selection has been specified: Examine selection
sampleOffset = m_selectionStart;
sampleLoopStart = 0;
sampleLoopEnd = m_selectionEnd - m_selectionStart;
} else if(m_sample.uFlags[CHN_SUSTAINLOOP] && m_sample.nSustainEnd >= m_sample.nSustainStart + MIN_SAMPLE_LENGTH)
{
// A sustain loop is set: Examine sample up to sustain loop and, if necessary, execute the loop several times
sampleOffset = 0;
sampleLoopStart = m_sample.nSustainStart;
sampleLoopEnd = m_sample.nSustainEnd;
} else if(m_sample.uFlags[CHN_LOOP] && m_sample.nLoopEnd >= m_sample.nLoopStart + MIN_SAMPLE_LENGTH)
{
// A normal loop is set: Examine sample up to loop and, if necessary, execute the loop several times
sampleOffset = 0;
sampleLoopStart = m_sample.nLoopStart;
sampleLoopEnd = m_sample.nLoopEnd;
}
// We should analyse at least a one second (= GetSampleRate() samples) long sample.
m_sampleLength = std::max(sampleLoopEnd, static_cast<SmpLength>(m_sample.GetSampleRate(m_modType))) + maxShift;
m_sampleLength = (m_sampleLength + 7) & ~7;
if(m_sampleData != nullptr)
{
delete[] m_sampleData;
}
m_sampleData = new int16[m_sampleLength];
if(m_sampleData == nullptr)
{
return false;
}
// Copy sample over.
switch(m_sample.GetElementarySampleSize())
{
case 1:
CopySamples(m_sample.sample8() + sampleOffset * m_sample.GetNumChannels(), sampleLoopStart, sampleLoopEnd);
return true;
case 2:
CopySamples(m_sample.sample16() + sampleOffset * m_sample.GetNumChannels(), sampleLoopStart, sampleLoopEnd);
return true;
}
return false;
}
bool Autotune::CanApply() const
{
return (m_sample.HasSampleData() && m_sample.nLength >= MIN_SAMPLE_LENGTH) || m_sample.uFlags[CHN_ADLIB];
}
namespace
{
struct AutotuneHistogramEntry
{
int index;
uint64 sum;
};
struct AutotuneHistogram
{
std::array<uint64, HISTORY_BINS> histogram{};
};
struct AutotuneContext
{
const int16 *m_sampleData;
double pitchReference;
SmpLength processLength;
uint32 sampleFreq;
};
#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2)
static inline AutotuneHistogramEntry CalculateNoteHistogramSSE2(int note, AutotuneContext ctx)
{
const SmpLength autocorrShift = NoteToShift(ctx.sampleFreq, note, ctx.pitchReference);
uint64 autocorrSum = 0;
{
const __m128i *normalData = reinterpret_cast<const __m128i *>(ctx.m_sampleData);
const __m128i *shiftedData = reinterpret_cast<const __m128i *>(ctx.m_sampleData + autocorrShift);
for(SmpLength i = ctx.processLength / 8; i != 0; i--)
{
__m128i normal = _mm_loadu_si128(normalData++);
__m128i shifted = _mm_loadu_si128(shiftedData++);
__m128i diff = _mm_sub_epi16(normal, shifted); // 8 16-bit differences
__m128i squares = _mm_madd_epi16(diff, diff); // Multiply and add: 4 32-bit squares
__m128i sum1 = _mm_shuffle_epi32(squares, _MM_SHUFFLE(0, 1, 2, 3)); // Move upper two integers to lower
__m128i sum2 = _mm_add_epi32(squares, sum1); // Now we can add the (originally) upper two and lower two integers
__m128i sum3 = _mm_shuffle_epi32(sum2, _MM_SHUFFLE(1, 1, 1, 1)); // Move the second-lowest integer to lowest position
__m128i sum4 = _mm_add_epi32(sum2, sum3); // Add the two lowest positions
autocorrSum += _mm_cvtsi128_si32(sum4);
}
}
return {note % HISTORY_BINS, autocorrSum};
}
#endif
static inline AutotuneHistogramEntry CalculateNoteHistogram(int note, AutotuneContext ctx)
{
const SmpLength autocorrShift = NoteToShift(ctx.sampleFreq, note, ctx.pitchReference);
uint64 autocorrSum = 0;
{
const int16 *normalData = ctx.m_sampleData;
const int16 *shiftedData = ctx.m_sampleData + autocorrShift;
// Add up squared differences of all values
for(SmpLength i = ctx.processLength; i != 0; i--, normalData++, shiftedData++)
{
autocorrSum += (*normalData - *shiftedData) * (*normalData - *shiftedData);
}
}
return {note % HISTORY_BINS, autocorrSum};
}
static inline AutotuneHistogram operator+(AutotuneHistogram a, AutotuneHistogram b) noexcept
{
AutotuneHistogram result;
for(std::size_t i = 0; i < HISTORY_BINS; ++i)
{
result.histogram[i] = a.histogram[i] + b.histogram[i];
}
return result;
}
static inline AutotuneHistogram & operator+=(AutotuneHistogram &a, AutotuneHistogram b) noexcept
{
for(std::size_t i = 0; i < HISTORY_BINS; ++i)
{
a.histogram[i] += b.histogram[i];
}
return a;
}
static inline AutotuneHistogram &operator+=(AutotuneHistogram &a, AutotuneHistogramEntry b) noexcept
{
a.histogram[b.index] += b.sum;
return a;
}
struct AutotuneHistogramReduce
{
inline AutotuneHistogram operator()(AutotuneHistogram a, AutotuneHistogram b) noexcept
{
return a + b;
}
inline AutotuneHistogram operator()(AutotuneHistogramEntry a, AutotuneHistogramEntry b) noexcept
{
AutotuneHistogram result;
result += a;
result += b;
return result;
}
inline AutotuneHistogram operator()(AutotuneHistogramEntry a, AutotuneHistogram b) noexcept
{
b += a;
return b;
}
inline AutotuneHistogram operator()(AutotuneHistogram a, AutotuneHistogramEntry b) noexcept
{
a += b;
return a;
}
};
} // local
bool Autotune::Apply(double pitchReference, int targetNote)
{
if(!CanApply())
{
return false;
}
const uint32 sampleFreq = m_sample.GetSampleRate(m_modType);
// At the lowest frequency, we get the highest autocorrelation shift amount.
const SmpLength maxShift = NoteToShift(sampleFreq, START_NOTE, pitchReference);
if(!PrepareSample(maxShift))
{
return false;
}
// We don't process the autocorrelation overhead.
const SmpLength processLength = m_sampleLength - maxShift;
AutotuneContext ctx;
ctx.m_sampleData = m_sampleData;
ctx.pitchReference = pitchReference;
ctx.processLength = processLength;
ctx.sampleFreq = sampleFreq;
// Note that we cannot use a fake integer iterator here because of the requirement on ForwardIterator to return a reference to the elements.
std::array<int, END_NOTE - START_NOTE> notes;
std::iota(notes.begin(), notes.end(), START_NOTE);
AutotuneHistogram autocorr =
#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2)
(CPU::HasFeatureSet(CPU::feature::sse2)) ? std::transform_reduce(std::execution::par_unseq, std::begin(notes), std::end(notes), AutotuneHistogram{}, AutotuneHistogramReduce{}, [ctx](int note) { return CalculateNoteHistogramSSE2(note, ctx); } ) :
#endif
std::transform_reduce(std::execution::par_unseq, std::begin(notes), std::end(notes), AutotuneHistogram{}, AutotuneHistogramReduce{}, [ctx](int note) { return CalculateNoteHistogram(note, ctx); } );
// Interpolate the histogram...
AutotuneHistogram interpolated;
for(int i = 0; i < HISTORY_BINS; i++)
{
interpolated.histogram[i] = autocorr.histogram[i];
const int kernelWidth = 4;
for(int ki = kernelWidth; ki >= 0; ki--)
{
// Choose bins to interpolate with
int left = i - ki;
if(left < 0) left += HISTORY_BINS;
int right = i + ki;
if(right >= HISTORY_BINS) right -= HISTORY_BINS;
interpolated.histogram[i] = interpolated.histogram[i] / 2 + (autocorr.histogram[left] + autocorr.histogram[right]) / 2;
}
}
// ...and find global minimum
int minimumBin = static_cast<int>(std::min_element(std::begin(interpolated.histogram), std::end(interpolated.histogram)) - std::begin(interpolated.histogram));
// Center target notes around C
if(targetNote >= 6)
{
targetNote -= 12;
}
// Center bins around target note
minimumBin -= targetNote * BINS_PER_NOTE;
if(minimumBin >= 6 * BINS_PER_NOTE)
{
minimumBin -= 12 * BINS_PER_NOTE;
}
minimumBin += targetNote * BINS_PER_NOTE;
const double newFundamentalFreq = NoteToFrequency(static_cast<double>(69 - targetNote) + static_cast<double>(minimumBin) / BINS_PER_NOTE, pitchReference);
if(const auto newFreq = mpt::saturate_round<uint32>(sampleFreq * pitchReference / newFundamentalFreq); newFreq != sampleFreq)
m_sample.nC5Speed = newFreq;
else
return false;
if((m_modType & (MOD_TYPE_XM | MOD_TYPE_MOD)))
{
m_sample.FrequencyToTranspose();
if((m_modType & MOD_TYPE_MOD))
{
m_sample.RelativeTone = 0;
}
}
return true;
}
/////////////////////////////////////////////////////////////
// CAutotuneDlg
int CAutotuneDlg::m_pitchReference = 440; // Pitch reference in Hz
int CAutotuneDlg::m_targetNote = 0; // Target note (C- = 0, C# = 1, etc...)
void CAutotuneDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAutotuneDlg)
DDX_Control(pDX, IDC_COMBO1, m_CbnNoteBox);
//}}AFX_DATA_MAP
}
BOOL CAutotuneDlg::OnInitDialog()
{
CDialog::OnInitDialog();
m_CbnNoteBox.ResetContent();
for(int note = 0; note < 12; note++)
{
const int item = m_CbnNoteBox.AddString(mpt::ToCString(CSoundFile::GetDefaultNoteName(note)));
m_CbnNoteBox.SetItemData(item, note);
if(note == m_targetNote)
{
m_CbnNoteBox.SetCurSel(item);
}
}
SetDlgItemInt(IDC_EDIT1, m_pitchReference, FALSE);
return TRUE;
}
void CAutotuneDlg::OnOK()
{
int pitch = GetDlgItemInt(IDC_EDIT1);
if(pitch <= 0)
{
MessageBeep(MB_ICONWARNING);
return;
}
CDialog::OnOK();
m_targetNote = (int)m_CbnNoteBox.GetItemData(m_CbnNoteBox.GetCurSel());
m_pitchReference = pitch;
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,81 @@
/*
* Autotune.h
* ----------
* Purpose: Class for tuning a sample to a given base note automatically.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "../soundlib/Snd_defs.h"
#include "resource.h"
OPENMPT_NAMESPACE_BEGIN
struct ModSample;
class Autotune
{
protected:
ModSample &m_sample;
MODTYPE m_modType;
SmpLength m_selectionStart, m_selectionEnd;
int16 *m_sampleData = nullptr;
SmpLength m_sampleLength = 0;
public:
Autotune(ModSample &smp, MODTYPE type, SmpLength selStart, SmpLength selEnd) : m_sample(smp), m_modType(type), m_selectionStart(selStart), m_selectionEnd(selEnd)
{ };
~Autotune()
{
delete[] m_sampleData;
}
bool CanApply() const;
bool Apply(double pitchReference, int targetNote);
protected:
template <class T>
void CopySamples(const T* origSample, SmpLength sampleLoopStart, SmpLength sampleLoopEnd);
bool PrepareSample(SmpLength maxShift);
};
class CAutotuneDlg : public CDialog
{
protected:
static int m_pitchReference; // Pitch reference (440Hz by default)
static int m_targetNote; // Note which the sample should be tuned to (C by default)
CComboBox m_CbnNoteBox;
public:
CAutotuneDlg(CWnd *parent) : CDialog(IDD_AUTOTUNE, parent)
{ };
int GetPitchReference() const { return m_pitchReference; }
int GetTargetNote() const { return m_targetNote; }
protected:
BOOL OnInitDialog() override;
void OnOK() override;
void DoDataExchange(CDataExchange* pDX) override;
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,171 @@
/*
* BuildVariants.cpp
* -----------------
* Purpose: Handling of various OpenMPT build variants.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "BuildVariants.h"
#include "../common/version.h"
#include "../misc/mptOS.h"
#include "../misc/mptCPU.h"
#include "Mptrack.h"
OPENMPT_NAMESPACE_BEGIN
bool BuildVariants::IsKnownSystem()
{
return false
|| mpt::OS::Windows::IsOriginal()
|| (mpt::OS::Windows::IsWine() && theApp.GetWineVersion()->Version().IsValid())
;
}
BuildVariants::Variants BuildVariants::GetBuildVariant()
{
#if defined(MPT_BUILD_RETRO)
return Retro;
#else
#if defined(_WIN32_WINNT)
#if (_WIN32_WINNT >= 0x0A00) // Windows 10
return Standard;
#else
return Legacy;
#endif
#else
return Unknown;
#endif
#endif
}
mpt::ustring BuildVariants::GetBuildVariantName(BuildVariants::Variants variant)
{
mpt::ustring result;
switch(variant)
{
case Standard:
result = U_("Standard");
break;
case Legacy:
result = U_("Legacy");
break;
case Retro:
result = U_("Retro");
break;
case Unknown:
result = U_("Unknown");
break;
}
return result;
}
mpt::ustring BuildVariants::GetBuildVariantDescription(BuildVariants::Variants variant)
{
mpt::ustring result;
switch(variant)
{
case Standard:
result = U_("");
break;
case Legacy:
result = U_("");
break;
case Retro:
result = U_(" RETRO");
break;
case Unknown:
result = U_("");
break;
}
return result;
}
mpt::ustring BuildVariants::GuessCurrentBuildName()
{
if(GetBuildVariant() == Unknown)
{
return mpt::ustring();
}
if(mpt::arch_bits == 64)
{
if(GetBuildVariant() == Standard)
{
return U_("win64");
} else
{
return U_("win64old");
}
} else if(mpt::arch_bits == 32)
{
if(GetBuildVariant() == Standard)
{
return U_("win32");
} else
{
return U_("win32old");
}
} else
{
return mpt::ustring();
}
}
bool BuildVariants::ProcessorCanRunCurrentBuild()
{
#ifdef MPT_ENABLE_ARCH_INTRINSICS
if((CPU::Info::Get().AvailableFeatures & CPU::GetMinimumFeatures()) != CPU::GetMinimumFeatures())
{
return false;
}
#endif // MPT_ENABLE_ARCH_INTRINSICS
return true;
}
bool BuildVariants::SystemCanRunCurrentBuild()
{
if(mpt::OS::Windows::HostCanRun(mpt::OS::Windows::GetHostArchitecture(), mpt::OS::Windows::GetProcessArchitecture()) == mpt::OS::Windows::EmulationLevel::NA)
{
return false;
}
#ifdef MPT_ENABLE_ARCH_INTRINSICS
if((CPU::Info::Get().AvailableFeatures & CPU::GetMinimumFeatures()) != CPU::GetMinimumFeatures())
{
return false;
}
#endif // MPT_ENABLE_ARCH_INTRINSICS
if(IsKnownSystem())
{
if(mpt::OS::Windows::IsOriginal())
{
if(mpt::OS::Windows::Version::Current().IsBefore(mpt::OS::Windows::Version::GetMinimumKernelLevel()))
{
return false;
}
if(mpt::OS::Windows::Version::Current().IsBefore(mpt::OS::Windows::Version::GetMinimumAPILevel()))
{
return false;
}
} else if(mpt::OS::Windows::IsWine())
{
if(theApp.GetWineVersion()->Version().IsBefore(mpt::OS::Wine::GetMinimumWineVersion()))
{
return false;
}
}
}
return true;
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,45 @@
/*
* BuildVariants.h
* ---------------
* Purpose: Handling of various OpenMPT build variants.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
OPENMPT_NAMESPACE_BEGIN
class BuildVariants
{
public:
enum Variants {
Standard,
Legacy,
Retro,
Unknown,
};
static bool IsKnownSystem();
static BuildVariants::Variants GetBuildVariant();
static mpt::ustring GetBuildVariantName(BuildVariants::Variants variant);
static mpt::ustring GetBuildVariantDescription(BuildVariants::Variants variant);
static mpt::ustring GuessCurrentBuildName();
static bool ProcessorCanRunCurrentBuild();
static bool SystemCanRunCurrentBuild();
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,55 @@
/*
* CDecimalSupport.cpp
* -------------------
* Purpose: Various extensions of the CDecimalSupport implementation.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "Snd_defs.h"
#include "CDecimalSupport.h"
OPENMPT_NAMESPACE_BEGIN
BEGIN_MESSAGE_MAP(CNumberEdit, CEdit)
ON_WM_CHAR()
ON_MESSAGE(WM_PASTE, &CNumberEdit::OnPaste)
END_MESSAGE_MAP()
void CNumberEdit::SetTempoValue(const TEMPO &t)
{
SetFixedValue(t.ToDouble(), 4);
}
TEMPO CNumberEdit::GetTempoValue()
{
double d;
GetDecimalValue(d);
return TEMPO(d);
}
void CNumberEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
BOOL bHandled = false;
CDecimalSupport<CNumberEdit>::OnChar(0, nChar, 0, bHandled);
if(!bHandled) CEdit::OnChar(nChar , nRepCnt, nFlags);
}
LPARAM CNumberEdit::OnPaste(WPARAM wParam, LPARAM lParam)
{
bool bHandled = false;
CDecimalSupport<CNumberEdit>::OnPaste(0, wParam, lParam, bHandled);
if(!bHandled)
return CEdit::DefWindowProc(WM_PASTE, wParam, lParam);
else
return 0;
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,389 @@
/*
* CDecimalSupport.h
* -----------------
* Purpose: Edit field which allows negative and fractional values to be entered
* Notes : Alexander Uckun's original code has been modified a bit to suit our purposes.
* Authors: OpenMPT Devs
* Alexander Uckun
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
OPENMPT_NAMESPACE_BEGIN
///////////////////////////////////////////////////////////////////////////////
/// \class CDecimalSupport
/// \brief decimal number support for your control
/// \author Alexander Uckun
/// \version 1.0
// Copyright (c) 2007 - Alexander Uckun
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
template <class T, int limit = _CVTBUFSIZE>
class CDecimalSupport
{
protected:
/// the locale dependant decimal separator
TCHAR m_DecimalSeparator[5];
/// the locale dependant negative sign
TCHAR m_NegativeSign[6];
bool m_allowNegative = true, m_allowFractions = true;
public:
#ifdef BEGIN_MSG_MAP
BEGIN_MSG_MAP(CDecimalSupport)
ALT_MSG_MAP(8)
MESSAGE_HANDLER(WM_CHAR, OnChar)
MESSAGE_HANDLER(WM_PASTE, OnPaste)
END_MSG_MAP()
#endif
/// \brief Initialize m_DecimalSeparator and m_NegativeSign
/// \remarks calls InitDecimalSeparator and InitNegativeSign
CDecimalSupport()
{
InitDecimalSeparator();
InitNegativeSign();
}
/// \brief sets m_DecimalSeparator
/// \remarks calls GetLocaleInfo with LOCALE_SDECIMAL to set m_DecimalSeparator
/// \param[in] Locale the locale parameter (see GetLocaleInfo)
/// \return the number of TCHARs written to the destination buffer
int InitDecimalSeparator(LCID Locale = LOCALE_USER_DEFAULT)
{
return ::GetLocaleInfo(Locale, LOCALE_SDECIMAL, m_DecimalSeparator, sizeof(m_DecimalSeparator) / sizeof(TCHAR));
}
/// \brief sets m_NegativeSign
/// \remarks calls GetLocaleInfo with LOCALE_SNEGATIVESIGN to set m_NegativeSign
/// \param[in] Locale the locale parameter (see GetLocaleInfo)
/// \return the number of TCHARs written to the destination buffer
int InitNegativeSign(LCID Locale = LOCALE_USER_DEFAULT)
{
return ::GetLocaleInfo(Locale, LOCALE_SNEGATIVESIGN, m_NegativeSign, sizeof(m_NegativeSign) / sizeof(TCHAR));
}
/// callback for the WM_PASTE message
/// validates the input
/// \param uMsg
/// \param wParam
/// \param lParam
/// \param[out] bHandled true, if the text is a valid number
/// \return 0
LRESULT OnPaste(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, bool &bHandled)
{
bHandled = false;
int neg_sign = 0;
int dec_point = 0;
T* pT = static_cast<T*>(this);
int nStartChar;
int nEndChar;
pT->GetSel(nStartChar, nEndChar);
TCHAR buffer[limit];
pT->GetWindowText(buffer, limit);
// Check if the text already contains a decimal point
for (TCHAR* x = buffer; *x; ++x)
{
if (x - buffer == nStartChar) x += nEndChar - nStartChar;
if (*x == m_DecimalSeparator[0] || *x == _T('.')) ++dec_point;
if (*x == m_NegativeSign[0] || *x == _T('-')) ++neg_sign;
}
#ifdef _UNICODE
if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) return 0;
#else
if (!IsClipboardFormatAvailable(CF_TEXT)) return 0;
#endif
if (!OpenClipboard((HWND) *pT)) return 0;
#ifdef _UNICODE
HGLOBAL hglb = GetClipboardData(CF_UNICODETEXT);
#else
HGLOBAL hglb = ::GetClipboardData(CF_TEXT);
#endif
if (hglb != NULL)
{
TCHAR *lptstr = static_cast<TCHAR *>(GlobalLock(hglb));
if (lptstr != nullptr)
{
bHandled = true;
for (TCHAR* s = lptstr; *s; ++s)
{
if ((*s == m_NegativeSign[0] ||*s == _T('-')) && m_allowNegative)
{
for (TCHAR* t = m_NegativeSign + 1; *t; ++t, ++s)
{
if (*t != *(s+1)) ++neg_sign;
}
if (neg_sign || nStartChar > 0)
{
bHandled = false;
break;
}
++neg_sign;
continue;
}
if ((*s == m_DecimalSeparator[0] || *s == _T('.')) && m_allowFractions)
{
for (TCHAR* t = m_DecimalSeparator + 1; *t ; ++t, ++s)
{
if (*t != *(s+1)) ++dec_point;
}
if (dec_point)
{
bHandled = false;
break;
}
++dec_point;
continue;
}
if (*s == _T('\r'))
{
// Stop at new line
*s = 0;
break;
}
if (*s < _T('0') || *s > _T('9'))
{
bHandled = false;
break;
}
}
if(bHandled) pT->ReplaceSel(lptstr, true);
GlobalUnlock(hglb);
}
}
CloseClipboard();
return 0;
}
/// callback for the WM_CHAR message
/// handles the decimal point and the negative sign keys
/// \param uMsg
/// \param[in] wParam contains the pressed key
/// \param lParam
/// \param[out] bHandled true, if the key press was handled in this function
/// \return 0
LRESULT OnChar(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
{
bHandled = false;
if ((static_cast<TCHAR>(wParam) == m_DecimalSeparator[0] || wParam == _T('.')) && m_allowFractions)
{
T* pT = static_cast<T*>(this);
int nStartChar;
int nEndChar;
pT->GetSel(nStartChar, nEndChar);
TCHAR buffer[limit];
pT->GetWindowText(buffer, limit);
//Verify that the control doesn't already contain a decimal point
for (TCHAR* x = buffer; *x; ++x)
{
if (x - buffer == nStartChar) x += nEndChar - nStartChar;
if (*x == m_DecimalSeparator[0]) return 0;
}
pT->ReplaceSel(m_DecimalSeparator, true);
bHandled = true;
}
if ((static_cast<TCHAR>(wParam) == m_NegativeSign[0] || wParam == _T('-')) && m_allowNegative)
{
T* pT = static_cast<T*>(this);
int nStartChar;
int nEndChar;
pT->GetSel(nStartChar, nEndChar);
if (nStartChar) return 0;
TCHAR buffer[limit];
pT->GetWindowText(buffer, limit);
//Verify that the control doesn't already contain a negative sign
if (nEndChar == 0 && buffer[0] == m_NegativeSign[0]) return 0;
pT->ReplaceSel(m_NegativeSign, true);
bHandled = true;
}
return 0;
}
/// converts the controls text to double
/// \param[out] d the converted value
/// \return true on success
bool GetDecimalValue(double& d) const
{
TCHAR szBuff[limit];
static_cast<const T*>(this)->GetWindowText(szBuff, limit);
return TextToDouble(szBuff, d);
}
/// converts a string to double
/// \remarks the decimal separator and the negative sign may change in the string
/// \param[in, out] szBuff the string to convert
/// \param[out] d the converted value
/// \return true on success
bool TextToDouble(TCHAR* szBuff, double& d) const
{
//replace the locale dependant separator with .
if (m_DecimalSeparator[0] != _T('.'))
{
for (TCHAR* x = szBuff; *x; ++x)
{
if (*x == m_DecimalSeparator[0])
{
*x = _T('.');
break;
}
}
}
TCHAR* endPtr;
//replace the negative sign with -
if (szBuff[0] == m_NegativeSign[0]) szBuff[0] = _T('-');
d = _tcstod(szBuff, &endPtr);
return *endPtr == _T('\0');
}
/// sets a number as the controls text
/// \param[in] d the value
/// \param[in] count digits after the decimal point
void SetFixedValue(double d, int count)
{
int decimal_pos;
int sign;
char digits[limit];
_fcvt_s(digits, d, count, &decimal_pos, &sign);
return DisplayDecimalValue(digits, decimal_pos, sign);
}
/// sets a number as the controls text
/// \param[in] d the value
/// \param[in] count total number of digits
void SetDecimalValue(double d, int count)
{
int decimal_pos;
int sign;
char digits[limit];
_ecvt_s(digits, d, count, &decimal_pos, &sign);
DisplayDecimalValue(digits, decimal_pos, sign);
}
/// sets a number as the controls text
/// \param[in] d the value
/// \remarks the total number of digits is calculated using the GetLimitText function
void SetDecimalValue(double d)
{
SetDecimalValue(d, std::min(limit, static_cast<int>(static_cast<const T*>(this)->GetLimitText())) - 2);
}
/// sets the controls text
/// \param[in] digits array containing the digits
/// \param[in] decimal_pos the position of the decimal point
/// \param[in] sign 1 if negative
void DisplayDecimalValue(const char* digits, int decimal_pos, int sign)
{
TCHAR szBuff[limit];
DecimalToText(szBuff, limit, digits, decimal_pos, sign);
static_cast<T*>(this)->SetWindowText(szBuff);
}
/// convert a digit array to string
/// \param[out] szBuff target buffer for output
/// \param[in] buflen maximum characters in output buffer
/// \param[in] digits array containing the digits
/// \param[in] decimal_pos the position of the decimal point
/// \param[in] sign 1 if negative
void DecimalToText(TCHAR* szBuff, size_t buflen, const char* digits, int decimal_pos, int sign) const
{
int i = 0;
size_t pos = 0;
if (sign)
{
for (const TCHAR *x = m_NegativeSign; *x ; ++x, ++pos) szBuff[pos] = *x;
}
for (; pos < buflen && digits[i] && i < decimal_pos ; ++i, ++pos) szBuff[pos] = digits[i];
if (decimal_pos < 1) szBuff[pos++] = _T('0');
size_t last_nonzero = pos;
for (const TCHAR *x = m_DecimalSeparator; *x ; ++x, ++pos) szBuff[pos] = *x;
for (; pos < buflen && decimal_pos < 0; ++decimal_pos, ++pos) szBuff[pos] = _T('0');
for (; pos < buflen && digits[i]; ++i, ++pos)
{
szBuff[pos] = digits[i];
if (digits[i] != '0') last_nonzero = pos+1;
}
szBuff[std::min(buflen - 1, last_nonzero)] = _T('\0');
}
void AllowNegative(bool allow)
{
m_allowNegative = allow;
}
void AllowFractions(bool allow)
{
m_allowFractions = allow;
}
};
class CNumberEdit : public CEdit, public CDecimalSupport<CNumberEdit>
{
public:
void SetTempoValue(const TEMPO &t);
TEMPO GetTempoValue();
protected:
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg LPARAM OnPaste(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,154 @@
/*
* CImageListEx.cpp
* ----------------
* Purpose: A class that extends MFC's CImageList to handle alpha-blended images properly.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "CImageListEx.h"
#include "Image.h"
#include "../misc/mptColor.h"
#include "Mptrack.h"
OPENMPT_NAMESPACE_BEGIN
bool CImageListEx::Create(UINT resourceID, int cx, int cy, int nInitial, int nGrow, CDC *dc, double scaling, bool disabled, const mpt::span<const int> invertImages)
{
std::unique_ptr<RawGDIDIB> bitmap;
try
{
bitmap = LoadPixelImage(GetResource(MAKEINTRESOURCE(resourceID), _T("PNG")), scaling, cx, cy);
cx = mpt::saturate_round<int>(cx * scaling);
cy = mpt::saturate_round<int>(cy * scaling);
} catch(...)
{
return false;
}
const RawGDIDIB::Pixel buttonColor = GetSysColor(COLOR_BTNFACE);
const bool isDark = mpt::Color::GetLuma(buttonColor.r, buttonColor.g, buttonColor.b) < 128;
if(isDark)
{
// Invert brightness of icons on dark themes
for(const int img : invertImages)
{
for(int y = 0; y < cy; y++)
{
RawGDIDIB::Pixel *pixel = &(*bitmap)(img * cx, y);
for(int x = 0; x < cx; x++, pixel++)
{
auto hsv = mpt::Color::RGB{pixel->r / 255.0f, pixel->g / 255.0f, pixel->b / 255.0f}.ToHSV();
hsv.v = (1.0f - hsv.v) * (1.0f - hsv.s) + (hsv.v) * hsv.s;
const auto rgb = hsv.ToRGB();
pixel->r = mpt::saturate_cast<uint8>(rgb.r * 255.0f);
pixel->g = mpt::saturate_cast<uint8>(rgb.g * 255.0f);
pixel->b = mpt::saturate_cast<uint8>(rgb.b * 255.0f);
}
}
}
}
if(disabled)
{
// Grayed out icons
for(auto &pixel : bitmap->Pixels())
{
if(pixel.a != 0)
{
uint8 y = mpt::Color::GetLuma(pixel.r, pixel.g, pixel.b);
pixel.r = pixel.g = pixel.b = y;
if(isDark)
pixel.a -= pixel.a / 3;
else
pixel.a /= 2;
}
}
}
bool result;
if(dc == nullptr) dc = CDC::FromHandle(GetDC(NULL));
// Use 1-bit transperency when there is no alpha channel.
if(GetDeviceCaps(dc->GetSafeHdc(), BITSPIXEL) * GetDeviceCaps(dc->GetSafeHdc(), PLANES) < 32)
{
const uint32 rowSize = (bitmap->Width() + 31u) / 32u * 4u;
std::vector<uint8> bitmapMask(rowSize * bitmap->Height());
RawGDIDIB::Pixel *pixel = bitmap->Pixels().data();
for(uint32 y = 0; y < bitmap->Height(); y++)
{
uint8 *mask = bitmapMask.data() + rowSize * y;
for(uint32 x = 0; x < bitmap->Width(); x++, pixel++)
{
if(pixel->a != 0)
{
// Pixel not fully transparent - multiply with default background colour
#define MIXCOLOR(c) (((pixel-> c ) * pixel->a + (buttonColor. c) * (255 - pixel->a)) >> 8);
pixel->r = MIXCOLOR(r);
pixel->g = MIXCOLOR(g);
pixel->b = MIXCOLOR(b);
#undef MIXCOLOR
} else
{
// Transparent pixel
mask[x / 8u] |= 1 << (7 - (x & 7));
}
}
}
CBitmap ddb, dibMask;
CopyToCompatibleBitmap(ddb, *dc, *bitmap);
struct
{
BITMAPINFOHEADER bi;
RGBQUAD col[2];
} bi;
MemsetZero(bi);
bi.bi.biSize = sizeof(BITMAPINFOHEADER);
bi.bi.biWidth = bitmap->Width();
bi.bi.biHeight = -(int32)bitmap->Height();
bi.bi.biPlanes = 1;
bi.bi.biBitCount = 1;
bi.bi.biCompression = BI_RGB;
bi.bi.biSizeImage = static_cast<DWORD>(bitmapMask.size() * sizeof(decltype(bitmapMask[0])));
bi.col[1].rgbBlue = bi.col[1].rgbGreen = bi.col[1].rgbRed = 255;
dibMask.CreateCompatibleBitmap(dc, bitmap->Width(), bitmap->Height());
SetDIBits(dc->GetSafeHdc(), dibMask, 0, bitmap->Height(), bitmapMask.data(), reinterpret_cast<BITMAPINFO *>(&bi), DIB_RGB_COLORS);
result = CImageList::Create(cx, cy, ILC_COLOR24 | ILC_MASK, nInitial, nGrow)
&& CImageList::Add(&ddb, &dibMask);
} else
{
// 32-bit image on modern system
// Make fully transparent pixels use the mask color. This should hopefully make the icons look "somewhat" okay
// on system where the alpha channel is magically missing in 32-bit mode (https://bugs.openmpt.org/view.php?id=520)
for(auto &pixel : bitmap->Pixels())
{
if(pixel.a == 0)
{
pixel.r = 255;
pixel.g = 0;
pixel.b = 255;
}
}
CBitmap ddb;
CopyToCompatibleBitmap(ddb, *dc, *bitmap);
result = CImageList::Create(cx, cy, ILC_COLOR32 | ILC_MASK, nInitial, nGrow)
&& CImageList::Add(&ddb, RGB(255, 0, 255));
}
return result;
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,24 @@
/*
* CImageListEx.h
* --------------
* Purpose: A class that extends MFC's CImageList to handle alpha-blended images properly. Also provided 1-bit transparency fallback when needed.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "mpt/base/span.hpp"
OPENMPT_NAMESPACE_BEGIN
class CImageListEx : public CImageList
{
public:
bool Create(UINT resourceID, int cx, int cy, int nInitial, int nGrow, CDC *dc, double scaling, bool disabled, const mpt::span<const int> invertImages = {});
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,104 @@
/*
* CListCtrl.h
* -----------
* Purpose: A class that extends MFC's CListCtrl with some more functionality and to handle unicode strings in ANSI builds.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "MPTrackUtil.h"
OPENMPT_NAMESPACE_BEGIN
class CListCtrlEx : public CListCtrl
{
public:
struct Header
{
const TCHAR *text = nullptr;
int width = 0;
UINT mask = 0;
};
void SetHeaders(const mpt::span<const Header> &header)
{
for(int i = 0; i < static_cast<int>(header.size()); i++)
{
int width = header[i].width;
InsertColumn(i, header[i].text, header[i].mask, width >= 0 ? Util::ScalePixels(width, m_hWnd) : 16);
if(width < 0)
SetColumnWidth(i, width);
}
}
void SetItemDataPtr(int item, void *value)
{
SetItemData(item, reinterpret_cast<DWORD_PTR>(value));
}
void *GetItemDataPtr(int item)
{
return reinterpret_cast<void *>(GetItemData(item));
}
// Unicode strings in ANSI builds
#ifndef UNICODE
BOOL SetItemText(int nItem, int nSubItem, const WCHAR *lpszText)
{
ASSERT(::IsWindow(m_hWnd));
ASSERT((GetStyle() & LVS_OWNERDATA)==0);
LVITEMW lvi;
lvi.iSubItem = nSubItem;
lvi.pszText = (LPWSTR) lpszText;
return (BOOL) ::SendMessage(m_hWnd, LVM_SETITEMTEXTW, nItem, (LPARAM)&lvi);
}
using CListCtrl::SetItemText;
#endif
};
#ifdef MPT_MFC_FULL
class CMFCListCtrlEx : public CMFCListCtrl
{
public:
struct Header
{
const TCHAR *text = nullptr;
int width = 0;
UINT mask = 0;
};
void SetHeaders(const mpt::span<const Header> &header)
{
for(int i = 0; i < static_cast<int>(header.size()); i++)
{
InsertColumn(i, header[i].text, header[i].mask, Util::ScalePixels(header[i].width, m_hWnd));
}
}
// Unicode strings in ANSI builds
#ifndef UNICODE
BOOL SetItemText(int nItem, int nSubItem, const WCHAR *lpszText)
{
ASSERT(::IsWindow(m_hWnd));
ASSERT((GetStyle() & LVS_OWNERDATA)==0);
LVITEMW lvi;
lvi.iSubItem = nSubItem;
lvi.pszText = (LPWSTR) lpszText;
return (BOOL) ::SendMessage(m_hWnd, LVM_SETITEMTEXTW, nItem, (LPARAM)&lvi);
}
using CListCtrl::SetItemText;
#endif
};
#endif // MPT_MFC_FULL
OPENMPT_NAMESPACE_END
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,115 @@
/*
* ChannelManagerDlg.h
* -------------------
* Purpose: Dialog class for moving, removing, managing channels
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "PatternEditorDialogs.h"
OPENMPT_NAMESPACE_BEGIN
class CModDoc;
class CChannelManagerDlg: public CDialog
{
enum Tab
{
kSoloMute = 0,
kRecordSelect = 1,
kPluginState = 2,
kReorderRemove = 3,
};
public:
static CChannelManagerDlg * sharedInstance() { return sharedInstance_; }
static CChannelManagerDlg * sharedInstanceCreate();
static void DestroySharedInstance() { delete sharedInstance_; sharedInstance_ = nullptr; }
void SetDocument(CModDoc *modDoc);
CModDoc *GetDocument() const { return m_ModDoc; }
bool IsDisplayed() const;
void Update(UpdateHint hint, CObject* pHint);
void Show();
void Hide();
private:
static CChannelManagerDlg *sharedInstance_;
QuickChannelProperties m_quickChannelProperties;
protected:
enum ButtonAction : uint8
{
kUndetermined,
kAction1,
kAction2,
};
enum MouseButton : uint8
{
CM_BT_NONE,
CM_BT_LEFT,
CM_BT_RIGHT,
};
CChannelManagerDlg();
~CChannelManagerDlg();
CHANNELINDEX memory[4][MAX_BASECHANNELS];
std::array<CHANNELINDEX, MAX_BASECHANNELS> pattern;
std::bitset<MAX_BASECHANNELS> removed;
std::bitset<MAX_BASECHANNELS> select;
std::bitset<MAX_BASECHANNELS> state;
CRect move[MAX_BASECHANNELS];
CRect m_drawableArea;
CModDoc *m_ModDoc = nullptr;
HBITMAP m_bkgnd = nullptr;
Tab m_currentTab = kSoloMute;
int m_downX = 0, m_downY = 0;
int m_moveX = 0, m_moveY = 0;
int m_buttonHeight = 0;
ButtonAction m_buttonAction;
bool m_leftButton = false;
bool m_rightButton = false;
bool m_moveRect = false;
bool m_show = false;
bool ButtonHit(CPoint point, CHANNELINDEX *id, CRect *invalidate) const;
void MouseEvent(UINT nFlags, CPoint point, MouseButton button);
void ResetState(bool bSelection = true, bool bMove = true, bool bButton = true, bool bInternal = true, bool bOrder = false);
void ResizeWindow();
//{{AFX_VIRTUAL(CChannelManagerDlg)
BOOL OnInitDialog() override;
//}}AFX_VIRTUAL
//{{AFX_MSG(CChannelManagerDlg)
afx_msg void OnApply();
afx_msg void OnClose();
afx_msg void OnSelectAll();
afx_msg void OnInvert();
afx_msg void OnAction1();
afx_msg void OnAction2();
afx_msg void OnStore();
afx_msg void OnRestore();
afx_msg void OnTabSelchange(NMHDR*, LRESULT* pResult);
afx_msg void OnPaint();
afx_msg void OnMouseMove(UINT nFlags,CPoint point);
afx_msg void OnLButtonUp(UINT nFlags,CPoint point);
afx_msg void OnLButtonDown(UINT nFlags,CPoint point);
afx_msg void OnRButtonUp(UINT nFlags,CPoint point);
afx_msg void OnRButtonDown(UINT nFlags,CPoint point);
afx_msg void OnMButtonDown(UINT nFlags,CPoint point);
afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
afx_msg void OnRButtonDblClk(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP();
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,483 @@
/*
* ChildFrm.cpp
* ------------
* Purpose: Implementation of the MDI document child windows.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include <afxpriv.h>
#include "Mptrack.h"
#include "Mainfrm.h"
#include "Childfrm.h"
#include "Moddoc.h"
#include "Globals.h"
#include "View_gen.h"
#include "Ctrl_pat.h"
#include "View_pat.h"
#include "Ctrl_smp.h"
#include "View_smp.h"
#include "Ctrl_ins.h"
#include "View_ins.h"
#include "view_com.h"
#include "Childfrm.h"
#include "ChannelManagerDlg.h"
#include "mpt/io/io.hpp"
#include "mpt/io/io_stdstream.hpp"
#include "../common/FileReader.h"
#include <sstream>
OPENMPT_NAMESPACE_BEGIN
/////////////////////////////////////////////////////////////////////////////
// CChildFrame
IMPLEMENT_DYNCREATE(CChildFrame, CMDIChildWnd)
BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd)
//{{AFX_MSG_MAP(CChildFrame)
ON_WM_DESTROY()
ON_WM_NCACTIVATE()
ON_WM_MDIACTIVATE()
ON_MESSAGE(WM_MOD_CHANGEVIEWCLASS, &CChildFrame::OnChangeViewClass)
ON_MESSAGE(WM_MOD_INSTRSELECTED, &CChildFrame::OnInstrumentSelected)
// toolbar "tooltip" notification
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXT, 0, 0xFFFF, &CChildFrame::OnToolTipText)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
CChildFrame *CChildFrame::m_lastActiveFrame = nullptr;
int CChildFrame::glMdiOpenCount = 0;
/////////////////////////////////////////////////////////////////////////////
// CChildFrame construction/destruction
CChildFrame::CChildFrame()
{
m_bInitialActivation=true; //rewbs.fix3185
m_szCurrentViewClassName[0] = 0;
m_hWndCtrl = m_hWndView = NULL;
m_bMaxWhenClosed = false;
glMdiOpenCount++;
}
CChildFrame::~CChildFrame()
{
if ((--glMdiOpenCount) == 0)
{
TrackerSettings::Instance().gbMdiMaximize = m_bMaxWhenClosed;
}
}
BOOL CChildFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
// create a splitter with 2 rows, 1 column
if (!m_wndSplitter.CreateStatic(this, 2, 1)) return FALSE;
// add the first splitter pane - the default view in row 0
int cy = Util::ScalePixels(TrackerSettings::Instance().glGeneralWindowHeight, m_hWnd); //rewbs.varWindowSize - default to general tab.
if (cy <= 1) cy = (lpcs->cy*2) / 3;
if (!m_wndSplitter.CreateView(0, 0, pContext->m_pNewViewClass, CSize(0, cy), pContext)) return FALSE;
// Get 2nd window handle
CModControlView *pModView;
if ((pModView = GetModControlView()) != nullptr)
{
m_hWndCtrl = pModView->m_hWnd;
pModView->SetMDIParentFrame(m_hWnd);
}
const BOOL bStatus = ChangeViewClass(RUNTIME_CLASS(CViewGlobals), pContext);
// If it all worked, we now have a splitter window which contain two different views
return bStatus;
}
void CChildFrame::SetSplitterHeight(int cy)
{
if (cy <= 1) cy = 188; //default to 188? why not..
m_wndSplitter.SetRowInfo(0, Util::ScalePixels(cy, m_hWnd), 15);
}
BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs)
{
return CMDIChildWnd::PreCreateWindow(cs);
}
BOOL CChildFrame::OnNcActivate(BOOL bActivate)
{
if(bActivate && m_hWndView)
{
// Need this in addition to OnMDIActivate when switching from a non-MDI window such as a plugin editor
CMainFrame::GetMainFrame()->SetMidiRecordWnd(m_hWndView);
}
if(m_hWndCtrl)
::SendMessage(m_hWndCtrl, bActivate ? WM_MOD_MDIACTIVATE : WM_MOD_MDIDEACTIVATE, 0, 0);
if(m_hWndView)
::SendMessage(m_hWndView, bActivate ? WM_MOD_MDIACTIVATE : WM_MOD_MDIDEACTIVATE, 0, 0);
return CMDIChildWnd::OnNcActivate(bActivate);
}
void CChildFrame::OnMDIActivate(BOOL bActivate, CWnd *pActivateWnd, CWnd *pDeactivateWnd)
{
CMDIChildWnd::OnMDIActivate(bActivate, pActivateWnd, pDeactivateWnd);
if(bActivate)
{
MPT_ASSERT(pActivateWnd == this);
CMainFrame::GetMainFrame()->UpdateEffectKeys(static_cast<CModDoc *>(GetActiveDocument()));
CMainFrame::GetMainFrame()->SetMidiRecordWnd(m_hWndView);
m_lastActiveFrame = this;
}
if(m_hWndCtrl)
::SendMessage(m_hWndCtrl, bActivate ? WM_MOD_MDIACTIVATE : WM_MOD_MDIDEACTIVATE, 0, 0);
if(m_hWndView)
::SendMessage(m_hWndView, bActivate ? WM_MOD_MDIACTIVATE : WM_MOD_MDIDEACTIVATE, 0, 0);
// Update channel manager according to active document
auto instance = CChannelManagerDlg::sharedInstance();
if(instance != nullptr)
{
if(!bActivate && pActivateWnd == nullptr)
instance->SetDocument(nullptr);
else if(bActivate)
instance->SetDocument(static_cast<CModDoc *>(GetActiveDocument()));
}
}
void CChildFrame::ActivateFrame(int nCmdShow)
{
if ((glMdiOpenCount == 1) && (TrackerSettings::Instance().gbMdiMaximize) && (nCmdShow == -1))
{
nCmdShow = SW_SHOWMAXIMIZED;
}
CMDIChildWnd::ActivateFrame(nCmdShow);
// When song first loads, initialise patternViewState to point to start of song.
CView *pView = GetActiveView();
CModDoc *pModDoc = nullptr;
if (pView) pModDoc = (CModDoc *)pView->GetDocument();
if ((m_hWndCtrl) && (pModDoc))
{
if (m_bInitialActivation && m_ViewPatterns.nPattern == 0)
{
if(!pModDoc->GetSoundFile().Order().empty())
m_ViewPatterns.nPattern = pModDoc->GetSoundFile().Order()[0];
m_bInitialActivation = false;
}
}
}
void CChildFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
{
// update our parent window first
GetMDIFrame()->OnUpdateFrameTitle(bAddToTitle);
if ((GetStyle() & FWS_ADDTOTITLE) == 0) return; // leave child window alone!
CDocument* pDocument = GetActiveDocument();
if (bAddToTitle)
{
CString szText;
if (pDocument == nullptr)
{
szText.Preallocate(m_strTitle.GetLength() + 10);
szText = m_strTitle;
} else
{
szText.Preallocate(pDocument->GetTitle().GetLength() + 10);
szText = pDocument->GetTitle();
if (pDocument->IsModified()) szText += _T("*");
}
if (m_nWindow > 0)
szText.AppendFormat(_T(":%d"), m_nWindow);
// set title if changed, but don't remove completely
AfxSetWindowText(m_hWnd, szText);
}
}
BOOL CChildFrame::ChangeViewClass(CRuntimeClass* pViewClass, CCreateContext* pContext)
{
CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
CWnd *pWnd;
if (!strcmp(pViewClass->m_lpszClassName, m_szCurrentViewClassName)) return TRUE;
if (m_szCurrentViewClassName[0])
{
m_szCurrentViewClassName[0] = 0;
m_wndSplitter.DeleteView(1, 0);
}
if ((m_hWndView) && (pMainFrm))
{
if (pMainFrm->GetMidiRecordWnd() == m_hWndView)
{
pMainFrm->SetMidiRecordWnd(NULL);
}
}
m_hWndView = NULL;
if (!m_wndSplitter.CreateView(1, 0, pViewClass, CSize(0, 0), pContext)) return FALSE;
// Get 2nd window handle
if ((pWnd = m_wndSplitter.GetPane(1, 0)) != NULL) m_hWndView = pWnd->m_hWnd;
strcpy(m_szCurrentViewClassName, pViewClass->m_lpszClassName);
m_wndSplitter.RecalcLayout();
if ((m_hWndView) && (m_hWndCtrl))
{
::PostMessage(m_hWndView, WM_MOD_VIEWMSG, VIEWMSG_SETCTRLWND, (LPARAM)m_hWndCtrl);
::PostMessage(m_hWndCtrl, WM_MOD_CTRLMSG, CTRLMSG_SETVIEWWND, (LPARAM)m_hWndView);
pMainFrm->SetMidiRecordWnd(m_hWndView);
}
return TRUE;
}
void CChildFrame::ForceRefresh()
{
CModControlView *pModView;
if ((pModView = GetModControlView()) != nullptr)
{
pModView->ForceRefresh();
}
return;
}
void CChildFrame::SavePosition(BOOL bForce)
{
if (m_hWnd)
{
m_bMaxWhenClosed = IsZoomed() != FALSE;
if (bForce) TrackerSettings::Instance().gbMdiMaximize = m_bMaxWhenClosed;
if (!IsIconic())
{
CWnd *pWnd = m_wndSplitter.GetPane(0, 0);
if (pWnd)
{
CRect rect(0, 0, 0, 0);
pWnd->GetWindowRect(&rect);
if(rect.Width() == 0)
return;
int l = Util::ScalePixelsInv(rect.Height(), m_hWnd);
//rewbs.varWindowSize - not the nicest piece of code, but we need to distinguish between the views:
if (strcmp(CViewGlobals::classCViewGlobals.m_lpszClassName, m_szCurrentViewClassName) == 0)
TrackerSettings::Instance().glGeneralWindowHeight = l;
else if (strcmp(CViewPattern::classCViewPattern.m_lpszClassName, m_szCurrentViewClassName) == 0)
TrackerSettings::Instance().glPatternWindowHeight = l;
else if (strcmp(CViewSample::classCViewSample.m_lpszClassName, m_szCurrentViewClassName) == 0)
TrackerSettings::Instance().glSampleWindowHeight = l;
else if (strcmp(CViewInstrument::classCViewInstrument.m_lpszClassName, m_szCurrentViewClassName) == 0)
TrackerSettings::Instance().glInstrumentWindowHeight = l;
else if (strcmp(CViewComments::classCViewComments.m_lpszClassName, m_szCurrentViewClassName) == 0)
TrackerSettings::Instance().glCommentsWindowHeight = l;
}
}
}
}
int CChildFrame::GetSplitterHeight()
{
if (m_hWnd)
{
CRect rect;
CWnd *pWnd = m_wndSplitter.GetPane(0, 0);
if (pWnd)
{
pWnd->GetWindowRect(&rect);
return Util::ScalePixelsInv(rect.Height(), m_hWnd);
}
}
return 15; // tidy default
};
LRESULT CChildFrame::SendCtrlMessage(UINT uMsg, LPARAM lParam) const
{
if(m_hWndCtrl)
return ::SendMessage(m_hWndCtrl, WM_MOD_CTRLMSG, uMsg, lParam);
return 0;
}
LRESULT CChildFrame::SendViewMessage(UINT uMsg, LPARAM lParam) const
{
if(m_hWndView)
return ::SendMessage(m_hWndView, WM_MOD_VIEWMSG, uMsg, lParam);
return 0;
}
LRESULT CChildFrame::OnInstrumentSelected(WPARAM wParam, LPARAM lParam)
{
CView *pView = GetActiveView();
CModDoc *pModDoc = NULL;
if (pView) pModDoc = (CModDoc *)pView->GetDocument();
if ((m_hWndCtrl) && (pModDoc))
{
auto nIns = lParam;
if ((!wParam) && (pModDoc->GetNumInstruments() > 0))
{
nIns = pModDoc->FindSampleParent(static_cast<SAMPLEINDEX>(nIns));
if(nIns == INSTRUMENTINDEX_INVALID)
{
nIns = 0;
}
}
::SendMessage(m_hWndCtrl, WM_MOD_CTRLMSG, CTRLMSG_PAT_SETINSTRUMENT, nIns);
}
return 0;
}
/////////////////////////////////////////////////////////////////////////////
// CChildFrame message handlers
void CChildFrame::OnDestroy()
{
SavePosition();
if(m_lastActiveFrame == this)
m_lastActiveFrame = nullptr;
CMDIChildWnd::OnDestroy();
}
BOOL CChildFrame::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult)
{
auto pTTT = reinterpret_cast<TOOLTIPTEXT *>(pNMHDR);
TCHAR szFullText[256] = _T("");
CString strTipText;
UINT_PTR nID = pNMHDR->idFrom;
if (pTTT->uFlags & TTF_IDISHWND)
{
// idFrom is actually the HWND of the tool
nID = static_cast<UINT_PTR>(::GetDlgCtrlID(reinterpret_cast<HWND>(nID)));
}
if ((nID >= 1000) && (nID < 65536) && (m_hWndCtrl) && (::SendMessage(m_hWndCtrl, WM_MOD_GETTOOLTIPTEXT, nID, (LPARAM)szFullText)))
{
strTipText = szFullText;
} else
{
// allow top level routing frame to handle the message
if (GetRoutingFrame() != NULL) return FALSE;
if (nID != 0) // will be zero on a separator
{
AfxLoadString((UINT)nID, szFullText);
// this is the command id, not the button index
AfxExtractSubString(strTipText, szFullText, 1, _T('\n'));
}
}
mpt::String::WriteCStringBuf(pTTT->szText) = strTipText;
*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
}
LRESULT CChildFrame::OnChangeViewClass(WPARAM wParam, LPARAM lParam)
{
CModControlDlg *pDlg = (CModControlDlg *)lParam;
if (pDlg)
{
CRuntimeClass *pNewViewClass = pDlg->GetAssociatedViewClass();
if (pNewViewClass) ChangeViewClass(pNewViewClass);
::PostMessage(m_hWndCtrl, WM_MOD_CTRLMSG, CTRLMSG_ACTIVATEPAGE, (LPARAM)wParam);
}
return 0;
}
const char *CChildFrame::GetCurrentViewClassName() const
{
return m_szCurrentViewClassName;
}
std::string CChildFrame::SerializeView() const
{
std::ostringstream f(std::ios::out | std::ios::binary);
// Version
mpt::IO::WriteVarInt(f, 0u);
// Current page
mpt::IO::WriteVarInt(f, static_cast<uint8>(GetModControlView()->GetActivePage()));
CModControlView *view = GetModControlView();
if (strcmp(CViewPattern::classCViewPattern.m_lpszClassName, m_szCurrentViewClassName) == 0)
{
mpt::IO::WriteVarInt(f, (uint32)view->SendMessage(WM_MOD_CTRLMSG, CTRLMSG_GETCURRENTORDER)); // Order number
} else if (strcmp(CViewSample::classCViewSample.m_lpszClassName, m_szCurrentViewClassName) == 0)
{
mpt::IO::WriteVarInt(f, (uint32)view->SendMessage(WM_MOD_CTRLMSG, CTRLMSG_GETCURRENTINSTRUMENT)); // Sample number
} else if (strcmp(CViewInstrument::classCViewInstrument.m_lpszClassName, m_szCurrentViewClassName) == 0)
{
mpt::IO::WriteVarInt(f, (uint32)view->SendMessage(WM_MOD_CTRLMSG, CTRLMSG_GETCURRENTINSTRUMENT)); // Instrument number
}
return f.str();
}
void CChildFrame::DeserializeView(FileReader &file)
{
uint32 version, page;
if(file.ReadVarInt(version) && version == 0 &&
file.ReadVarInt(page) && page >= 0 && page < CModControlView::MAX_PAGES)
{
UINT pageDlg = 0;
switch(page)
{
case CModControlView::VIEW_GLOBALS:
pageDlg = IDD_CONTROL_GLOBALS;
break;
case CModControlView::VIEW_PATTERNS:
pageDlg = IDD_CONTROL_PATTERNS;
file.ReadVarInt(m_ViewPatterns.initialOrder);
break;
case CModControlView::VIEW_SAMPLES:
pageDlg = IDD_CONTROL_SAMPLES;
file.ReadVarInt(m_ViewSamples.initialSample);
break;
case CModControlView::VIEW_INSTRUMENTS:
pageDlg = IDD_CONTROL_INSTRUMENTS;
file.ReadVarInt(m_ViewInstruments.initialInstrument);
break;
case CModControlView::VIEW_COMMENTS:
pageDlg = IDD_CONTROL_COMMENTS;
break;
}
GetModControlView()->PostMessage(WM_MOD_ACTIVATEVIEW, pageDlg, (LPARAM)-1);
}
}
void CChildFrame::ToggleViews()
{
auto focus = ::GetFocus();
if(focus == GetHwndView() || ::IsChild(GetHwndView(), focus))
SendCtrlMessage(CTRLMSG_SETFOCUS);
else if(focus == GetHwndCtrl() || ::IsChild(GetHwndCtrl(), focus))
SendViewMessage(VIEWMSG_SETFOCUS);
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,156 @@
/*
* Childfrm.h
* ----------
* Purpose: Implementation of tab interface class.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "PatternCursor.h"
#include "../common/FileReaderFwd.h"
OPENMPT_NAMESPACE_BEGIN
class CModControlView;
class CModControlDlg;
struct GENERALVIEWSTATE
{
PlugParamIndex nParam = 0;
CHANNELINDEX nTab = 0;
PLUGINDEX nPlugin = 0;
bool initialized = false;
};
struct PATTERNVIEWSTATE
{
PATTERNINDEX nPattern = 0;
PatternCursor cursor = 0;
PatternRect selection;
PatternCursor::Columns nDetailLevel = PatternCursor::firstColumn;
ORDERINDEX nOrder = 0;
ORDERINDEX initialOrder = ORDERINDEX_INVALID;
bool initialized = false;
};
struct SAMPLEVIEWSTATE
{
SmpLength dwScrollPos = 0;
SmpLength dwBeginSel = 0;
SmpLength dwEndSel = 0;
SAMPLEINDEX nSample = 0;
SAMPLEINDEX initialSample = 0;
};
struct INSTRUMENTVIEWSTATE
{
float zoom = 4;
EnvelopeType nEnv = ENV_VOLUME;
INSTRUMENTINDEX initialInstrument = 0;
bool bGrid = false;
bool initialized = false;
};
struct COMMENTVIEWSTATE
{
UINT nId = 0;
bool initialized = false;
};
class CChildFrame: public CMDIChildWnd
{
friend class CModControlDlg;
DECLARE_DYNCREATE(CChildFrame)
public:
CChildFrame();
protected:
static CChildFrame *m_lastActiveFrame;
static int glMdiOpenCount;
// Attributes
protected:
CSplitterWnd m_wndSplitter;
HWND m_hWndCtrl, m_hWndView;
GENERALVIEWSTATE m_ViewGeneral;
PATTERNVIEWSTATE m_ViewPatterns;
SAMPLEVIEWSTATE m_ViewSamples;
INSTRUMENTVIEWSTATE m_ViewInstruments;
COMMENTVIEWSTATE m_ViewComments;
CHAR m_szCurrentViewClassName[256];
bool m_bMaxWhenClosed;
bool m_bInitialActivation;
// Operations
public:
CModControlView *GetModControlView() const { return (CModControlView *)m_wndSplitter.GetPane(0, 0); }
BOOL ChangeViewClass(CRuntimeClass* pNewViewClass, CCreateContext* pContext=NULL);
void ForceRefresh();
void SavePosition(BOOL bExit=FALSE);
const char *GetCurrentViewClassName() const;
LRESULT SendCtrlMessage(UINT uMsg, LPARAM lParam = 0) const;
LRESULT SendViewMessage(UINT uMsg, LPARAM lParam = 0) const;
LRESULT ActivateView(UINT nId, LPARAM lParam) { return ::SendMessage(m_hWndCtrl, WM_MOD_ACTIVATEVIEW, nId, lParam); }
HWND GetHwndCtrl() const { return m_hWndCtrl; }
HWND GetHwndView() const { return m_hWndView; }
GENERALVIEWSTATE &GetGeneralViewState() { return m_ViewGeneral; }
PATTERNVIEWSTATE &GetPatternViewState() { return m_ViewPatterns; }
SAMPLEVIEWSTATE &GetSampleViewState() { return m_ViewSamples; }
INSTRUMENTVIEWSTATE &GetInstrumentViewState() { return m_ViewInstruments; }
COMMENTVIEWSTATE &GetCommentViewState() { return m_ViewComments; }
void SetSplitterHeight(int x);
int GetSplitterHeight();
std::string SerializeView() const;
void DeserializeView(FileReader &file);
void ToggleViews();
static CChildFrame *LastActiveFrame() { return m_lastActiveFrame; }
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CChildFrame)
public:
BOOL OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) override;
BOOL PreCreateWindow(CREATESTRUCT& cs) override;
void ActivateFrame(int nCmdShow) override;
void OnUpdateFrameTitle(BOOL bAddToTitle) override;
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CChildFrame();
// Generated message map functions
protected:
//{{AFX_MSG(CChildFrame)
afx_msg void OnDestroy();
afx_msg BOOL OnNcActivate(BOOL bActivate);
afx_msg void OnMDIActivate(BOOL bActivate, CWnd *pActivateWnd, CWnd *pDeactivateWnd);
afx_msg LRESULT OnChangeViewClass(WPARAM, LPARAM lParam);
afx_msg LRESULT OnInstrumentSelected(WPARAM, LPARAM lParam);
afx_msg BOOL OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.
OPENMPT_NAMESPACE_END
@@ -0,0 +1,977 @@
/*
* CleanupSong.cpp
* ---------------
* Purpose: Dialog for cleaning up modules (rearranging, removing unused items).
* 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 "Moddoc.h"
#include "Mainfrm.h"
#include "CleanupSong.h"
#include "../common/mptStringBuffer.h"
#include "../soundlib/mod_specifications.h"
#include "../soundlib/modsmp_ctrl.h"
#include "../tracklib/SampleEdit.h"
OPENMPT_NAMESPACE_BEGIN
// Default checkbox state
bool CModCleanupDlg::m_CheckBoxes[kMaxCleanupOptions] =
{
true, false, true, true, // patterns
false, false, // orders
true, false, false, true, // samples
true, false, // instruments
true, false, // plugins
false, true, // misc
};
// Checkbox -> Control ID LUT
WORD const CModCleanupDlg::m_CleanupIDtoDlgID[kMaxCleanupOptions] =
{
// patterns
IDC_CHK_CLEANUP_PATTERNS, IDC_CHK_REMOVE_PATTERNS,
IDC_CHK_REARRANGE_PATTERNS, IDC_CHK_REMOVE_DUPLICATES,
// orders
IDC_CHK_MERGE_SEQUENCES, IDC_CHK_REMOVE_ORDERS,
// samples
IDC_CHK_CLEANUP_SAMPLES, IDC_CHK_REMOVE_SAMPLES,
IDC_CHK_REARRANGE_SAMPLES, IDC_CHK_OPTIMIZE_SAMPLES,
// instruments
IDC_CHK_CLEANUP_INSTRUMENTS, IDC_CHK_REMOVE_INSTRUMENTS,
// plugins
IDC_CHK_CLEANUP_PLUGINS, IDC_CHK_REMOVE_PLUGINS,
// misc
IDC_CHK_RESET_VARIABLES, IDC_CHK_UNUSED_CHANNELS,
};
// Options that are mutually exclusive to each other
CModCleanupDlg::CleanupOptions const CModCleanupDlg::m_MutuallyExclusive[CModCleanupDlg::kMaxCleanupOptions] =
{
// patterns
kRemovePatterns, kCleanupPatterns,
kRemovePatterns, kRemovePatterns,
// orders
kRemoveOrders, kMergeSequences,
// samples
kRemoveSamples, kCleanupSamples,
kRemoveSamples, kRemoveSamples,
// instruments
kRemoveAllInstruments, kCleanupInstruments,
// plugins
kRemoveAllPlugins, kCleanupPlugins,
// misc
kNone, kNone,
};
///////////////////////////////////////////////////////////////////////
// CModCleanupDlg
BEGIN_MESSAGE_MAP(CModCleanupDlg, CDialog)
//{{AFX_MSG_MAP(CModTypeDlg)
ON_COMMAND(IDC_BTN_CLEANUP_SONG, &CModCleanupDlg::OnPresetCleanupSong)
ON_COMMAND(IDC_BTN_COMPO_CLEANUP, &CModCleanupDlg::OnPresetCompoCleanup)
ON_COMMAND(IDC_CHK_CLEANUP_PATTERNS, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_COMMAND(IDC_CHK_REMOVE_PATTERNS, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_COMMAND(IDC_CHK_REARRANGE_PATTERNS, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_COMMAND(IDC_CHK_REMOVE_DUPLICATES, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_COMMAND(IDC_CHK_MERGE_SEQUENCES, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_COMMAND(IDC_CHK_REMOVE_ORDERS, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_COMMAND(IDC_CHK_CLEANUP_SAMPLES, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_COMMAND(IDC_CHK_REMOVE_SAMPLES, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_COMMAND(IDC_CHK_REARRANGE_SAMPLES, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_COMMAND(IDC_CHK_OPTIMIZE_SAMPLES, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_COMMAND(IDC_CHK_CLEANUP_INSTRUMENTS, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_COMMAND(IDC_CHK_REMOVE_INSTRUMENTS, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_COMMAND(IDC_CHK_CLEANUP_PLUGINS, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_COMMAND(IDC_CHK_REMOVE_PLUGINS, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_COMMAND(IDC_CHK_RESET_VARIABLES, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_COMMAND(IDC_CHK_UNUSED_CHANNELS, &CModCleanupDlg::OnVerifyMutualExclusive)
ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &CModCleanupDlg::OnToolTipNotify)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
BOOL CModCleanupDlg::OnInitDialog()
{
CDialog::OnInitDialog();
for(int i = 0; i < kMaxCleanupOptions; i++)
{
CheckDlgButton(m_CleanupIDtoDlgID[i], (m_CheckBoxes[i]) ? BST_CHECKED : BST_UNCHECKED);
}
CSoundFile &sndFile = modDoc.GetSoundFile();
GetDlgItem(m_CleanupIDtoDlgID[kMergeSequences])->EnableWindow((sndFile.Order.GetNumSequences() > 1) ? TRUE : FALSE);
GetDlgItem(m_CleanupIDtoDlgID[kRemoveSamples])->EnableWindow((sndFile.GetNumSamples() > 0) ? TRUE : FALSE);
GetDlgItem(m_CleanupIDtoDlgID[kRearrangeSamples])->EnableWindow((sndFile.GetNumSamples() > 1) ? TRUE : FALSE);
GetDlgItem(m_CleanupIDtoDlgID[kCleanupInstruments])->EnableWindow((sndFile.GetNumInstruments() > 0) ? TRUE : FALSE);
GetDlgItem(m_CleanupIDtoDlgID[kRemoveAllInstruments])->EnableWindow((sndFile.GetNumInstruments() > 0) ? TRUE : FALSE);
EnableToolTips(TRUE);
return TRUE;
}
void CModCleanupDlg::OnOK()
{
ScopedLogCapturer logcapturer(modDoc, _T("cleanup"), this);
for(int i = 0; i < kMaxCleanupOptions; i++)
{
m_CheckBoxes[i] = IsDlgButtonChecked(m_CleanupIDtoDlgID[i]) != BST_UNCHECKED;
}
bool modified = false;
// Orders
if(m_CheckBoxes[kMergeSequences]) modified |= MergeSequences();
if(m_CheckBoxes[kRemoveOrders]) modified |= RemoveAllOrders();
// Patterns
if(m_CheckBoxes[kRemovePatterns]) modified |= RemoveAllPatterns();
if(m_CheckBoxes[kCleanupPatterns]) modified |= RemoveUnusedPatterns();
if(m_CheckBoxes[kRemoveDuplicatePatterns]) modified |= RemoveDuplicatePatterns();
if(m_CheckBoxes[kRearrangePatterns]) modified |= RearrangePatterns();
// Instruments
if(modDoc.GetNumInstruments() > 0)
{
if(m_CheckBoxes[kRemoveAllInstruments]) modified |= RemoveAllInstruments();
if(m_CheckBoxes[kCleanupInstruments]) modified |= RemoveUnusedInstruments();
}
// Samples
if(m_CheckBoxes[kRemoveSamples]) modified |= RemoveAllSamples();
if(m_CheckBoxes[kCleanupSamples]) modified |= RemoveUnusedSamples();
if(m_CheckBoxes[kOptimizeSamples]) modified |= OptimizeSamples();
if(modDoc.GetNumSamples() > 1)
{
if(m_CheckBoxes[kRearrangeSamples]) modified |= RearrangeSamples();
}
// Plugins
if(m_CheckBoxes[kRemoveAllPlugins]) modified |= RemoveAllPlugins();
if(m_CheckBoxes[kCleanupPlugins]) modified |= RemoveUnusedPlugins();
// Create samplepack
if(m_CheckBoxes[kResetVariables]) modified |= ResetVariables();
// Remove unused channels
if(m_CheckBoxes[kCleanupChannels]) modified |= RemoveUnusedChannels();
if(modified) modDoc.SetModified();
modDoc.UpdateAllViews(nullptr, UpdateHint().ModType());
logcapturer.ShowLog(true);
CDialog::OnOK();
}
void CModCleanupDlg::OnVerifyMutualExclusive()
{
HWND hFocus = GetFocus()->m_hWnd;
for(int i = 0; i < kMaxCleanupOptions; i++)
{
// if this item is focussed, we have just (un)checked it.
if(hFocus == GetDlgItem(m_CleanupIDtoDlgID[i])->m_hWnd)
{
// if we just unchecked it, there's nothing to verify.
if(IsDlgButtonChecked(m_CleanupIDtoDlgID[i]) == BST_UNCHECKED)
return;
// now we can disable all elements that are mutually exclusive.
if(m_MutuallyExclusive[i] != kNone)
CheckDlgButton(m_CleanupIDtoDlgID[m_MutuallyExclusive[i]], BST_UNCHECKED);
// find other elements which are mutually exclusive with the selected element.
for(int j = 0; j < kMaxCleanupOptions; j++)
{
if(m_MutuallyExclusive[j] == i)
CheckDlgButton(m_CleanupIDtoDlgID[j], BST_UNCHECKED);
}
return;
}
}
}
void CModCleanupDlg::OnPresetCleanupSong()
{
// patterns
CheckDlgButton(IDC_CHK_CLEANUP_PATTERNS, BST_CHECKED);
CheckDlgButton(IDC_CHK_REMOVE_PATTERNS, BST_UNCHECKED);
CheckDlgButton(IDC_CHK_REARRANGE_PATTERNS, BST_CHECKED);
CheckDlgButton(IDC_CHK_REMOVE_DUPLICATES, BST_CHECKED);
// orders
CheckDlgButton(IDC_CHK_MERGE_SEQUENCES, BST_UNCHECKED);
CheckDlgButton(IDC_CHK_REMOVE_ORDERS, BST_UNCHECKED);
// samples
CheckDlgButton(IDC_CHK_CLEANUP_SAMPLES, BST_CHECKED);
CheckDlgButton(IDC_CHK_REMOVE_SAMPLES, BST_UNCHECKED);
CheckDlgButton(IDC_CHK_REARRANGE_SAMPLES, BST_UNCHECKED);
CheckDlgButton(IDC_CHK_OPTIMIZE_SAMPLES, BST_CHECKED);
// instruments
CheckDlgButton(IDC_CHK_CLEANUP_INSTRUMENTS, BST_CHECKED);
CheckDlgButton(IDC_CHK_REMOVE_INSTRUMENTS, BST_UNCHECKED);
// plugins
CheckDlgButton(IDC_CHK_CLEANUP_PLUGINS, BST_CHECKED);
CheckDlgButton(IDC_CHK_REMOVE_PLUGINS, BST_UNCHECKED);
// misc
CheckDlgButton(IDC_CHK_SAMPLEPACK, BST_UNCHECKED);
CheckDlgButton(IDC_CHK_UNUSED_CHANNELS, BST_CHECKED);
}
void CModCleanupDlg::OnPresetCompoCleanup()
{
// patterns
CheckDlgButton(IDC_CHK_CLEANUP_PATTERNS, BST_UNCHECKED);
CheckDlgButton(IDC_CHK_REMOVE_PATTERNS, BST_CHECKED);
CheckDlgButton(IDC_CHK_REARRANGE_PATTERNS, BST_UNCHECKED);
CheckDlgButton(IDC_CHK_REMOVE_DUPLICATES, BST_UNCHECKED);
// orders
CheckDlgButton(IDC_CHK_MERGE_SEQUENCES, BST_UNCHECKED);
CheckDlgButton(IDC_CHK_REMOVE_ORDERS, BST_CHECKED);
// samples
CheckDlgButton(IDC_CHK_CLEANUP_SAMPLES, BST_UNCHECKED);
CheckDlgButton(IDC_CHK_REMOVE_SAMPLES, BST_UNCHECKED);
CheckDlgButton(IDC_CHK_REARRANGE_SAMPLES, BST_CHECKED);
CheckDlgButton(IDC_CHK_OPTIMIZE_SAMPLES, BST_UNCHECKED);
// instruments
CheckDlgButton(IDC_CHK_CLEANUP_INSTRUMENTS, BST_UNCHECKED);
CheckDlgButton(IDC_CHK_REMOVE_INSTRUMENTS, BST_CHECKED);
// plugins
CheckDlgButton(IDC_CHK_CLEANUP_PLUGINS, BST_UNCHECKED);
CheckDlgButton(IDC_CHK_REMOVE_PLUGINS, BST_CHECKED);
// misc
CheckDlgButton(IDC_CHK_SAMPLEPACK, BST_CHECKED);
CheckDlgButton(IDC_CHK_UNUSED_CHANNELS, BST_CHECKED);
}
BOOL CModCleanupDlg::OnToolTipNotify(UINT, NMHDR *pNMHDR, LRESULT *)
{
TOOLTIPTEXT* pTTT = (TOOLTIPTEXT*)pNMHDR;
UINT_PTR nID = pNMHDR->idFrom;
if (pTTT->uFlags & TTF_IDISHWND)
{
// idFrom is actually the HWND of the tool
nID = ::GetDlgCtrlID((HWND)nID);
}
LPCTSTR lpszText = nullptr;
switch(nID)
{
// patterns
case IDC_CHK_CLEANUP_PATTERNS:
lpszText = _T("Remove all unused patterns and rearrange them.");
break;
case IDC_CHK_REMOVE_PATTERNS:
lpszText = _T("Remove all patterns.");
break;
case IDC_CHK_REARRANGE_PATTERNS:
lpszText = _T("Number the patterns given by their order in the sequence.");
break;
case IDC_CHK_REMOVE_DUPLICATES:
lpszText = _T("Merge patterns with identical content.");
break;
// orders
case IDC_CHK_REMOVE_ORDERS:
lpszText = _T("Reset the order list.");
break;
case IDC_CHK_MERGE_SEQUENCES:
lpszText = _T("Merge multiple sequences into one.");
break;
// samples
case IDC_CHK_CLEANUP_SAMPLES:
lpszText = _T("Remove all unused samples.");
break;
case IDC_CHK_REMOVE_SAMPLES:
lpszText = _T("Remove all samples.");
break;
case IDC_CHK_REARRANGE_SAMPLES:
lpszText = _T("Reorder sample list by removing empty samples.");
break;
case IDC_CHK_OPTIMIZE_SAMPLES:
lpszText = _T("Remove unused data after the sample loop end.");
break;
// instruments
case IDC_CHK_CLEANUP_INSTRUMENTS:
lpszText = _T("Remove all unused instruments.");
break;
case IDC_CHK_REMOVE_INSTRUMENTS:
lpszText = _T("Remove all instruments and convert them to samples.");
break;
// plugins
case IDC_CHK_CLEANUP_PLUGINS:
lpszText = _T("Remove all unused plugins.");
break;
case IDC_CHK_REMOVE_PLUGINS:
lpszText = _T("Remove all plugins.");
break;
// misc
case IDC_CHK_SAMPLEPACK:
lpszText = _T("Convert the module to .IT and reset song / sample / instrument variables");
break;
case IDC_CHK_UNUSED_CHANNELS:
lpszText = _T("Removes all empty pattern channels.");
break;
default:
lpszText = _T("");
break;
}
pTTT->lpszText = const_cast<LPTSTR>(lpszText);
return TRUE;
}
///////////////////////////////////////////////////////////////////////
// Actual cleanup implementations
bool CModCleanupDlg::RemoveDuplicatePatterns()
{
CSoundFile &sndFile = modDoc.GetSoundFile();
const PATTERNINDEX numPatterns = sndFile.Patterns.Size();
std::vector<PATTERNINDEX> patternMapping(numPatterns, PATTERNINDEX_INVALID);
BeginWaitCursor();
CriticalSection cs;
PATTERNINDEX foundDupes = 0;
for(PATTERNINDEX pat1 = 0; pat1 < numPatterns; pat1++)
{
if(!sndFile.Patterns.IsValidPat(pat1))
continue;
const CPattern &pattern1 = sndFile.Patterns[pat1];
for(PATTERNINDEX pat2 = pat1 + 1; pat2 < numPatterns; pat2++)
{
if(!sndFile.Patterns.IsValidPat(pat2) || patternMapping[pat2] != PATTERNINDEX_INVALID)
continue;
const CPattern &pattern2 = sndFile.Patterns[pat2];
if(pattern1 == pattern2)
{
modDoc.GetPatternUndo().PrepareUndo(pat2, 0, 0, pattern2.GetNumChannels(), pattern2.GetNumRows(), "Remove Duplicate Patterns", foundDupes != 0, false);
sndFile.Patterns.Remove(pat2);
patternMapping[pat2] = pat1;
foundDupes++;
}
}
}
if(foundDupes != 0)
{
modDoc.AddToLog(MPT_AFORMAT("{} duplicate pattern{} merged.")(foundDupes, foundDupes == 1 ? "" : "s"));
// Fix order list
for(auto &order : sndFile.Order)
{
for(auto &pat : order)
{
if(pat < numPatterns && patternMapping[pat] != PATTERNINDEX_INVALID)
{
pat = patternMapping[pat];
}
}
}
}
EndWaitCursor();
return foundDupes != 0;
}
// Remove unused patterns
bool CModCleanupDlg::RemoveUnusedPatterns()
{
CSoundFile &sndFile = modDoc.GetSoundFile();
const PATTERNINDEX numPatterns = sndFile.Patterns.Size();
std::vector<bool> patternUsed(numPatterns, false);
BeginWaitCursor();
// First, find all used patterns in all sequences.
for(auto &order : sndFile.Order)
{
for(auto pat : order)
{
if(pat < numPatterns)
{
patternUsed[pat] = true;
}
}
}
// Remove all other patterns.
CriticalSection cs;
PATTERNINDEX numRemovedPatterns = 0;
for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
{
if(!patternUsed[pat] && sndFile.Patterns.IsValidPat(pat))
{
numRemovedPatterns++;
modDoc.GetPatternUndo().PrepareUndo(pat, 0, 0, sndFile.GetNumChannels(), sndFile.Patterns[pat].GetNumRows(), "Remove Unused Patterns", numRemovedPatterns != 0, false);
sndFile.Patterns.Remove(pat);
}
}
EndWaitCursor();
if(numRemovedPatterns)
{
modDoc.AddToLog(MPT_AFORMAT("{} pattern{} removed.")(numRemovedPatterns, numRemovedPatterns == 1 ? "" : "s"));
return true;
}
return false;
}
// Rearrange patterns (first pattern in order list = 0, etc...)
bool CModCleanupDlg::RearrangePatterns()
{
CSoundFile &sndFile = modDoc.GetSoundFile();
const PATTERNINDEX numPatterns = sndFile.Patterns.Size();
std::vector<PATTERNINDEX> newIndex(numPatterns, PATTERNINDEX_INVALID);
bool modified = false;
BeginWaitCursor();
CriticalSection cs;
// First, find all used patterns in all sequences.
PATTERNINDEX patOrder = 0;
for(auto &order : sndFile.Order)
{
for(auto &pat : order)
{
if(pat < numPatterns)
{
if(newIndex[pat] == PATTERNINDEX_INVALID)
{
newIndex[pat] = patOrder++;
}
pat = newIndex[pat];
}
}
}
// All unused patterns are moved to the end of the pattern list.
for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
{
PATTERNINDEX &index = newIndex[pat];
if(index == PATTERNINDEX_INVALID && sndFile.Patterns.IsValidPat(pat))
{
index = patOrder++;
}
}
// Also need new indices for any non-existent patterns
for(auto &index : newIndex)
{
if(index == PATTERNINDEX_INVALID)
{
index = patOrder++;
}
}
modDoc.GetPatternUndo().RearrangePatterns(newIndex);
// Now rearrange the actual patterns
for(PATTERNINDEX i = 0; i < static_cast<PATTERNINDEX>(newIndex.size()); i++)
{
PATTERNINDEX j = newIndex[i];
if(i == j)
continue;
while(i < j)
j = newIndex[j];
std::swap(sndFile.Patterns[i], sndFile.Patterns[j]);
modified = true;
}
EndWaitCursor();
return modified;
}
// Remove unused samples
bool CModCleanupDlg::RemoveUnusedSamples()
{
CSoundFile &sndFile = modDoc.GetSoundFile();
std::vector<bool> samplesUsed(sndFile.GetNumSamples() + 1, true);
BeginWaitCursor();
// Check if any samples are not referenced in the patterns (sample mode) or by an instrument (instrument mode).
// This doesn't check yet if a sample is referenced by an instrument, but actually unused in the patterns.
for(SAMPLEINDEX smp = 1; smp <= sndFile.GetNumSamples(); smp++) if (sndFile.GetSample(smp).HasSampleData())
{
if(!modDoc.IsSampleUsed(smp))
{
samplesUsed[smp] = false;
}
}
SAMPLEINDEX nRemoved = sndFile.RemoveSelectedSamples(samplesUsed);
const SAMPLEINDEX unusedInsSamples = sndFile.DetectUnusedSamples(samplesUsed);
EndWaitCursor();
if(unusedInsSamples)
{
mpt::ustring s = MPT_UFORMAT("OpenMPT detected {} sample{} referenced by an instrument,\nbut not used in the song. Do you want to remove them?")
( unusedInsSamples
, (unusedInsSamples == 1) ? U_("") : U_("s")
);
if(Reporting::Confirm(s, "Sample Cleanup", false, false, this) == cnfYes)
{
nRemoved += sndFile.RemoveSelectedSamples(samplesUsed);
}
}
if(nRemoved > 0)
{
modDoc.AddToLog(LogNotification, MPT_UFORMAT("{} unused sample{} removed")(nRemoved, (nRemoved == 1) ? U_("") : U_("s")));
}
return (nRemoved > 0);
}
// Check if the stereo channels of a sample contain identical data
template<typename T>
static bool ComapreStereoChannels(SmpLength length, const T *sampleData)
{
for(SmpLength i = 0; i < length; i++, sampleData += 2)
{
if(sampleData[0] != sampleData[1])
{
return false;
}
}
return true;
}
// Remove unused sample data
bool CModCleanupDlg::OptimizeSamples()
{
CSoundFile &sndFile = modDoc.GetSoundFile();
SAMPLEINDEX numLoopOpt = 0, numStereoOpt = 0;
std::vector<bool> stereoOptSamples(sndFile.GetNumSamples(), false);
for(SAMPLEINDEX smp = 1; smp <= sndFile.GetNumSamples(); smp++)
{
const ModSample &sample = sndFile.GetSample(smp);
// Determine how much of the sample will be played
SmpLength loopLength = sample.nLength;
if(sample.uFlags[CHN_LOOP])
{
loopLength = sample.nLoopEnd;
if(sample.uFlags[CHN_SUSTAINLOOP])
{
loopLength = std::max(sample.nLoopEnd, sample.nSustainEnd);
}
}
// Check if the sample contains identical stereo channels
if(sample.GetNumChannels() == 2)
{
bool identicalChannels = false;
if(sample.GetElementarySampleSize() == 1)
{
identicalChannels = ComapreStereoChannels(loopLength, sample.sample8());
} else if(sample.GetElementarySampleSize() == 2)
{
identicalChannels = ComapreStereoChannels(loopLength, sample.sample16());
}
if(identicalChannels)
{
numStereoOpt++;
stereoOptSamples[smp - 1] = true;
}
}
if(sample.HasSampleData() && sample.nLength > loopLength + 2) numLoopOpt++;
}
if(!numLoopOpt && !numStereoOpt) return false;
std::string s;
if(numLoopOpt)
s = MPT_AFORMAT("{} sample{} unused data after the loop end point.\n")(numLoopOpt, (numLoopOpt == 1) ? " has" : "s have");
if(numStereoOpt)
s += MPT_AFORMAT("{} stereo sample{} actually mono.\n")(numStereoOpt, (numStereoOpt == 1) ? " is" : "s are");
if(numLoopOpt + numStereoOpt == 1)
s += "Do you want to optimize it and remove this unused data?";
else
s += "Do you want to optimize them and remove this unused data?";
if(Reporting::Confirm(s.c_str(), "Sample Optimization", false, false, this) != cnfYes)
{
return false;
}
for(SAMPLEINDEX smp = 1; smp <= sndFile.m_nSamples; smp++)
{
ModSample &sample = sndFile.GetSample(smp);
// Determine how much of the sample will be played
SmpLength loopLength = sample.nLength;
if(sample.uFlags[CHN_LOOP])
{
loopLength = sample.nLoopEnd;
// Sustain loop is played before normal loop, and it can actually be located after the normal loop.
if(sample.uFlags[CHN_SUSTAINLOOP])
{
loopLength = std::max(sample.nLoopEnd, sample.nSustainEnd);
}
}
if(sample.nLength > loopLength && loopLength >= 2)
{
modDoc.GetSampleUndo().PrepareUndo(smp, sundo_delete, "Trim Unused Data", loopLength, sample.nLength);
SampleEdit::ResizeSample(sample, loopLength, sndFile);
}
// Convert stereo samples with identical channels to mono
if(stereoOptSamples[smp - 1])
{
modDoc.GetSampleUndo().PrepareUndo(smp, sundo_replace, "Mono Conversion");
ctrlSmp::ConvertToMono(sample, sndFile, ctrlSmp::onlyLeft);
}
}
if(numLoopOpt)
{
s = MPT_AFORMAT("{} sample loop{} optimized")(numLoopOpt, (numLoopOpt == 1) ? "" : "s");
modDoc.AddToLog(s);
}
if(numStereoOpt)
{
s = MPT_AFORMAT("{} sample{} converted to mono")(numStereoOpt, (numStereoOpt == 1) ? "" : "s");
modDoc.AddToLog(s);
}
return true;
}
// Rearrange sample list
bool CModCleanupDlg::RearrangeSamples()
{
CSoundFile &sndFile = modDoc.GetSoundFile();
if(sndFile.GetNumSamples() < 2)
return false;
std::vector<SAMPLEINDEX> sampleMap;
sampleMap.reserve(sndFile.GetNumSamples());
// First, find out which sample slots are unused and create the new sample map only with used samples
for(SAMPLEINDEX i = 1; i <= sndFile.GetNumSamples(); i++)
{
if(sndFile.GetSample(i).HasSampleData())
{
sampleMap.push_back(i);
}
}
// Nothing found to remove...
if(sndFile.GetNumSamples() == sampleMap.size())
{
return false;
}
return (modDoc.ReArrangeSamples(sampleMap) != SAMPLEINDEX_INVALID);
}
// Remove unused instruments
bool CModCleanupDlg::RemoveUnusedInstruments()
{
CSoundFile &sndFile = modDoc.GetSoundFile();
if(!sndFile.GetNumInstruments())
return false;
deleteInstrumentSamples removeSamples = doNoDeleteAssociatedSamples;
if(Reporting::Confirm("Remove samples associated with unused instruments?", "Removing unused instruments", false, false, this) == cnfYes)
{
removeSamples = deleteAssociatedSamples;
}
BeginWaitCursor();
std::vector<bool> instrUsed(sndFile.GetNumInstruments());
bool prevUsed = true, reorder = false;
INSTRUMENTINDEX numUsed = 0, lastUsed = 1;
for(INSTRUMENTINDEX i = 0; i < sndFile.GetNumInstruments(); i++)
{
instrUsed[i] = (modDoc.IsInstrumentUsed(i + 1));
if(instrUsed[i])
{
numUsed++;
lastUsed = i;
if(!prevUsed)
{
reorder = true;
}
}
prevUsed = instrUsed[i];
}
EndWaitCursor();
if(reorder && numUsed >= 1)
{
reorder = (Reporting::Confirm("Do you want to reorganize the remaining instruments?", "Removing unused instruments", false, false, this) == cnfYes);
} else
{
reorder = false;
}
const INSTRUMENTINDEX numRemoved = sndFile.GetNumInstruments() - numUsed;
if(numRemoved != 0)
{
BeginWaitCursor();
std::vector<INSTRUMENTINDEX> instrMap;
instrMap.reserve(sndFile.GetNumInstruments());
for(INSTRUMENTINDEX i = 0; i < sndFile.GetNumInstruments(); i++)
{
if(instrUsed[i])
{
instrMap.push_back(i + 1);
} else if(!reorder && i < lastUsed)
{
instrMap.push_back(INSTRUMENTINDEX_INVALID);
}
}
modDoc.ReArrangeInstruments(instrMap, removeSamples);
EndWaitCursor();
modDoc.AddToLog(LogNotification, MPT_UFORMAT("{} unused instrument{} removed")(numRemoved, (numRemoved == 1) ? U_("") : U_("s")));
return true;
}
return false;
}
// Remove ununsed plugins
bool CModCleanupDlg::RemoveUnusedPlugins()
{
CSoundFile &sndFile = modDoc.GetSoundFile();
std::vector<bool> usedmap(MAX_MIXPLUGINS, false);
for(PLUGINDEX nPlug = 0; nPlug < MAX_MIXPLUGINS; nPlug++)
{
// Is the plugin assigned to a channel?
for(CHANNELINDEX nChn = 0; nChn < sndFile.GetNumChannels(); nChn++)
{
if (sndFile.ChnSettings[nChn].nMixPlugin == nPlug + 1)
{
usedmap[nPlug] = true;
break;
}
}
// Is the plugin used by an instrument?
for(INSTRUMENTINDEX nIns = 1; nIns <= sndFile.GetNumInstruments(); nIns++)
{
if (sndFile.Instruments[nIns] && (sndFile.Instruments[nIns]->nMixPlug == nPlug + 1))
{
usedmap[nPlug] = true;
break;
}
}
// Is the plugin assigned to master?
if(sndFile.m_MixPlugins[nPlug].IsMasterEffect())
usedmap[nPlug] = true;
// All outputs of used plugins count as used
if(usedmap[nPlug])
{
if(!sndFile.m_MixPlugins[nPlug].IsOutputToMaster())
{
PLUGINDEX output = sndFile.m_MixPlugins[nPlug].GetOutputPlugin();
if(output != PLUGINDEX_INVALID)
{
usedmap[output] = true;
}
}
}
}
PLUGINDEX numRemoved = modDoc.RemovePlugs(usedmap);
if(numRemoved != 0)
{
modDoc.AddToLog(LogInformation, MPT_UFORMAT("{} unused plugin{} removed")(numRemoved, (numRemoved == 1) ? U_("") : U_("s")));
return true;
}
return false;
}
// Reset variables (convert to IT, reset global/smp/ins vars, etc.)
bool CModCleanupDlg::ResetVariables()
{
CSoundFile &sndFile = modDoc.GetSoundFile();
if(Reporting::Confirm(_T("OpenMPT will convert the module to IT format and reset all song, sample and instrument attributes to default values. Continue?"), _T("Resetting variables"), false, false, this) == cnfNo)
return false;
// Stop play.
CMainFrame::GetMainFrame()->StopMod(&modDoc);
BeginWaitCursor();
CriticalSection cs;
// Convert to IT...
modDoc.ChangeModType(MOD_TYPE_IT);
sndFile.SetDefaultPlaybackBehaviour(sndFile.GetType());
sndFile.SetMixLevels(MixLevels::Compatible);
sndFile.m_songArtist.clear();
sndFile.m_nTempoMode = TempoMode::Classic;
sndFile.m_SongFlags = SONG_LINEARSLIDES;
sndFile.m_MidiCfg.Reset();
// Global vars
sndFile.m_nDefaultTempo.Set(125);
sndFile.m_nDefaultSpeed = 6;
sndFile.m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME;
sndFile.m_nSamplePreAmp = 48;
sndFile.m_nVSTiVolume = 48;
sndFile.Order().SetRestartPos(0);
if(sndFile.Order().empty())
{
modDoc.InsertPattern(64, 0);
}
// Reset instruments (if there are any)
for(INSTRUMENTINDEX i = 1; i <= sndFile.GetNumInstruments(); i++) if(sndFile.Instruments[i])
{
sndFile.Instruments[i]->nFadeOut = 256;
sndFile.Instruments[i]->nGlobalVol = 64;
sndFile.Instruments[i]->nPan = 128;
sndFile.Instruments[i]->dwFlags.reset(INS_SETPANNING);
sndFile.Instruments[i]->nMixPlug = 0;
sndFile.Instruments[i]->nVolSwing = 0;
sndFile.Instruments[i]->nPanSwing = 0;
sndFile.Instruments[i]->nCutSwing = 0;
sndFile.Instruments[i]->nResSwing = 0;
}
for(CHANNELINDEX chn = 0; chn < sndFile.GetNumChannels(); chn++)
{
sndFile.InitChannel(chn);
}
// reset samples
SampleEdit::ResetSamples(sndFile, SampleEdit::SmpResetCompo);
cs.Leave();
EndWaitCursor();
return true;
}
bool CModCleanupDlg::RemoveUnusedChannels()
{
// Avoid M.K. modules to become xCHN modules if some channels are unused.
if(modDoc.GetModType() == MOD_TYPE_MOD && modDoc.GetNumChannels() == 4)
return false;
std::vector<bool> usedChannels;
modDoc.CheckUsedChannels(usedChannels, modDoc.GetNumChannels() - modDoc.GetSoundFile().GetModSpecifications().channelsMin);
return modDoc.RemoveChannels(usedChannels);
}
// Remove all patterns
bool CModCleanupDlg::RemoveAllPatterns()
{
CSoundFile &sndFile = modDoc.GetSoundFile();
if(sndFile.Patterns.Size() == 0) return false;
modDoc.GetPatternUndo().ClearUndo();
sndFile.Patterns.ResizeArray(0);
sndFile.SetCurrentOrder(0);
return true;
}
// Remove all orders
bool CModCleanupDlg::RemoveAllOrders()
{
CSoundFile &sndFile = modDoc.GetSoundFile();
sndFile.Order.Initialize();
sndFile.SetCurrentOrder(0);
return true;
}
// Remove all samples
bool CModCleanupDlg::RemoveAllSamples()
{
CSoundFile &sndFile = modDoc.GetSoundFile();
if (sndFile.GetNumSamples() == 0) return false;
std::vector<bool> keepSamples(sndFile.GetNumSamples() + 1, false);
sndFile.RemoveSelectedSamples(keepSamples);
SampleEdit::ResetSamples(sndFile, SampleEdit::SmpResetInit, 1, MAX_SAMPLES - 1);
return true;
}
// Remove all instruments
bool CModCleanupDlg::RemoveAllInstruments()
{
CSoundFile &sndFile = modDoc.GetSoundFile();
if(sndFile.GetNumInstruments() == 0) return false;
modDoc.ConvertInstrumentsToSamples();
for(INSTRUMENTINDEX i = 1; i <= sndFile.GetNumInstruments(); i++)
{
sndFile.DestroyInstrument(i, doNoDeleteAssociatedSamples);
}
sndFile.m_nInstruments = 0;
return true;
}
// Remove all plugins
bool CModCleanupDlg::RemoveAllPlugins()
{
std::vector<bool> keepMask(MAX_MIXPLUGINS, false);
modDoc.RemovePlugs(keepMask);
return true;
}
bool CModCleanupDlg::MergeSequences()
{
return modDoc.GetSoundFile().Order.MergeSequences();
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,97 @@
/*
* CleanupSong.h
* ---------------
* Purpose: Dialog for cleaning up modules (rearranging, removing unused items).
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
OPENMPT_NAMESPACE_BEGIN
class CModCleanupDlg: public CDialog
{
private:
enum CleanupOptions
{
// patterns
kCleanupPatterns = 0,
kRemovePatterns,
kRearrangePatterns,
kRemoveDuplicatePatterns,
// orders
kMergeSequences,
kRemoveOrders,
// samples
kCleanupSamples,
kRemoveSamples,
kRearrangeSamples,
kOptimizeSamples,
// instruments
kCleanupInstruments,
kRemoveAllInstruments,
// plugins
kCleanupPlugins,
kRemoveAllPlugins,
// misc
kResetVariables,
kCleanupChannels,
kNone,
kMaxCleanupOptions = kNone
};
CModDoc &modDoc;
static bool m_CheckBoxes[kMaxCleanupOptions]; // Checkbox state
static const WORD m_CleanupIDtoDlgID[kMaxCleanupOptions]; // Checkbox -> Control ID LUT
static const CleanupOptions m_MutuallyExclusive[kMaxCleanupOptions]; // Options that are mutually exclusive to each other.
// Actual cleanup implementations:
// Patterns
bool RemoveDuplicatePatterns();
bool RemoveUnusedPatterns(); // Remove unused patterns
bool RearrangePatterns(); // Rearrange patterns
bool RemoveAllPatterns();
// Orders
bool MergeSequences();
bool RemoveAllOrders();
// Samples
bool RemoveUnusedSamples(); // Remove unused samples
bool RemoveAllSamples();
bool RearrangeSamples(); // Rearrange sample list
bool OptimizeSamples(); // Remove unused sample data
// Instruments
bool RemoveUnusedInstruments(); // Remove unused instruments
bool RemoveAllInstruments();
// Plugins
bool RemoveUnusedPlugins(); // Remove ununsed plugins
bool RemoveAllPlugins();
// Misc
bool ResetVariables(); // Turn module into samplepack (convert to IT, remove patterns, etc.)
bool RemoveUnusedChannels();
public:
CModCleanupDlg(CModDoc &modParent, CWnd *parent) : CDialog(IDD_CLEANUP_SONG, parent), modDoc(modParent) { }
protected:
//{{AFX_VIRTUAL(CModCleanupDlg)
virtual BOOL OnInitDialog();
virtual void OnOK();
//}}AFX_VIRTUAL
BOOL OnToolTipNotify(UINT id, NMHDR* pNMHDR, LRESULT* pResult);
//{{AFX_MSG(CModCleanupDlg)
afx_msg void OnPresetCleanupSong();
afx_msg void OnPresetCompoCleanup();
afx_msg void OnVerifyMutualExclusive();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,105 @@
/*
* Clipboard.h
* -----------
* Purpose: RAII wrapper around operating system clipboard
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
class Clipboard
{
public:
// Open clipboard for writing (size > 0) or reading (size == 0).
Clipboard(UINT clipFormat, size_t size = 0)
: m_clipFormat(clipFormat)
{
m_opened = theApp.GetMainWnd()->OpenClipboard() != FALSE;
if(size > 0)
{
if(m_opened && (m_hCpy = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, size)) != nullptr)
{
::EmptyClipboard();
m_data = mpt::as_span(static_cast<std::byte *>(::GlobalLock(m_hCpy)), size);
}
} else
{
HGLOBAL hCpy = ::GetClipboardData(m_clipFormat);
void *p = nullptr;
if(hCpy != nullptr && (p = ::GlobalLock(hCpy)) != nullptr)
{
m_data = mpt::as_span(mpt::void_cast<std::byte *>(p), ::GlobalSize(hCpy));
}
}
}
bool IsValid() const
{
return m_opened && m_hCpy && m_data.data();
}
template<typename T>
T *As()
{
return mpt::byte_cast<T*>(m_data.data());
}
mpt::byte_span Get()
{
return m_data;
}
std::string_view GetString() const
{
if(m_data.data())
return { mpt::byte_cast<const char *>(m_data.data()), m_data.size() };
else
return {};
}
Clipboard operator =(mpt::const_byte_span data)
{
MPT_ASSERT(m_data.size() >= data.size());
std::copy(data.begin(), data.end(), m_data.begin());
return *this;
}
template <typename T>
Clipboard operator =(const T &v)
{
mpt::const_byte_span data = mpt::as_raw_memory(v);
MPT_ASSERT(m_data.size() >= data.size());
std::copy(data.begin(), data.end(), m_data.begin());
return *this;
}
void Close()
{
if(m_hCpy)
{
::GlobalUnlock(m_hCpy);
::SetClipboardData(m_clipFormat, static_cast<HANDLE>(m_hCpy));
m_hCpy = nullptr;
}
if(m_opened)
{
::CloseClipboard();
m_opened = false;
}
}
~Clipboard()
{
Close();
}
protected:
HGLOBAL m_hCpy = nullptr;
mpt::byte_span m_data;
UINT m_clipFormat;
bool m_opened = false;
};
@@ -0,0 +1,144 @@
/*
* CloseMainDialog.cpp
* -------------------
* Purpose: Dialog showing a list of unsaved documents, with the ability to choose which documents should be saved or not.
* Notes : (currently none)
* Authors: 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 "Moddoc.h"
#include "CloseMainDialog.h"
OPENMPT_NAMESPACE_BEGIN
BEGIN_MESSAGE_MAP(CloseMainDialog, ResizableDialog)
ON_COMMAND(IDC_BUTTON1, &CloseMainDialog::OnSaveAll)
ON_COMMAND(IDC_BUTTON2, &CloseMainDialog::OnSaveNone)
ON_COMMAND(IDC_CHECK1, &CloseMainDialog::OnSwitchFullPaths)
END_MESSAGE_MAP()
void CloseMainDialog::DoDataExchange(CDataExchange* pDX)
{
ResizableDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(DoDataExchange)
DDX_Control(pDX, IDC_LIST1, m_List);
//}}AFX_DATA_MAP
}
CloseMainDialog::CloseMainDialog() : ResizableDialog(IDD_CLOSEDOCUMENTS)
{
};
CString CloseMainDialog::FormatTitle(const CModDoc *modDoc, bool fullPath)
{
return MPT_CFORMAT("{} ({})")
(mpt::ToCString(modDoc->GetSoundFile().GetCharsetInternal(), modDoc->GetSoundFile().GetTitle()),
(!fullPath || modDoc->GetPathNameMpt().empty()) ? modDoc->GetTitle() : modDoc->GetPathNameMpt().ToCString());
}
BOOL CloseMainDialog::OnInitDialog()
{
ResizableDialog::OnInitDialog();
// Create list of unsaved documents
m_List.ResetContent();
CheckDlgButton(IDC_CHECK1, BST_CHECKED);
m_List.SetRedraw(FALSE);
for(const auto &modDoc : theApp.GetOpenDocuments())
{
if(modDoc->IsModified())
{
int item = m_List.AddString(FormatTitle(modDoc, true));
m_List.SetItemDataPtr(item, modDoc);
m_List.SetSel(item, TRUE);
}
}
m_List.SetRedraw(TRUE);
if(m_List.GetCount() == 0)
{
// No modified documents...
OnOK();
}
return TRUE;
}
void CloseMainDialog::OnOK()
{
const int count = m_List.GetCount();
for(int i = 0; i < count; i++)
{
CModDoc *modDoc = static_cast<CModDoc *>(m_List.GetItemDataPtr(i));
MPT_ASSERT(modDoc != nullptr);
if(m_List.GetSel(i))
{
modDoc->ActivateWindow();
if(modDoc->DoFileSave() == FALSE)
{
// If something went wrong, or if the user decided to cancel saving (when using "Save As"), we'll better not proceed...
OnCancel();
return;
}
} else
{
modDoc->SetModified(FALSE);
}
}
ResizableDialog::OnOK();
}
void CloseMainDialog::OnSaveAll()
{
if(m_List.GetCount() == 1)
m_List.SetSel(0, TRUE); // SelItemRange can't select one item: https://jeffpar.github.io/kbarchive/kb/129/Q129428/
else
m_List.SelItemRange(TRUE, 0, m_List.GetCount() - 1);
OnOK();
}
void CloseMainDialog::OnSaveNone()
{
if(m_List.GetCount() == 1)
m_List.SetSel(0, FALSE); // SelItemRange can't select one item: https://jeffpar.github.io/kbarchive/kb/129/Q129428/
else
m_List.SelItemRange(FALSE, 0, m_List.GetCount() - 1);
OnOK();
}
// Switch between full path / filename only display
void CloseMainDialog::OnSwitchFullPaths()
{
const int count = m_List.GetCount();
const bool fullPath = (IsDlgButtonChecked(IDC_CHECK1) == BST_CHECKED);
m_List.SetRedraw(FALSE);
for(int i = 0; i < count; i++)
{
CModDoc *modDoc = static_cast<CModDoc *>(m_List.GetItemDataPtr(i));
int item = m_List.InsertString(i + 1, FormatTitle(modDoc, fullPath));
m_List.SetItemDataPtr(item, modDoc);
m_List.SetSel(item, m_List.GetSel(i));
m_List.DeleteString(i);
}
m_List.SetRedraw(TRUE);
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,43 @@
/*
* CloseMainDialog.h
* -----------------
* Purpose: Dialog showing a list of unsaved documents, with the ability to choose which documents should be saved or not.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "InputHandler.h"
#include "ResizableDialog.h"
OPENMPT_NAMESPACE_BEGIN
class CloseMainDialog: public ResizableDialog
{
protected:
CListBox m_List;
CPoint m_minSize;
BypassInputHandler m_bih;
static CString FormatTitle(const CModDoc *modDoc, bool fullPath);
public:
CloseMainDialog();
protected:
void DoDataExchange(CDataExchange* pDX) override;
BOOL OnInitDialog() override;
void OnOK() override;
afx_msg void OnSaveAll();
afx_msg void OnSaveNone();
afx_msg void OnSwitchFullPaths();
DECLARE_MESSAGE_MAP()
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,560 @@
/*
* ColorConfigDlg.cpp
* ------------------
* Purpose: Implementation of the display setup dialog.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "Mainfrm.h"
#include "ColorConfigDlg.h"
#include "Settings.h"
#include "FileDialog.h"
#include "ColorSchemes.h"
#include "../common/mptStringBuffer.h"
OPENMPT_NAMESPACE_BEGIN
static constexpr struct ColorDescriptions
{
const TCHAR *name;
int previewImage;
ModColor colorIndex[3];
const TCHAR *descText[3];
} colorDefs[] =
{
{ _T("Pattern Editor"), 0, { MODCOLOR_BACKNORMAL, MODCOLOR_TEXTNORMAL, MODCOLOR_BACKHILIGHT }, { _T("Background:"), _T("Foreground:"), _T("Highlighted:") } },
{ _T("Active Row"), 0, { MODCOLOR_BACKCURROW, MODCOLOR_TEXTCURROW, {} }, { _T("Background:"), _T("Foreground:"), nullptr } },
{ _T("Pattern Selection"), 0, { MODCOLOR_BACKSELECTED, MODCOLOR_TEXTSELECTED, {} }, { _T("Background:"), _T("Foreground:"), nullptr } },
{ _T("Play Cursor"), 0, { MODCOLOR_BACKPLAYCURSOR, MODCOLOR_TEXTPLAYCURSOR, {} }, { _T("Background:"), _T("Foreground:"), nullptr } },
{ _T("Note Highlight"), 0, { MODCOLOR_NOTE, MODCOLOR_INSTRUMENT, MODCOLOR_VOLUME }, { _T("Note:"), _T("Instrument:"), _T("Volume:") } },
{ _T("Effect Highlight"), 0, { MODCOLOR_PANNING, MODCOLOR_PITCH, MODCOLOR_GLOBALS }, { _T("Panning Effects:"), _T("Pitch Effects:"), _T("Global Effects:") } },
{ _T("Invalid Commands"), 0, { MODCOLOR_DODGY_COMMANDS, {}, {} }, { _T("Invalid Note:"), nullptr, nullptr } },
{ _T("Channel Separator"), 0, { MODCOLOR_SEPHILITE, MODCOLOR_SEPFACE, MODCOLOR_SEPSHADOW }, { _T("Highlight:"), _T("Face:"), _T("Shadow:") } },
{ _T("Next/Prev Pattern"), 0, { MODCOLOR_BLENDCOLOR, {}, {} }, { _T("Blend Colour:"), nullptr, nullptr } },
{ _T("Sample Waveform"), 1, { MODCOLOR_SAMPLE, MODCOLOR_BACKSAMPLE, MODCOLOR_SAMPLESELECTED }, { _T("Sample Data:"), _T("Background:"), _T("Selection:") } },
{ _T("Sample Markers"), 1, { MODCOLOR_SAMPLE_LOOPMARKER, MODCOLOR_SAMPLE_SUSTAINMARKER, MODCOLOR_SAMPLE_CUEPOINT}, { _T("Loop Marker:"), _T("Sustain Marker:"), _T("Cue Point:") } },
{ _T("Instrument Editor"), 2, { MODCOLOR_ENVELOPES, MODCOLOR_ENVELOPE_RELEASE, MODCOLOR_BACKENV }, { _T("Envelopes:"), _T("Release Envelope:"), _T("Background:") } },
{ _T("VU-Meters"), 0, { MODCOLOR_VUMETER_HI, MODCOLOR_VUMETER_MED, MODCOLOR_VUMETER_LO }, { _T("Hi:"), _T("Med:"), _T("Lo:") } },
{ _T("VU-Meters (Plugins)"), 0, { MODCOLOR_VUMETER_HI_VST, MODCOLOR_VUMETER_MED_VST, MODCOLOR_VUMETER_LO_VST }, { _T("Hi:"), _T("Med:"), _T("Lo:") } }
};
#define PREVIEWBMP_WIDTH 88
#define PREVIEWBMP_HEIGHT 39
BEGIN_MESSAGE_MAP(COptionsColors, CPropertyPage)
ON_WM_DRAWITEM()
ON_WM_VSCROLL()
ON_CBN_SELCHANGE(IDC_COMBO1, &COptionsColors::OnColorSelChanged)
ON_CBN_SELCHANGE(IDC_COMBO2, &COptionsColors::OnSettingsChanged)
ON_CBN_SELCHANGE(IDC_COMBO3, &COptionsColors::OnPresetChange)
ON_EN_CHANGE(IDC_PRIMARYHILITE, &COptionsColors::OnSettingsChanged)
ON_EN_CHANGE(IDC_SECONDARYHILITE, &COptionsColors::OnSettingsChanged)
ON_COMMAND(IDC_BUTTON1, &COptionsColors::OnSelectColor1)
ON_COMMAND(IDC_BUTTON2, &COptionsColors::OnSelectColor2)
ON_COMMAND(IDC_BUTTON3, &COptionsColors::OnSelectColor3)
ON_COMMAND(IDC_BUTTON9, &COptionsColors::OnChoosePatternFont)
ON_COMMAND(IDC_BUTTON10, &COptionsColors::OnChooseCommentFont)
ON_COMMAND(IDC_BUTTON11, &COptionsColors::OnClearWindowCache)
ON_COMMAND(IDC_LOAD_COLORSCHEME, &COptionsColors::OnLoadColorScheme)
ON_COMMAND(IDC_SAVE_COLORSCHEME, &COptionsColors::OnSaveColorScheme)
ON_COMMAND(IDC_CHECK1, &COptionsColors::OnSettingsChanged)
ON_COMMAND(IDC_CHECK2, &COptionsColors::OnPreviewChanged)
ON_COMMAND(IDC_CHECK3, &COptionsColors::OnSettingsChanged)
ON_COMMAND(IDC_CHECK4, &COptionsColors::OnPreviewChanged)
ON_COMMAND(IDC_CHECK5, &COptionsColors::OnSettingsChanged)
ON_COMMAND(IDC_RADIO1, &COptionsColors::OnSettingsChanged)
ON_COMMAND(IDC_RADIO2, &COptionsColors::OnSettingsChanged)
ON_COMMAND(IDC_RADIO3, &COptionsColors::OnSettingsChanged)
ON_COMMAND(IDC_RADIO4, &COptionsColors::OnSettingsChanged)
ON_COMMAND(IDC_RADIO5, &COptionsColors::OnSettingsChanged)
END_MESSAGE_MAP()
void COptionsColors::DoDataExchange(CDataExchange* pDX)
{
CPropertyPage::DoDataExchange(pDX);
//{{AFX_DATA_MAP(COptionsColors)
DDX_Control(pDX, IDC_COMBO1, m_ComboItem);
DDX_Control(pDX, IDC_COMBO2, m_ComboFont);
DDX_Control(pDX, IDC_COMBO3, m_ComboPreset);
DDX_Control(pDX, IDC_BUTTON4, m_BtnPreview);
DDX_Control(pDX, IDC_TEXT1, m_TxtColor[0]);
DDX_Control(pDX, IDC_TEXT2, m_TxtColor[1]);
DDX_Control(pDX, IDC_TEXT3, m_TxtColor[2]);
DDX_Control(pDX, IDC_SPIN1, m_ColorSpin);
//}}AFX_DATA_MAP
}
COptionsColors::COptionsColors()
: CPropertyPage(IDD_OPTIONS_COLORS)
, CustomColors(TrackerSettings::Instance().rgbCustomColors)
{
}
static CString FormatFontName(const FontSetting &font)
{
return mpt::ToCString(font.name + U_(", ") + mpt::ufmt::val(font.size / 10));
}
BOOL COptionsColors::OnInitDialog()
{
CPropertyPage::OnInitDialog();
m_pPreviewDib = LoadDib(MAKEINTRESOURCE(IDB_COLORSETUP));
for (size_t i = 0; i < std::size(colorDefs); i++)
{
m_ComboItem.SetItemData(m_ComboItem.AddString(colorDefs[i].name), i);
}
m_ComboItem.SetCurSel(0);
m_BtnColor[0].SubclassDlgItem(IDC_BUTTON1, this);
m_BtnColor[1].SubclassDlgItem(IDC_BUTTON2, this);
m_BtnColor[2].SubclassDlgItem(IDC_BUTTON3, this);
m_BtnPreview.SetWindowPos(nullptr,
0, 0,
Util::ScalePixels(PREVIEWBMP_WIDTH * 2, m_hWnd) + 2, Util::ScalePixels(PREVIEWBMP_HEIGHT * 2, m_hWnd) + 2,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
if (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_STDHIGHLIGHT) CheckDlgButton(IDC_CHECK1, BST_CHECKED);
if (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_EFFECTHILIGHT) CheckDlgButton(IDC_CHECK2, BST_CHECKED);
if (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_2NDHIGHLIGHT) CheckDlgButton(IDC_CHECK4, BST_CHECKED);
CheckDlgButton(IDC_CHECK5, TrackerSettings::Instance().rememberSongWindows ? BST_CHECKED : BST_UNCHECKED);
SetDlgItemInt(IDC_PRIMARYHILITE, TrackerSettings::Instance().m_nRowHighlightMeasures);
SetDlgItemInt(IDC_SECONDARYHILITE, TrackerSettings::Instance().m_nRowHighlightBeats);
CheckRadioButton(IDC_RADIO1, IDC_RADIO2, TrackerSettings::Instance().accidentalFlats ? IDC_RADIO2 : IDC_RADIO1);
CheckRadioButton(IDC_RADIO3, IDC_RADIO5, IDC_RADIO3 + static_cast<int>(TrackerSettings::Instance().defaultRainbowChannelColors.Get()));
patternFont = TrackerSettings::Instance().patternFont;
m_ComboFont.AddString(_T("Built-in (small)"));
m_ComboFont.AddString(_T("Built-in (large)"));
m_ComboFont.AddString(_T("Built-in (small, x2)"));
m_ComboFont.AddString(_T("Built-in (large, x2)"));
m_ComboFont.AddString(_T("Built-in (small, x3)"));
m_ComboFont.AddString(_T("Built-in (large, x3)"));
int sel = 0;
if(patternFont.name == PATTERNFONT_SMALL)
{
sel = patternFont.size * 2;
} else if(patternFont.name == PATTERNFONT_LARGE)
{
sel = patternFont.size * 2 + 1;
} else
{
m_ComboFont.AddString(FormatFontName(patternFont));
sel = 6;
}
m_ComboFont.SetCurSel(sel);
commentFont = TrackerSettings::Instance().commentsFont;
SetDlgItemText(IDC_BUTTON10, FormatFontName(commentFont));
m_ComboPreset.SetRedraw(FALSE);
m_ComboPreset.InitStorage(static_cast<int>(2 + std::size(ColorSchemes)), 20 * sizeof(TCHAR));
m_ComboPreset.AddString(_T("Choose a Colour Scheme..."));
m_ComboPreset.AddString(_T("OpenMPT (Default)"));
for(const auto &preset : ColorSchemes)
{
m_ComboPreset.SetItemDataPtr(m_ComboPreset.AddString(preset.name), const_cast<ColorScheme *>(&preset));
}
m_ComboPreset.SetCurSel(0);
m_ComboPreset.SetRedraw(TRUE);
m_ColorSpin.SetRange32(-1, 1);
OnColorSelChanged();
return TRUE;
}
BOOL COptionsColors::OnKillActive()
{
int highlightMeasures = GetDlgItemInt(IDC_PRIMARYHILITE);
int highlightBeats = GetDlgItemInt(IDC_SECONDARYHILITE);
if(highlightBeats > highlightMeasures)
{
Reporting::Warning("Error: Primary highlight must be greater than or equal secondary highlight.");
::SetFocus(::GetDlgItem(m_hWnd, IDC_PRIMARYHILITE));
return 0;
}
return CPropertyPage::OnKillActive();
}
void COptionsColors::OnOK()
{
TrackerSettings::Instance().m_dwPatternSetup &= ~(PATTERN_STDHIGHLIGHT|PATTERN_2NDHIGHLIGHT|PATTERN_EFFECTHILIGHT);
if(IsDlgButtonChecked(IDC_CHECK1)) TrackerSettings::Instance().m_dwPatternSetup |= PATTERN_STDHIGHLIGHT;
if(IsDlgButtonChecked(IDC_CHECK2)) TrackerSettings::Instance().m_dwPatternSetup |= PATTERN_EFFECTHILIGHT;
if(IsDlgButtonChecked(IDC_CHECK4)) TrackerSettings::Instance().m_dwPatternSetup |= PATTERN_2NDHIGHLIGHT;
TrackerSettings::Instance().rememberSongWindows = IsDlgButtonChecked(IDC_CHECK5) != BST_UNCHECKED;
TrackerSettings::Instance().accidentalFlats = IsDlgButtonChecked(IDC_RADIO2) != BST_UNCHECKED;
int channelColors = GetCheckedRadioButton(IDC_RADIO3, IDC_RADIO5);
if(channelColors == IDC_RADIO3)
TrackerSettings::Instance().defaultRainbowChannelColors = DefaultChannelColors::NoColors;
else if(channelColors == IDC_RADIO4)
TrackerSettings::Instance().defaultRainbowChannelColors = DefaultChannelColors::Rainbow;
else
TrackerSettings::Instance().defaultRainbowChannelColors = DefaultChannelColors::Random;
FontSetting newPatternFont = patternFont;
const int fontSel = m_ComboFont.GetCurSel();
switch(fontSel)
{
case 0:
case 2:
case 4:
default:
newPatternFont.name = PATTERNFONT_SMALL;
newPatternFont.size = fontSel / 2;
break;
case 1:
case 3:
case 5:
newPatternFont.name = PATTERNFONT_LARGE;
newPatternFont.size = fontSel / 2;
break;
case 6:
break;
}
TrackerSettings::Instance().patternFont = newPatternFont;
TrackerSettings::Instance().commentsFont = commentFont;
TrackerSettings::Instance().m_nRowHighlightMeasures = GetDlgItemInt(IDC_PRIMARYHILITE);
TrackerSettings::Instance().m_nRowHighlightBeats = GetDlgItemInt(IDC_SECONDARYHILITE);
TrackerSettings::Instance().rgbCustomColors = CustomColors;
CMainFrame::UpdateColors();
CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
if (pMainFrm) pMainFrm->PostMessage(WM_MOD_INVALIDATEPATTERNS, HINT_MPTOPTIONS);
CSoundFile::SetDefaultNoteNames();
CPropertyPage::OnOK();
}
BOOL COptionsColors::OnSetActive()
{
CMainFrame::m_nLastOptionsPage = OPTIONS_PAGE_COLORS;
return CPropertyPage::OnSetActive();
}
void COptionsColors::OnChoosePatternFont()
{
LOGFONT lf;
MemsetZero(lf);
const int32 size = patternFont.size < 10 ? 120 : patternFont.size;
// Point size to pixels
lf.lfHeight = -MulDiv(size, Util::GetDPIy(m_hWnd), 720);
lf.lfWeight = patternFont.flags[FontSetting::Bold] ? FW_BOLD : FW_NORMAL;
lf.lfItalic = patternFont.flags[FontSetting::Italic] ? TRUE : FALSE;
mpt::String::WriteWinBuf(lf.lfFaceName) = mpt::ToWin(patternFont.name);
CFontDialog dlg(&lf);
dlg.m_cf.hwndOwner = m_hWnd;
if(patternFont.name != PATTERNFONT_SMALL && patternFont.name != PATTERNFONT_LARGE)
{
dlg.m_cf.lpLogFont = &lf;
}
dlg.m_cf.Flags &= ~CF_EFFECTS;
dlg.m_cf.Flags |= /*CF_FIXEDPITCHONLY | */CF_FORCEFONTEXIST | CF_NOSCRIPTSEL;
if(dlg.DoModal() == IDOK)
{
while(m_ComboFont.GetCount() > 6)
{
m_ComboFont.DeleteString(6);
}
patternFont.name = mpt::ToUnicode(dlg.GetFaceName());
patternFont.size = dlg.GetSize();
patternFont.flags = FontSetting::None;
if(dlg.IsBold()) patternFont.flags |= FontSetting::Bold;
if(dlg.IsItalic()) patternFont.flags |= FontSetting::Italic;
m_ComboFont.AddString(FormatFontName(patternFont));
m_ComboFont.SetCurSel(6);
OnSettingsChanged();
}
}
void COptionsColors::OnChooseCommentFont()
{
LOGFONT lf;
MemsetZero(lf);
// Point size to pixels
lf.lfHeight = -MulDiv(commentFont.size, Util::GetDPIy(m_hWnd), 720);
lf.lfWeight = commentFont.flags[FontSetting::Bold] ? FW_BOLD : FW_NORMAL;
lf.lfItalic = commentFont.flags[FontSetting::Italic] ? TRUE : FALSE;
mpt::String::WriteWinBuf(lf.lfFaceName) = mpt::ToWin(commentFont.name);
CFontDialog dlg(&lf);
dlg.m_cf.hwndOwner = m_hWnd;
dlg.m_cf.lpLogFont = &lf;
dlg.m_cf.Flags &= ~CF_EFFECTS;
dlg.m_cf.Flags |= CF_FORCEFONTEXIST | CF_NOSCRIPTSEL;
if(dlg.DoModal() == IDOK)
{
commentFont.name = mpt::ToUnicode(dlg.GetFaceName());
commentFont.size = dlg.GetSize();
commentFont.flags = FontSetting::None;
if(dlg.IsBold()) commentFont.flags |= FontSetting::Bold;
if(dlg.IsItalic()) commentFont.flags |= FontSetting::Italic;
SetDlgItemText(IDC_BUTTON10, FormatFontName(commentFont));
OnSettingsChanged();
}
}
void COptionsColors::OnDrawItem(int nIdCtl, LPDRAWITEMSTRUCT lpdis)
{
if(!lpdis || nIdCtl != IDC_BUTTON4 || !m_pPreviewDib)
{
CDialog::OnDrawItem(nIdCtl, lpdis);
return;
}
const int img = colorDefs[m_nColorItem].previewImage;
auto &p = m_pPreviewDib->bmiColors;
if (IsDlgButtonChecked(IDC_CHECK2))
{
p[1] = rgb2quad(CustomColors[MODCOLOR_GLOBALS]);
p[3] = rgb2quad(CustomColors[MODCOLOR_PITCH]);
p[5] = rgb2quad(CustomColors[MODCOLOR_INSTRUMENT]);
p[6] = rgb2quad(CustomColors[MODCOLOR_VOLUME]);
p[12] = rgb2quad(CustomColors[MODCOLOR_NOTE]);
p[14] = rgb2quad(CustomColors[MODCOLOR_PANNING]);
} else
{
p[1] = rgb2quad(CustomColors[MODCOLOR_TEXTNORMAL]);
p[3] = rgb2quad(CustomColors[MODCOLOR_TEXTNORMAL]);
p[5] = rgb2quad(CustomColors[MODCOLOR_TEXTNORMAL]);
p[6] = rgb2quad(CustomColors[MODCOLOR_TEXTNORMAL]);
p[12] = rgb2quad(CustomColors[MODCOLOR_TEXTNORMAL]);
p[14] = rgb2quad(CustomColors[MODCOLOR_TEXTNORMAL]);
}
p[4] = rgb2quad(CustomColors[MODCOLOR_TEXTNORMAL]);
p[8] = rgb2quad(CustomColors[MODCOLOR_TEXTNORMAL]);
p[9] = rgb2quad(CustomColors[MODCOLOR_SAMPLE]);
p[10] = rgb2quad(CustomColors[MODCOLOR_BACKNORMAL]);
p[11] = rgb2quad(CustomColors[MODCOLOR_BACKHILIGHT]);
p[13] = rgb2quad(CustomColors[MODCOLOR_ENVELOPES]);
p[15] = rgb2quad(img ? RGB(255, 255, 255) : CustomColors[MODCOLOR_BACKNORMAL]);
// Special cases: same bitmap, different palette
switch(m_nColorItem)
{
// Current Row
case 1:
p[8] = rgb2quad(CustomColors[MODCOLOR_TEXTCURROW]);
p[11] = rgb2quad(CustomColors[MODCOLOR_BACKCURROW]);
break;
// Selection
case 2:
p[5] = rgb2quad(CustomColors[MODCOLOR_TEXTSELECTED]);
p[6] = rgb2quad(CustomColors[MODCOLOR_TEXTSELECTED]);
p[8] = rgb2quad(CustomColors[MODCOLOR_TEXTSELECTED]);
p[11] = rgb2quad(CustomColors[MODCOLOR_BACKSELECTED]);
p[12] = rgb2quad(CustomColors[MODCOLOR_TEXTSELECTED]);
break;
// Play Cursor
case 3:
p[8] = rgb2quad(CustomColors[MODCOLOR_TEXTPLAYCURSOR]);
p[11] = rgb2quad(CustomColors[MODCOLOR_BACKPLAYCURSOR]);
break;
// Sample Editor
case 9:
case 10:
p[0] = rgb2quad(CustomColors[MODCOLOR_BACKSAMPLE]);
p[7] = rgb2quad(GetSysColor(COLOR_BTNFACE));
p[8] = rgb2quad(GetSysColor(COLOR_BTNSHADOW));
p[10] = rgb2quad(CustomColors[MODCOLOR_SAMPLE_LOOPMARKER]);
p[11] = rgb2quad(CustomColors[MODCOLOR_SAMPLE_CUEPOINT]);
p[14] = rgb2quad(CustomColors[MODCOLOR_SAMPLE_SUSTAINMARKER]);
p[15] = rgb2quad(CustomColors[MODCOLOR_SAMPLESELECTED]);
break;
// Envelope Editor
case 11:
p[0] = rgb2quad(CustomColors[MODCOLOR_BACKENV]);
p[2] = rgb2quad(CustomColors[MODCOLOR_ENVELOPE_RELEASE]);
break;
}
CRect rect = lpdis->rcItem;
HDC hdc = lpdis->hDC;
::DrawEdge(hdc, rect, BDR_SUNKENINNER, BF_RECT | BF_ADJUST);
StretchDIBits( hdc,
rect.left,
rect.top,
rect.Width(),
rect.Height(),
0,
m_pPreviewDib->bmiHeader.biHeight - ((img+1) * PREVIEWBMP_HEIGHT),
m_pPreviewDib->bmiHeader.biWidth,
PREVIEWBMP_HEIGHT,
m_pPreviewDib->lpDibBits,
(LPBITMAPINFO)m_pPreviewDib,
DIB_RGB_COLORS,
SRCCOPY);
}
static COLORREF rgbCustomColors[16] =
{
0x808080, 0x0000FF, 0x00FF00, 0x00FFFF,
0xFF0000, 0xFF00FF, 0xFFFF00, 0xFFFFFF,
0xC0C0C0, 0x80FFFF, 0xE0E8E0, 0x606060,
0x505050, 0x404040, 0x004000, 0x000000,
};
void COptionsColors::SelectColor(int colorIndex)
{
auto &color = CustomColors[colorDefs[m_nColorItem].colorIndex[colorIndex]];
CHOOSECOLOR cc;
cc.lStructSize = sizeof(CHOOSECOLOR);
cc.hwndOwner = m_hWnd;
cc.hInstance = NULL;
cc.rgbResult = color;
cc.lpCustColors = rgbCustomColors;
cc.Flags = CC_RGBINIT | CC_FULLOPEN;
cc.lCustData = 0;
cc.lpfnHook = nullptr;
cc.lpTemplateName = nullptr;
if(::ChooseColor(&cc))
{
color = cc.rgbResult;
m_BtnColor[colorIndex].SetColor(color);
OnSettingsChanged();
}
}
void COptionsColors::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
{
CPropertyPage::OnVScroll(nSBCode, nPos, pScrollBar);
int newSel = m_ComboItem.GetCurSel() - m_ColorSpin.GetPos32();
if(newSel >= 0)
{
m_ComboItem.SetCurSel(newSel);
OnColorSelChanged();
}
m_ColorSpin.SetPos(0);
}
void COptionsColors::OnColorSelChanged()
{
int sel = m_ComboItem.GetCurSel();
if (sel >= 0)
{
m_nColorItem = static_cast<uint32>(m_ComboItem.GetItemData(sel));
OnUpdateDialog();
}
}
void COptionsColors::OnSettingsChanged()
{
SetModified(TRUE);
}
void COptionsColors::OnUpdateDialog()
{
const ColorDescriptions &cd = colorDefs[m_nColorItem];
for(int i = 0; i < 3; i++)
{
if(cd.descText[i])
{
m_TxtColor[i].SetWindowText(cd.descText[i]);
m_BtnColor[i].SetColor(CustomColors[cd.colorIndex[i]]);
}
m_TxtColor[i].ShowWindow(cd.descText[i] ? SW_SHOW : SW_HIDE);
m_BtnColor[i].ShowWindow(cd.descText[i] ? SW_SHOW : SW_HIDE);
}
m_BtnPreview.Invalidate(FALSE);
}
void COptionsColors::OnPreviewChanged()
{
OnSettingsChanged();
m_BtnPreview.Invalidate(FALSE);
for(int i = 0; i < 3; i++)
{
m_BtnColor[i].SetColor(CustomColors[colorDefs[m_nColorItem].colorIndex[i]]);
}
}
void COptionsColors::OnPresetChange()
{
auto curSel = m_ComboPreset.GetCurSel();
if(curSel == 0)
return;
TrackerSettings::GetDefaultColourScheme(CustomColors);
auto scheme = static_cast<const ColorScheme *>(m_ComboPreset.GetItemDataPtr(curSel));
if(scheme != nullptr)
{
for(const auto &c : scheme->colors)
{
CustomColors[c.id] = c.color;
}
}
OnPreviewChanged();
}
void COptionsColors::OnLoadColorScheme()
{
FileDialog dlg = OpenFileDialog()
.DefaultExtension("mptcolor")
.ExtensionFilter("OpenMPT Color Schemes|*.mptcolor||")
.WorkingDirectory(theApp.GetConfigPath());
if(!dlg.Show(this)) return;
// Ensure that all colours are reset (for outdated colour schemes)
TrackerSettings::GetDefaultColourScheme(CustomColors);
{
IniFileSettingsContainer file(dlg.GetFirstFile());
for(uint32 i = 0; i < MAX_MODCOLORS; i++)
{
CustomColors[i] = file.Read<int32>(U_("Colors"), MPT_UFORMAT("Color{}")(mpt::ufmt::dec0<2>(i)), CustomColors[i]);
}
}
OnPreviewChanged();
}
void COptionsColors::OnSaveColorScheme()
{
FileDialog dlg = SaveFileDialog()
.DefaultExtension("mptcolor")
.ExtensionFilter("OpenMPT Color Schemes|*.mptcolor||")
.WorkingDirectory(theApp.GetConfigPath());
if(!dlg.Show(this)) return;
{
IniFileSettingsContainer file(dlg.GetFirstFile());
for(uint32 i = 0; i < MAX_MODCOLORS; i++)
{
file.Write<int32>(U_("Colors"), MPT_UFORMAT("Color{}")(mpt::ufmt::dec0<2>(i)), CustomColors[i]);
}
}
}
void COptionsColors::OnClearWindowCache()
{
SettingsContainer &settings = theApp.GetSongSettings();
// First, forget all settings...
settings.ForgetAll();
// Then make sure they are gone for good.
::DeleteFile(theApp.GetSongSettingsFilename().AsNative().c_str());
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,62 @@
/*
* ColorConfigDlg.cpp
* ------------------
* Purpose: Implementation of the color setup dialog.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "ColorPickerButton.h"
OPENMPT_NAMESPACE_BEGIN
class COptionsColors : public CPropertyPage
{
protected:
std::array<COLORREF, MAX_MODCOLORS> CustomColors;
CComboBox m_ComboItem, m_ComboFont, m_ComboPreset;
ColorPickerButton m_BtnColor[3];
CButton m_BtnPreview;
CSpinButtonCtrl m_ColorSpin;
CStatic m_TxtColor[3];
MODPLUGDIB *m_pPreviewDib = nullptr;
FontSetting patternFont, commentFont;
uint32 m_nColorItem = 0;
public:
COptionsColors();
~COptionsColors() { delete m_pPreviewDib; }
protected:
BOOL OnInitDialog() override;
BOOL OnKillActive() override;
void OnOK() override;
void DoDataExchange(CDataExchange* pDX) override;
BOOL OnSetActive() override;
void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
void SelectColor(int colorIndex);
afx_msg void OnChoosePatternFont();
afx_msg void OnChooseCommentFont();
afx_msg void OnUpdateDialog();
afx_msg void OnDrawItem(int nIdCtl, LPDRAWITEMSTRUCT lpdis);
afx_msg void OnColorSelChanged();
afx_msg void OnSettingsChanged();
afx_msg void OnSelectColor1() { SelectColor(0); }
afx_msg void OnSelectColor2() { SelectColor(1); }
afx_msg void OnSelectColor3() { SelectColor(2); }
afx_msg void OnPresetChange();
afx_msg void OnLoadColorScheme();
afx_msg void OnSaveColorScheme();
afx_msg void OnClearWindowCache();
afx_msg void OnPreviewChanged();
DECLARE_MESSAGE_MAP();
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,99 @@
/*
* ColorPickerButton.cpp
* ---------------------
* Purpose: A button for picking UI colors
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "ColorPickerButton.h"
#include "MPTrackUtil.h"
#include "Sndfile.h"
#include <algorithm>
OPENMPT_NAMESPACE_BEGIN
void ColorPickerButton::SetColor(COLORREF color)
{
m_color = color;
SetWindowText(MPT_TFORMAT("Colour: {}% red, {}% green, {}% blue")
(Util::muldivr(GetRValue(color), 100, 255), Util::muldivr(GetGValue(color), 100, 255), Util::muldivr(GetBValue(color), 100, 255)).c_str());
Invalidate(FALSE);
}
std::optional<COLORREF> ColorPickerButton::PickColor(const CSoundFile &sndFile, CHANNELINDEX chn)
{
static std::array<COLORREF, 16> colors = {0};
// Build a set of currently used channel colors to be displayed in the color picker.
// Channels that are close to the currently edited channel are preferred.
std::map<COLORREF, int> usedColors;
for(CHANNELINDEX i = 0; i < sndFile.GetNumChannels(); i++)
{
auto color = sndFile.ChnSettings[i].color;
if(color == ModChannelSettings::INVALID_COLOR)
continue;
const int distance = std::abs(static_cast<int>(i) - chn);
usedColors[color] = usedColors.count(color) ? std::min(distance, usedColors[color]) : distance;
}
std::vector<std::pair<COLORREF, int>> sortedColors(usedColors.begin(), usedColors.end());
std::sort(sortedColors.begin(), sortedColors.end(), [](const auto &l, const auto &r) { return l.second < r.second; });
size_t numColors = std::min(colors.size(), sortedColors.size());
if(numColors < colors.size())
{
// Try to keep as many currently unused colors as possible by shifting them to the end
std::stable_sort(colors.begin(), colors.end(), [&usedColors](COLORREF l, COLORREF r) { return usedColors.count(l) > usedColors.count(r); });
}
auto col = sortedColors.begin();
for(size_t i = 0; i < numColors; i++)
{
colors[i] = (col++)->first;
}
CColorDialog dlg;
dlg.m_cc.hwndOwner = m_hWnd;
dlg.m_cc.rgbResult = m_color;
dlg.m_cc.Flags |= CC_RGBINIT | CC_FULLOPEN;
dlg.m_cc.lpCustColors = colors.data();
if(dlg.DoModal() == IDOK)
{
SetColor(dlg.GetColor());
return dlg.GetColor();
}
return {};
}
void ColorPickerButton::DrawItem(DRAWITEMSTRUCT *dis)
{
if(dis == nullptr)
return;
HDC hdc = dis->hDC;
CRect rect = dis->rcItem;
::DrawEdge(hdc, rect, (dis->itemState & ODS_SELECTED) ? BDR_SUNKENINNER : BDR_RAISEDINNER, BF_RECT | BF_ADJUST);
if(m_color == ModChannelSettings::INVALID_COLOR || (dis->itemState & ODS_DISABLED))
{
::FillRect(hdc, rect, GetSysColorBrush(COLOR_BTNFACE));
} else
{
::SetDCBrushColor(hdc, m_color);
::FillRect(hdc, rect, GetStockBrush(DC_BRUSH));
}
if((dis->itemState & ODS_FOCUS))
{
int offset = Util::ScalePixels(1, dis->hwndItem);
rect.DeflateRect(offset, offset);
::DrawFocusRect(hdc, rect);
}
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,32 @@
/*
* ColorPickerButton.h
* -------------------
* Purpose: A button for picking UI colors
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "../soundlib/Snd_defs.h"
OPENMPT_NAMESPACE_BEGIN
class CSoundFile;
class ColorPickerButton : public CButton
{
public:
void SetColor(COLORREF color);
std::optional<COLORREF> PickColor(const CSoundFile &sndFile, CHANNELINDEX chn);
protected:
COLORREF m_color = 0;
void DrawItem(DRAWITEMSTRUCT *dis) override;
};
OPENMPT_NAMESPACE_END
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,68 @@
/*
* ColourEdit.cpp
* --------------
* Purpose: Implementation of a coloured edit UI item.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "ColourEdit.h"
OPENMPT_NAMESPACE_BEGIN
/////////////////////////////////////////////////////////////////////////////
// CColourEdit
CColourEdit::CColourEdit()
{
m_crText = RGB(0, 0, 0); //default text color
}
CColourEdit::~CColourEdit()
{
if(m_brBackGnd.GetSafeHandle()) //delete brush
m_brBackGnd.DeleteObject();
}
BEGIN_MESSAGE_MAP(CColourEdit, CEdit)
ON_WM_CTLCOLOR_REFLECT()
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CColourEdit message handlers
HBRUSH CColourEdit::CtlColor(CDC *pDC, UINT nCtlColor)
{
MPT_UNREFERENCED_PARAMETER(nCtlColor);
pDC->SetTextColor(m_crText); //set text color
pDC->SetBkColor(m_crBackGnd); //set the text's background color
return m_brBackGnd; //return the brush used for background - this sets control background
}
/////////////////////////////////////////////////////////////////////////////
// Implementation
void CColourEdit::SetBackColor(COLORREF rgb)
{
m_crBackGnd = rgb; //set background color ref (used for text's background)
if(m_brBackGnd.GetSafeHandle()) //free brush
m_brBackGnd.DeleteObject();
m_brBackGnd.CreateSolidBrush(rgb); //set brush to new color
Invalidate(TRUE); //redraw
}
void CColourEdit::SetTextColor(COLORREF rgb)
{
m_crText = rgb; // set text color ref
Invalidate(TRUE); // redraw
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,38 @@
/*
* ColourEdit.h
* ------------
* Purpose: Implementation of a coloured edit UI item.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
OPENMPT_NAMESPACE_BEGIN
class CColourEdit : public CEdit
{
public:
CColourEdit();
~CColourEdit();
public:
void SetTextColor(COLORREF rgb);
void SetBackColor(COLORREF rgb);
private:
COLORREF m_crText;
COLORREF m_crBackGnd;
CBrush m_brBackGnd;
protected:
afx_msg HBRUSH CtlColor(CDC* pDC, UINT nCtlColor);
DECLARE_MESSAGE_MAP()
};
OPENMPT_NAMESPACE_END
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,344 @@
/*
* Ctrl_com.cpp
* ------------
* Purpose: Song comments tab, upper 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 "Moddoc.h"
#include "Globals.h"
#include "Ctrl_com.h"
#include "view_com.h"
#include "InputHandler.h"
#include "../soundlib/mod_specifications.h"
//#define MPT_COMMENTS_LONG_LINES_WRAP
//#define MPT_COMMENTS_LONG_LINES_TRUNCATE
#define MPT_COMMENTS_MARGIN 4
OPENMPT_NAMESPACE_BEGIN
BEGIN_MESSAGE_MAP(CCtrlComments, CModControlDlg)
//{{AFX_MSG_MAP(CCtrlComments)
ON_EN_UPDATE(IDC_EDIT_COMMENTS, &CCtrlComments::OnCommentsUpdated)
ON_EN_CHANGE(IDC_EDIT_COMMENTS, &CCtrlComments::OnCommentsChanged)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
void CCtrlComments::DoDataExchange(CDataExchange* pDX)
{
CModControlDlg::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CCtrlComments)
DDX_Control(pDX, IDC_EDIT_COMMENTS, m_EditComments);
//}}AFX_DATA_MAP
}
CCtrlComments::CCtrlComments(CModControlView &parent, CModDoc &document) : CModControlDlg(parent, document)
{
}
CRuntimeClass *CCtrlComments::GetAssociatedViewClass()
{
return RUNTIME_CLASS(CViewComments);
}
void CCtrlComments::OnActivatePage(LPARAM)
{
// Don't stop generating VU meter messages
m_modDoc.SetNotifications(Notification::Default);
m_modDoc.SetFollowWnd(m_hWnd);
m_EditComments.SetFocus();
}
void CCtrlComments::OnDeactivatePage()
{
CModControlDlg::OnDeactivatePage();
}
BOOL CCtrlComments::OnInitDialog()
{
CModControlDlg::OnInitDialog();
// Initialize comments
UINT margin = Util::ScalePixels(MPT_COMMENTS_MARGIN, m_EditComments.m_hWnd);
m_EditComments.SetMargins(margin, margin);
UpdateView(CommentHint().ModType());
m_EditComments.SetFocus();
m_EditComments.FmtLines(FALSE);
m_bInitialized = TRUE;
return FALSE;
}
void CCtrlComments::RecalcLayout()
{
CRect rcClient, rect;
int cx0, cy0;
if ((!m_hWnd) || (!m_EditComments.m_hWnd)) return;
GetClientRect(&rcClient);
m_EditComments.GetWindowRect(&rect);
ScreenToClient(&rect);
cx0 = rect.Width();
cy0 = rect.Height();
rect.bottom = rcClient.bottom - 3;
rect.right = rcClient.right - rect.left;
if ((rect.right > rect.left) && (rect.bottom > rect.top))
{
int cx = rect.Width(), cy = rect.Height();
if(m_sndFile.GetModSpecifications().commentLineLengthMax != 0)
{
int cxmax = Util::ScalePixels(GetSystemMetrics(SM_CXBORDER) + MPT_COMMENTS_MARGIN + m_sndFile.GetModSpecifications().commentLineLengthMax * charWidth + MPT_COMMENTS_MARGIN + GetSystemMetrics(SM_CXVSCROLL) + GetSystemMetrics(SM_CXBORDER) - 1, m_EditComments.m_hWnd);
if (cx > cxmax && cxmax != 0) cx = cxmax;
//SetWindowLong(m_EditComments.m_hWnd, GWL_STYLE, GetWindowLong(m_EditComments.m_hWnd, GWL_STYLE) & ~WS_HSCROLL);
} else
{
//SetWindowLong(m_EditComments.m_hWnd, GWL_STYLE, GetWindowLong(m_EditComments.m_hWnd, GWL_STYLE) | WS_HSCROLL);
}
if ((cx != cx0) || (cy != cy0)) m_EditComments.SetWindowPos(NULL, 0,0, cx, cy, SWP_NOMOVE|SWP_NOZORDER|SWP_DRAWFRAME);
}
}
void CCtrlComments::UpdateView(UpdateHint hint, CObject *pHint)
{
CommentHint commentHint = hint.ToType<CommentHint>();
if (pHint == this || !commentHint.GetType()[HINT_MODCOMMENTS | HINT_MPTOPTIONS | HINT_MODTYPE]) return;
if (m_nLockCount) return;
m_nLockCount++;
static FontSetting previousFont;
FontSetting font = TrackerSettings::Instance().commentsFont;
// Point size to pixels
int32 fontSize = -MulDiv(font.size, m_nDPIy, 720);
if(previousFont != font)
{
previousFont = font;
CMainFrame::GetCommentsFont() = ::CreateFont(fontSize, 0, 0, 0, font.flags[FontSetting::Bold] ? FW_BOLD : FW_NORMAL,
font.flags[FontSetting::Italic] ? TRUE :FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
FIXED_PITCH | FF_MODERN, mpt::ToCString(font.name));
}
m_EditComments.SendMessage(WM_SETFONT, (WPARAM)CMainFrame::GetCommentsFont());
CDC * pDC = m_EditComments.GetDC();
pDC->SelectObject(CMainFrame::GetCommentsFont());
TEXTMETRIC tm;
pDC->GetTextMetrics(&tm);
charWidth = tm.tmAveCharWidth;
m_EditComments.ReleaseDC(pDC);
RecalcLayout();
m_EditComments.SetRedraw(FALSE);
std::string text = m_sndFile.m_songMessage.GetFormatted(SongMessage::leCRLF);
for(std::size_t i = 0; i < text.length(); ++i)
{
// replace control characters
char c = text[i];
if(c > '\0' && c < ' ' && c != '\r' && c != '\n')
{
c = ' ';
}
text[i] = c;
}
CString new_text = mpt::ToCString(m_sndFile.GetCharsetInternal(), text);
CString old_text;
m_EditComments.GetWindowText(old_text);
if(new_text != old_text)
{
m_EditComments.SetWindowText(new_text);
}
if(commentHint.GetType() & HINT_MODTYPE)
{
m_EditComments.SetReadOnly(!m_sndFile.GetModSpecifications().hasComments);
}
m_EditComments.SetRedraw(TRUE);
m_nLockCount--;
}
void CCtrlComments::OnCommentsUpdated()
{
#if defined(MPT_COMMENTS_LONG_LINES_TRUNCATE) || defined(MPT_COMMENTS_LONG_LINES_WRAP)
if(m_Reformatting)
{
return;
}
if(!m_sndFile.GetModSpecifications().hasComments)
{
return;
}
if(m_sndFile.GetModSpecifications().commentLineLengthMax == 0)
{
return;
}
m_Reformatting = true;
const std::size_t maxline = m_sndFile.GetModSpecifications().commentLineLengthMax;
int beg = 0;
int end = 0;
m_EditComments.GetSel(beg, end);
CString text;
m_EditComments.GetWindowText(text);
std::string lines_new;
lines_new.reserve(text.GetLength());
bool modified = false;
std::size_t pos = 0;
#if defined(MPT_COMMENTS_LONG_LINES_WRAP)
std::string lines = text.GetString();
std::size_t line_length = 0;
for(std::size_t i = 0; i < lines.length(); ++i)
{
if(lines[i] == '\r')
{
// nothing
} else if (lines[i] == '\n')
{
line_length = 0;
} else
{
line_length += 1;
}
if(line_length > maxline)
{
modified = true;
lines_new.push_back('\r');
lines_new.push_back('\n');
if(beg >= 0)
{
if(beg >= pos)
{
beg += 2;
}
}
if(end >= 0)
{
if(end >= pos)
{
end += 2;
}
}
pos += 2;
line_length = 1;
}
lines_new.push_back(lines[i]);
pos++;
}
#elif defined(MPT_COMMENTS_LONG_LINES_TRUNCATE)
std::vector<std::string> lines = mpt::String::Split<std::string>(std::string(text.GetString()), std::string("\r\n"));
for(std::size_t i = 0; i < lines.size(); ++i)
{
if(i > 0)
{
pos += 2;
}
if(lines[i].length() > maxline)
{
modified = true;
pos += maxline;
for(std::size_t n = 0; n < lines[i].length() - maxline; ++n)
{
if(beg >= 0)
{
if(beg > pos)
{
beg--;
}
}
if(end >= 0)
{
if(end > pos)
{
end--;
}
}
}
lines[i] = lines[i].substr(0, maxline);
} else
{
pos += lines[i].length();
}
}
lines_new = mpt::String::Combine(lines, std::string("\r\n"));
#endif
if(modified)
{
text = lines_new.c_str();
m_EditComments.SetWindowText(text);
m_EditComments.SetSel(beg, end);
}
m_Reformatting = false;
#endif
}
void CCtrlComments::OnCommentsChanged()
{
if(m_nLockCount)
return;
if ((!m_bInitialized) || (!m_EditComments.m_hWnd) || (!m_EditComments.GetModify())) return;
CString text;
m_EditComments.GetWindowText(text);
m_EditComments.SetModify(FALSE);
if(m_sndFile.m_songMessage.SetFormatted(mpt::ToCharset(m_sndFile.GetCharsetInternal(), text), SongMessage::leCRLF))
{
m_modDoc.SetModified();
m_modDoc.UpdateAllViews(nullptr, CommentHint(), this);
}
}
BOOL CCtrlComments::PreTranslateMessage(MSG *pMsg)
{
if(pMsg->message == WM_KEYDOWN && pMsg->wParam == 'A' && GetKeyState(VK_CONTROL) < 0)
{
// Ctrl-A is not handled by multiline edit boxes
if(::GetFocus() == m_EditComments.m_hWnd)
m_EditComments.SetSel(0, -1);
} else if(pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_TAB && CMainFrame::GetMainFrame()->GetInputHandler()->GetModifierMask() == ModNone)
{
int selStart, selEnd;
m_EditComments.GetSel(selStart, selEnd);
int posInLine = (selStart - m_EditComments.LineIndex(m_EditComments.LineFromChar(selStart)));
CString tabs(_T(' '), 4 - (posInLine % 4));
m_EditComments.ReplaceSel(tabs, TRUE);
m_EditComments.SetSel(-1, -1, TRUE);
return TRUE;
}
return CModControlDlg::PreTranslateMessage(pMsg);
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,48 @@
/*
* Ctrl_com.h
* ----------
* Purpose: Song comments tab, upper panel.
* Notes : (currently none)
* Authors: Olivier Lapicque
* OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
OPENMPT_NAMESPACE_BEGIN
class CCtrlComments final : public CModControlDlg
{
protected:
CEdit m_EditComments;
int charWidth = 0;
bool m_Reformatting = false;
public:
CCtrlComments(CModControlView &parent, CModDoc &document);
//{{AFX_VIRTUAL(CCtrlComments)
Setting<LONG> &GetSplitPosRef() override { return TrackerSettings::Instance().glCommentsWindowHeight; }
BOOL OnInitDialog() override;
void DoDataExchange(CDataExchange *pDX) override; // DDX/DDV support
void RecalcLayout() override;
void UpdateView(UpdateHint hint, CObject *pObj = nullptr) override;
CRuntimeClass *GetAssociatedViewClass() override;
void OnActivatePage(LPARAM) override;
void OnDeactivatePage() override;
BOOL PreTranslateMessage(MSG *pMsg) override;
//}}AFX_VIRTUAL
protected:
//{{AFX_MSG(CCtrlComments)
afx_msg void OnCommentsUpdated();
afx_msg void OnCommentsChanged();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,816 @@
/*
* Ctrl_gen.cpp
* ------------
* Purpose: General tab, upper 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 "Moddoc.h"
#include "Globals.h"
#include "dlg_misc.h"
#include "Ctrl_gen.h"
#include "View_gen.h"
#include "../common/misc_util.h"
#include "../common/mptTime.h"
#include "../soundlib/mod_specifications.h"
OPENMPT_NAMESPACE_BEGIN
BEGIN_MESSAGE_MAP(CCtrlGeneral, CModControlDlg)
//{{AFX_MSG_MAP(CCtrlGeneral)
ON_WM_VSCROLL()
ON_COMMAND(IDC_BUTTON1, &CCtrlGeneral::OnTapTempo)
ON_COMMAND(IDC_BUTTON_MODTYPE, &CCtrlGeneral::OnSongProperties)
ON_COMMAND(IDC_CHECK_LOOPSONG, &CCtrlGeneral::OnLoopSongChanged)
ON_EN_CHANGE(IDC_EDIT_SONGTITLE, &CCtrlGeneral::OnTitleChanged)
ON_EN_CHANGE(IDC_EDIT_ARTIST, &CCtrlGeneral::OnArtistChanged)
ON_EN_CHANGE(IDC_EDIT_TEMPO, &CCtrlGeneral::OnTempoChanged)
ON_EN_CHANGE(IDC_EDIT_SPEED, &CCtrlGeneral::OnSpeedChanged)
ON_EN_CHANGE(IDC_EDIT_GLOBALVOL, &CCtrlGeneral::OnGlobalVolChanged)
ON_EN_CHANGE(IDC_EDIT_RESTARTPOS, &CCtrlGeneral::OnRestartPosChanged)
ON_EN_CHANGE(IDC_EDIT_VSTIVOL, &CCtrlGeneral::OnVSTiVolChanged)
ON_EN_CHANGE(IDC_EDIT_SAMPLEPA, &CCtrlGeneral::OnSamplePAChanged)
ON_MESSAGE(WM_MOD_UPDATEPOSITION, &CCtrlGeneral::OnUpdatePosition)
ON_EN_SETFOCUS(IDC_EDIT_SONGTITLE, &CCtrlGeneral::OnEnSetfocusEditSongtitle)
ON_EN_KILLFOCUS(IDC_EDIT_RESTARTPOS, &CCtrlGeneral::OnRestartPosDone)
ON_CBN_SELCHANGE(IDC_COMBO1, &CCtrlGeneral::OnResamplingChanged)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
void CCtrlGeneral::DoDataExchange(CDataExchange* pDX)
{
CModControlDlg::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CCtrlGeneral)
DDX_Control(pDX, IDC_EDIT_SONGTITLE, m_EditTitle);
DDX_Control(pDX, IDC_EDIT_ARTIST, m_EditArtist);
//DDX_Control(pDX, IDC_EDIT_TEMPO, m_EditTempo);
DDX_Control(pDX, IDC_SPIN_TEMPO, m_SpinTempo);
DDX_Control(pDX, IDC_EDIT_SPEED, m_EditSpeed);
DDX_Control(pDX, IDC_SPIN_SPEED, m_SpinSpeed);
DDX_Control(pDX, IDC_EDIT_GLOBALVOL, m_EditGlobalVol);
DDX_Control(pDX, IDC_SPIN_GLOBALVOL, m_SpinGlobalVol);
DDX_Control(pDX, IDC_EDIT_VSTIVOL, m_EditVSTiVol);
DDX_Control(pDX, IDC_SPIN_VSTIVOL, m_SpinVSTiVol);
DDX_Control(pDX, IDC_EDIT_SAMPLEPA, m_EditSamplePA);
DDX_Control(pDX, IDC_SPIN_SAMPLEPA, m_SpinSamplePA);
DDX_Control(pDX, IDC_EDIT_RESTARTPOS, m_EditRestartPos);
DDX_Control(pDX, IDC_SPIN_RESTARTPOS, m_SpinRestartPos);
DDX_Control(pDX, IDC_SLIDER_SONGTEMPO, m_SliderTempo);
DDX_Control(pDX, IDC_SLIDER_VSTIVOL, m_SliderVSTiVol);
DDX_Control(pDX, IDC_SLIDER_GLOBALVOL, m_SliderGlobalVol);
DDX_Control(pDX, IDC_SLIDER_SAMPLEPREAMP, m_SliderSamplePreAmp);
DDX_Control(pDX, IDC_BUTTON_MODTYPE, m_BtnModType);
DDX_Control(pDX, IDC_VUMETER_LEFT, m_VuMeterLeft);
DDX_Control(pDX, IDC_VUMETER_RIGHT, m_VuMeterRight);
DDX_Control(pDX, IDC_COMBO1, m_CbnResampling);
//}}AFX_DATA_MAP
}
CCtrlGeneral::CCtrlGeneral(CModControlView &parent, CModDoc &document) : CModControlDlg(parent, document)
{
}
BOOL CCtrlGeneral::OnInitDialog()
{
const auto &specs = m_sndFile.GetModSpecifications();
CModControlDlg::OnInitDialog();
// Song Title
m_EditTitle.SetLimitText(specs.modNameLengthMax);
m_SpinGlobalVol.SetRange(0, (short)(256 / GetGlobalVolumeFactor()));
m_SpinSamplePA.SetRange(0, 2000);
m_SpinVSTiVol.SetRange(0, 2000);
m_SpinRestartPos.SetRange32(0, ORDERINDEX_MAX);
m_SliderGlobalVol.SetRange(0, MAX_SLIDER_GLOBAL_VOL);
m_SliderVSTiVol.SetRange(0, MAX_SLIDER_VSTI_VOL);
m_SliderSamplePreAmp.SetRange(0, MAX_SLIDER_SAMPLE_VOL);
m_SpinTempo.SetRange(-10, 10);
m_SliderTempo.SetLineSize(1);
m_SliderTempo.SetPageSize(10);
m_EditTempo.SubclassDlgItem(IDC_EDIT_TEMPO, this);
m_EditTempo.AllowNegative(false);
m_editsLocked = false;
UpdateView(GeneralHint().ModType());
OnActivatePage(0);
m_bInitialized = TRUE;
return FALSE;
}
CRuntimeClass *CCtrlGeneral::GetAssociatedViewClass()
{
return RUNTIME_CLASS(CViewGlobals);
}
void CCtrlGeneral::RecalcLayout()
{
}
void CCtrlGeneral::OnActivatePage(LPARAM)
{
m_modDoc.SetNotifications(Notification::Default);
m_modDoc.SetFollowWnd(m_hWnd);
PostViewMessage(VIEWMSG_SETACTIVE, NULL);
SetFocus();
// Combo boxes randomly disappear without this... why?
Invalidate();
}
void CCtrlGeneral::OnDeactivatePage()
{
m_modDoc.SetFollowWnd(NULL);
m_VuMeterLeft.SetVuMeter(0, true);
m_VuMeterRight.SetVuMeter(0, true);
m_tapTimer = nullptr; // Reset high-precision clock if required
}
TEMPO CCtrlGeneral::TempoSliderRange() const
{
return (TEMPO_SPLIT_THRESHOLD - m_tempoMin) + TEMPO((m_tempoMax - TEMPO_SPLIT_THRESHOLD).GetInt() / TEMPO_SPLIT_PRECISION, 0);
}
TEMPO CCtrlGeneral::SliderToTempo(int value) const
{
if(m_tempoMax < TEMPO_SPLIT_THRESHOLD)
{
return m_tempoMax - TEMPO(value, 0);
} else
{
const auto tempoSliderSplit = TempoToSlider(TEMPO_SPLIT_THRESHOLD);
if(value <= tempoSliderSplit)
return m_tempoMax - TEMPO(value * TEMPO_SPLIT_PRECISION, 0);
else
return m_tempoMin + TempoSliderRange() - TEMPO(value, 0);
}
}
int CCtrlGeneral::TempoToSlider(TEMPO tempo) const
{
if(m_tempoMax < TEMPO_SPLIT_THRESHOLD)
{
return (m_tempoMax - tempo).GetInt();
} else
{
if(tempo < TEMPO_SPLIT_THRESHOLD)
return (TempoSliderRange() - (std::max(m_tempoMin, tempo) - m_tempoMin)).GetInt();
else
return (m_tempoMax - std::min(m_tempoMax, tempo)).GetInt() / TEMPO_SPLIT_PRECISION;
}
}
void CCtrlGeneral::OnTapTempo()
{
using TapType = decltype(m_tapTimer->Now());
static std::array<TapType, 32> tapTime;
static TapType lastTap = 0;
static uint32 numTaps = 0;
if(m_tapTimer == nullptr)
m_tapTimer = std::make_unique<Util::MultimediaClock>(1);
const uint32 now = m_tapTimer->Now();
if(now - lastTap >= 2000)
numTaps = 0;
lastTap = now;
if(static_cast<size_t>(numTaps) >= tapTime.size())
{
// Shift back the previously recorded tap history
// cppcheck false-positive
// cppcheck-suppress mismatchingContainers
std::copy(tapTime.begin() + 1, tapTime.end(), tapTime.begin());
numTaps = static_cast<uint32>(tapTime.size() - 1);
}
tapTime[numTaps++] = now;
if(numTaps <= 1)
return;
// Now apply least squares to tap history
double sum = 0.0, weightedSum = 0.0;
for(uint32 i = 0; i < numTaps; i++)
{
const double tapMs = tapTime[i] / 1000.0;
sum += tapMs;
weightedSum += i * tapMs;
}
const double lengthSum = numTaps * (numTaps - 1) / 2;
const double lengthSumSum = lengthSum * (2 * numTaps - 1) / 3.0;
const double secondsPerBeat = (numTaps * weightedSum - lengthSum * sum) / (lengthSumSum * numTaps - lengthSum * lengthSum);
double newTempo = 60.0 / secondsPerBeat;
if(m_sndFile.m_nTempoMode != TempoMode::Modern)
newTempo *= (m_sndFile.m_nDefaultSpeed * m_sndFile.m_nDefaultRowsPerBeat) / 24.0;
if(!m_sndFile.GetModSpecifications().hasFractionalTempo)
newTempo = std::round(newTempo);
TEMPO t(newTempo);
Limit(t, m_tempoMin, m_tempoMax);
m_EditTempo.SetTempoValue(t);
}
void CCtrlGeneral::UpdateView(UpdateHint hint, CObject *pHint)
{
if (pHint == this) return;
FlagSet<HintType> hintType = hint.GetType();
const bool updateAll = hintType[HINT_MODTYPE];
const auto resamplingModes = Resampling::AllModes();
if (hintType == HINT_MPTOPTIONS || updateAll)
{
CString defaultResampler;
if(m_sndFile.m_SongFlags[SONG_ISAMIGA] && TrackerSettings::Instance().ResamplerEmulateAmiga != Resampling::AmigaFilter::Off)
defaultResampler = _T("Amiga Resampler");
else
defaultResampler = CTrackApp::GetResamplingModeName(TrackerSettings::Instance().ResamplerMode, 1, false);
m_CbnResampling.ResetContent();
m_CbnResampling.SetItemData(m_CbnResampling.AddString(_T("Default (") + defaultResampler + _T(")")), SRCMODE_DEFAULT);
for(auto mode : resamplingModes)
{
m_CbnResampling.SetItemData(m_CbnResampling.AddString(CTrackApp::GetResamplingModeName(mode, 2, true)), mode);
}
m_CbnResampling.Invalidate(FALSE);
}
if(updateAll)
{
const auto &specs = m_sndFile.GetModSpecifications();
// S3M HACK: ST3 will ignore speed 255, even though it can be used with Axx.
if(m_sndFile.GetType() == MOD_TYPE_S3M)
m_SpinSpeed.SetRange32(1, 254);
else
m_SpinSpeed.SetRange32(specs.speedMin, specs.speedMax);
m_tempoMin = specs.GetTempoMin();
m_tempoMax = specs.GetTempoMax();
// IT Hack: There are legacy OpenMPT-made ITs out there which use a higher default speed than 255.
// Changing the upper tempo limit in the mod specs would break them, so do it here instead.
if(m_sndFile.GetType() == MOD_TYPE_IT && m_sndFile.m_nDefaultTempo <= TEMPO(255, 0))
m_tempoMax.Set(255);
// Lower resolution for BPM above 256
if(m_tempoMax >= TEMPO_SPLIT_THRESHOLD)
m_SliderTempo.SetRange(0, TempoSliderRange().GetInt());
else
m_SliderTempo.SetRange(0, m_tempoMax.GetInt() - m_tempoMin.GetInt());
m_EditTempo.AllowFractions(specs.hasFractionalTempo);
const BOOL bIsNotMOD = (m_sndFile.GetType() != MOD_TYPE_MOD);
const BOOL bIsNotMOD_XM = ((bIsNotMOD) && (m_sndFile.GetType() != MOD_TYPE_XM));
m_EditArtist.EnableWindow(specs.hasArtistName);
m_EditTempo.EnableWindow(bIsNotMOD);
m_SpinTempo.EnableWindow(bIsNotMOD);
GetDlgItem(IDC_BUTTON1)->EnableWindow(bIsNotMOD);
m_SliderTempo.EnableWindow(bIsNotMOD);
m_EditSpeed.EnableWindow(bIsNotMOD);
m_SpinSpeed.EnableWindow(bIsNotMOD);
const BOOL globalVol = bIsNotMOD_XM || m_sndFile.m_nDefaultGlobalVolume != MAX_GLOBAL_VOLUME;
m_SliderGlobalVol.EnableWindow(globalVol);
m_EditGlobalVol.EnableWindow(globalVol);
m_SpinGlobalVol.EnableWindow(globalVol);
m_EditSamplePA.EnableWindow(bIsNotMOD);
m_SpinSamplePA.EnableWindow(bIsNotMOD);
m_SliderVSTiVol.EnableWindow(bIsNotMOD);
m_EditVSTiVol.EnableWindow(bIsNotMOD);
m_SpinVSTiVol.EnableWindow(bIsNotMOD);
m_EditRestartPos.EnableWindow((specs.hasRestartPos || m_sndFile.Order().GetRestartPos() != 0));
m_SpinRestartPos.EnableWindow(m_EditRestartPos.IsWindowEnabled());
//Note: Sample volume slider is not disabled for MOD
//on purpose (can be used to control play volume)
}
if(updateAll || (hint.GetCategory() == HINTCAT_GLOBAL && hintType[HINT_MODCHANNELS]))
{
// MOD Type
mpt::ustring modType;
switch(m_sndFile.GetType())
{
case MOD_TYPE_MOD: modType = U_("MOD (ProTracker)"); break;
case MOD_TYPE_S3M: modType = U_("S3M (Scream Tracker)"); break;
case MOD_TYPE_XM: modType = U_("XM (FastTracker 2)"); break;
case MOD_TYPE_IT: modType = U_("IT (Impulse Tracker)"); break;
case MOD_TYPE_MPT: modType = U_("MPTM (OpenMPT)"); break;
default: modType = MPT_UFORMAT("{} ({})")(mpt::ToUpperCase(m_sndFile.m_modFormat.type), m_sndFile.m_modFormat.formatName); break;
}
CString s;
s.Format(_T("%s, %u channel%s"), mpt::ToCString(modType).GetString(), m_sndFile.GetNumChannels(), (m_sndFile.GetNumChannels() != 1) ? _T("s") : _T(""));
m_BtnModType.SetWindowText(s);
}
if (updateAll || (hint.GetCategory() == HINTCAT_SEQUENCE && hintType[HINT_MODSEQUENCE | HINT_RESTARTPOS]))
{
// Set max valid restart position
m_SpinRestartPos.SetRange32(0, std::max(m_sndFile.Order().GetRestartPos(), static_cast<ORDERINDEX>(m_sndFile.Order().GetLengthTailTrimmed() - 1)));
SetDlgItemInt(IDC_EDIT_RESTARTPOS, m_sndFile.Order().GetRestartPos(), FALSE);
}
if (updateAll || (hint.GetCategory() == HINTCAT_GENERAL && hintType[HINT_MODGENERAL]))
{
if (!m_editsLocked)
{
m_EditTitle.SetWindowText(mpt::ToCString(m_sndFile.GetCharsetInternal(), m_sndFile.GetTitle()));
m_EditArtist.SetWindowText(mpt::ToCString(m_sndFile.m_songArtist));
m_EditTempo.SetTempoValue(m_sndFile.m_nDefaultTempo);
SetDlgItemInt(IDC_EDIT_SPEED, m_sndFile.m_nDefaultSpeed, FALSE);
SetDlgItemInt(IDC_EDIT_GLOBALVOL, m_sndFile.m_nDefaultGlobalVolume / GetGlobalVolumeFactor(), FALSE);
SetDlgItemInt(IDC_EDIT_VSTIVOL, m_sndFile.m_nVSTiVolume, FALSE);
SetDlgItemInt(IDC_EDIT_SAMPLEPA, m_sndFile.m_nSamplePreAmp, FALSE);
}
m_SliderGlobalVol.SetPos(MAX_SLIDER_GLOBAL_VOL - m_sndFile.m_nDefaultGlobalVolume);
m_SliderVSTiVol.SetPos(MAX_SLIDER_VSTI_VOL - m_sndFile.m_nVSTiVolume);
m_SliderSamplePreAmp.SetPos(MAX_SLIDER_SAMPLE_VOL - m_sndFile.m_nSamplePreAmp);
m_SliderTempo.SetPos(TempoToSlider(m_sndFile.m_nDefaultTempo));
}
if(updateAll || hintType == HINT_MPTOPTIONS || (hint.GetCategory() == HINTCAT_GENERAL && hintType[HINT_MODGENERAL]))
{
for(int i = 0; i < m_CbnResampling.GetCount(); ++i)
{
if(m_sndFile.m_nResampling == static_cast<ResamplingMode>(m_CbnResampling.GetItemData(i)))
{
m_CbnResampling.SetCurSel(i);
break;
}
}
}
CheckDlgButton(IDC_CHECK_LOOPSONG, (TrackerSettings::Instance().gbLoopSong) ? TRUE : FALSE);
if (hintType[HINT_MPTOPTIONS])
{
m_VuMeterLeft.InvalidateRect(NULL, FALSE);
m_VuMeterRight.InvalidateRect(NULL, FALSE);
}
}
void CCtrlGeneral::OnVScroll(UINT code, UINT pos, CScrollBar *pscroll)
{
CDialog::OnVScroll(code, pos, pscroll);
if (m_bInitialized)
{
CSliderCtrl* pSlider = (CSliderCtrl*) pscroll;
if (pSlider == &m_SliderTempo)
{
const TEMPO tempo = SliderToTempo(m_SliderTempo.GetPos());
if ((tempo >= m_sndFile.GetModSpecifications().GetTempoMin()) && (tempo <= m_sndFile.GetModSpecifications().GetTempoMax()) && (tempo != m_sndFile.m_nDefaultTempo))
{
m_sndFile.m_nDefaultTempo = m_sndFile.m_PlayState.m_nMusicTempo = tempo;
m_modDoc.SetModified();
m_modDoc.UpdateAllViews(nullptr, GeneralHint().General(), this);
m_EditTempo.SetTempoValue(tempo);
}
}
else if (pSlider == &m_SliderGlobalVol)
{
const UINT gv = MAX_SLIDER_GLOBAL_VOL - m_SliderGlobalVol.GetPos();
if ((gv >= 0) && (gv <= MAX_SLIDER_GLOBAL_VOL) && (gv != m_sndFile.m_nDefaultGlobalVolume))
{
m_sndFile.m_PlayState.m_nGlobalVolume = gv;
m_sndFile.m_nDefaultGlobalVolume = gv;
m_modDoc.SetModified();
m_modDoc.UpdateAllViews(nullptr, GeneralHint().General(), this);
SetDlgItemInt(IDC_EDIT_GLOBALVOL, m_sndFile.m_nDefaultGlobalVolume / GetGlobalVolumeFactor(), FALSE);
}
}
else if (pSlider == &m_SliderSamplePreAmp)
{
const UINT spa = MAX_SLIDER_SAMPLE_VOL - m_SliderSamplePreAmp.GetPos();
if ((spa >= 0) && (spa <= MAX_SLIDER_SAMPLE_VOL) && (spa != m_sndFile.m_nSamplePreAmp))
{
m_sndFile.m_nSamplePreAmp = spa;
if(m_sndFile.GetType() != MOD_TYPE_MOD)
m_modDoc.SetModified();
m_modDoc.UpdateAllViews(nullptr, GeneralHint().General(), this);
SetDlgItemInt(IDC_EDIT_SAMPLEPA, m_sndFile.m_nSamplePreAmp, FALSE);
}
}
else if (pSlider == &m_SliderVSTiVol)
{
const UINT vv = MAX_SLIDER_VSTI_VOL - m_SliderVSTiVol.GetPos();
if ((vv >= 0) && (vv <= MAX_SLIDER_VSTI_VOL) && (vv != m_sndFile.m_nVSTiVolume))
{
m_sndFile.m_nVSTiVolume = vv;
m_sndFile.RecalculateGainForAllPlugs();
m_modDoc.SetModified();
m_modDoc.UpdateAllViews(nullptr, GeneralHint().General(), this);
SetDlgItemInt(IDC_EDIT_VSTIVOL, m_sndFile.m_nVSTiVolume, FALSE);
}
}
else if(pSlider == (CSliderCtrl*)&m_SpinTempo)
{
int pos32 = m_SpinTempo.GetPos32();
if(pos32 != 0)
{
TEMPO newTempo;
if(m_sndFile.GetModSpecifications().hasFractionalTempo)
{
pos32 *= TEMPO::fractFact;
if(CMainFrame::GetMainFrame()->GetInputHandler()->CtrlPressed())
pos32 /= 100;
else if(CMainFrame::GetMainFrame()->GetInputHandler()->ShiftPressed())
pos32 /= 10;
newTempo.SetRaw(pos32);
} else
{
newTempo = TEMPO(pos32, 0);
}
newTempo += m_sndFile.m_nDefaultTempo;
Limit(newTempo, m_tempoMin, m_tempoMax);
m_sndFile.m_nDefaultTempo = m_sndFile.m_PlayState.m_nMusicTempo = newTempo;
m_modDoc.SetModified();
LockControls();
m_modDoc.UpdateAllViews(nullptr, GeneralHint().General(), this);
UnlockControls();
m_SliderTempo.SetPos(TempoToSlider(newTempo));
m_EditTempo.SetTempoValue(newTempo);
}
m_SpinTempo.SetPos(0);
}
}
}
void CCtrlGeneral::OnTitleChanged()
{
if (!m_EditTitle.m_hWnd || !m_EditTitle.GetModify()) return;
CString title;
m_EditTitle.GetWindowText(title);
if(m_sndFile.SetTitle(mpt::ToCharset(m_sndFile.GetCharsetInternal(), title)))
{
m_EditTitle.SetModify(FALSE);
m_modDoc.SetModified();
m_modDoc.UpdateAllViews(nullptr, GeneralHint().General(), this);
}
}
void CCtrlGeneral::OnArtistChanged()
{
if (!m_EditArtist.m_hWnd || !m_EditArtist.GetModify()) return;
mpt::ustring artist = GetWindowTextUnicode(m_EditArtist);
if(artist != m_sndFile.m_songArtist)
{
m_EditArtist.SetModify(FALSE);
m_sndFile.m_songArtist = artist;
m_modDoc.SetModified();
m_modDoc.UpdateAllViews(NULL, GeneralHint().General(), this);
}
}
void CCtrlGeneral::OnTempoChanged()
{
if (m_bInitialized && m_EditTempo.GetWindowTextLength() > 0)
{
TEMPO tempo = m_EditTempo.GetTempoValue();
Limit(tempo, m_tempoMin, m_tempoMax);
if(!m_sndFile.GetModSpecifications().hasFractionalTempo) tempo.Set(tempo.GetInt());
if (tempo != m_sndFile.m_nDefaultTempo)
{
m_editsLocked = true;
m_EditTempo.SetModify(FALSE);
m_sndFile.m_nDefaultTempo = tempo;
m_sndFile.m_PlayState.m_nMusicTempo = tempo;
m_modDoc.SetModified();
m_modDoc.UpdateAllViews(nullptr, GeneralHint().General());
m_editsLocked = false;
}
}
}
void CCtrlGeneral::OnSpeedChanged()
{
TCHAR s[16];
if(m_bInitialized)
{
m_EditSpeed.GetWindowText(s, mpt::saturate_cast<int>(std::size(s)));
if (s[0])
{
UINT n = ConvertStrTo<UINT>(s);
n = Clamp(n, m_sndFile.GetModSpecifications().speedMin, m_sndFile.GetModSpecifications().speedMax);
if (n != m_sndFile.m_nDefaultSpeed)
{
m_editsLocked = true;
m_EditSpeed.SetModify(FALSE);
m_sndFile.m_nDefaultSpeed = n;
m_sndFile.m_PlayState.m_nMusicSpeed = n;
m_modDoc.SetModified();
m_modDoc.UpdateAllViews(nullptr, GeneralHint().General(), this);
// Update envelope grid view
m_modDoc.UpdateAllViews(nullptr, InstrumentHint().Envelope(), this);
m_editsLocked = false;
}
}
}
}
void CCtrlGeneral::OnVSTiVolChanged()
{
TCHAR s[16];
if (m_bInitialized)
{
m_EditVSTiVol.GetWindowText(s, mpt::saturate_cast<int>(std::size(s)));
if (s[0])
{
UINT n = ConvertStrTo<UINT>(s);
Limit(n, 0u, 2000u);
if (n != m_sndFile.m_nVSTiVolume)
{
m_editsLocked = true;
m_sndFile.m_nVSTiVolume = n;
m_sndFile.RecalculateGainForAllPlugs();
m_modDoc.SetModified();
m_modDoc.UpdateAllViews(nullptr, GeneralHint().General(), this);
UpdateView(GeneralHint().General());
m_editsLocked = false;
}
}
}
}
void CCtrlGeneral::OnSamplePAChanged()
{
TCHAR s[16];
if(m_bInitialized)
{
m_EditSamplePA.GetWindowText(s, mpt::saturate_cast<int>(std::size(s)));
if (s[0])
{
UINT n = ConvertStrTo<UINT>(s);
Limit(n, 0u, 2000u);
if (n != m_sndFile.m_nSamplePreAmp)
{
m_editsLocked = true;
m_sndFile.m_nSamplePreAmp = n;
m_modDoc.SetModified();
m_modDoc.UpdateAllViews(nullptr, GeneralHint().General(), this);
UpdateView(GeneralHint().General());
m_editsLocked = false;
}
}
}
}
void CCtrlGeneral::OnGlobalVolChanged()
{
TCHAR s[16];
if(m_bInitialized)
{
m_EditGlobalVol.GetWindowText(s, mpt::saturate_cast<int>(std::size(s)));
if (s[0])
{
UINT n = ConvertStrTo<ORDERINDEX>(s) * GetGlobalVolumeFactor();
Limit(n, 0u, 256u);
if (n != m_sndFile.m_nDefaultGlobalVolume)
{
m_editsLocked = true;
m_EditGlobalVol.SetModify(FALSE);
m_sndFile.m_nDefaultGlobalVolume = n;
m_sndFile.m_PlayState.m_nGlobalVolume = n;
m_modDoc.SetModified();
m_modDoc.UpdateAllViews(nullptr, GeneralHint().General(), this);
UpdateView(GeneralHint().General());
m_editsLocked = false;
}
}
}
}
void CCtrlGeneral::OnRestartPosChanged()
{
if(!m_bInitialized)
return;
TCHAR s[32];
m_EditRestartPos.GetWindowText(s, mpt::saturate_cast<int>(std::size(s)));
if(!s[0])
return;
ORDERINDEX n = ConvertStrTo<ORDERINDEX>(s);
LimitMax(n, m_sndFile.Order().GetLastIndex());
while(n > 0 && n < m_sndFile.Order().GetLastIndex() && !m_sndFile.Order().IsValidPat(n))
n++;
if(n == m_sndFile.Order().GetRestartPos())
return;
m_EditRestartPos.SetModify(FALSE);
m_sndFile.Order().SetRestartPos(n);
m_modDoc.SetModified();
m_modDoc.UpdateAllViews(nullptr, SequenceHint(m_sndFile.Order.GetCurrentSequenceIndex()).RestartPos(), this);
}
void CCtrlGeneral::OnRestartPosDone()
{
if(m_bInitialized)
SetDlgItemInt(IDC_EDIT_RESTARTPOS, m_sndFile.Order().GetRestartPos());
}
void CCtrlGeneral::OnSongProperties()
{
m_modDoc.OnSongProperties();
}
void CCtrlGeneral::OnLoopSongChanged()
{
m_modDoc.SetLoopSong(IsDlgButtonChecked(IDC_CHECK_LOOPSONG) != BST_UNCHECKED);
}
LRESULT CCtrlGeneral::OnUpdatePosition(WPARAM, LPARAM lParam)
{
Notification *pnotify = (Notification *)lParam;
if (pnotify)
{
m_VuMeterLeft.SetVuMeter(pnotify->masterVUout[0] & (~Notification::ClipVU), pnotify->type[Notification::Stop]);
m_VuMeterRight.SetVuMeter(pnotify->masterVUout[1] & (~Notification::ClipVU), pnotify->type[Notification::Stop]);
}
return 0;
}
BOOL CCtrlGeneral::GetToolTipText(UINT uId, LPTSTR pszText)
{
const TCHAR moreRecentMixModeNote[] = _T("Use a more recent mixmode to see dB offsets.");
if ((pszText) && (uId))
{
const bool displayDBValues = m_sndFile.GetPlayConfig().getDisplayDBValues();
const CWnd *wnd = GetDlgItem(uId);
const bool isEnabled = wnd ? (wnd->IsWindowEnabled() != FALSE) : true; // nullptr check is for a Wine bug workaround (https://bugs.openmpt.org/view.php?id=1553)
mpt::tstring notAvailable;
if(!isEnabled)
notAvailable = MPT_TFORMAT("Feature is not available in the {} format.")(mpt::ToWin(mpt::Charset::ASCII, mpt::ToUpperCaseAscii(m_sndFile.GetModSpecifications().fileExtension)));
switch(uId)
{
case IDC_BUTTON_MODTYPE:
_tcscpy(pszText, _T("Song Properties"));
{
const auto keyText = CMainFrame::GetInputHandler()->m_activeCommandSet->GetKeyTextFromCommand(kcViewSongProperties, 0);
if (!keyText.IsEmpty())
_tcscat(pszText, MPT_TFORMAT(" ({})")(keyText).c_str());
}
return TRUE;
case IDC_BUTTON1:
if(isEnabled)
_tcscpy(pszText, _T("Click button multiple times to tap in the desired tempo."));
else
_tcscpy(pszText, notAvailable.c_str());
return TRUE;
case IDC_SLIDER_SAMPLEPREAMP:
_tcscpy(pszText, displayDBValues ? CModDoc::LinearToDecibels(m_sndFile.m_nSamplePreAmp, m_sndFile.GetPlayConfig().getNormalSamplePreAmp()).GetString() : moreRecentMixModeNote);
return TRUE;
case IDC_SLIDER_VSTIVOL:
if(isEnabled)
_tcscpy(pszText, displayDBValues ? CModDoc::LinearToDecibels(m_sndFile.m_nVSTiVolume, m_sndFile.GetPlayConfig().getNormalVSTiVol()).GetString() : moreRecentMixModeNote);
else
_tcscpy(pszText, notAvailable.c_str());
return TRUE;
case IDC_SLIDER_GLOBALVOL:
if(isEnabled)
_tcscpy(pszText, displayDBValues ? CModDoc::LinearToDecibels(m_sndFile.m_PlayState.m_nGlobalVolume, m_sndFile.GetPlayConfig().getNormalGlobalVol()).GetString() : moreRecentMixModeNote);
else
_tcscpy(pszText, notAvailable.c_str());
return TRUE;
case IDC_SLIDER_SONGTEMPO:
case IDC_EDIT_ARTIST:
case IDC_EDIT_TEMPO:
case IDC_EDIT_SPEED:
case IDC_EDIT_RESTARTPOS:
case IDC_EDIT_GLOBALVOL:
case IDC_EDIT_VSTIVOL:
if(isEnabled)
break;
_tcscpy(pszText, notAvailable.c_str());
return TRUE;
}
}
return FALSE;
}
void CCtrlGeneral::OnEnSetfocusEditSongtitle()
{
m_EditTitle.SetLimitText(m_sndFile.GetModSpecifications().modNameLengthMax);
}
void CCtrlGeneral::OnResamplingChanged()
{
int sel = m_CbnResampling.GetCurSel();
if(sel >= 0)
{
m_sndFile.m_nResampling = static_cast<ResamplingMode>(m_CbnResampling.GetItemData(sel));
if(m_sndFile.GetModSpecifications().hasDefaultResampling)
{
m_modDoc.SetModified();
m_modDoc.UpdateAllViews(nullptr, GeneralHint().General(), this);
}
}
}
////////////////////////////////////////////////////////////////////////////////
//
// CVuMeter
//
BEGIN_MESSAGE_MAP(CVuMeter, CWnd)
ON_WM_PAINT()
END_MESSAGE_MAP()
void CVuMeter::OnPaint()
{
CRect rect;
CPaintDC dc(this);
GetClientRect(&rect);
dc.FillSolidRect(rect.left, rect.top, rect.Width(), rect.Height(), RGB(0,0,0));
m_lastDisplayedLevel = -1;
DrawVuMeter(dc, true);
}
void CVuMeter::SetVuMeter(int level, bool force)
{
level >>= 8;
if (level != m_lastLevel)
{
DWORD curTime = timeGetTime();
if(curTime - m_lastVuUpdateTime >= TrackerSettings::Instance().VuMeterUpdateInterval || force)
{
m_lastLevel = level;
CClientDC dc(this);
DrawVuMeter(dc);
m_lastVuUpdateTime = curTime;
}
}
}
void CVuMeter::DrawVuMeter(CDC &dc, bool /*redraw*/)
{
CRect rect;
GetClientRect(&rect);
int vu = (m_lastLevel * (rect.bottom-rect.top)) >> 8;
int cy = rect.bottom - rect.top;
if (cy < 1) cy = 1;
for (int ry=rect.bottom-1; ry>rect.top; ry-=2)
{
int y0 = rect.bottom - ry;
int n = Clamp((y0 * NUM_VUMETER_PENS) / cy, 0, NUM_VUMETER_PENS - 1);
if (vu < y0)
n += NUM_VUMETER_PENS;
dc.FillSolidRect(rect.left, ry, rect.Width(), 1, CMainFrame::gcolrefVuMeter[n]);
}
m_lastDisplayedLevel = m_lastLevel;
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,118 @@
/*
* ctrl_gen.h
* ----------
* Purpose: General tab, upper panel.
* Notes : (currently none)
* Authors: Olivier Lapicque
* OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "CDecimalSupport.h"
OPENMPT_NAMESPACE_BEGIN
namespace Util { class MultimediaClock; }
class CVuMeter final : public CWnd
{
protected:
int m_lastDisplayedLevel = -1, m_lastLevel = 0;
DWORD m_lastVuUpdateTime;
public:
CVuMeter() { m_lastVuUpdateTime = timeGetTime(); }
void SetVuMeter(int level, bool force = false);
protected:
void DrawVuMeter(CDC &dc, bool redraw = false);
protected:
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP();
};
class CCtrlGeneral final : public CModControlDlg
{
public:
CCtrlGeneral(CModControlView &parent, CModDoc &document);
Setting<LONG> &GetSplitPosRef() override { return TrackerSettings::Instance().glGeneralWindowHeight; }
private:
// Determine how the global volume slider should be scaled to actual global volume.
// Display range for XM / S3M should be 0...64, for other formats it's 0...256.
uint32 GetGlobalVolumeFactor() const
{
return (m_sndFile.GetType() & (MOD_TYPE_XM | MOD_TYPE_S3M)) ? uint32(MAX_SLIDER_GLOBAL_VOL / 64) : uint32(MAX_SLIDER_GLOBAL_VOL / 128);
}
public:
CEdit m_EditTitle, m_EditArtist;
CEdit m_EditSpeed, m_EditGlobalVol, m_EditRestartPos,
m_EditSamplePA, m_EditVSTiVol;
CNumberEdit m_EditTempo;
CButton m_BtnModType;
CSpinButtonCtrl m_SpinTempo, m_SpinSpeed, m_SpinGlobalVol, m_SpinRestartPos,
m_SpinSamplePA, m_SpinVSTiVol;
CComboBox m_CbnResampling;
CSliderCtrl m_SliderTempo, m_SliderSamplePreAmp, m_SliderGlobalVol, m_SliderVSTiVol;
CVuMeter m_VuMeterLeft, m_VuMeterRight;
std::unique_ptr<Util::MultimediaClock> m_tapTimer;
bool m_editsLocked = false;
TEMPO m_tempoMin, m_tempoMax;
//{{AFX_VIRTUAL(CCtrlGeneral)
BOOL OnInitDialog() override;
void DoDataExchange(CDataExchange *pDX) override; // DDX/DDV support
void RecalcLayout() override;
void UpdateView(UpdateHint hint, CObject *pObj = nullptr) override;
CRuntimeClass *GetAssociatedViewClass() override;
void OnActivatePage(LPARAM) override;
void OnDeactivatePage() override;
BOOL GetToolTipText(UINT uId, LPTSTR pszText) override;
//}}AFX_VIRTUAL
protected:
static constexpr int MAX_SLIDER_GLOBAL_VOL = 256;
static constexpr int MAX_SLIDER_VSTI_VOL = 255;
static constexpr int MAX_SLIDER_SAMPLE_VOL = 255;
// At this point, the tempo slider moves in more coarse steps to provide detailed values in the regions where it matters
static constexpr auto TEMPO_SPLIT_THRESHOLD = TEMPO(256, 0);
static constexpr int TEMPO_SPLIT_PRECISION = 3;
TEMPO TempoSliderRange() const;
TEMPO SliderToTempo(int value) const;
int TempoToSlider(TEMPO tempo) const;
//{{AFX_MSG(CCtrlGeneral)
afx_msg LRESULT OnUpdatePosition(WPARAM, LPARAM);
afx_msg void OnVScroll(UINT, UINT, CScrollBar *);
afx_msg void OnTapTempo();
afx_msg void OnTitleChanged();
afx_msg void OnArtistChanged();
afx_msg void OnTempoChanged();
afx_msg void OnSpeedChanged();
afx_msg void OnGlobalVolChanged();
afx_msg void OnVSTiVolChanged();
afx_msg void OnSamplePAChanged();
afx_msg void OnRestartPosChanged();
afx_msg void OnRestartPosDone();
afx_msg void OnSongProperties();
afx_msg void OnLoopSongChanged();
afx_msg void OnEnSetfocusEditSongtitle();
afx_msg void OnResamplingChanged();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
OPENMPT_NAMESPACE_END
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,204 @@
/*
* Ctrl_ins.h
* ----------
* Purpose: Instrument tab, upper panel.
* Notes : (currently none)
* Authors: Olivier Lapicque
* OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "CDecimalSupport.h"
OPENMPT_NAMESPACE_BEGIN
class CNoteMapWnd;
class CCtrlInstruments;
class CNoteMapWnd: public CStatic
{
protected:
CModDoc &m_modDoc;
CCtrlInstruments &m_pParent;
UINT m_nNote = (NOTE_MIDDLEC - NOTE_MIN), m_nOldNote = 0, m_nOldIns = 0;
INSTRUMENTINDEX m_nInstrument = 0;
int m_cxFont = 0, m_cyFont = 0;
CHANNELINDEX m_noteChannel = 0;
ModCommand::NOTE m_nPlayingNote = NOTE_NONE;
bool m_bIns = false;
bool m_undo = true;
private:
void MapTranspose(int nAmount);
void PrepareUndo(const char *description);
public:
CNoteMapWnd(CCtrlInstruments &parent, CModDoc &document)
: m_modDoc(document)
, m_pParent(parent)
{
EnableActiveAccessibility();
}
void SetCurrentInstrument(INSTRUMENTINDEX nIns);
void SetCurrentNote(UINT nNote);
void EnterNote(UINT note);
bool HandleChar(WPARAM c);
bool HandleNav(WPARAM k);
void PlayNote(UINT note);
void StopNote();
void UpdateAccessibleTitle();
public:
//{{AFX_VIRTUAL(CNoteMapWnd)
BOOL PreTranslateMessage(MSG* pMsg) override;
HRESULT get_accName(VARIANT varChild, BSTR *pszName) override;
//}}AFX_VIRTUAL
protected:
//{{AFX_MSG(CNoteMapWnd)
afx_msg void OnLButtonDown(UINT, CPoint);
afx_msg void OnMButtonDown(UINT flags, CPoint pt) { OnLButtonDown(flags, pt); }
afx_msg void OnRButtonDown(UINT, CPoint);
afx_msg void OnLButtonDblClk(UINT, CPoint);
afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
afx_msg void OnSetFocus(CWnd *pOldWnd);
afx_msg void OnKillFocus(CWnd *pNewWnd);
afx_msg BOOL OnEraseBkGnd(CDC *) { return TRUE; }
afx_msg void OnPaint();
afx_msg void OnMapCopySample();
afx_msg void OnMapCopyNote();
afx_msg void OnMapTransposeUp();
afx_msg void OnMapTransposeDown();
afx_msg void OnMapReset();
afx_msg void OnTransposeSamples();
afx_msg void OnMapRemove();
afx_msg void OnEditSample(UINT nID);
afx_msg void OnEditSampleMap();
afx_msg void OnInstrumentDuplicate();
afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM); //rewbs.customKeys
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
class CCtrlInstruments: public CModControlDlg
{
protected:
CModControlBar m_ToolBar;
CSpinButtonCtrl m_SpinInstrument, m_SpinFadeOut, m_SpinGlobalVol, m_SpinPanning;
CSpinButtonCtrl m_SpinMidiPR, m_SpinPPS, m_SpinMidiBK, m_SpinPWD;
CComboBox m_ComboNNA, m_ComboDCT, m_ComboDCA, m_ComboPPC, m_CbnMidiCh, m_CbnMixPlug, m_CbnResampling, m_CbnFilterMode, m_CbnPluginVolumeHandling;
CEdit m_EditName, m_EditFileName, m_EditGlobalVol, m_EditPanning, m_EditFadeOut;
CNumberEdit m_EditPPS, m_EditPWD;
CButton m_CheckPanning, m_CheckCutOff, m_CheckResonance, velocityStyle;
CSliderCtrl m_SliderVolSwing, m_SliderPanSwing, m_SliderCutSwing, m_SliderResSwing, m_SliderCutOff, m_SliderResonance;
CNoteMapWnd m_NoteMap;
CSliderCtrl m_SliderAttack;
CSpinButtonCtrl m_SpinAttack;
//Tuning
CComboBox m_ComboTuning;
// Pitch/Tempo lock
CNumberEdit m_EditPitchTempoLock;
CButton m_CheckPitchTempoLock;
INSTRUMENTINDEX m_nInstrument = 1;
bool m_openendPluginListWithMouse = false;
bool m_startedHScroll = false;
bool m_startedEdit = false;
void UpdateTuningComboBox();
void BuildTuningComboBox();
void UpdatePluginList();
public:
CCtrlInstruments(CModControlView &parent, CModDoc &document);
public:
void SetModified(InstrumentHint hint, bool updateAll);
BOOL SetCurrentInstrument(UINT nIns, BOOL bUpdNum=TRUE);
bool InsertInstrument(bool duplicate);
bool OpenInstrument(const mpt::PathString &fileName);
bool OpenInstrument(const CSoundFile &sndFile, INSTRUMENTINDEX nInstr);
void SaveInstrument(bool doBatchSave);
BOOL EditSample(UINT nSample);
void UpdateFilterText();
Setting<LONG> &GetSplitPosRef() override {return TrackerSettings::Instance().glInstrumentWindowHeight;}
public:
//{{AFX_VIRTUAL(CCtrlInstruments)
BOOL OnInitDialog() override;
void DoDataExchange(CDataExchange* pDX) override; // DDX/DDV support
CRuntimeClass *GetAssociatedViewClass() override;
void RecalcLayout() override;
void OnActivatePage(LPARAM) override;
void OnDeactivatePage() override;
void UpdateView(UpdateHint hint, CObject *pObj = nullptr) override;
LRESULT OnModCtrlMsg(WPARAM wParam, LPARAM lParam) override;
BOOL GetToolTipText(UINT uId, LPTSTR pszText) override;
BOOL PreTranslateMessage(MSG* pMsg) override;
//}}AFX_VIRTUAL
protected:
void PrepareUndo(const char *description);
//{{AFX_MSG(CCtrlInstruments)
afx_msg void OnEditFocus();
afx_msg void OnVScroll(UINT nCode, UINT nPos, CScrollBar *pSB);
afx_msg void OnHScroll(UINT nCode, UINT nPos, CScrollBar *pSB);
afx_msg void OnTbnDropDownToolBar(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnInstrumentChanged();
afx_msg void OnPrevInstrument();
afx_msg void OnNextInstrument();
afx_msg void OnInstrumentNew();
afx_msg void OnInstrumentDuplicate() { InsertInstrument(true); }
afx_msg void OnInstrumentOpen();
afx_msg void OnInstrumentSave();
afx_msg void OnInstrumentSaveOne() { SaveInstrument(false); }
afx_msg void OnInstrumentSaveAll() { SaveInstrument(true); }
afx_msg void OnInstrumentPlay();
afx_msg void OnNameChanged();
afx_msg void OnFileNameChanged();
afx_msg void OnFadeOutVolChanged();
afx_msg void OnGlobalVolChanged();
afx_msg void OnSetPanningChanged();
afx_msg void OnPanningChanged();
afx_msg void OnNNAChanged();
afx_msg void OnDCTChanged();
afx_msg void OnDCAChanged();
afx_msg void OnMPRChanged();
afx_msg void OnMPRKillFocus();
afx_msg void OnMBKChanged();
afx_msg void OnMCHChanged();
afx_msg void OnResamplingChanged();
afx_msg void OnMixPlugChanged();
afx_msg void OnPPSChanged();
afx_msg void OnPPCChanged();
afx_msg void OnFilterModeChanged();
afx_msg void OnPluginVelocityHandlingChanged();
afx_msg void OnPluginVolumeHandlingChanged();
afx_msg void OnPitchWheelDepthChanged();
afx_msg void OnOpenPluginList() { m_openendPluginListWithMouse = true; }
afx_msg void OnAttackChanged();
afx_msg void OnEnableCutOff();
afx_msg void OnEnableResonance();
afx_msg void OnEditSampleMap();
afx_msg void TogglePluginEditor();
afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM);
afx_msg void OnCbnSelchangeCombotuning();
afx_msg void OnEnChangeEditPitchTempoLock();
afx_msg void OnBnClickedCheckPitchtempolock();
afx_msg void OnEnKillFocusEditPitchTempoLock();
afx_msg void OnEnKillFocusEditFadeOut();
afx_msg void OnXButtonUp(UINT nFlags, UINT nButton, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
OPENMPT_NAMESPACE_END
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,289 @@
/*
* Ctrl_pat.h
* ----------
* Purpose: Pattern tab, upper panel.
* Notes : (currently none)
* Authors: Olivier Lapicque
* OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "Globals.h"
#include "PatternCursor.h"
OPENMPT_NAMESPACE_BEGIN
class COrderList;
class CCtrlPatterns;
struct OrdSelection
{
ORDERINDEX firstOrd = 0, lastOrd = 0;
ORDERINDEX GetSelCount() const { return lastOrd - firstOrd + 1; }
};
class COrderList: public CWnd
{
friend class CCtrlPatterns;
protected:
HFONT m_hFont = nullptr;
int m_cxFont = 0, m_cyFont = 0;
//m_nXScroll : The order at the beginning of shown orderlist
//m_nScrollPos: The same as order
//m_nScrollPos2nd: 2nd selection point if multiple orders are selected
// (not neccessarily the higher order - GetCurSel() is taking care of that.)
ORDERINDEX m_nXScroll = 0, m_nScrollPos = 0, m_nScrollPos2nd = ORDERINDEX_INVALID, m_nDropPos, m_nMouseDownPos, m_playPos = ORDERINDEX_INVALID;
ORDERINDEX m_nDragOrder;
//To tell how many orders('orderboxes') to show at least
//on both sides of current order(when updating orderslist position).
int m_nOrderlistMargins;
CModDoc &m_modDoc;
CCtrlPatterns &m_pParent;
bool m_bScrolling = false, m_bDragging = false;
public:
COrderList(CCtrlPatterns &parent, CModDoc &document);
public:
BOOL Init(const CRect&, HFONT hFont);
void UpdateView(UpdateHint hint, CObject *pObj = nullptr);
void InvalidateSelection();
PATTERNINDEX GetCurrentPattern() const;
// make the current selection the secondary selection (used for keyboard orderlist navigation)
inline void SetCurSelTo2ndSel(bool isSelectionKeyPressed)
{
if(isSelectionKeyPressed && m_nScrollPos2nd == ORDERINDEX_INVALID) m_nScrollPos2nd = m_nScrollPos;
else if(!isSelectionKeyPressed && m_nScrollPos2nd != ORDERINDEX_INVALID) m_nScrollPos2nd = ORDERINDEX_INVALID;
};
void SetSelection(ORDERINDEX firstOrd, ORDERINDEX lastOrd = ORDERINDEX_INVALID);
// Why VC wants to inline this huge function is beyond my understanding...
MPT_NOINLINE bool SetCurSel(ORDERINDEX sel, bool setPlayPos = true, bool shiftClick = false, bool ignoreCurSel = false);
void UpdateScrollInfo();
void UpdateInfoText();
int GetFontWidth();
void QueuePattern(CPoint pt);
// Check if this module is currently playing
bool IsPlaying() const;
ORDERINDEX GetOrderFromPoint(const CPoint &pt) const;
CRect GetRectFromOrder(ORDERINDEX ord) const;
// Get the currently selected pattern(s).
// Set ignoreSelection to true if only the first selected point is important.
OrdSelection GetCurSel(bool ignoreSelection = false) const;
// Sets target margin value and returns the effective margin value.
ORDERINDEX SetMargins(int);
// Returns the effective margin value.
ORDERINDEX GetMargins() { return GetMargins(GetMarginsMax()); }
// Returns the effective margin value.
ORDERINDEX GetMargins(const ORDERINDEX maxMargins) const { return std::min(maxMargins, static_cast<ORDERINDEX>(m_nOrderlistMargins)); }
// Returns maximum margin value given current window width.
ORDERINDEX GetMarginsMax() { return GetMarginsMax(GetLength()); }
// Returns maximum margin value when shown sequence has nLength orders.
// For example: If length is 4 orders -> maxMargins = 4/2 - 1 = 1;
// if maximum is 5 -> maxMargins = (int)5/2 = 2
ORDERINDEX GetMarginsMax(const ORDERINDEX length) const { return (length > 0 && length % 2 == 0) ? length / 2 - 1 : length / 2; }
// Returns the number of sequence items visible in the list.
ORDERINDEX GetLength();
// Return true if given order is in margins given that first shown order
// is 'startOrder'. Begin part of the whole sequence
// is not interpreted to be in margins regardless of the margin value.
bool IsOrderInMargins(int order, int startOrder);
// Ensure that a given order index is visible in the orderlist view.
void EnsureVisible(ORDERINDEX order);
// Set given sqeuence and update orderlist display.
void SelectSequence(const SEQUENCEINDEX nSeq);
// Helper function for entering pattern number
void EnterPatternNum(int enterNum);
void OnCopy(bool onlyOrders);
// Update play state and order lock ranges after inserting order items.
void InsertUpdatePlaystate(ORDERINDEX first, ORDERINDEX last);
// Update play state and order lock ranges after deleting order items.
void DeleteUpdatePlaystate(ORDERINDEX first, ORDERINDEX last);
//{{AFX_VIRTUAL(COrderList)
BOOL PreTranslateMessage(MSG *pMsg) override;
INT_PTR OnToolHitTest(CPoint point, TOOLINFO* pTI) const override;
HRESULT get_accName(VARIANT varChild, BSTR *pszName) override;
//}}AFX_VIRTUAL
protected:
ModSequence& Order();
const ModSequence& Order() const;
void SetScrollPos(int pos);
int GetScrollPos(bool getTrackPos = false);
// Resizes the order list if the specified order is past the order list length
bool EnsureEditable(ORDERINDEX ord);
//{{AFX_MSG(COrderList)
afx_msg void OnPaint();
afx_msg BOOL OnEraseBkgnd(CDC *) { return TRUE; }
afx_msg void OnSetFocus(CWnd *);
afx_msg void OnKillFocus(CWnd *);
afx_msg void OnLButtonDown(UINT, CPoint);
afx_msg void OnLButtonDblClk(UINT, CPoint);
afx_msg void OnRButtonDown(UINT, CPoint);
afx_msg void OnLButtonUp(UINT, CPoint);
afx_msg void OnMButtonDown(UINT, CPoint);
afx_msg void OnMouseMove(UINT, CPoint);
afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar*);
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnSwitchToView();
afx_msg void OnInsertOrder();
afx_msg void OnInsertSeparatorPattern();
afx_msg void OnDeleteOrder();
afx_msg void OnRenderOrder();
afx_msg void OnPatternProperties();
afx_msg void OnPlayerPlay();
afx_msg void OnPlayerPause();
afx_msg void OnPlayerPlayFromStart();
afx_msg void OnPatternPlayFromStart();
afx_msg void OnCreateNewPattern();
afx_msg void OnDuplicatePattern();
afx_msg void OnMergePatterns();
afx_msg void OnPatternCopy();
afx_msg void OnPatternPaste();
afx_msg void OnSetRestartPos();
afx_msg void OnEditCopy() { OnCopy(false); }
afx_msg void OnEditCopyOrders() { OnCopy(true); }
afx_msg void OnEditCut();
afx_msg LRESULT OnDragonDropping(WPARAM bDoDrop, LPARAM lParam);
afx_msg LRESULT OnHelpHitTest(WPARAM, LPARAM lParam);
afx_msg void OnSelectSequence(UINT nid);
afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM);
afx_msg void OnLockPlayback();
afx_msg void OnUnlockPlayback();
afx_msg BOOL OnToolTipText(UINT, NMHDR *pNMHDR, LRESULT *pResult);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
// CPatEdit: Edit control that switches back to the pattern view if Tab key is pressed.
class CPatEdit: public CEdit
{
protected:
CCtrlPatterns *m_pParent = nullptr;
public:
CPatEdit() = default;
void SetParent(CCtrlPatterns *parent) { m_pParent = parent; }
BOOL PreTranslateMessage(MSG *pMsg) override;
};
class CCtrlPatterns: public CModControlDlg
{
friend class COrderList;
protected:
COrderList m_OrderList;
CButton m_BtnPrev, m_BtnNext;
CComboBox m_CbnInstrument;
CPatEdit m_EditSpacing, m_EditPatName, m_EditSequence;
CSpinButtonCtrl m_SpinInstrument, m_SpinSpacing, m_SpinSequence;
CModControlBar m_ToolBar;
INSTRUMENTINDEX m_nInstrument = 0;
PatternCursor::Columns m_nDetailLevel = PatternCursor::lastColumn; // Visible Columns
bool m_bRecord = false, m_bVUMeters = false, m_bPluginNames = false;
public:
CCtrlPatterns(CModControlView &parent, CModDoc &document);
Setting<LONG> &GetSplitPosRef() override { return TrackerSettings::Instance().glPatternWindowHeight; }
public:
const ModSequence &Order() const;
ModSequence &Order();
void SetCurrentPattern(PATTERNINDEX nPat);
BOOL SetCurrentInstrument(UINT nIns);
BOOL GetFollowSong() { return IsDlgButtonChecked(IDC_PATTERN_FOLLOWSONG); }
BOOL GetLoopPattern() {return IsDlgButtonChecked(IDC_PATTERN_LOOP);}
COrderList &GetOrderList() { return m_OrderList; }
//{{AFX_VIRTUAL(CCtrlPatterns)
BOOL OnInitDialog() override;
void DoDataExchange(CDataExchange* pDX) override; // DDX/DDV support
void RecalcLayout() override;
void UpdateView(UpdateHint hint = UpdateHint(), CObject *pObj = nullptr) override;
CRuntimeClass *GetAssociatedViewClass() override;
LRESULT OnModCtrlMsg(WPARAM wParam, LPARAM lParam) override;
void OnActivatePage(LPARAM) override;
void OnDeactivatePage() override;
BOOL GetToolTipText(UINT, LPTSTR) override;
//}}AFX_VIRTUAL
protected:
//{{AFX_MSG(CCtrlPatterns)
afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
afx_msg void OnSequenceNext();
afx_msg void OnSequencePrev();
afx_msg void OnChannelManager();
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg void OnPlayerPause();
afx_msg void OnPatternNew();
afx_msg void OnPatternDuplicate();
afx_msg void OnPatternMerge();
afx_msg void OnPatternStop();
afx_msg void OnPatternPlay();
afx_msg void OnPatternPlayNoLoop();
afx_msg void OnPatternPlayRow();
afx_msg void OnPatternPlayFromStart();
afx_msg void OnPatternRecord();
afx_msg void OnPatternVUMeters();
afx_msg void OnPatternViewPlugNames();
afx_msg void OnPatternProperties();
afx_msg void OnPatternExpand();
afx_msg void OnPatternShrink();
afx_msg void OnPatternAmplify();
afx_msg void OnPatternCopy();
afx_msg void OnPatternPaste();
afx_msg void OnFollowSong();
afx_msg void OnChangeLoopStatus();
afx_msg void OnSwitchToView();
afx_msg void OnInstrumentChanged();
afx_msg void OnPrevInstrument();
afx_msg void OnNextInstrument();
afx_msg void OnSpacingChanged();
afx_msg void OnPatternNameChanged();
afx_msg void OnSequenceNameChanged();
afx_msg void OnChordEditor();
afx_msg void OnDetailLo();
afx_msg void OnDetailMed();
afx_msg void OnDetailHi();
afx_msg void OnEditUndo();
afx_msg void OnUpdateRecord(CCmdUI *pCmdUI);
afx_msg void TogglePluginEditor();
afx_msg void OnToggleOverflowPaste();
afx_msg void OnSequenceNumChanged();
afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
bool HasValidPlug(INSTRUMENTINDEX instr) const;
public:
afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
afx_msg void OnXButtonUp(UINT nFlags, UINT nButton, CPoint point);
afx_msg BOOL OnToolTip(UINT id, NMHDR *pTTTStruct, LRESULT *pResult);
};
OPENMPT_NAMESPACE_END
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,170 @@
/*
* Ctrl_smp.h
* ----------
* Purpose: Sample tab, upper panel.
* Notes : (currently none)
* Authors: Olivier Lapicque
* OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "../soundlib/SampleIO.h"
#include "../tracklib/FadeLaws.h"
OPENMPT_NAMESPACE_BEGIN
enum OpenSampleTypes
{
OpenSampleKnown = (1<<0),
OpenSampleRaw = (1<<1),
};
MPT_DECLARE_ENUM(OpenSampleTypes)
class CCtrlSamples: public CModControlDlg
{
protected:
friend class DoPitchShiftTimeStretch;
struct SampleSelectionPoints
{
SmpLength nStart;
SmpLength nEnd;
bool selectionActive; // does sample selection exist or not?
};
CModControlBar m_ToolBar1, m_ToolBar2;
CEdit m_EditSample, m_EditName, m_EditFileName, m_EditFineTune;
CEdit m_EditLoopStart, m_EditLoopEnd, m_EditSustainStart, m_EditSustainEnd;
CEdit m_EditVibSweep, m_EditVibDepth, m_EditVibRate;
CEdit m_EditVolume, m_EditGlobalVol, m_EditPanning;
CSpinButtonCtrl m_SpinVolume, m_SpinGlobalVol, m_SpinPanning, m_SpinVibSweep, m_SpinVibDepth, m_SpinVibRate;
CSpinButtonCtrl m_SpinLoopStart, m_SpinLoopEnd, m_SpinSustainStart, m_SpinSustainEnd;
CSpinButtonCtrl m_SpinFineTune, m_SpinSample;
CSpinButtonCtrl m_SpinSequenceMs, m_SpinSeekWindowMs, m_SpinOverlap, m_SpinStretchAmount;
CComboBox m_ComboAutoVib, m_ComboLoopType, m_ComboSustainType, m_ComboZoom, m_CbnBaseNote;
CButton m_CheckPanning;
double m_dTimeStretchRatio = 100;
uint32 m_nSequenceMs = 0;
uint32 m_nSeekWindowMs = 0;
uint32 m_nOverlapMs = 0;
SAMPLEINDEX m_nSample = 1;
INSTRUMENTINDEX m_editInstrumentName = INSTRUMENTINDEX_INVALID;
bool m_rememberRawFormat = false;
bool m_startedEdit = false;
CComboBox m_ComboPitch, m_ComboQuality, m_ComboFFT;
void UpdateTimeStretchParameters();
void ReadTimeStretchParameters();
void ApplyAmplify(const double amp, const double fadeInStart, const double fadeOutEnd, const bool fadeIn, const bool fadeOut, const Fade::Law fadeLaw);
void ApplyResample(SAMPLEINDEX smp, uint32 newRate, ResamplingMode mode, bool ignoreSelection = false, bool updatePatternCommands = false);
SampleSelectionPoints GetSelectionPoints();
void SetSelectionPoints(SmpLength nStart, SmpLength nEnd);
void PropagateAutoVibratoChanges();
bool IsOPLInstrument() const;
INSTRUMENTINDEX GetParentInstrumentWithSameName() const;
public:
CCtrlSamples(CModControlView &parent, CModDoc &document);
~CCtrlSamples();
bool SetCurrentSample(SAMPLEINDEX nSmp, LONG lZoom = -1, bool bUpdNum = true);
bool InsertSample(bool duplicate, int8 *confirm = nullptr);
bool OpenSample(const mpt::PathString &fileName, FlagSet<OpenSampleTypes> types = OpenSampleKnown | OpenSampleRaw);
bool OpenSample(const CSoundFile &sndFile, SAMPLEINDEX nSample);
void OpenSamples(const std::vector<mpt::PathString> &files, FlagSet<OpenSampleTypes> types);
void SaveSample(bool doBatchSave);
void Normalize(bool allSamples);
void RemoveDCOffset(bool allSamples);
Setting<LONG> &GetSplitPosRef() override {return TrackerSettings::Instance().glSampleWindowHeight;}
public:
//{{AFX_VIRTUAL(CCtrlSamples)
BOOL OnInitDialog() override;
void DoDataExchange(CDataExchange* pDX) override; // DDX/DDV support
CRuntimeClass *GetAssociatedViewClass() override;
void RecalcLayout() override;
void OnActivatePage(LPARAM) override;
void OnDeactivatePage() override;
void UpdateView(UpdateHint hint, CObject *pObj = nullptr) override;
LRESULT OnModCtrlMsg(WPARAM wParam, LPARAM lParam) override;
BOOL GetToolTipText(UINT uId, LPTSTR pszText) override;
BOOL PreTranslateMessage(MSG* pMsg) override;
//}}AFX_VIRTUAL
protected:
//{{AFX_MSG(CCtrlSamples)
afx_msg void OnEditFocus();
afx_msg void OnSampleChanged();
afx_msg void OnZoomChanged();
afx_msg void OnPrevInstrument();
afx_msg void OnNextInstrument();
afx_msg void OnTbnDropDownToolBar(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnSampleNew();
afx_msg void OnSampleDuplicate() { InsertSample(true); }
afx_msg void OnSampleOpen();
afx_msg void OnSampleOpenKnown();
afx_msg void OnSampleOpenRaw();
afx_msg void OnSampleSave();
afx_msg void OnSampleSaveOne() { SaveSample(false); }
afx_msg void OnSampleSaveAll() { SaveSample(true); }
afx_msg void OnSamplePlay();
afx_msg void OnNormalize();
afx_msg void OnAmplify();
afx_msg void OnQuickFade();
afx_msg void OnRemoveDCOffset();
afx_msg void OnResample();
afx_msg void OnReverse();
afx_msg void OnSilence();
afx_msg void OnInvert();
afx_msg void OnSignUnSign();
afx_msg void OnAutotune();
afx_msg void OnNameChanged();
afx_msg void OnFileNameChanged();
afx_msg void OnVolumeChanged();
afx_msg void OnGlobalVolChanged();
afx_msg void OnSetPanningChanged();
afx_msg void OnPanningChanged();
afx_msg void OnFineTuneChanged();
afx_msg void OnFineTuneChangedDone();
afx_msg void OnBaseNoteChanged();
afx_msg void OnLoopTypeChanged();
afx_msg void OnLoopPointsChanged();
afx_msg void OnSustainTypeChanged();
afx_msg void OnSustainPointsChanged();
afx_msg void OnVibTypeChanged();
afx_msg void OnVibDepthChanged();
afx_msg void OnVibSweepChanged();
afx_msg void OnVibRateChanged();
afx_msg void OnXFade();
afx_msg void OnStereoSeparation();
afx_msg void OnKeepSampleOnDisk();
afx_msg void OnVScroll(UINT, UINT, CScrollBar *);
afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM);
afx_msg void OnXButtonUp(UINT nFlags, UINT nButton, CPoint point);
afx_msg void OnPitchShiftTimeStretch();
afx_msg void OnEnableStretchToSize();
afx_msg void OnEstimateSampleSize();
afx_msg void OnInitOPLInstrument();
MPT_NOINLINE void SetModified(SampleHint hint, bool updateAll, bool waveformModified);
void PrepareUndo(const char *description, sampleUndoTypes type = sundo_none, SmpLength start = 0, SmpLength end = 0);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,500 @@
/*
* DefaultVstEditor.cpp
* --------------------
* Purpose: Implementation of the default plugin editor that is used if a plugin does not provide an own editor GUI.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "DefaultVstEditor.h"
#include "../soundlib/Sndfile.h"
#include "../soundlib/plugins/PlugInterface.h"
OPENMPT_NAMESPACE_BEGIN
#ifndef NO_PLUGINS
// Window proportions
struct Measurements
{
enum
{
edSpacing = 5, // Spacing between elements
edLineHeight = 20, // Line of a single parameter line
edEditWidth = 45, // Width of the parameter edit box
edPerMilWidth = 30, // Width of the per mil label
edRightWidth = edEditWidth + edSpacing + edPerMilWidth, // Width of the right part of a parameter control set (edit box, param value)
edTotalHeight = 2 * edLineHeight + edSpacing, // Height of one set of controls
};
const int spacing;
const int lineHeight;
const int editWidth;
const int perMilWidth;
const int rightWidth;
const int totalHeight;
Measurements(HWND hWnd)
: spacing(Util::ScalePixels(edSpacing, hWnd))
, lineHeight(Util::ScalePixels(edLineHeight, hWnd))
, editWidth(Util::ScalePixels(edEditWidth, hWnd))
, perMilWidth(Util::ScalePixels(edPerMilWidth, hWnd))
, rightWidth(Util::ScalePixels(edRightWidth, hWnd))
, totalHeight(Util::ScalePixels(edTotalHeight, hWnd))
{ }
};
// Create a set of parameter controls
ParamControlSet::ParamControlSet(CWnd *parent, const CRect &rect, int setID, const Measurements &m)
{
// Offset of components on the right side
const int horizSplit = rect.left + rect.Width() - m.rightWidth;
// Parameter name
nameLabel.Create(_T(""), WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE, CRect(rect.left, rect.top, horizSplit - m.spacing, rect.top + m.lineHeight), parent);
nameLabel.SetFont(parent->GetFont());
// Parameter value as reported by the plugin
valueLabel.Create(_T(""), WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE, CRect(horizSplit, rect.top, rect.right, rect.top + m.lineHeight), parent);
valueLabel.SetFont(parent->GetFont());
// Parameter value slider
valueSlider.Create(WS_CHILD | WS_VISIBLE | WS_TABSTOP /* | TBS_NOTICKS | TBS_BOTH */ | TBS_AUTOTICKS, CRect(rect.left, rect.bottom - m.lineHeight, horizSplit - m.spacing, rect.bottom), parent, ID_PLUGINEDITOR_SLIDERS_BASE + setID);
valueSlider.SetFont(parent->GetFont());
valueSlider.SetRange(0, PARAM_RESOLUTION);
valueSlider.SetTicFreq(PARAM_RESOLUTION / 10);
// Parameter value edit box
valueEdit.CreateEx(WS_EX_CLIENTEDGE, _T("EDIT"), NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | ES_AUTOHSCROLL | ES_NUMBER, CRect(horizSplit, rect.bottom - m.lineHeight, horizSplit + m.editWidth, rect.bottom), parent, ID_PLUGINEDITOR_EDIT_BASE + setID);
valueEdit.SetFont(parent->GetFont());
// "Per mil" label
perMilLabel.Create(mpt::ToCString(mpt::Charset::UTF8, "\xE2\x80\xB0"), WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE, CRect(horizSplit + m.editWidth + m.spacing, rect.bottom - m.lineHeight, rect.right, rect.bottom), parent);
perMilLabel.SetFont(parent->GetFont());
}
ParamControlSet::~ParamControlSet()
{
nameLabel.DestroyWindow();
valueLabel.DestroyWindow();
valueSlider.DestroyWindow();
valueEdit.DestroyWindow();
perMilLabel.DestroyWindow();
}
// Enable a set of parameter controls
void ParamControlSet::EnableControls(bool enable)
{
const BOOL b = enable ? TRUE : FALSE;
nameLabel.EnableWindow(b);
valueLabel.EnableWindow(b);
valueSlider.EnableWindow(b);
valueEdit.EnableWindow(b);
perMilLabel.EnableWindow(b);
}
// Reset the content of a set of parameter controls
void ParamControlSet::ResetContent()
{
nameLabel.SetWindowText(_T(""));
valueLabel.SetWindowText(_T(""));
valueSlider.SetPos(0);
valueEdit.SetWindowText(_T(""));
}
void ParamControlSet::SetParamName(const CString &name)
{
nameLabel.SetWindowText(name);
}
void ParamControlSet::SetParamValue(int value, const CString &text)
{
valueSlider.SetPos(value);
if (&valueEdit != valueEdit.GetFocus())
{
// Don't update textbox when it has focus, else this will prevent user from changing the content.
CString paramValue;
paramValue.Format(_T("%0000d"), value);
valueEdit.SetWindowText(paramValue);
}
valueLabel.SetWindowText(text);
}
int ParamControlSet::GetParamValueFromSlider() const
{
return valueSlider.GetPos();
}
int ParamControlSet::GetParamValueFromEdit() const
{
TCHAR s[16];
valueEdit.GetWindowText(s, 16);
int val = _tstoi(s);
Limit(val, int(0), int(PARAM_RESOLUTION));
return val;
}
BEGIN_MESSAGE_MAP(CDefaultVstEditor, CAbstractVstEditor)
//{{AFX_MSG_MAP(CDefaultVstEditor)
ON_CONTROL_RANGE(EN_CHANGE, ID_PLUGINEDITOR_EDIT_BASE, ID_PLUGINEDITOR_EDIT_BASE + NUM_PLUGINEDITOR_PARAMETERS - 1, &CDefaultVstEditor::OnParamTextboxChanged)
//}}AFX_MSG_MAP
ON_WM_HSCROLL()
ON_WM_VSCROLL()
ON_WM_MOUSEWHEEL()
END_MESSAGE_MAP()
void CDefaultVstEditor::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CDefaultVstEditor)
DDX_Control(pDX, IDC_SCROLLBAR1, paramScroller);
//}}AFX_DATA_MAP
}
CDefaultVstEditor::CDefaultVstEditor(IMixPlugin &plugin) : CAbstractVstEditor(plugin)
{
m_nControlLock = 0;
paramOffset = 0;
controls.clear();
}
CDefaultVstEditor::~CDefaultVstEditor()
{
for(size_t i = 0; i < controls.size(); i++)
{
delete controls[i];
}
}
void CDefaultVstEditor::CreateControls()
{
// Already initialized.
if(!controls.empty())
{
return;
}
Measurements m(m_hWnd);
CRect window;
GetWindowRect(&window);
CRect rect;
GetClientRect(&rect);
int origHeight = rect.bottom;
// Get parameter scroll bar dimensions and move it to the right side of the window
CRect scrollRect;
paramScroller.GetClientRect(&scrollRect);
scrollRect.bottom = rect.bottom;
scrollRect.MoveToX(rect.right - scrollRect.right);
// Ignore this space in our calculation from now on.
rect.right -= scrollRect.Width();
controls.clear();
// Create a bit of border space
rect.DeflateRect(m.spacing, m.spacing);
rect.bottom = m.totalHeight;
for(int i = 0; i < NUM_PLUGINEDITOR_PARAMETERS; i++)
{
try
{
controls.push_back(new ParamControlSet(this, rect, i, m));
rect.OffsetRect(0, m.totalHeight + m.spacing);
} catch(mpt::out_of_memory e)
{
mpt::delete_out_of_memory(e);
}
}
// Calculate new ideal window height.
const int heightChange = (rect.bottom - m.totalHeight + m.spacing) - origHeight;
// Update parameter scroll bar height
scrollRect.bottom += heightChange;
paramScroller.MoveWindow(scrollRect, FALSE);
// Resize window height
window.bottom += heightChange;
MoveWindow(&window);
// Set scrollbar page size.
SCROLLINFO sbInfo;
paramScroller.GetScrollInfo(&sbInfo);
sbInfo.nPage = NUM_PLUGINEDITOR_PARAMETERS;
paramScroller.SetScrollInfo(&sbInfo);
UpdateControls(true);
}
void CDefaultVstEditor::UpdateControls(bool updateParamNames)
{
const PlugParamIndex numParams = m_VstPlugin.GetNumParameters();
const PlugParamIndex scrollMax = numParams - std::min(numParams, static_cast<PlugParamIndex>(NUM_PLUGINEDITOR_PARAMETERS));
LimitMax(paramOffset, scrollMax);
int curScrollMin, curScrollMax;
paramScroller.GetScrollRange(&curScrollMin, &curScrollMax);
if(static_cast<PlugParamIndex>(curScrollMax) != scrollMax)
{
// Number of parameters changed - update scrollbar limits
paramScroller.SetScrollRange(0, scrollMax);
paramScroller.SetScrollPos(paramOffset);
paramScroller.EnableWindow(scrollMax > 0 ? TRUE : FALSE);
updateParamNames = true;
}
m_VstPlugin.CacheParameterNames(paramOffset, std::min(paramOffset + NUM_PLUGINEDITOR_PARAMETERS, numParams));
for(PlugParamIndex i = 0; i < NUM_PLUGINEDITOR_PARAMETERS; i++)
{
const PlugParamIndex param = paramOffset + i;
if(param >= numParams)
{
// This param doesn't exist.
controls[i]->EnableControls(false);
controls[i]->ResetContent();
continue;
}
controls[i]->EnableControls();
if(updateParamNames)
{
// Update param name
controls[i]->SetParamName(m_VstPlugin.GetFormattedParamName(param));
}
UpdateParamDisplay(param);
}
}
void CDefaultVstEditor::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
CSliderCtrl* pScrolledSlider = reinterpret_cast<CSliderCtrl*>(pScrollBar);
// Check if any of the value sliders were affected.
for(size_t i = 0; i < controls.size(); i++)
{
if ((pScrolledSlider->GetDlgCtrlID() == controls[i]->GetSliderID()) && (nSBCode != SB_ENDSCROLL))
{
OnParamSliderChanged(controls[i]->GetSliderID());
break;
}
}
CAbstractVstEditor::OnHScroll(nSBCode, nPos, pScrollBar);
}
void CDefaultVstEditor::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
if (pScrollBar == &paramScroller)
{
// Get the minimum and maximum scrollbar positions.
int minpos;
int maxpos;
pScrollBar->GetScrollRange(&minpos, &maxpos);
//maxpos = pScrollBar->GetScrollLimit();
SCROLLINFO sbInfo;
paramScroller.GetScrollInfo(&sbInfo);
// Get the current position of scroll box.
int curpos = pScrollBar->GetScrollPos();
// Determine the new position of scroll box.
switch(nSBCode)
{
case SB_LEFT: // Scroll to far left.
curpos = minpos;
break;
case SB_RIGHT: // Scroll to far right.
curpos = maxpos;
break;
case SB_ENDSCROLL: // End scroll.
break;
case SB_LINELEFT: // Scroll left.
if(curpos > minpos)
curpos--;
break;
case SB_LINERIGHT: // Scroll right.
if(curpos < maxpos)
curpos++;
break;
case SB_PAGELEFT: // Scroll one page left.
if(curpos > minpos)
{
curpos = std::max(minpos, curpos - static_cast<int>(sbInfo.nPage));
}
break;
case SB_PAGERIGHT: // Scroll one page right.
if(curpos < maxpos)
{
curpos = std::min(maxpos, curpos + static_cast<int>(sbInfo.nPage));
}
break;
case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
curpos = nPos; // of the scroll box at the end of the drag operation.
break;
case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
curpos = nPos; // position that the scroll box has been dragged to.
break;
}
// Set the new position of the thumb (scroll box).
pScrollBar->SetScrollPos(curpos);
paramOffset = curpos;
UpdateControls(true);
}
CAbstractVstEditor::OnVScroll(nSBCode, nPos, pScrollBar);
}
BOOL CDefaultVstEditor::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
MPT_UNREFERENCED_PARAMETER(nFlags);
MPT_UNREFERENCED_PARAMETER(pt);
// Mouse wheel - scroll parameter list
int minpos, maxpos;
paramScroller.GetScrollRange(&minpos, &maxpos);
if(minpos != maxpos)
{
paramOffset -= mpt::signum(zDelta);
Limit(paramOffset, PlugParamIndex(minpos), PlugParamIndex(maxpos));
paramScroller.SetScrollPos(paramOffset);
UpdateControls(true);
}
return CAbstractVstEditor::OnMouseWheel(nFlags, zDelta, pt);
}
bool CDefaultVstEditor::OpenEditor(CWnd *parent)
{
Create(IDD_DEFAULTPLUGINEDITOR, parent);
CreateControls();
return CAbstractVstEditor::OpenEditor(parent);
}
// Called when a change occurs to the parameter textbox
// If the change is triggered by the user, we'll need to notify the plugin and update
// the other GUI controls
void CDefaultVstEditor::OnParamTextboxChanged(UINT id)
{
if (m_nControlLock)
{
// Lock will be set if the GUI change was triggered internally (in UpdateParamDisplays).
// We're only interested in handling changes triggered by the user.
return;
}
const PlugParamIndex param = paramOffset + id - ID_PLUGINEDITOR_EDIT_BASE;
// Extract value and update
SetParam(param, controls[param - paramOffset]->GetParamValueFromEdit());
}
// Called when a change occurs to the parameter slider
// If the change is triggered by the user, we'll need to notify the plugin and update
// the other GUI controls
void CDefaultVstEditor::OnParamSliderChanged(UINT id)
{
if (m_nControlLock)
{
// Lock will be set if the GUI change was triggered internally (in UpdateParamDisplays).
// We're only interested in handling changes triggered by the user.
return;
}
const PlugParamIndex param = paramOffset + id - ID_PLUGINEDITOR_SLIDERS_BASE;
// Extract value and update
SetParam(param, controls[param - paramOffset]->GetParamValueFromSlider());
}
// Update a given parameter to a given value and notify plugin
void CDefaultVstEditor::SetParam(PlugParamIndex param, int value)
{
if(param >= m_VstPlugin.GetNumParameters())
{
return;
}
m_VstPlugin.SetScaledUIParam(param, static_cast<PlugParamValue>(value) / static_cast<PlugParamValue>(PARAM_RESOLUTION));
// Update other GUI controls
UpdateParamDisplay(param);
// Act as if an automation message has been sent by the plugin (record param changes, set document modified, etc...)
m_VstPlugin.AutomateParameter(param);
}
//Update all GUI controls with the new param value
void CDefaultVstEditor::UpdateParamDisplay(PlugParamIndex param)
{
if(m_nControlLock || param < paramOffset || param >= paramOffset + NUM_PLUGINEDITOR_PARAMETERS)
{
//Just to make sure we're not here as a consequence of an internal GUI change, and avoid modifying a parameter that doesn't exist on the GUI.
return;
}
// Get the actual parameter value from the plugin
const int val = static_cast<int>(m_VstPlugin.GetScaledUIParam(param) * static_cast<float>(PARAM_RESOLUTION) + 0.5f);
// Update the GUI controls
// Set lock to indicate that the changes to the GUI are internal - no need to notify the plug and re-update GUI.
m_nControlLock++;
controls[param - paramOffset]->SetParamValue(val, m_VstPlugin.GetFormattedParamValue(param));
// Unset lock - done with internal GUI updates.
m_nControlLock--;
}
#endif // NO_PLUGINS
OPENMPT_NAMESPACE_END
@@ -0,0 +1,104 @@
/*
* DefaultVstEditor.h
* ------------------
* Purpose: Implementation of the default plugin editor that is used if a plugin does not provide an own editor GUI.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#ifndef NO_PLUGINS
#include "Mptrack.h"
#include "AbstractVstEditor.h"
OPENMPT_NAMESPACE_BEGIN
enum
{
PARAM_RESOLUTION = 1000,
NUM_PLUGINEDITOR_PARAMETERS = 8, // Parameters on screen
};
struct Measurements;
class ParamControlSet
{
protected:
CSliderCtrl valueSlider;
CEdit valueEdit;
CStatic nameLabel;
CStatic valueLabel;
CStatic perMilLabel;
public:
ParamControlSet(CWnd *parent, const CRect &rect, int setID, const Measurements &m);
~ParamControlSet();
void EnableControls(bool enable = true);
void ResetContent();
void SetParamName(const CString &name);
void SetParamValue(int value, const CString &text);
int GetParamValueFromSlider() const;
int GetParamValueFromEdit() const;
int GetSliderID() const { return valueSlider.GetDlgCtrlID(); };
};
class CDefaultVstEditor : public CAbstractVstEditor
{
protected:
std::vector<ParamControlSet *> controls;
CScrollBar paramScroller;
PlugParamIndex paramOffset;
int m_nControlLock;
public:
CDefaultVstEditor(IMixPlugin &plugin);
virtual ~CDefaultVstEditor();
virtual void UpdateParamDisplays() { CAbstractVstEditor::UpdateParamDisplays(); UpdateControls(false); };
virtual bool OpenEditor(CWnd *parent);
// Plugins may not request to change the GUI size, since we use our own GUI.
virtual bool IsResizable() const { return false; };
virtual bool SetSize(int, int) { return false; };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
DECLARE_MESSAGE_MAP()
afx_msg void OnParamTextboxChanged(UINT id);
afx_msg void OnParamSliderChanged(UINT id);
afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
protected:
void CreateControls();
void UpdateControls(bool updateParamNames);
void SetParam(PlugParamIndex param, int value);
void UpdateParamDisplay(PlugParamIndex param);
};
OPENMPT_NAMESPACE_END
#endif // NO_PLUGINS
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,67 @@
/*
* EffectInfo.h
* ------------
* Purpose: Provide information about effect names, parameter interpretation to the tracker interface.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "modcommand.h"
OPENMPT_NAMESPACE_BEGIN
class CSoundFile;
class EffectInfo
{
protected:
const CSoundFile &sndFile;
public:
EffectInfo(const CSoundFile &sf) : sndFile(sf) {};
// Effects Description
bool GetEffectName(CString &pszDescription, ModCommand::COMMAND command, UINT param, bool bXX = false) const; // bXX: Nxx: ...
// Get size of list of known effect commands
UINT GetNumEffects() const;
// Get range information, effect name, etc... from a given effect.
bool GetEffectInfo(UINT ndx, CString *s, bool bXX = false, ModCommand::PARAM *prangeMin = nullptr, ModCommand::PARAM *prangeMax = nullptr) const;
// Get effect index in effect list from effect command + param
LONG GetIndexFromEffect(ModCommand::COMMAND command, ModCommand::PARAM param) const;
// Get effect command + param from effect index
EffectCommand GetEffectFromIndex(UINT ndx, ModCommand::PARAM &refParam) const;
EffectCommand GetEffectFromIndex(UINT ndx) const;
// Get parameter mask from effect (for extended effects)
UINT GetEffectMaskFromIndex(UINT ndx) const;
// Get precise effect name, also with explanation of effect parameter
bool GetEffectNameEx(CString &pszName, const ModCommand &m, uint32 param, CHANNELINDEX chn) const;
// Check whether an effect is extended (with parameter nibbles)
bool IsExtendedEffect(UINT ndx) const;
// Map an effect value to slider position
UINT MapValueToPos(UINT ndx, UINT param) const;
// Map slider position to an effect value
UINT MapPosToValue(UINT ndx, UINT pos) const;
// Volume column effects description
// Get size of list of known volume commands
UINT GetNumVolCmds() const;
// Get effect index in effect list from volume command
LONG GetIndexFromVolCmd(ModCommand::VOLCMD volcmd) const;
// Get volume command from effect index
VolumeCommand GetVolCmdFromIndex(UINT ndx) const;
// Get range information, effect name, etc... from a given effect.
bool GetVolCmdInfo(UINT ndx, CString *s, ModCommand::VOL *prangeMin = nullptr, ModCommand::VOL *prangeMax = nullptr) const;
// Get effect name and parameter description
bool GetVolCmdParamInfo(const ModCommand &m, CString *s) const;
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,873 @@
/*
* EffectVis.cpp
* -------------
* Purpose: Implementation of parameter visualisation dialog.
* Notes : (currenlty none)
* Authors: 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 "Childfrm.h"
#include "Moddoc.h"
#include "Globals.h"
#include "View_pat.h"
#include "EffectVis.h"
OPENMPT_NAMESPACE_BEGIN
CEffectVis::EditAction CEffectVis::m_nAction = CEffectVis::kAction_OverwriteFX;
IMPLEMENT_DYNAMIC(CEffectVis, CDialog)
CEffectVis::CEffectVis(CViewPattern *pViewPattern, ROWINDEX startRow, ROWINDEX endRow, CHANNELINDEX nchn, CModDoc &modDoc, PATTERNINDEX pat)
: effectInfo(modDoc.GetSoundFile())
, m_ModDoc(modDoc)
, m_SndFile(modDoc.GetSoundFile())
, m_pViewPattern(pViewPattern)
{
m_nFillEffect = effectInfo.GetIndexFromEffect(CMD_SMOOTHMIDI, 0);
m_templatePCNote.Set(NOTE_PCS, 1, 0, 0);
UpdateSelection(startRow, endRow, nchn, pat);
}
BEGIN_MESSAGE_MAP(CEffectVis, CDialog)
ON_WM_ERASEBKGND()
ON_WM_PAINT()
ON_WM_SIZE()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MOUSEMOVE()
ON_WM_RBUTTONDOWN()
ON_WM_RBUTTONUP()
ON_CBN_SELCHANGE(IDC_VISACTION, &CEffectVis::OnActionChanged)
ON_CBN_SELCHANGE(IDC_VISEFFECTLIST, &CEffectVis::OnEffectChanged)
END_MESSAGE_MAP()
void CEffectVis::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_VISSTATUS, m_edVisStatus);
DDX_Control(pDX, IDC_VISEFFECTLIST, m_cmbEffectList);
DDX_Control(pDX, IDC_VISACTION, m_cmbActionList);
}
void CEffectVis::OnActionChanged()
{
m_nAction = static_cast<EditAction>(m_cmbActionList.GetItemData(m_cmbActionList.GetCurSel()));
if (m_nAction == kAction_FillPC
|| m_nAction == kAction_OverwritePC
|| m_nAction == kAction_Preserve)
m_cmbEffectList.EnableWindow(FALSE);
else
m_cmbEffectList.EnableWindow(TRUE);
}
void CEffectVis::OnEffectChanged()
{
m_nFillEffect = static_cast<UINT>(m_cmbEffectList.GetItemData(m_cmbEffectList.GetCurSel()));
}
void CEffectVis::OnPaint()
{
CPaintDC dc(this); // device context for painting
ShowVis(&dc);
}
uint16 CEffectVis::GetParam(ROWINDEX row) const
{
uint16 paramValue = 0;
if(m_SndFile.Patterns.IsValidPat(m_nPattern))
{
const ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
if (m.IsPcNote())
{
paramValue = m.GetValueEffectCol();
} else
{
paramValue = m.param;
}
}
return paramValue;
}
// Sets a row's param value based on the vertical cursor position.
// Sets either plain pattern effect parameter or PC note parameter
// as appropriate, depending on contents of row.
void CEffectVis::SetParamFromY(ROWINDEX row, int y)
{
if(!m_SndFile.Patterns.IsValidPat(m_nPattern))
return;
ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
if (IsPcNote(row))
{
uint16 param = ScreenYToPCParam(y);
m.SetValueEffectCol(param);
} else
{
ModCommand::PARAM param = ScreenYToFXParam(y);
// Cap the parameter value as appropriate, based on effect type (e.g. Zxx gets capped to [0x00,0x7F])
effectInfo.GetEffectFromIndex(effectInfo.GetIndexFromEffect(m.command, param), param);
m.param = param;
}
}
EffectCommand CEffectVis::GetCommand(ROWINDEX row) const
{
if(m_SndFile.Patterns.IsValidPat(m_nPattern))
return static_cast<EffectCommand>(m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan)->command);
else
return CMD_NONE;
}
void CEffectVis::SetCommand(ROWINDEX row, EffectCommand command)
{
if(m_SndFile.Patterns.IsValidPat(m_nPattern))
{
ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
if(m.IsPcNote())
{
// Clear PC note
m.note = 0;
m.instr = 0;
m.volcmd = VOLCMD_NONE;
m.vol = 0;
}
m.command = command;
}
}
int CEffectVis::RowToScreenX(ROWINDEX row) const
{
if ((row >= m_startRow) || (row <= m_endRow))
return mpt::saturate_round<int>(m_rcDraw.left + m_innerBorder + (row - m_startRow) * m_pixelsPerRow);
return -1;
}
int CEffectVis::RowToScreenY(ROWINDEX row) const
{
int screenY = -1;
if(m_SndFile.Patterns.IsValidPat(m_nPattern))
{
const ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
if (m.IsPcNote())
{
uint16 paramValue = m.GetValueEffectCol();
screenY = PCParamToScreenY(paramValue);
} else
{
uint16 paramValue = m.param;
screenY = FXParamToScreenY(paramValue);
}
}
return screenY;
}
int CEffectVis::FXParamToScreenY(uint16 param) const
{
if(param >= 0x00 && param <= 0xFF)
return mpt::saturate_round<int>(m_rcDraw.bottom - param * m_pixelsPerFXParam);
return -1;
}
int CEffectVis::PCParamToScreenY(uint16 param) const
{
if(param >= 0x00 && param <= ModCommand::maxColumnValue)
return mpt::saturate_round<int>(m_rcDraw.bottom - param*m_pixelsPerPCParam);
return -1;
}
ModCommand::PARAM CEffectVis::ScreenYToFXParam(int y) const
{
if(y <= FXParamToScreenY(0xFF))
return 0xFF;
if(y >= FXParamToScreenY(0x00))
return 0x00;
return mpt::saturate_round<ModCommand::PARAM>((m_rcDraw.bottom - y) / m_pixelsPerFXParam);
}
uint16 CEffectVis::ScreenYToPCParam(int y) const
{
if(y <= PCParamToScreenY(ModCommand::maxColumnValue))
return ModCommand::maxColumnValue;
if(y >= PCParamToScreenY(0x00))
return 0x00;
return mpt::saturate_round<uint16>((m_rcDraw.bottom - y) / m_pixelsPerPCParam);
}
ROWINDEX CEffectVis::ScreenXToRow(int x) const
{
if(x <= RowToScreenX(m_startRow))
return m_startRow;
if(x >= RowToScreenX(m_endRow))
return m_endRow;
return mpt::saturate_round<ROWINDEX>(m_startRow + (x - m_innerBorder) / m_pixelsPerRow);
}
void CEffectVis::DrawGrid()
{
// Lots of room for optimisation here.
// Draw vertical grid lines
ROWINDEX nBeat = m_SndFile.m_nDefaultRowsPerBeat, nMeasure = m_SndFile.m_nDefaultRowsPerMeasure;
if(m_SndFile.Patterns[m_nPattern].GetOverrideSignature())
{
nBeat = m_SndFile.Patterns[m_nPattern].GetRowsPerBeat();
nMeasure = m_SndFile.Patterns[m_nPattern].GetRowsPerMeasure();
}
m_dcGrid.FillSolidRect(&m_rcDraw, 0);
auto oldPen = m_dcGrid.SelectStockObject(DC_PEN);
for(ROWINDEX row = m_startRow; row <= m_endRow; row++)
{
if(row % nMeasure == 0)
m_dcGrid.SetDCPenColor(RGB(0xFF, 0xFF, 0xFF));
else if(row % nBeat == 0)
m_dcGrid.SetDCPenColor(RGB(0x99, 0x99, 0x99));
else
m_dcGrid.SetDCPenColor(RGB(0x55, 0x55, 0x55));
int x1 = RowToScreenX(row);
m_dcGrid.MoveTo(x1, m_rcDraw.top);
m_dcGrid.LineTo(x1, m_rcDraw.bottom);
}
// Draw horizontal grid lines
constexpr UINT numHorizontalLines = 4;
for(UINT i = 0; i < numHorizontalLines; i++)
{
COLORREF c = 0;
switch(i % 4)
{
case 0: c = RGB(0x00, 0x00, 0x00); break;
case 1: c = RGB(0x40, 0x40, 0x40); break;
case 2: c = RGB(0x80, 0x80, 0x80); break;
case 3: c = RGB(0xCC, 0xCC, 0xCC); break;
}
m_dcGrid.SetDCPenColor(c);
int y1 = m_rcDraw.bottom / numHorizontalLines * i;
m_dcGrid.MoveTo(m_rcDraw.left + m_innerBorder, y1);
m_dcGrid.LineTo(m_rcDraw.right - m_innerBorder, y1);
}
m_dcGrid.SelectObject(oldPen);
}
void CEffectVis::SetPlayCursor(PATTERNINDEX nPat, ROWINDEX nRow)
{
if(nPat == m_nPattern && nRow == m_nOldPlayPos)
return;
if(m_nOldPlayPos >= m_startRow && m_nOldPlayPos <= m_endRow)
{
// erase current playpos
int x1 = RowToScreenX(m_nOldPlayPos);
m_dcPlayPos.SelectStockObject(BLACK_PEN);
m_dcPlayPos.MoveTo(x1,m_rcDraw.top);
m_dcPlayPos.LineTo(x1,m_rcDraw.bottom);
}
if((nRow < m_startRow) || (nRow > m_endRow) || (nPat != m_nPattern))
return;
int x1 = RowToScreenX(nRow);
m_dcPlayPos.SelectStockObject(DC_PEN);
m_dcPlayPos.SetDCPenColor(TrackerSettings::Instance().rgbCustomColors[MODCOLOR_SAMPLE]);
m_dcPlayPos.MoveTo(x1,m_rcDraw.top);
m_dcPlayPos.LineTo(x1,m_rcDraw.bottom);
m_nOldPlayPos = nRow;
InvalidateRect(NULL, FALSE);
}
void CEffectVis::ShowVis(CDC *pDC)
{
if (m_forceRedraw)
{
m_forceRedraw = false;
// if we already have a memory dc, destroy it (this occurs for a re-size)
if (m_dcGrid.m_hDC)
{
m_dcGrid.SelectObject(m_pbOldGrid);
m_dcGrid.DeleteDC();
m_dcNodes.SelectObject(m_pbOldNodes);
m_dcNodes.DeleteDC();
m_dcPlayPos.SelectObject(m_pbOldPlayPos);
m_dcPlayPos.DeleteDC();
m_bPlayPos.DeleteObject();
m_bGrid.DeleteObject();
m_bNodes.DeleteObject();
}
// create a memory based dc for drawing the grid
m_dcGrid.CreateCompatibleDC(pDC);
m_bGrid.CreateCompatibleBitmap(pDC, m_rcDraw.Width(), m_rcDraw.Height());
m_pbOldGrid = *m_dcGrid.SelectObject(&m_bGrid);
// create a memory based dc for drawing the nodes
m_dcNodes.CreateCompatibleDC(pDC);
m_bNodes.CreateCompatibleBitmap(pDC, m_rcDraw.Width(), m_rcDraw.Height());
m_pbOldNodes = *m_dcNodes.SelectObject(&m_bNodes);
// create a memory based dc for drawing the nodes
m_dcPlayPos.CreateCompatibleDC(pDC);
m_bPlayPos.CreateCompatibleBitmap(pDC, m_rcDraw.Width(), m_rcDraw.Height());
m_pbOldPlayPos = *m_dcPlayPos.SelectObject(&m_bPlayPos);
SetPlayCursor(m_nPattern, m_nOldPlayPos);
DrawGrid();
DrawNodes();
}
// display the new image, combining the nodes with the grid
ShowVisImage(pDC);
}
void CEffectVis::ShowVisImage(CDC *pDC)
{
// to avoid flicker, establish a memory dc, draw to it
// and then BitBlt it to the destination "pDC"
CDC memDC;
memDC.CreateCompatibleDC(pDC);
if (!memDC)
return;
CBitmap memBitmap;
memBitmap.CreateCompatibleBitmap(pDC, m_rcDraw.Width(), m_rcDraw.Height());
CBitmap *oldBitmap = memDC.SelectObject(&memBitmap);
// make sure we have the bitmaps
if (!m_dcGrid.m_hDC)
return;
if (!m_dcNodes.m_hDC)
return;
if (!m_dcPlayPos.m_hDC)
return;
if (memDC.m_hDC != nullptr)
{
// draw the grid
memDC.BitBlt(0, 0, m_rcDraw.Width(), m_rcDraw.Height(), &m_dcGrid, 0, 0, SRCCOPY);
// merge the nodes image with the grid
memDC.TransparentBlt(0, 0, m_rcDraw.Width(), m_rcDraw.Height(), &m_dcNodes, 0, 0, m_rcDraw.Width(), m_rcDraw.Height(), 0x00000000);
// further merge the playpos
memDC.TransparentBlt(0, 0, m_rcDraw.Width(), m_rcDraw.Height(), &m_dcPlayPos, 0, 0, m_rcDraw.Width(), m_rcDraw.Height(), 0x00000000);
// copy the resulting bitmap to the destination
pDC->BitBlt(0, 0, m_rcDraw.Width(), m_rcDraw.Height(), &memDC, 0, 0, SRCCOPY);
}
memDC.SelectObject(oldBitmap);
}
void CEffectVis::DrawNodes()
{
if(m_rcDraw.IsRectEmpty())
return;
//Draw
const int lineWidth = Util::ScalePixels(1, m_hWnd);
const int nodeSizeHalf = m_nodeSizeHalf;
const int nodeSizeHalf2 = nodeSizeHalf - lineWidth + 1;
const int nodeSize = 2 * nodeSizeHalf + 1;
//erase
if ((ROWINDEX)m_nRowToErase < m_startRow || m_nParamToErase < 0)
{
m_dcNodes.FillSolidRect(&m_rcDraw, 0);
} else
{
int x = RowToScreenX(m_nRowToErase);
CRect r(x - nodeSizeHalf, m_rcDraw.top, x + nodeSizeHalf + 1, m_rcDraw.bottom);
m_dcNodes.FillSolidRect(&r, 0);
}
for (ROWINDEX row = m_startRow; row <= m_endRow; row++)
{
COLORREF col = IsPcNote(row) ? RGB(0xFF, 0xFF, 0x00) : RGB(0xD0, 0xFF, 0xFF);
int x = RowToScreenX(row);
int y = RowToScreenY(row);
m_dcNodes.FillSolidRect(x - nodeSizeHalf, y - nodeSizeHalf, nodeSize, lineWidth, col); // Top
m_dcNodes.FillSolidRect(x + nodeSizeHalf2, y - nodeSizeHalf, lineWidth, nodeSize, col); // Right
m_dcNodes.FillSolidRect(x - nodeSizeHalf, y + nodeSizeHalf2, nodeSize, lineWidth, col); // Bottom
m_dcNodes.FillSolidRect(x - nodeSizeHalf, y - nodeSizeHalf, lineWidth, nodeSize, col); // Left
}
}
void CEffectVis::InvalidateRow(int row)
{
if (((UINT)row < m_startRow) || ((UINT)row > m_endRow)) return;
//It seems this optimisation doesn't work properly yet. Disable in Update()
int x = RowToScreenX(row);
invalidated.bottom = m_rcDraw.bottom;
invalidated.top = m_rcDraw.top;
invalidated.left = x - m_nodeSizeHalf;
invalidated.right = x + m_nodeSizeHalf + 1;
InvalidateRect(&invalidated, FALSE);
}
void CEffectVis::OpenEditor(CWnd *parent)
{
Create(IDD_EFFECTVISUALIZER, parent);
m_forceRedraw = true;
if(TrackerSettings::Instance().effectVisWidth > 0 && TrackerSettings::Instance().effectVisHeight > 0)
{
WINDOWPLACEMENT wnd;
wnd.length = sizeof(wnd);
GetWindowPlacement(&wnd);
wnd.showCmd = SW_SHOWNOACTIVATE;
CRect rect = wnd.rcNormalPosition;
if(TrackerSettings::Instance().effectVisX > int32_min && TrackerSettings::Instance().effectVisY > int32_min)
{
CRect mainRect;
CMainFrame::GetMainFrame()->GetWindowRect(mainRect);
rect.left = mainRect.left + MulDiv(TrackerSettings::Instance().effectVisX, Util::GetDPIx(m_hWnd), 96);
rect.top = mainRect.top + MulDiv(TrackerSettings::Instance().effectVisY, Util::GetDPIx(m_hWnd), 96);
}
rect.right = rect.left + MulDiv(TrackerSettings::Instance().effectVisWidth, Util::GetDPIx(m_hWnd), 96);
rect.bottom = rect.top + MulDiv(TrackerSettings::Instance().effectVisHeight, Util::GetDPIx(m_hWnd), 96);
wnd.rcNormalPosition = rect;
SetWindowPlacement(&wnd);
}
ShowWindow(SW_SHOW);
}
void CEffectVis::OnClose()
{
DoClose();
}
void CEffectVis::OnOK()
{
OnClose();
}
void CEffectVis::OnCancel()
{
OnClose();
}
void CEffectVis::DoClose()
{
WINDOWPLACEMENT wnd;
wnd.length = sizeof(wnd);
GetWindowPlacement(&wnd);
CRect mainRect;
CMainFrame::GetMainFrame()->GetWindowRect(mainRect);
CRect rect = wnd.rcNormalPosition;
rect.MoveToXY(rect.left - mainRect.left, rect.top - mainRect.top);
TrackerSettings::Instance().effectVisWidth = MulDiv(rect.Width(), 96, Util::GetDPIx(m_hWnd));
TrackerSettings::Instance().effectVisHeight = MulDiv(rect.Height(), 96, Util::GetDPIy(m_hWnd));
TrackerSettings::Instance().effectVisX = MulDiv(rect.left, 96, Util::GetDPIx(m_hWnd));
TrackerSettings::Instance().effectVisY = MulDiv(rect.top, 96, Util::GetDPIy(m_hWnd));
m_dcGrid.SelectObject(m_pbOldGrid);
m_dcGrid.DeleteDC();
m_dcNodes.SelectObject(m_pbOldNodes);
m_dcNodes.DeleteDC();
m_dcPlayPos.SelectObject(m_pbOldPlayPos);
m_dcPlayPos.DeleteDC();
m_bGrid.DeleteObject();
m_bNodes.DeleteObject();
m_bPlayPos.DeleteObject();
DestroyWindow();
}
void CEffectVis::PostNcDestroy()
{
m_pViewPattern->m_pEffectVis = nullptr;
}
void CEffectVis::OnSize(UINT nType, int cx, int cy)
{
MPT_UNREFERENCED_PARAMETER(nType);
MPT_UNREFERENCED_PARAMETER(cx);
MPT_UNREFERENCED_PARAMETER(cy);
GetClientRect(&m_rcFullWin);
m_rcDraw.SetRect(m_rcFullWin.left, m_rcFullWin.top, m_rcFullWin.right, m_rcFullWin.bottom - m_marginBottom);
const int actionListWidth = Util::ScalePixels(170, m_hWnd);
const int commandListWidth = Util::ScalePixels(160, m_hWnd);
if (IsWindow(m_edVisStatus.m_hWnd))
m_edVisStatus.SetWindowPos(this, m_rcFullWin.left, m_rcDraw.bottom, m_rcFullWin.right-commandListWidth-actionListWidth, m_rcFullWin.bottom-m_rcDraw.bottom, SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_SHOWWINDOW|SWP_NOZORDER);
if (IsWindow(m_cmbActionList))
m_cmbActionList.SetWindowPos(this, m_rcFullWin.right-commandListWidth-actionListWidth, m_rcDraw.bottom, actionListWidth, m_rcFullWin.bottom-m_rcDraw.bottom, SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_SHOWWINDOW|SWP_NOZORDER);
if (IsWindow(m_cmbEffectList))
m_cmbEffectList.SetWindowPos(this, m_rcFullWin.right-commandListWidth, m_rcDraw.bottom, commandListWidth, m_rcFullWin.bottom-m_rcDraw.bottom, SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_SHOWWINDOW|SWP_NOZORDER);
if(m_nRows)
m_pixelsPerRow = (float)(m_rcDraw.Width() - m_innerBorder * 2) / (float)m_nRows;
else
m_pixelsPerRow = 1;
m_pixelsPerFXParam = (float)(m_rcDraw.Height())/(float)0xFF;
m_pixelsPerPCParam = (float)(m_rcDraw.Height())/(float)ModCommand::maxColumnValue;
m_forceRedraw = true;
InvalidateRect(NULL, FALSE); //redraw everything
}
void CEffectVis::Update()
{
DrawNodes();
if (::IsWindow(m_hWnd))
{
OnPaint();
if (m_nRowToErase<0)
InvalidateRect(NULL, FALSE); // redraw everything
else
{
InvalidateRow(m_nRowToErase);
m_nParamToErase=-1;
m_nRowToErase=-1;
}
}
}
void CEffectVis::UpdateSelection(ROWINDEX startRow, ROWINDEX endRow, CHANNELINDEX nchn, PATTERNINDEX pat)
{
m_startRow = startRow;
m_endRow = endRow;
m_nRows = endRow - startRow;
m_nChan = nchn;
m_nPattern = pat;
//Check pattern, start row and channel exist
if(!m_SndFile.Patterns.IsValidPat(m_nPattern) || !m_SndFile.Patterns[m_nPattern].IsValidRow(m_startRow) || m_nChan >= m_SndFile.GetNumChannels())
{
DoClose();
return;
}
//Check end exists
if(!m_SndFile.Patterns[m_nPattern].IsValidRow(m_endRow))
{
m_endRow = m_SndFile.Patterns[m_nPattern].GetNumRows() - 1;
}
if(m_nRows)
m_pixelsPerRow = (float)(m_rcDraw.Width() - m_innerBorder * 2) / (float)m_nRows;
else
m_pixelsPerRow = 1;
m_pixelsPerFXParam = (float)(m_rcDraw.Height())/(float)0xFF;
m_pixelsPerPCParam = (float)(m_rcDraw.Height())/(float)ModCommand::maxColumnValue;
m_forceRedraw = true;
Update();
}
void CEffectVis::OnRButtonDown(UINT nFlags, CPoint point)
{
if (!(m_dwStatus & FXVSTATUS_LDRAGGING))
{
SetFocus();
SetCapture();
m_nDragItem = ScreenXToRow(point.x);
m_dwStatus |= FXVSTATUS_RDRAGGING;
m_ModDoc.GetPatternUndo().PrepareUndo(static_cast<PATTERNINDEX>(m_nPattern), m_nChan, m_nDragItem, 1, 1, "Parameter Editor entry");
OnMouseMove(nFlags, point);
}
CDialog::OnRButtonDown(nFlags, point);
}
void CEffectVis::OnRButtonUp(UINT nFlags, CPoint point)
{
ReleaseCapture();
m_dwStatus = 0x00;
m_nDragItem = -1;
CDialog::OnRButtonUp(nFlags, point);
}
void CEffectVis::OnMouseMove(UINT nFlags, CPoint point)
{
CDialog::OnMouseMove(nFlags, point);
ROWINDEX row = ScreenXToRow(point.x);
if ((m_dwStatus & FXVSTATUS_RDRAGGING) && (m_nDragItem>=0) )
{
m_nRowToErase = m_nDragItem;
m_nParamToErase = GetParam(m_nDragItem);
MakeChange(m_nDragItem, point.y);
} else if ((m_dwStatus & FXVSTATUS_LDRAGGING))
{
// Interpolate if we detect that rows have been skipped but the left mouse button was not released.
// This ensures we produce a smooth curve even when we are not notified of mouse movements at a high frequency (e.g. if CPU usage is high)
const int steps = std::abs((int)row - (int)m_nLastDrawnRow);
if (m_nLastDrawnRow != ROWINDEX_INVALID && m_nLastDrawnRow > m_startRow && steps > 1)
{
int direction = ((int)(row - m_nLastDrawnRow) > 0) ? 1 : -1;
float factor = (float)(point.y - m_nLastDrawnY)/(float)steps + 0.5f;
int currentRow;
for (int i=1; i<=steps; i++)
{
currentRow = m_nLastDrawnRow+(direction*i);
int interpolatedY = mpt::saturate_round<int>(m_nLastDrawnY + ((float)i * factor));
MakeChange(currentRow, interpolatedY);
}
//Don't use single value update
m_nRowToErase = -1;
m_nParamToErase = -1;
} else
{
m_nRowToErase = -1;
m_nParamToErase = -1;
MakeChange(row, point.y);
}
// Remember last modified point in case we need to interpolate
m_nLastDrawnRow = row;
m_nLastDrawnY = point.y;
}
//update status bar
CString status;
CString effectName;
uint16 paramValue;
if (IsPcNote(row))
{
paramValue = ScreenYToPCParam(point.y);
effectName.Format(_T("%s"), _T("Param Control")); // TODO - show smooth & plug+param
} else
{
paramValue = ScreenYToFXParam(point.y);
effectInfo.GetEffectInfo(effectInfo.GetIndexFromEffect(GetCommand(row), ModCommand::PARAM(GetParam(row))), &effectName, true);
}
status.Format(_T("Pat: %d\tChn: %d\tRow: %d\tVal: %02X (%03d) [%s]"),
m_nPattern, m_nChan+1, static_cast<signed int>(row), paramValue, paramValue, effectName.GetString());
m_edVisStatus.SetWindowText(status);
}
void CEffectVis::OnLButtonDown(UINT nFlags, CPoint point)
{
if (!(m_dwStatus & FXVSTATUS_RDRAGGING))
{
SetFocus();
SetCapture();
m_nDragItem = ScreenXToRow(point.x);
m_dwStatus |= FXVSTATUS_LDRAGGING;
m_ModDoc.GetPatternUndo().PrepareUndo(static_cast<PATTERNINDEX>(m_nPattern), m_nChan, m_startRow, 1, m_endRow - m_startRow + 1, "Parameter Editor entry");
OnMouseMove(nFlags, point);
}
CDialog::OnLButtonDown(nFlags, point);
}
void CEffectVis::OnLButtonUp(UINT nFlags, CPoint point)
{
ReleaseCapture();
m_dwStatus = 0x00;
CDialog::OnLButtonUp(nFlags, point);
m_nLastDrawnRow = ROWINDEX_INVALID;
}
BOOL CEffectVis::OnInitDialog()
{
CDialog::OnInitDialog();
int dpi = Util::GetDPIx(m_hWnd);
m_nodeSizeHalf = MulDiv(3, dpi, 96);
m_marginBottom = MulDiv(20, dpi, 96);
m_innerBorder = MulDiv(4, dpi, 96);
// If first selected row is a PC event (or some other row but there aren't any other effects), default to PC note overwrite mode
// and use it as a template for new PC notes that will be created via the visualiser.
bool isPCevent = IsPcNote(m_startRow);
if(!isPCevent)
{
for(ROWINDEX row = m_startRow; row <= m_endRow; row++)
{
if(IsPcNote(row))
{
isPCevent = true;
} else if(GetCommand(row) != CMD_NONE)
{
isPCevent = false;
break;
}
}
}
if(m_ModDoc.GetModType() == MOD_TYPE_MPT && isPCevent)
{
m_nAction = kAction_OverwritePC;
if(m_SndFile.Patterns.IsValidPat(m_nPattern))
{
ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(m_startRow, m_nChan);
m_templatePCNote.Set(m.note, m.instr, m.GetValueVolCol(), 0);
}
m_cmbEffectList.EnableWindow(FALSE);
} else
{
// Otherwise, default to FX overwrite and
// use effect of first selected row as default effect type
m_nAction = kAction_OverwriteFX;
m_nFillEffect = effectInfo.GetIndexFromEffect(GetCommand(m_startRow), ModCommand::PARAM(GetParam(m_startRow)));
if (m_nFillEffect < 0 || m_nFillEffect >= MAX_EFFECTS)
m_nFillEffect = effectInfo.GetIndexFromEffect(CMD_SMOOTHMIDI, 0);
}
CString s;
UINT numfx = effectInfo.GetNumEffects();
m_cmbEffectList.ResetContent();
int k;
for (UINT i=0; i<numfx; i++)
{
if (effectInfo.GetEffectInfo(i, &s, true))
{
k =m_cmbEffectList.AddString(s);
m_cmbEffectList.SetItemData(k, i);
if ((int)i == m_nFillEffect)
m_cmbEffectList.SetCurSel(k);
}
}
m_cmbActionList.ResetContent();
m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Overwrite with effect:")), kAction_OverwriteFX);
m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Overwrite effect next to note:")), kAction_OverwriteFXWithNote);
m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Fill blanks with effect:")), kAction_FillFX);
if (m_ModDoc.GetModType() == MOD_TYPE_MPT)
{
m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Overwrite with PC note")), kAction_OverwritePC);
m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Fill blanks with PC note")), kAction_FillPC);
}
m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Never change effect type")), kAction_Preserve);
m_cmbActionList.SetCurSel(m_nAction);
return true;
}
void CEffectVis::MakeChange(ROWINDEX row, int y)
{
if(!m_SndFile.Patterns.IsValidPat(m_nPattern))
return;
ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
switch (m_nAction)
{
case kAction_FillFX:
// Only set command if there isn't a command already at this row and it's not a PC note
if (GetCommand(row) == CMD_NONE && !IsPcNote(row))
{
SetCommand(row, effectInfo.GetEffectFromIndex(m_nFillEffect));
}
// Always set param
SetParamFromY(row, y);
break;
case kAction_OverwriteFXWithNote:
if(!m_SndFile.Patterns.IsValidPat(m_nPattern))
break;
if(!m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan)->IsNote())
break;
[[fallthrough]];
case kAction_OverwriteFX:
// Always set command and param. Blows away any PC notes.
SetCommand(row, effectInfo.GetEffectFromIndex(m_nFillEffect));
SetParamFromY(row, y);
break;
case kAction_FillPC:
// Fill only empty slots with PC notes - leave other slots alone.
if (m.IsEmpty())
{
SetPcNote(row);
}
// Always set param
SetParamFromY(row, y);
break;
case kAction_OverwritePC:
// Always convert to PC Note and set param value
SetPcNote(row);
SetParamFromY(row, y);
break;
case kAction_Preserve:
if (GetCommand(row) != CMD_NONE || IsPcNote(row))
{
// Only set param if we have an effect type or if this is a PC note.
// Never change the effect type.
SetParamFromY(row, y);
}
break;
}
m_ModDoc.SetModified();
m_ModDoc.UpdateAllViews(nullptr, PatternHint(m_nPattern).Data());
}
void CEffectVis::SetPcNote(ROWINDEX row)
{
if(!m_SndFile.Patterns.IsValidPat(m_nPattern))
return;
ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
m.Set(m_templatePCNote.note, m_templatePCNote.instr, m_templatePCNote.GetValueVolCol(), 0);
}
bool CEffectVis::IsPcNote(ROWINDEX row) const
{
if(m_SndFile.Patterns.IsValidPat(m_nPattern))
return m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan)->IsPcNote();
else
return false;
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,145 @@
/*
* EffectVis.h
* -----------
* Purpose: Implementation of parameter visualisation dialog.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "EffectInfo.h"
OPENMPT_NAMESPACE_BEGIN
class CViewPattern;
class CModDoc;
class CSoundFile;
#define FXVSTATUS_LDRAGGING 0x01
#define FXVSTATUS_RDRAGGING 0x02
// EffectVis dialog
class CEffectVis : public CDialog
{
DECLARE_DYNAMIC(CEffectVis)
public:
enum EditAction
{
kAction_OverwriteFX,
kAction_OverwriteFXWithNote,
kAction_FillFX,
kAction_OverwritePC,
kAction_FillPC,
kAction_Preserve
};
CEffectVis(CViewPattern *pViewPattern, ROWINDEX startRow, ROWINDEX endRow, CHANNELINDEX nchn, CModDoc &modDoc, PATTERNINDEX pat);
void UpdateSelection(ROWINDEX startRow, ROWINDEX endRow, CHANNELINDEX nchn, PATTERNINDEX pat);
void Update();
void OpenEditor(CWnd *parent);
void SetPlayCursor(PATTERNINDEX nPat, ROWINDEX nRow);
void DoClose();
afx_msg void OnSize(UINT nType, int cx, int cy);
protected:
void DoDataExchange(CDataExchange* pDX) override; // DDX/DDV support
void PostNcDestroy() override;
EffectInfo effectInfo;
CBitmap m_bGrid, m_bNodes, m_bPlayPos;
HBITMAP m_pbOldGrid = nullptr, m_pbOldNodes = nullptr, m_pbOldPlayPos = nullptr;
CDC m_dcGrid, m_dcNodes, m_dcPlayPos;
void DrawNodes();
void DrawGrid();
void ShowVis(CDC *pDC);
void ShowVisImage(CDC *pDC);
RECT invalidated;
ROWINDEX m_nLastDrawnRow = ROWINDEX_INVALID; // for interpolation
int m_nLastDrawnY = -1; // for interpolation
int m_nRowToErase = -1;
int m_nParamToErase = -1;
int m_nodeSizeHalf; // Half width of a node;
int m_marginBottom;
int m_innerBorder;
ROWINDEX m_nOldPlayPos = ROWINDEX_INVALID;
ModCommand m_templatePCNote;
protected:
ROWINDEX m_startRow;
ROWINDEX m_endRow;
ROWINDEX m_nRows;
CHANNELINDEX m_nChan;
PATTERNINDEX m_nPattern;
int m_nFillEffect;
static EditAction m_nAction;
int m_nDragItem = -1;
UINT m_nBtnMouseOver;
DWORD m_dwStatus = 0;
float m_pixelsPerRow = 1, m_pixelsPerFXParam = 1, m_pixelsPerPCParam = 1;
bool m_forceRedraw = true;
void InvalidateRow(int row);
int RowToScreenX(ROWINDEX row) const;
int RowToScreenY(ROWINDEX row) const;
int PCParamToScreenY(uint16 param) const;
int FXParamToScreenY(uint16 param) const;
uint16 GetParam(ROWINDEX row) const;
EffectCommand GetCommand(ROWINDEX row) const;
void SetParamFromY(ROWINDEX row, int y);
void SetCommand(ROWINDEX row, EffectCommand cmd);
ModCommand::PARAM ScreenYToFXParam(int y) const;
uint16 ScreenYToPCParam(int y) const;
ROWINDEX ScreenXToRow(int x) const;
bool IsPcNote(ROWINDEX row) const;
void SetPcNote(ROWINDEX row);
CModDoc &m_ModDoc;
CSoundFile &m_SndFile;
CRect m_rcDraw;
CRect m_rcFullWin;
CComboBox m_cmbEffectList, m_cmbActionList;
CEdit m_edVisStatus;
void OnOK() override;
void OnCancel() override;
afx_msg void OnClose();
CViewPattern *m_pViewPattern;
DECLARE_MESSAGE_MAP()
BOOL OnInitDialog() override;
afx_msg void OnPaint();
protected:
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
afx_msg void OnRButtonUp(UINT nFlags, CPoint point);
afx_msg void OnEffectChanged();
afx_msg void OnActionChanged();
afx_msg BOOL OnEraseBkgnd(CDC *) { return TRUE; }
void MakeChange(ROWINDEX currentRow, int newY);
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,853 @@
/*
* ExceptionHandler.cpp
* --------------------
* Purpose: Code for handling crashes (unhandled exceptions) in OpenMPT.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "Mainfrm.h"
#include "Mptrack.h"
#include "AboutDialog.h"
#include "InputHandler.h"
#include "openmpt/sounddevice/SoundDevice.hpp"
#include "Moddoc.h"
#include <shlwapi.h>
#include "ExceptionHandler.h"
#include "../misc/WriteMemoryDump.h"
#include "../common/version.h"
#include "../common/mptFileIO.h"
#include "../soundlib/mod_specifications.h"
#include <atomic>
OPENMPT_NAMESPACE_BEGIN
// Write full memory dump instead of minidump.
bool ExceptionHandler::fullMemDump = false;
bool ExceptionHandler::stopSoundDeviceOnCrash = true;
bool ExceptionHandler::stopSoundDeviceBeforeDump = false;
// Delegate to system-specific crash processing once our own crash handler is
// finished. This is useful to allow attaching a debugger.
bool ExceptionHandler::delegateToWindowsHandler = false;
// Allow debugging the unhandled exception filter. Normally, if a debugger is
// attached, no exceptions are unhandled because the debugger handles them. If
// debugExceptionHandler is true, an additional __try/__catch is inserted around
// InitInstance(), ExitInstance() and the main message loop, which will call our
// filter, which then can be stepped through in a debugger.
bool ExceptionHandler::debugExceptionHandler = false;
bool ExceptionHandler::useAnyCrashHandler = false;
bool ExceptionHandler::useImplicitFallbackSEH = false;
bool ExceptionHandler::useExplicitSEH = false;
bool ExceptionHandler::handleStdTerminate = false;
bool ExceptionHandler::handleMfcExceptions = false;
static thread_local ExceptionHandler::Context *g_Context = nullptr;
static LPTOP_LEVEL_EXCEPTION_FILTER g_OriginalUnhandledExceptionFilter = nullptr;
static std::terminate_handler g_OriginalTerminateHandler = nullptr;
static UINT g_OriginalErrorMode = 0;
ExceptionHandler::Context *ExceptionHandler::SetContext(Context *newContext) noexcept
{
Context *oldContext = g_Context;
g_Context = newContext;
return oldContext;
}
static std::atomic<int> & g_CrashCount()
{
static std::atomic<int> s_CrashCount(0);
return s_CrashCount;
}
static std::atomic<int> & g_TaintCountDriver()
{
static std::atomic<int> s_TaintCountDriver(0);
return s_TaintCountDriver;
}
static std::atomic<int> & g_TaintCountPlugin()
{
static std::atomic<int> s_TaintCountPlugin(0);
return s_TaintCountPlugin;
}
void ExceptionHandler::TaintProcess(ExceptionHandler::TaintReason reason)
{
switch(reason)
{
case ExceptionHandler::TaintReason::Driver:
g_TaintCountDriver().fetch_add(1);
break;
case ExceptionHandler::TaintReason::Plugin:
g_TaintCountPlugin().fetch_add(1);
break;
default:
MPT_ASSERT_NOTREACHED();
break;
}
}
enum DumpMode
{
DumpModeCrash = 0, // crash
DumpModeWarning = 1, // assert
DumpModeDebug = 2, // debug output (e.g. trace log)
};
struct CrashOutputDirectory
{
bool valid;
mpt::PathString path;
CrashOutputDirectory()
: valid(true)
{
const mpt::PathString timestampDir = mpt::PathString::FromCString((CTime::GetCurrentTime()).Format(_T("%Y-%m-%d %H.%M.%S\\")));
// Create a crash directory
path = mpt::GetTempDirectory() + P_("OpenMPT Crash Files\\");
if(!path.IsDirectory())
{
CreateDirectory(path.AsNative().c_str(), nullptr);
}
// Set compressed attribute in order to save disk space.
// Debugging information should clutter the users computer as little as possible.
// Performance is not important here.
// Ignore any errors.
SetFilesystemCompression(path);
// Compression will be inherited by children directories and files automatically.
path += timestampDir;
if(!path.IsDirectory())
{
if(!CreateDirectory(path.AsNative().c_str(), nullptr))
{
valid = false;
}
}
}
};
class DebugReporter
{
private:
int crashCount;
int taintCountDriver;
int taintCountPlugin;
bool stateFrozen;
const DumpMode mode;
const CrashOutputDirectory crashDirectory;
bool writtenMiniDump;
bool writtenTraceLog;
int rescuedFiles;
private:
static bool FreezeState(DumpMode mode);
static bool Cleanup(DumpMode mode);
bool GenerateDump(_EXCEPTION_POINTERS *pExceptionInfo);
bool GenerateTraceLog();
int RescueFiles();
bool HasWrittenDebug() const { return writtenMiniDump || writtenTraceLog; }
static void StopSoundDevice();
public:
DebugReporter(DumpMode mode, _EXCEPTION_POINTERS *pExceptionInfo);
~DebugReporter();
void ReportError(mpt::ustring errorMessage);
};
DebugReporter::DebugReporter(DumpMode mode, _EXCEPTION_POINTERS *pExceptionInfo)
: crashCount(g_CrashCount().fetch_add(1) + 1)
, taintCountDriver(g_TaintCountDriver().load())
, taintCountPlugin(g_TaintCountPlugin().load())
, stateFrozen(FreezeState(mode))
, mode(mode)
, writtenMiniDump(false)
, writtenTraceLog(false)
, rescuedFiles(0)
{
if(mode == DumpModeCrash || mode == DumpModeWarning)
{
writtenMiniDump = GenerateDump(pExceptionInfo);
}
if(mode == DumpModeCrash || mode == DumpModeWarning || mode == DumpModeDebug)
{
writtenTraceLog = GenerateTraceLog();
}
if(mode == DumpModeCrash || mode == DumpModeWarning)
{
rescuedFiles = RescueFiles();
}
}
DebugReporter::~DebugReporter()
{
Cleanup(mode);
}
bool DebugReporter::GenerateDump(_EXCEPTION_POINTERS *pExceptionInfo)
{
return WriteMemoryDump(pExceptionInfo, (crashDirectory.path + P_("crash.dmp")).AsNative().c_str(), ExceptionHandler::fullMemDump);
}
bool DebugReporter::GenerateTraceLog()
{
return mpt::log::Trace::Dump(crashDirectory.path + P_("trace.log"));
}
static void SaveDocumentSafe(CModDoc *pModDoc, const mpt::PathString &filename)
{
__try
{
pModDoc->OnSaveDocument(filename);
} __except(EXCEPTION_EXECUTE_HANDLER)
{
// nothing
}
}
// Rescue modified files...
int DebugReporter::RescueFiles()
{
int numFiles = 0;
auto documents = theApp.GetOpenDocuments();
for(auto modDoc : documents)
{
if(modDoc->IsModified())
{
if(numFiles == 0)
{
// Show the rescue directory in Explorer...
CTrackApp::OpenDirectory(crashDirectory.path);
}
mpt::PathString filename;
filename += crashDirectory.path;
filename += mpt::PathString::FromUnicode(mpt::ufmt::val(++numFiles));
filename += P_("_");
filename += mpt::PathString::FromCString(modDoc->GetTitle()).SanitizeComponent();
filename += P_(".");
filename += mpt::PathString::FromUTF8(modDoc->GetSoundFile().GetModSpecifications().fileExtension);
try
{
SaveDocumentSafe(modDoc, filename);
} catch(...)
{
continue;
}
}
}
return numFiles;
}
void DebugReporter::ReportError(mpt::ustring errorMessage)
{
if(!crashDirectory.valid)
{
errorMessage += UL_("\n\n");
errorMessage += UL_("Could not create the following directory for saving debug information and modified files to:\n");
errorMessage += crashDirectory.path.ToUnicode();
}
if(HasWrittenDebug())
{
errorMessage += UL_("\n\n");
errorMessage += UL_("Debug information has been saved to\n");
errorMessage += crashDirectory.path.ToUnicode();
}
if(rescuedFiles > 0)
{
errorMessage += UL_("\n\n");
if(rescuedFiles == 1)
{
errorMessage += UL_("1 modified file has been rescued, but it cannot be guaranteed that it is still intact.");
} else
{
errorMessage += MPT_UFORMAT("{} modified files have been rescued, but it cannot be guaranteed that they are still intact.")(rescuedFiles);
}
}
errorMessage += UL_("\n\n");
errorMessage += MPT_UFORMAT("OpenMPT {} {} ({} ({}))")
( Build::GetVersionStringExtended()
, mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture())
, SourceInfo::Current().GetUrlWithRevision()
, SourceInfo::Current().GetStateString()
);
errorMessage += UL_("\n\n");
errorMessage += MPT_UFORMAT("Session error count: {}\n")(crashCount);
if(taintCountDriver > 0 || taintCountPlugin > 0)
{
errorMessage += UL_("Process is in tainted state!\n");
errorMessage += MPT_UFORMAT("Previously masked driver crashes: {}\n")(taintCountDriver);
errorMessage += MPT_UFORMAT("Previously masked plugin crashes: {}\n")(taintCountPlugin);
}
errorMessage += UL_("\n");
{
mpt::SafeOutputFile sf(crashDirectory.path + P_("error.txt"), std::ios::binary, mpt::FlushMode::Full);
mpt::ofstream& f = sf;
f.imbue(std::locale::classic());
f << mpt::replace(mpt::ToCharset(mpt::Charset::UTF8, errorMessage), std::string("\n"), std::string("\r\n"));
}
if(auto ih = CMainFrame::GetInputHandler(); ih != nullptr)
{
mpt::SafeOutputFile sf(crashDirectory.path + P_("last-commands.txt"), std::ios::binary, mpt::FlushMode::Full);
mpt::ofstream &f = sf;
f.imbue(std::locale::classic());
const auto commandSet = ih->m_activeCommandSet.get();
f << "Last commands:\n";
for(size_t i = 0; i < ih->m_lastCommands.size(); i++)
{
CommandID id = ih->m_lastCommands[(ih->m_lastCommandPos + i) % ih->m_lastCommands.size()];
if(id == kcNull)
continue;
f << mpt::afmt::val(id);
if(commandSet)
f << " (" << mpt::ToCharset(mpt::Charset::UTF8, commandSet->GetCommandText(id)) << ")";
f << "\n";
}
}
{
mpt::SafeOutputFile sf(crashDirectory.path + P_("threads.txt"), std::ios::binary, mpt::FlushMode::Full);
mpt::ofstream& f = sf;
f.imbue(std::locale::classic());
f << MPT_AFORMAT("current : {}")(mpt::afmt::hex0<8>(GetCurrentThreadId())) << "\r\n";
f << MPT_AFORMAT("GUI : {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindGUI))) << "\r\n";
f << MPT_AFORMAT("Audio : {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindAudio))) << "\r\n";
f << MPT_AFORMAT("Notify : {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindNotify))) << "\r\n";
f << MPT_AFORMAT("WatchDir: {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindWatchdir))) << "\r\n";
}
static constexpr struct { const mpt::uchar * section; const mpt::uchar * key; } configAnonymize[] =
{
{ UL_("Version"), UL_("InstallGUID") },
{ UL_("Recent File List"), nullptr },
};
{
mpt::SafeOutputFile sf(crashDirectory.path + P_("active-settings.txt"), std::ios::binary, mpt::FlushMode::Full);
mpt::ofstream& f = sf;
f.imbue(std::locale::classic());
if(theApp.GetpSettings())
{
SettingsContainer &settings = theApp.GetSettings();
for(const auto &it : settings)
{
bool skipPath = false;
for(const auto &path : configAnonymize)
{
if((path.key == nullptr && path.section == it.first.GetRefSection()) // Omit entire section
|| (path.key != nullptr && it.first == SettingPath(path.section, path.key))) // Omit specific key
{
skipPath = true;
}
}
if(skipPath)
{
continue;
}
f
<< mpt::ToCharset(mpt::Charset::UTF8, it.first.FormatAsString() + U_(" = ") + it.second.GetRefValue().FormatValueAsString())
<< std::endl;
}
}
}
{
const mpt::PathString crashStoredSettingsFilename = crashDirectory.path + P_("stored-mptrack.ini");
CopyFile
( theApp.GetConfigFileName().AsNative().c_str()
, crashStoredSettingsFilename.AsNative().c_str()
, FALSE
);
IniFileSettingsContainer crashStoredSettings{crashStoredSettingsFilename};
for(const auto &path : configAnonymize)
{
if(path.key)
{
crashStoredSettings.Write(SettingPath(path.section, path.key), SettingValue(mpt::ustring()));
} else
{
crashStoredSettings.Remove(path.section);
}
}
crashStoredSettings.Flush();
}
/*
// This is very slow, we instead write active-settings.txt above.
{
IniFileSettingsBackend f(crashDirectory.path + P_("active-mptrack.ini"));
if(theApp.GetpSettings())
{
SettingsContainer & settings = theApp.GetSettings();
for(const auto &it : settings)
{
f.WriteSetting(it.first, it.second.GetRefValue());
}
}
}
*/
{
mpt::SafeOutputFile sf(crashDirectory.path + P_("about-openmpt.txt"), std::ios::binary, mpt::FlushMode::Full);
mpt::ofstream& f = sf;
f.imbue(std::locale::classic());
f << mpt::ToCharset(mpt::Charset::UTF8, CAboutDlg::GetTabText(0));
}
{
mpt::SafeOutputFile sf(crashDirectory.path + P_("about-components.txt"), std::ios::binary, mpt::FlushMode::Full);
mpt::ofstream& f = sf;
f.imbue(std::locale::classic());
f << mpt::ToCharset(mpt::Charset::UTF8, CAboutDlg::GetTabText(1));
}
Reporting::Error(errorMessage, (mode == DumpModeWarning) ? "OpenMPT Warning" : "OpenMPT Crash", CMainFrame::GetMainFrame());
}
// Freezes the state as much as possible in order to avoid further confusion by
// other (possibly still running) threads
bool DebugReporter::FreezeState(DumpMode mode)
{
MPT_TRACE();
// seal the trace log as early as possible
mpt::log::Trace::Seal();
if(mode == DumpModeCrash || mode == DumpModeWarning)
{
if(CMainFrame::GetMainFrame() && CMainFrame::GetMainFrame()->gpSoundDevice && CMainFrame::GetMainFrame()->gpSoundDevice->DebugIsFragileDevice())
{
// For fragile devices, always stop the device. Stop before the dumping if not in realtime context.
if(!CMainFrame::GetMainFrame()->gpSoundDevice->DebugInRealtimeCallback())
{
StopSoundDevice();
}
} else
{
if(ExceptionHandler::stopSoundDeviceOnCrash && ExceptionHandler::stopSoundDeviceBeforeDump)
{
StopSoundDevice();
}
}
}
return true;
}
static void StopSoundDeviceSafe(CMainFrame *pMainFrame)
{
__try
{
if(pMainFrame->gpSoundDevice)
{
pMainFrame->gpSoundDevice->Close();
}
if(pMainFrame->m_NotifyTimer)
{
pMainFrame->KillTimer(pMainFrame->m_NotifyTimer);
pMainFrame->m_NotifyTimer = 0;
}
} __except(EXCEPTION_EXECUTE_HANDLER)
{
// nothing
}
}
void DebugReporter::StopSoundDevice()
{
CMainFrame* pMainFrame = CMainFrame::GetMainFrame();
if(pMainFrame)
{
try
{
StopSoundDeviceSafe(pMainFrame);
} catch(...)
{
// nothing
}
}
}
bool DebugReporter::Cleanup(DumpMode mode)
{
MPT_TRACE();
if(mode == DumpModeCrash || mode == DumpModeWarning)
{
if(CMainFrame::GetMainFrame() && CMainFrame::GetMainFrame()->gpSoundDevice && CMainFrame::GetMainFrame()->gpSoundDevice->DebugIsFragileDevice())
{
// For fragile devices, always stop the device. Stop after the dumping if in realtime context.
if(CMainFrame::GetMainFrame()->gpSoundDevice->DebugInRealtimeCallback())
{
StopSoundDevice();
}
} else
{
if(ExceptionHandler::stopSoundDeviceOnCrash && !ExceptionHandler::stopSoundDeviceBeforeDump)
{
StopSoundDevice();
}
}
}
return true;
}
// Different entry points for different situations in which we want to dump some information
static bool IsCxxException(_EXCEPTION_POINTERS *pExceptionInfo)
{
if (!pExceptionInfo)
return false;
if (!pExceptionInfo->ExceptionRecord)
return false;
if (pExceptionInfo->ExceptionRecord->ExceptionCode != 0xE06D7363u)
return false;
return true;
}
template <typename E>
static const E * GetCxxException(_EXCEPTION_POINTERS *pExceptionInfo)
{
// https://blogs.msdn.microsoft.com/oldnewthing/20100730-00/?p=13273
struct info_a_t
{
DWORD bitmask; // Probably: 1=Const, 2=Volatile
DWORD destructor; // RVA (Relative Virtual Address) of destructor for that exception object
DWORD unknown;
DWORD catchableTypesPtr; // RVA of instance of type "B"
};
struct info_c_t
{
DWORD someBitmask;
DWORD typeInfo; // RVA of std::type_info for that type
DWORD memberDisplacement; // Add to ExceptionInformation[1] in EXCEPTION_RECORD to obtain 'this' pointer.
DWORD virtBaseRelated1; // -1 if no virtual base
DWORD virtBaseRelated2; // ?
DWORD objectSize; // Size of the object in bytes
DWORD probablyCopyCtr; // RVA of copy constructor (?)
};
if(!pExceptionInfo)
return nullptr;
if (!pExceptionInfo->ExceptionRecord)
return nullptr;
if(pExceptionInfo->ExceptionRecord->ExceptionCode != 0xE06D7363u)
return nullptr;
#ifdef _WIN64
if(pExceptionInfo->ExceptionRecord->NumberParameters != 4)
return nullptr;
#else
if(pExceptionInfo->ExceptionRecord->NumberParameters != 3)
return nullptr;
#endif
if(pExceptionInfo->ExceptionRecord->ExceptionInformation[0] != 0x19930520u)
return nullptr;
std::uintptr_t base_address = 0;
#ifdef _WIN64
base_address = pExceptionInfo->ExceptionRecord->ExceptionInformation[3];
#else
base_address = 0;
#endif
std::uintptr_t obj_address = pExceptionInfo->ExceptionRecord->ExceptionInformation[1];
if(!obj_address)
return nullptr;
std::uintptr_t info_a_address = pExceptionInfo->ExceptionRecord->ExceptionInformation[2];
if(!info_a_address)
return nullptr;
const info_a_t * info_a = reinterpret_cast<const info_a_t *>(info_a_address);
std::uintptr_t info_b_offset = info_a->catchableTypesPtr;
if(!info_b_offset)
return nullptr;
const DWORD * info_b = reinterpret_cast<const DWORD *>(base_address + info_b_offset);
for(DWORD type = 1; type <= info_b[0]; ++type)
{
std::uintptr_t info_c_offset = info_b[type];
if(!info_c_offset)
continue;
const info_c_t * info_c = reinterpret_cast<const info_c_t *>(base_address + info_c_offset);
if(!info_c->typeInfo)
continue;
const std::type_info * ti = reinterpret_cast<const std::type_info *>(base_address + info_c->typeInfo);
if(*ti != typeid(E))
continue;
const E * e = reinterpret_cast<const E *>(obj_address + info_c->memberDisplacement);
return e;
}
return nullptr;
}
void ExceptionHandler::UnhandledMFCException(CException * e, const MSG * pMsg)
{
DebugReporter report(DumpModeCrash, nullptr);
mpt::ustring errorMessage;
if(e && dynamic_cast<CSimpleException*>(e))
{
TCHAR tmp[1024 + 1];
MemsetZero(tmp);
if(dynamic_cast<CSimpleException*>(e)->GetErrorMessage(tmp, static_cast<UINT>(std::size(tmp) - 1)) != 0)
{
tmp[1024] = 0;
errorMessage = MPT_UFORMAT("Unhandled MFC exception occurred while processming window message '{}': {}.")
(mpt::ufmt::dec(pMsg ? pMsg->message : 0)
, mpt::ToUnicode(CString(tmp))
);
} else
{
errorMessage = MPT_UFORMAT("Unhandled MFC exception occurred while processming window message '{}': {}.")
(mpt::ufmt::dec(pMsg ? pMsg->message : 0)
, mpt::ToUnicode(CString(tmp))
);
}
}
else
{
errorMessage = MPT_UFORMAT("Unhandled MFC exception occurred while processming window message '{}'.")
( mpt::ufmt::dec(pMsg ? pMsg->message : 0)
);
}
report.ReportError(errorMessage);
}
static void UnhandledExceptionFilterImpl(_EXCEPTION_POINTERS *pExceptionInfo)
{
DebugReporter report(DumpModeCrash, pExceptionInfo);
mpt::ustring errorMessage;
const std::exception * pE = GetCxxException<std::exception>(pExceptionInfo);
if(g_Context)
{
if(!g_Context->description.empty())
{
errorMessage += MPT_UFORMAT("OpenMPT detected a crash in '{}'.\nThis is very likely not an OpenMPT bug. Please report the problem to the respective software author.\n")(g_Context->description);
} else
{
errorMessage += MPT_UFORMAT("OpenMPT detected a crash in unknown foreign code.\nThis is likely not an OpenMPT bug.\n")();
}
}
if(pE)
{
const std::exception & e = *pE;
errorMessage += MPT_UFORMAT("Unhandled C++ exception '{}' occurred at address 0x{}: '{}'.")
( mpt::ToUnicode(mpt::Charset::ASCII, typeid(e).name())
, mpt::ufmt::hex0<mpt::pointer_size*2>(reinterpret_cast<std::uintptr_t>(pExceptionInfo->ExceptionRecord->ExceptionAddress))
, mpt::get_exception_text<mpt::ustring>(e)
);
} else
{
errorMessage += MPT_UFORMAT("Unhandled exception 0x{} at address 0x{} occurred.")
( mpt::ufmt::HEX0<8>(pExceptionInfo->ExceptionRecord->ExceptionCode)
, mpt::ufmt::hex0<mpt::pointer_size*2>(reinterpret_cast<std::uintptr_t>(pExceptionInfo->ExceptionRecord->ExceptionAddress))
);
}
report.ReportError(errorMessage);
}
LONG ExceptionHandler::UnhandledExceptionFilterContinue(_EXCEPTION_POINTERS *pExceptionInfo)
{
UnhandledExceptionFilterImpl(pExceptionInfo);
// Disable the call to std::terminate() as that would re-renter the crash
// handler another time, but with less information available.
#if 0
// MSVC implements calling std::terminate by its own UnhandledExeptionFilter.
// However, we do overwrite it here, thus we have to call std::terminate
// ourselves.
if (IsCxxException(pExceptionInfo))
{
std::terminate();
}
#endif
// Let a potential debugger handle the exception...
return EXCEPTION_CONTINUE_SEARCH;
}
LONG ExceptionHandler::ExceptionFilter(_EXCEPTION_POINTERS *pExceptionInfo)
{
UnhandledExceptionFilterImpl(pExceptionInfo);
// Let a potential debugger handle the exception...
return EXCEPTION_EXECUTE_HANDLER;
}
static void mpt_unexpected_handler();
static void mpt_terminate_handler();
void ExceptionHandler::Register()
{
if(useImplicitFallbackSEH)
{
g_OriginalUnhandledExceptionFilter = ::SetUnhandledExceptionFilter(&UnhandledExceptionFilterContinue);
}
if(handleStdTerminate)
{
g_OriginalTerminateHandler = std::set_terminate(&mpt_terminate_handler);
}
}
void ExceptionHandler::ConfigureSystemHandler()
{
#if (_WIN32_WINNT >= 0x0600)
if(delegateToWindowsHandler)
{
//SetErrorMode(0);
g_OriginalErrorMode = ::GetErrorMode();
} else
{
g_OriginalErrorMode = ::SetErrorMode(::GetErrorMode() | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
}
#else // _WIN32_WINNT < 0x0600
if(delegateToWindowsHandler)
{
g_OriginalErrorMode = ::SetErrorMode(0);
} else
{
g_OriginalErrorMode = ::SetErrorMode(::SetErrorMode(0) | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
}
#endif // _WIN32_WINNT
}
void ExceptionHandler::UnconfigureSystemHandler()
{
::SetErrorMode(g_OriginalErrorMode);
g_OriginalErrorMode = 0;
}
void ExceptionHandler::Unregister()
{
if(handleStdTerminate)
{
std::set_terminate(g_OriginalTerminateHandler);
g_OriginalTerminateHandler = nullptr;
}
if(useImplicitFallbackSEH)
{
::SetUnhandledExceptionFilter(g_OriginalUnhandledExceptionFilter);
g_OriginalUnhandledExceptionFilter = nullptr;
}
}
static void mpt_terminate_handler()
{
DebugReporter(DumpModeCrash, nullptr).ReportError(U_("A C++ runtime crash occurred: std::terminate() called."));
#if 1
std::abort();
#else
if(g_OriginalTerminateHandler)
{
g_OriginalTerminateHandler();
} else
{
std::abort();
}
#endif
}
#if defined(MPT_ASSERT_HANDLER_NEEDED)
MPT_NOINLINE void AssertHandler(const mpt::source_location &loc, const char *expr, const char *msg)
{
DebugReporter report(msg ? DumpModeWarning : DumpModeCrash, nullptr);
if(IsDebuggerPresent())
{
OutputDebugString(_T("ASSERT("));
OutputDebugString(mpt::ToWin(mpt::Charset::ASCII, expr).c_str());
OutputDebugString(_T(") failed\n"));
DebugBreak();
} else
{
mpt::ustring errorMessage;
if(msg)
{
errorMessage = MPT_UFORMAT("Internal state inconsistency detected at {}({}). This is just a warning that could potentially lead to a crash later on: {} [{}].")
( mpt::ToUnicode(mpt::Charset::ASCII, loc.file_name() ? loc.file_name() : "")
, loc.line()
, mpt::ToUnicode(mpt::Charset::ASCII, msg)
, mpt::ToUnicode(mpt::Charset::ASCII, loc.function_name() ? loc.function_name() : "")
);
} else
{
errorMessage = MPT_UFORMAT("Internal error occurred at {}({}): ASSERT({}) failed in [{}].")
( mpt::ToUnicode(mpt::Charset::ASCII, loc.file_name() ? loc.file_name() : "")
, loc.line()
, mpt::ToUnicode(mpt::Charset::ASCII, expr)
, mpt::ToUnicode(mpt::Charset::ASCII, loc.function_name() ? loc.function_name() : "")
);
}
report.ReportError(errorMessage);
}
}
#endif // MPT_ASSERT_HANDLER_NEEDED
void DebugInjectCrash()
{
DebugReporter(DumpModeCrash, nullptr).ReportError(U_("Injected crash."));
}
void DebugTraceDump()
{
DebugReporter report(DumpModeDebug, nullptr);
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,95 @@
/*
* ExceptionHandler.h
* ------------------
* Purpose: Code for handling crashes (unhandled exceptions) in OpenMPT.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
OPENMPT_NAMESPACE_BEGIN
class ExceptionHandler
{
public:
struct Context
{
void SetDescription(mpt::ustring desc)
{
description = std::move(desc);
}
mpt::ustring description;
};
public:
static bool fullMemDump;
static bool stopSoundDeviceOnCrash;
static bool stopSoundDeviceBeforeDump;
static bool delegateToWindowsHandler;
static bool debugExceptionHandler;
// these are expected to be set on startup and never changed again
static bool useAnyCrashHandler;
static bool useImplicitFallbackSEH;
static bool useExplicitSEH;
static bool handleStdTerminate;
static bool handleStdUnexpected;
static bool handleMfcExceptions;
// Call this to activate unhandled exception filtering
// and register a std::terminate_handler.
static void Register();
static void ConfigureSystemHandler();
static void UnconfigureSystemHandler();
static void Unregister();
enum class TaintReason
{
Driver,
Plugin,
};
static void TaintProcess(TaintReason reason);
public:
static Context *SetContext(Context *newContext) noexcept;
class ContextSetter
{
private:
Context *m_OldContext;
public:
inline ContextSetter(Context *newContext) noexcept
: m_OldContext(SetContext(newContext))
{
return;
}
ContextSetter(const ContextSetter &) = delete;
ContextSetter &operator=(const ContextSetter &) = delete;
inline ~ContextSetter()
{
SetContext(m_OldContext);
}
};
static LONG WINAPI UnhandledExceptionFilterContinue(_EXCEPTION_POINTERS *pExceptionInfo);
static LONG WINAPI ExceptionFilter(_EXCEPTION_POINTERS *pExceptionInfo);
static void UnhandledMFCException(CException * e, const MSG * pMsg);
};
void DebugInjectCrash();
void DebugTraceDump();
OPENMPT_NAMESPACE_END
@@ -0,0 +1,367 @@
/*
* ExternalSamples.cpp
* -------------------
* Purpose: Dialogs for locating missing external samples and handling modified samples
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "Moddoc.h"
#include "ExternalSamples.h"
#include "FileDialog.h"
#include "FolderScanner.h"
#include "TrackerSettings.h"
#include "Reporting.h"
#include "resource.h"
OPENMPT_NAMESPACE_BEGIN
BEGIN_MESSAGE_MAP(MissingExternalSamplesDlg, ResizableDialog)
//{{AFX_MSG_MAP(ExternalSamplesDlg)
ON_NOTIFY(NM_DBLCLK, IDC_LIST1, &MissingExternalSamplesDlg::OnSetPath)
ON_COMMAND(IDC_BUTTON1, &MissingExternalSamplesDlg::OnScanFolder)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
void MissingExternalSamplesDlg::DoDataExchange(CDataExchange *pDX)
{
ResizableDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_LIST1, m_List);
}
MissingExternalSamplesDlg::MissingExternalSamplesDlg(CModDoc &modDoc, CWnd *parent)
: ResizableDialog(IDD_MISSINGSAMPLES, parent)
, m_modDoc(modDoc)
, m_sndFile(modDoc.GetSoundFile())
{
}
BOOL MissingExternalSamplesDlg::OnInitDialog()
{
ResizableDialog::OnInitDialog();
// Initialize table
const CListCtrlEx::Header headers[] =
{
{ _T("Sample"), 128, LVCFMT_LEFT },
{ _T("External Filename"), 308, LVCFMT_LEFT },
};
m_List.SetHeaders(headers);
m_List.SetExtendedStyle(m_List.GetExtendedStyle() | LVS_EX_FULLROWSELECT);
GenerateList();
SetWindowText((_T("Missing External Samples - ") + m_modDoc.GetPathNameMpt().GetFullFileName().AsNative()).c_str());
return TRUE;
}
void MissingExternalSamplesDlg::GenerateList()
{
m_List.SetRedraw(FALSE);
m_List.DeleteAllItems();
CString s;
for(SAMPLEINDEX smp = 1; smp <= m_sndFile.GetNumSamples(); smp++)
{
if(m_sndFile.IsExternalSampleMissing(smp))
{
s.Format(_T("%02u: "), smp);
s += mpt::ToCString(m_sndFile.GetCharsetInternal(), m_sndFile.GetSampleName(smp));
int insertAt = m_List.InsertItem(m_List.GetItemCount(), s);
if(insertAt == -1)
continue;
m_List.SetItemText(insertAt, 1, m_sndFile.GetSamplePath(smp).AsNative().c_str());
m_List.SetItemData(insertAt, smp);
}
}
m_List.SetRedraw(TRUE);
// Yay, we managed to find all samples!
if(!m_List.GetItemCount())
OnOK();
}
void MissingExternalSamplesDlg::OnSetPath(NMHDR *, LRESULT *)
{
const int item = m_List.GetSelectionMark();
if(item == -1) return;
const SAMPLEINDEX smp = static_cast<SAMPLEINDEX>(m_List.GetItemData(item));
const mpt::PathString path = m_modDoc.GetSoundFile().GetSamplePath(smp);
FileDialog dlg = OpenFileDialog()
.ExtensionFilter("All Samples|*.wav;*.flac|All files(*.*)|*.*||"); // Only show samples that we actually can save as well.
if(TrackerSettings::Instance().previewInFileDialogs)
dlg.EnableAudioPreview();
if(path.empty())
dlg.WorkingDirectory(TrackerSettings::Instance().PathSamples.GetWorkingDir());
else
dlg.DefaultFilename(path);
if(!dlg.Show()) return;
TrackerSettings::Instance().PathSamples.SetWorkingDir(dlg.GetWorkingDirectory());
SetSample(smp, dlg.GetFirstFile());
m_modDoc.UpdateAllViews(nullptr, SampleHint(smp).Info().Names().Data());
GenerateList();
}
void MissingExternalSamplesDlg::OnScanFolder()
{
if(m_isScanning)
{
m_isScanning = false;
return;
}
BrowseForFolder dlg(TrackerSettings::Instance().PathSamples.GetWorkingDir(), _T("Select a folder to search for missing samples..."));
if(dlg.Show())
{
TrackerSettings::Instance().PathSamples.SetWorkingDir(dlg.GetDirectory());
FolderScanner scan(dlg.GetDirectory(), FolderScanner::kOnlyFiles | FolderScanner::kFindInSubDirectories);
mpt::PathString fileName;
m_isScanning = true;
SetDlgItemText(IDC_BUTTON1, _T("&Cancel"));
GetDlgItem(IDOK)->EnableWindow(FALSE);
BeginWaitCursor();
DWORD64 lastTick = Util::GetTickCount64();
int foundFiles = 0;
bool anyMissing = true;
while(scan.Next(fileName) && m_isScanning && anyMissing)
{
anyMissing = false;
for(SAMPLEINDEX smp = 1; smp <= m_sndFile.GetNumSamples(); smp++)
{
if(m_sndFile.IsExternalSampleMissing(smp))
{
if(!mpt::PathString::CompareNoCase(m_sndFile.GetSamplePath(smp).GetFullFileName(), fileName.GetFullFileName()))
{
if(SetSample(smp, fileName))
{
foundFiles++;
}
} else
{
anyMissing = true;
}
}
}
const DWORD64 tick = Util::GetTickCount64();
if(tick < lastTick || tick > lastTick + 100)
{
lastTick = tick;
SetDlgItemText(IDC_STATIC1, fileName.AsNative().c_str());
MSG msg;
while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
}
EndWaitCursor();
GetDlgItem(IDOK)->EnableWindow(TRUE);
SetDlgItemText(IDC_BUTTON1, _T("&Scan Folder..."));
m_modDoc.UpdateAllViews(nullptr, SampleHint().Info().Data().Names());
if(foundFiles)
{
SetDlgItemText(IDC_STATIC1, MPT_CFORMAT("{} sample paths were relocated.")(foundFiles));
} else
{
SetDlgItemText(IDC_STATIC1, _T("No matching sample names found."));
}
m_isScanning = false;
GenerateList();
}
}
bool MissingExternalSamplesDlg::SetSample(SAMPLEINDEX smp, const mpt::PathString &fileName)
{
m_modDoc.GetSampleUndo().PrepareUndo(smp, sundo_replace, "Replace");
const mpt::PathString oldPath = m_sndFile.GetSamplePath(smp);
if(!m_sndFile.LoadExternalSample(smp, fileName))
{
Reporting::Information(_T("Unable to load sample:\n") + fileName.AsNative());
m_modDoc.GetSampleUndo().RemoveLastUndoStep(smp);
return false;
} else
{
// Maybe we just put the file into its regular place, in which case the module has not really been modified.
if(oldPath != fileName)
{
m_modDoc.SetModified();
}
return true;
}
}
BEGIN_MESSAGE_MAP(ModifiedExternalSamplesDlg, ResizableDialog)
//{{AFX_MSG_MAP(ExternalSamplesDlg)
ON_COMMAND(IDC_SAVE, &ModifiedExternalSamplesDlg::OnSaveSelected)
ON_COMMAND(IDC_CHECK1, &ModifiedExternalSamplesDlg::OnCheckAll)
ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST1, &ModifiedExternalSamplesDlg::OnSelectionChanged)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
void ModifiedExternalSamplesDlg::DoDataExchange(CDataExchange *pDX)
{
ResizableDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_LIST1, m_List);
}
ModifiedExternalSamplesDlg::ModifiedExternalSamplesDlg(CModDoc &modDoc, CWnd *parent)
: ResizableDialog(IDD_MODIFIEDSAMPLES, parent)
, m_modDoc(modDoc)
, m_sndFile(modDoc.GetSoundFile())
{
}
BOOL ModifiedExternalSamplesDlg::OnInitDialog()
{
ResizableDialog::OnInitDialog();
// Initialize table
const CListCtrlEx::Header headers[] =
{
{_T("Sample"), 120, LVCFMT_LEFT},
{_T("Status"), 54, LVCFMT_LEFT},
{_T("External Filename"), 262, LVCFMT_LEFT},
};
m_List.SetHeaders(headers);
m_List.SetExtendedStyle(m_List.GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_CHECKBOXES);
GenerateList();
SetWindowText((_T("Modified External Samples - ") + m_modDoc.GetPathNameMpt().GetFullFileName().AsNative()).c_str());
return TRUE;
}
void ModifiedExternalSamplesDlg::GenerateList()
{
m_List.SetRedraw(FALSE);
m_List.DeleteAllItems();
CString s;
mpt::winstring status;
for(SAMPLEINDEX smp = 1; smp <= m_sndFile.GetNumSamples(); smp++)
{
if(!m_sndFile.GetSample(smp).uFlags[SMP_KEEPONDISK])
continue;
if(m_sndFile.GetSample(smp).uFlags[SMP_MODIFIED])
status = _T("modified");
else if(!m_sndFile.GetSamplePath(smp).IsFile())
status = _T("missing");
else
continue;
s.Format(_T("%02u: "), smp);
s += mpt::ToCString(m_sndFile.GetCharsetInternal(), m_sndFile.GetSampleName(smp));
int insertAt = m_List.InsertItem(m_List.GetItemCount(), s);
if(insertAt == -1)
continue;
m_List.SetItemText(insertAt, 1, status.c_str());
m_List.SetItemText(insertAt, 2, m_sndFile.GetSamplePath(smp).AsNative().c_str());
m_List.SetCheck(insertAt, TRUE);
m_List.SetItemData(insertAt, smp);
}
m_List.SetRedraw(TRUE);
CheckDlgButton(IDC_CHECK1, BST_CHECKED);
OnSelectionChanged(nullptr, nullptr);
// Nothing modified?
if(!m_List.GetItemCount())
OnOK();
}
void ModifiedExternalSamplesDlg::OnCheckAll()
{
const BOOL check = IsDlgButtonChecked(IDC_CHECK1) ? TRUE : FALSE;
const int count = m_List.GetItemCount();
for(int i = 0; i < count; i++)
{
m_List.SetCheck(i, check);
}
}
void ModifiedExternalSamplesDlg::OnSelectionChanged(NMHDR *, LRESULT *)
{
int numChecked = 0;
const int count = m_List.GetItemCount();
for(int i = 0; i < count; i++)
{
if(m_List.GetCheck(i))
numChecked++;
}
const TCHAR *embedText, *saveText;
if(numChecked == count)
{
embedText = _T("&Embed All");
saveText = _T("&Save All");
} else if(!numChecked)
{
embedText = _T("&Embed None");
saveText = _T("&Save None");
} else
{
embedText = _T("&Embed Selected");
saveText = _T("&Save Selected");
}
GetDlgItem(IDOK)->SetWindowText(embedText);
GetDlgItem(IDC_SAVE)->SetWindowText(saveText);
}
void ModifiedExternalSamplesDlg::Execute(bool doSave)
{
ScopedLogCapturer log(m_modDoc, _T("Modified Samples"), this);
bool ok = true;
const int count = m_List.GetItemCount();
for(int i = 0; i < count; i++)
{
if(!m_List.GetCheck(i))
continue;
SAMPLEINDEX smp = static_cast<SAMPLEINDEX>(m_List.GetItemData(i));
if(doSave)
{
ok &= m_modDoc.SaveSample(smp);
} else
{
m_sndFile.GetSample(smp).uFlags.reset(SMP_KEEPONDISK);
m_modDoc.SetModified();
}
}
if(ok)
ResizableDialog::OnOK();
else
ResizableDialog::OnCancel();
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,73 @@
/*
* ExternalSamples.h
* -----------------
* Purpose: Dialogs for locating missing external samples and handling modified samples
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "ResizableDialog.h"
#include "CListCtrl.h"
OPENMPT_NAMESPACE_BEGIN
class CModDoc;
class CSoundFile;
class MissingExternalSamplesDlg : public ResizableDialog
{
protected:
CModDoc &m_modDoc;
CSoundFile &m_sndFile;
CListCtrlEx m_List;
bool m_isScanning = false;
public:
MissingExternalSamplesDlg(CModDoc &modDoc, CWnd *parent);
protected:
void GenerateList();
bool SetSample(SAMPLEINDEX smp, const mpt::PathString &fileName);
void DoDataExchange(CDataExchange *pDX) override;
BOOL OnInitDialog() override;
afx_msg void OnSetPath(NMHDR *, LRESULT *);
afx_msg void OnScanFolder();
DECLARE_MESSAGE_MAP()
};
class ModifiedExternalSamplesDlg : public ResizableDialog
{
protected:
CModDoc &m_modDoc;
CSoundFile &m_sndFile;
CListCtrlEx m_List;
public:
ModifiedExternalSamplesDlg(CModDoc &modDoc, CWnd *parent);
protected:
void GenerateList();
void Execute(bool doSave);
void DoDataExchange(CDataExchange *pDX) override;
BOOL OnInitDialog() override;
void OnOK() override { Execute(false); }
afx_msg void OnSaveSelected() { Execute(true); }
afx_msg void OnCheckAll();
afx_msg void OnSelectionChanged(NMHDR *pNMHDR, LRESULT *pResult);
DECLARE_MESSAGE_MAP()
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,229 @@
/*
* FileDialog.cpp
* --------------
* Purpose: File and folder selection dialogs implementation.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "FileDialog.h"
#include "Mainfrm.h"
#include "InputHandler.h"
OPENMPT_NAMESPACE_BEGIN
class CFileDialogEx : public CFileDialog
{
public:
CFileDialogEx(bool bOpenFileDialog,
LPCTSTR lpszDefExt,
LPCTSTR lpszFileName,
DWORD dwFlags,
LPCTSTR lpszFilter,
CWnd *pParentWnd,
DWORD dwSize,
BOOL bVistaStyle,
bool preview)
: CFileDialog(bOpenFileDialog ? TRUE : FALSE, lpszDefExt, lpszFileName, dwFlags, lpszFilter, pParentWnd, dwSize, bVistaStyle)
, m_fileNameBuf(65536)
, doPreview(preview)
, played(false)
{
// MFC's filename buffer is way too small for multi-selections of a large number of files.
_tcsncpy(m_fileNameBuf.data(), lpszFileName, m_fileNameBuf.size());
m_fileNameBuf.back() = '\0';
m_ofn.lpstrFile = m_fileNameBuf.data();
m_ofn.nMaxFile = mpt::saturate_cast<DWORD>(m_fileNameBuf.size());
}
~CFileDialogEx()
{
if(played)
{
CMainFrame::GetMainFrame()->StopPreview();
}
}
#if NTDDI_VERSION >= NTDDI_VISTA
// MFC's AddPlace() is declared as throw() but can in fact throw if any of the COM calls fail, e.g. because the place does not exist.
// Avoid this by re-implementing our own version which doesn't throw.
void AddPlace(const mpt::PathString &path)
{
if(m_bVistaStyle && path.IsDirectory())
{
CComPtr<IShellItem> shellItem;
HRESULT hr = SHCreateItemFromParsingName(path.ToWide().c_str(), nullptr, IID_IShellItem, reinterpret_cast<void **>(&shellItem));
if(SUCCEEDED(hr))
{
static_cast<IFileDialog*>(m_pIFileDialog)->AddPlace(shellItem, FDAP_TOP);
}
}
}
#endif
protected:
std::vector<TCHAR> m_fileNameBuf;
CString oldName;
bool doPreview, played;
void OnFileNameChange() override
{
if(doPreview)
{
CString name = GetPathName();
if(!name.IsEmpty() && name != oldName)
{
oldName = name;
if(CMainFrame::GetMainFrame()->PlaySoundFile(mpt::PathString::FromCString(name), NOTE_MIDDLEC))
{
played = true;
}
}
}
CFileDialog::OnFileNameChange();
}
};
// Display the file dialog.
bool FileDialog::Show(CWnd *parent)
{
m_filenames.clear();
// First, set up the dialog...
CFileDialogEx dlg(m_load,
m_defaultExtension.empty() ? nullptr : m_defaultExtension.c_str(),
m_defaultFilename.c_str(),
OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_ENABLESIZING | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | (m_multiSelect ? OFN_ALLOWMULTISELECT : 0) | (m_load ? 0 : (OFN_OVERWRITEPROMPT | OFN_NOREADONLYRETURN)),
m_extFilter.c_str(),
parent != nullptr ? parent : CMainFrame::GetMainFrame(),
0,
(mpt::OS::Windows::IsWine() || mpt::OS::Windows::Version::Current().IsBefore(mpt::OS::Windows::Version::WinVista)) ? FALSE : TRUE,
m_preview && TrackerSettings::Instance().previewInFileDialogs);
OPENFILENAME &ofn = dlg.GetOFN();
ofn.nFilterIndex = m_filterIndex != nullptr ? *m_filterIndex : 0;
if(!m_workingDirectory.empty())
{
ofn.lpstrInitialDir = m_workingDirectory.c_str();
}
#if NTDDI_VERSION >= NTDDI_VISTA
const auto places =
{
&TrackerSettings::Instance().PathPluginPresets,
&TrackerSettings::Instance().PathPlugins,
&TrackerSettings::Instance().PathSamples,
&TrackerSettings::Instance().PathInstruments,
&TrackerSettings::Instance().PathSongs,
};
for(const auto place : places)
{
dlg.AddPlace(place->GetDefaultDir());
}
for(const auto &place : m_places)
{
dlg.AddPlace(place);
}
#endif
// Do it!
BypassInputHandler bih;
if(dlg.DoModal() != IDOK)
{
return false;
}
// Retrieve variables
if(m_filterIndex != nullptr)
*m_filterIndex = ofn.nFilterIndex;
if(m_multiSelect)
{
#if NTDDI_VERSION >= NTDDI_VISTA
// Multiple files might have been selected
if(CComPtr<IShellItemArray> shellItems = dlg.GetResults(); shellItems != nullptr)
{
// Using the old-style GetNextPathName doesn't work properly when the user performs a search and selects files from different folders.
// Hence we use that only as a fallback.
DWORD numItems = 0;
shellItems->GetCount(&numItems);
for(DWORD i = 0; i < numItems; i++)
{
CComPtr<IShellItem> shellItem;
shellItems->GetItemAt(i, &shellItem);
LPWSTR filePath = nullptr;
if(HRESULT hr = shellItem->GetDisplayName(SIGDN_FILESYSPATH, &filePath); SUCCEEDED(hr))
{
m_filenames.push_back(mpt::PathString::FromWide(filePath));
::CoTaskMemFree(filePath);
}
}
} else
#endif
{
POSITION pos = dlg.GetStartPosition();
while(pos != nullptr)
{
m_filenames.push_back(mpt::PathString::FromCString(dlg.GetNextPathName(pos)));
}
}
} else
{
// Only one file
m_filenames.push_back(mpt::PathString::FromCString(dlg.GetPathName()));
}
if(m_filenames.empty())
{
return false;
}
m_workingDirectory = m_filenames.front().AsNative().substr(0, ofn.nFileOffset);
m_extension = m_filenames.front().AsNative().substr(ofn.nFileExtension);
return true;
}
// Helper callback to set start path.
int CALLBACK BrowseForFolder::BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM /*lParam*/, LPARAM lpData)
{
if(uMsg == BFFM_INITIALIZED && lpData != NULL)
{
const BrowseForFolder *that = reinterpret_cast<BrowseForFolder *>(lpData);
SendMessage(hwnd, BFFM_SETSELECTION, TRUE, reinterpret_cast<LPARAM>(that->m_workingDirectory.AsNative().c_str()));
}
return 0;
}
// Display the folder dialog.
bool BrowseForFolder::Show(CWnd *parent)
{
// Note: MFC's CFolderPickerDialog won't work on pre-Vista systems, as it tries to use OPENFILENAME.
BypassInputHandler bih;
TCHAR path[MAX_PATH];
BROWSEINFO bi;
MemsetZero(bi);
bi.hwndOwner = (parent != nullptr ? parent : theApp.m_pMainWnd)->m_hWnd;
if(!m_caption.IsEmpty()) bi.lpszTitle = m_caption;
bi.pszDisplayName = path;
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;
bi.lpfn = BrowseCallbackProc;
bi.lParam = reinterpret_cast<LPARAM>(this);
LPITEMIDLIST pid = SHBrowseForFolder(&bi);
bool success = pid != nullptr && SHGetPathFromIDList(pid, path);
CoTaskMemFree(pid);
if(success)
{
m_workingDirectory = mpt::PathString::FromNative(path);
}
return success;
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,120 @@
/*
* FileDialog.h
* ------------
* Purpose: File and folder selection dialogs implementation.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include <string>
#include <vector>
OPENMPT_NAMESPACE_BEGIN
// Generic open / save file dialog. Cannot be instanced by the user, use OpenFileDialog / SaveFileDialog instead.
class FileDialog
{
public:
using PathList = std::vector<mpt::PathString>;
protected:
mpt::RawPathString m_defaultExtension;
mpt::RawPathString m_defaultFilename;
mpt::RawPathString m_extFilter;
mpt::RawPathString m_lastPreviewFile;
mpt::RawPathString m_workingDirectory;
mpt::RawPathString m_extension;
PathList m_filenames;
PathList m_places;
int *m_filterIndex = nullptr;
bool m_load;
bool m_multiSelect = false;
bool m_preview = false;
protected:
FileDialog(bool load) : m_load(load) { }
public:
// Default extension to use if none is specified.
FileDialog &DefaultExtension(const mpt::PathString &ext) { m_defaultExtension = ext.AsNative(); return *this; }
FileDialog &DefaultExtension(const AnyStringLocale &ext) { m_defaultExtension = mpt::ToWin(ext); return *this; }
// Default suggested filename.
FileDialog &DefaultFilename(const mpt::PathString &name) { m_defaultFilename = name.AsNative(); return *this; }
FileDialog &DefaultFilename(const AnyStringLocale &name) { m_defaultFilename = mpt::ToWin(name); return *this; }
// List of possible extensions. Format: "description|extensions|...|description|extensions||"
FileDialog &ExtensionFilter(const mpt::PathString &filter) { m_extFilter = filter.AsNative(); return *this; }
FileDialog &ExtensionFilter(const AnyStringLocale &filter) { m_extFilter = mpt::ToWin(filter); return *this; }
// Default directory of the dialog.
FileDialog &WorkingDirectory(const mpt::PathString &dir) { m_workingDirectory = dir.AsNative(); return *this; }
// Pointer to a variable holding the index of the last extension filter to use. Holds the selected filter after the dialog has been closed.
FileDialog &FilterIndex(int *index) { m_filterIndex = index; return *this; }
// Enable preview of instrument files (if globally enabled).
FileDialog &EnableAudioPreview() { m_preview = true; return *this; }
// Add a directory to the application-specific quick-access directories in the file dialog
FileDialog &AddPlace(mpt::PathString path) { m_places.push_back(std::move(path)); return *this; }
// Show the file selection dialog.
bool Show(CWnd *parent = nullptr);
// Get some selected file. Mostly useful when only one selected file is possible anyway.
mpt::PathString GetFirstFile() const
{
if(!m_filenames.empty())
return m_filenames.front();
else
return {};
}
// Gets a reference to all selected filenames.
const PathList &GetFilenames() const { return m_filenames; }
// Gets directory in which the selected files are placed.
mpt::PathString GetWorkingDirectory() const { return mpt::PathString::FromNative(m_workingDirectory); }
// Gets the extension of the first selected file, without dot.
mpt::PathString GetExtension() const { return mpt::PathString::FromNative(m_extension); }
};
// Dialog for opening files
class OpenFileDialog : public FileDialog
{
public:
OpenFileDialog() : FileDialog(true) { }
// Enable selection of multiple files
OpenFileDialog &AllowMultiSelect() { m_multiSelect = true; return *this; }
};
// Dialog for saving files
class SaveFileDialog : public FileDialog
{
public:
SaveFileDialog() : FileDialog(false) { }
};
// Folder browser.
class BrowseForFolder
{
protected:
mpt::PathString m_workingDirectory;
CString m_caption;
public:
BrowseForFolder(const mpt::PathString &dir, const CString &caption) : m_workingDirectory(dir), m_caption(caption) { }
// Show the folder selection dialog.
bool Show(CWnd *parent = nullptr);
// Gets selected directory.
mpt::PathString GetDirectory() const { return m_workingDirectory; }
protected:
static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData);
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,90 @@
/*
* FolderScanner.cpp
* -----------------
* Purpose: Class for finding files in folders.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "FolderScanner.h"
#include <tchar.h>
OPENMPT_NAMESPACE_BEGIN
FolderScanner::FolderScanner(const mpt::PathString &path, FlagSet<ScanType> type, mpt::PathString filter)
: m_paths(1, path)
, m_filter(std::move(filter))
, m_hFind(INVALID_HANDLE_VALUE)
, m_type(type)
{
MemsetZero(m_wfd);
}
FolderScanner::~FolderScanner()
{
FindClose(m_hFind);
}
bool FolderScanner::Next(mpt::PathString &file)
{
bool found = false;
do
{
if(m_hFind == INVALID_HANDLE_VALUE)
{
if(m_paths.empty())
{
return false;
}
m_currentPath = m_paths.back();
m_paths.pop_back();
m_currentPath.EnsureTrailingSlash();
m_hFind = FindFirstFile((m_currentPath + m_filter).AsNative().c_str(), &m_wfd);
}
BOOL nextFile = FALSE;
if(m_hFind != INVALID_HANDLE_VALUE)
{
do
{
file = m_currentPath + mpt::PathString::FromNative(m_wfd.cFileName);
if(m_wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if(_tcscmp(m_wfd.cFileName, _T("..")) && _tcscmp(m_wfd.cFileName, _T(".")))
{
if(m_type[kFindInSubDirectories])
{
// Add sub directory
m_paths.push_back(file);
}
if(m_type[kOnlyDirectories])
{
found = true;
}
}
} else if(m_type[kOnlyFiles])
{
found = true;
}
} while((nextFile = FindNextFile(m_hFind, &m_wfd)) != FALSE && !found);
}
if(nextFile == FALSE)
{
// Done with this directory, advance to next
if(m_hFind != INVALID_HANDLE_VALUE)
{
FindClose(m_hFind);
}
m_hFind = INVALID_HANDLE_VALUE;
}
} while(!found);
return true;
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,48 @@
/*
* FolderScanner.h
* ---------------
* Purpose: Class for finding files in folders.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "../common/mptPathString.h"
OPENMPT_NAMESPACE_BEGIN
class FolderScanner
{
public:
enum ScanType
{
kOnlyFiles = 0x01,
kOnlyDirectories = 0x02,
kFilesAndDirectories = kOnlyFiles | kOnlyDirectories,
kFindInSubDirectories = 0x04,
};
protected:
std::vector<mpt::PathString> m_paths;
mpt::PathString m_currentPath;
mpt::PathString m_filter;
HANDLE m_hFind;
WIN32_FIND_DATA m_wfd;
FlagSet<ScanType> m_type;
public:
FolderScanner(const mpt::PathString &path, FlagSet<ScanType> type, mpt::PathString filter = MPT_PATHSTRING("*.*"));
~FolderScanner();
// Return one file or directory at a time in parameter file. Returns true if a file was found (file parameter is valid), false if no more files can be found (file parameter is not touched).
bool Next(mpt::PathString &file);
};
MPT_DECLARE_ENUM(FolderScanner::ScanType)
OPENMPT_NAMESPACE_END
@@ -0,0 +1,227 @@
/*
* GeneralConfigDlg.cpp
* --------------------
* Purpose: Implementation of the general settings dialog.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "Mainfrm.h"
#include "GeneralConfigDlg.h"
#include "Settings.h"
#include "FileDialog.h"
#include "../common/mptStringBuffer.h"
#include "FolderScanner.h"
OPENMPT_NAMESPACE_BEGIN
BEGIN_MESSAGE_MAP(COptionsGeneral, CPropertyPage)
ON_LBN_SELCHANGE(IDC_LIST1, &COptionsGeneral::OnOptionSelChanged)
ON_CLBN_CHKCHANGE(IDC_LIST1, &COptionsGeneral::OnSettingsChanged)
ON_COMMAND(IDC_RADIO1, &COptionsGeneral::OnSettingsChanged)
ON_COMMAND(IDC_RADIO2, &COptionsGeneral::OnSettingsChanged)
ON_COMMAND(IDC_RADIO3, &COptionsGeneral::OnSettingsChanged)
ON_EN_CHANGE(IDC_EDIT1, &COptionsGeneral::OnSettingsChanged)
ON_CBN_SELCHANGE(IDC_COMBO1, &COptionsGeneral::OnDefaultTypeChanged)
ON_CBN_SELCHANGE(IDC_COMBO2, &COptionsGeneral::OnTemplateChanged)
ON_CBN_EDITCHANGE(IDC_COMBO2, &COptionsGeneral::OnTemplateChanged)
ON_COMMAND(IDC_BUTTON1, &COptionsGeneral::OnBrowseTemplate)
END_MESSAGE_MAP()
static constexpr struct GeneralOptionsDescriptions
{
uint32 flag;
const char *name, *description;
} generalOptionsList[] =
{
{PATTERN_PLAYNEWNOTE, "Play new notes while recording", "When this option is enabled, notes entered in the pattern editor will always be played (If not checked, notes won't be played in record mode)."},
{PATTERN_PLAYEDITROW, "Play whole row while recording", "When this option is enabled, all notes on the current row are played when entering notes in the pattern editor."},
{PATTERN_PLAYNAVIGATEROW, "Play whole row when navigating", "When this option is enabled, all notes on the current row are played when navigating vertically in the pattern editor."},
{PATTERN_PLAYTRANSPOSE, "Play notes when transposing", "When transposing a single note, the new note is previewed."},
{PATTERN_CENTERROW, "Always center active row", "Turn on this option to have the active row always centered in the pattern editor."},
{PATTERN_SMOOTHSCROLL, "Smooth pattern scrolling", "Scroll patterns tick by tick rather than row by row at the expense of an increased CPU load."},
{PATTERN_HEXDISPLAY, "Display rows in hex", "With this option enabled, row numbers and sequence numbers will be displayed in hexadecimal."},
{PATTERN_WRAP, "Cursor wrap in pattern editor", "When this option is active, going past the end of a pattern row or channel will move the cursor to the beginning. When \"Continuous scroll\"-option is enabled, row wrap is disabled."},
{PATTERN_DRAGNDROPEDIT, "Drag and Drop Editing", "Enable moving a selection in the pattern editor (copying if pressing shift while dragging)"},
{PATTERN_FLATBUTTONS, "Flat Buttons", "Use flat buttons in toolbars"},
{PATTERN_SINGLEEXPAND, "Single click to expand tree", "Single-clicking in the left tree view will expand a node."},
{PATTERN_MUTECHNMODE, "Ignored muted channels", "Notes will not be played on muted channels (unmuting will only start on a new note)."},
{PATTERN_NOEXTRALOUD, "No loud sample preview", "Disable loud playback of samples in the sample/instrument editor. Sample volume depends on the sample volume slider on the general tab when activated (if disabled, samples are previewed at 0 dB)."},
{PATTERN_SHOWPREVIOUS, "Show Prev/Next patterns", "Displays grayed-out version of the previous/next patterns in the pattern editor. Does not work if \"always center active row\" is disabled."},
{PATTERN_CONTSCROLL, "Continuous scroll", "Jumps to the next pattern when moving past the end of a pattern"},
{PATTERN_KBDNOTEOFF, "Record note off", "Record note off when a key is released on the PC keyboard."},
{PATTERN_FOLLOWSONGOFF, "Follow Song off by default", "Ensure Follow Song is off when opening or starting a new song."},
{PATTERN_NOFOLLOWONCLICK,"Disable Follow Song on click", "Follow Song is deactivated when clicking into the pattern."},
{PATTERN_OLDCTXMENUSTYLE, "Old style pattern context menu", "Check this option to hide unavailable items in the pattern editor context menu. Uncheck to grey-out unavailable items instead."},
{PATTERN_SYNCMUTE, "Maintain sample sync on mute", "Samples continue to be processed when channels are muted (like in IT2 and FT2)"},
{PATTERN_SYNCSAMPLEPOS, "Maintain sample sync on seek", "Sample that are still active from previous patterns are continued to be played after seeking.\nNote: Some pattern commands may prevent samples from being synced. This feature may slow down seeking."},
{PATTERN_AUTODELAY, "Automatic delay commands", "Automatically insert appropriate note-delay commands when recording notes during live playback."},
{PATTERN_NOTEFADE, "Note fade on key up", "Enable to fade / stop notes on key up in pattern tab."},
{PATTERN_OVERFLOWPASTE, "Overflow paste mode", "Wrap pasted pattern data into next pattern. This is useful for creating echo channels."},
{PATTERN_RESETCHANNELS, "Reset channels on loop", "If enabled, channels will be reset to their initial state when song looping is enabled.\nNote: This does not affect manual song loops (i.e. triggered by pattern commands) and is not recommended to be enabled."},
{PATTERN_LIVEUPDATETREE,"Update sample status in tree", "If enabled, active samples and instruments will be indicated by a different icon in the treeview."},
{PATTERN_NOCLOSEDIALOG, "Disable modern close dialog", "When closing the main window, a confirmation window is shown for every unsaved document instead of one single window with a list of unsaved documents."},
{PATTERN_DBLCLICKSELECT, "Double-click to select channel", "Instead of showing the note properties, double-clicking a pattern cell selects the whole channel."},
{PATTERN_SHOWDEFAULTVOLUME, "Show default volume commands", "If there is no volume command next to a note + instrument combination, the sample's default volume is shown."},
};
void COptionsGeneral::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CModTypeDlg)
DDX_Control(pDX, IDC_LIST1, m_CheckList);
DDX_Control(pDX, IDC_EDIT1, m_defaultArtist);
DDX_Control(pDX, IDC_COMBO2, m_defaultTemplate);
DDX_Control(pDX, IDC_COMBO1, m_defaultFormat);
//}}AFX_DATA_MAP
}
BOOL COptionsGeneral::OnInitDialog()
{
CPropertyPage::OnInitDialog();
m_defaultArtist.SetWindowText(mpt::ToCString(TrackerSettings::Instance().defaultArtist));
const struct
{
MODTYPE type;
const TCHAR *str;
} formats[] =
{
{ MOD_TYPE_MOD, _T("MOD") },
{ MOD_TYPE_XM, _T("XM") },
{ MOD_TYPE_S3M, _T("S3M") },
{ MOD_TYPE_IT, _T("IT") },
{ MOD_TYPE_MPT, _T("MPTM") },
};
m_defaultFormat.SetCurSel(0);
for(const auto &fmt : formats)
{
auto idx = m_defaultFormat.AddString(fmt.str);
m_defaultFormat.SetItemData(idx, fmt.type);
if(fmt.type == TrackerSettings::Instance().defaultModType)
{
m_defaultFormat.SetCurSel(idx);
}
}
const mpt::PathString basePath = theApp.GetConfigPath() + P_("TemplateModules\\");
FolderScanner scanner(basePath, FolderScanner::kOnlyFiles | FolderScanner::kFindInSubDirectories);
mpt::PathString file;
while(scanner.Next(file))
{
mpt::RawPathString fileW = file.AsNative();
fileW = fileW.substr(basePath.Length());
::SendMessage(m_defaultTemplate.m_hWnd, CB_ADDSTRING, 0, (LPARAM)fileW.c_str());
}
file = TrackerSettings::Instance().defaultTemplateFile;
if(file.GetPath() == basePath)
{
file = file.GetFullFileName();
}
m_defaultTemplate.SetWindowText(file.AsNative().c_str());
CheckRadioButton(IDC_RADIO1, IDC_RADIO3, IDC_RADIO1 + TrackerSettings::Instance().defaultNewFileAction);
for(const auto &opt : generalOptionsList)
{
auto idx = m_CheckList.AddString(mpt::ToCString(mpt::Charset::ASCII, opt.name));
const int check = (TrackerSettings::Instance().m_dwPatternSetup & opt.flag) != 0 ? BST_CHECKED : BST_UNCHECKED;
m_CheckList.SetCheck(idx, check);
}
m_CheckList.SetCurSel(0);
OnOptionSelChanged();
return TRUE;
}
void COptionsGeneral::OnOK()
{
TrackerSettings::Instance().defaultArtist = GetWindowTextUnicode(m_defaultArtist);
TrackerSettings::Instance().defaultModType = static_cast<MODTYPE>(m_defaultFormat.GetItemData(m_defaultFormat.GetCurSel()));
TrackerSettings::Instance().defaultTemplateFile = mpt::PathString::FromCString(GetWindowTextString(m_defaultTemplate));
NewFileAction action = nfDefaultFormat;
int newActionRadio = GetCheckedRadioButton(IDC_RADIO1, IDC_RADIO3);
if(newActionRadio == IDC_RADIO2) action = nfSameAsCurrent;
if(newActionRadio == IDC_RADIO3) action = nfDefaultTemplate;
if(action == nfDefaultTemplate && TrackerSettings::Instance().defaultTemplateFile.Get().empty())
{
action = nfDefaultFormat;
::MessageBeep(MB_ICONWARNING);
}
TrackerSettings::Instance().defaultNewFileAction = action;
for(int i = 0; i < mpt::saturate_cast<int>(std::size(generalOptionsList)); i++)
{
const bool check = (m_CheckList.GetCheck(i) != BST_UNCHECKED);
if(check) TrackerSettings::Instance().m_dwPatternSetup |= generalOptionsList[i].flag;
else TrackerSettings::Instance().m_dwPatternSetup &= ~generalOptionsList[i].flag;
}
CMainFrame::GetMainFrame()->SetupMiscOptions();
CPropertyPage::OnOK();
}
BOOL COptionsGeneral::OnSetActive()
{
CMainFrame::m_nLastOptionsPage = OPTIONS_PAGE_GENERAL;
return CPropertyPage::OnSetActive();
}
void COptionsGeneral::OnOptionSelChanged()
{
const char *desc = "";
const int sel = m_CheckList.GetCurSel();
if ((sel >= 0) && (sel < mpt::saturate_cast<int>(std::size(generalOptionsList))))
{
desc = generalOptionsList[sel].description;
}
SetDlgItemText(IDC_TEXT1, mpt::ToCString(mpt::Charset::ASCII, desc));
}
void COptionsGeneral::OnBrowseTemplate()
{
mpt::PathString basePath = theApp.GetInstallPath() + P_("TemplateModules\\");
mpt::PathString defaultFile = mpt::PathString::FromCString(GetWindowTextString(m_defaultTemplate));
if(defaultFile.empty()) defaultFile = TrackerSettings::Instance().defaultTemplateFile;
OpenFileDialog dlg;
if(defaultFile.empty())
{
dlg.WorkingDirectory(basePath);
} else
{
if(defaultFile.AsNative().find_first_of(_T("/\\")) == mpt::RawPathString::npos)
{
// Relative path
defaultFile = basePath + defaultFile;
}
dlg.DefaultFilename(defaultFile);
}
if(dlg.Show(this))
{
defaultFile = dlg.GetFirstFile();
if(defaultFile.GetPath() == basePath)
{
defaultFile = defaultFile.GetFullFileName();
}
m_defaultTemplate.SetWindowText(defaultFile.AsNative().c_str());
OnTemplateChanged();
}
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,42 @@
/*
* GeneralConfigDlg.h
* ------------------
* Purpose: Implementation of the general settings dialog.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
OPENMPT_NAMESPACE_BEGIN
class COptionsGeneral: public CPropertyPage
{
protected:
CEdit m_defaultArtist;
CComboBox m_defaultTemplate, m_defaultFormat;
CCheckListBox m_CheckList;
public:
COptionsGeneral() : CPropertyPage(IDD_OPTIONS_GENERAL) {}
protected:
BOOL OnInitDialog() override;
void OnOK() override;
BOOL OnSetActive() override;
void DoDataExchange(CDataExchange* pDX) override;
afx_msg void OnOptionSelChanged();
afx_msg void OnSettingsChanged() { SetModified(TRUE); }
afx_msg void OnBrowseTemplate();
afx_msg void OnDefaultTypeChanged() { CheckRadioButton(IDC_RADIO1, IDC_RADIO3, IDC_RADIO1); OnSettingsChanged(); }
afx_msg void OnTemplateChanged() { CheckRadioButton(IDC_RADIO1, IDC_RADIO3, IDC_RADIO3); OnSettingsChanged(); }
DECLARE_MESSAGE_MAP();
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,843 @@
/*
* globals.cpp
* -----------
* Purpose: Implementation of various views of the tracker interface.
* Notes : (currently none)
* Authors: 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 "Moddoc.h"
#include "Childfrm.h"
#include "Globals.h"
#include "Ctrl_gen.h"
#include "Ctrl_pat.h"
#include "Ctrl_smp.h"
#include "Ctrl_ins.h"
#include "Ctrl_com.h"
#include "ImageLists.h"
#include "../soundlib/mod_specifications.h"
OPENMPT_NAMESPACE_BEGIN
/////////////////////////////////////////////////////////////////////////////
// CModControlDlg
BEGIN_MESSAGE_MAP(CModControlDlg, CDialog)
//{{AFX_MSG_MAP(CModControlDlg)
ON_WM_SIZE()
#if !defined(MPT_BUILD_RETRO)
ON_MESSAGE(WM_DPICHANGED, &CModControlDlg::OnDPIChanged)
#endif
ON_MESSAGE(WM_MOD_UNLOCKCONTROLS, &CModControlDlg::OnUnlockControls)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, &CModControlDlg::OnToolTipText)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, &CModControlDlg::OnToolTipText)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
CModControlDlg::CModControlDlg(CModControlView &parent, CModDoc &document) : m_modDoc(document), m_sndFile(document.GetSoundFile()), m_parent(parent)
{
m_bInitialized = FALSE;
m_hWndView = NULL;
m_nLockCount = 0;
}
CModControlDlg::~CModControlDlg()
{
ASSERT(m_hWnd == NULL);
}
BOOL CModControlDlg::OnInitDialog()
{
CDialog::OnInitDialog();
m_nDPIx = Util::GetDPIx(m_hWnd);
m_nDPIy = Util::GetDPIy(m_hWnd);
EnableToolTips(TRUE);
return TRUE;
}
LRESULT CModControlDlg::OnDPIChanged(WPARAM wParam, LPARAM)
{
m_nDPIx = LOWORD(wParam);
m_nDPIy = HIWORD(wParam);
return 0;
}
void CModControlDlg::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
if (((nType == SIZE_RESTORED) || (nType == SIZE_MAXIMIZED)) && (cx > 0) && (cy > 0))
{
RecalcLayout();
}
}
LRESULT CModControlDlg::OnModCtrlMsg(WPARAM wParam, LPARAM lParam)
{
switch(wParam)
{
case CTRLMSG_SETVIEWWND:
m_hWndView = (HWND)lParam;
break;
case CTRLMSG_ACTIVATEPAGE:
OnActivatePage(lParam);
break;
case CTRLMSG_DEACTIVATEPAGE:
OnDeactivatePage();
break;
case CTRLMSG_SETFOCUS:
GetParentFrame()->SetActiveView(&m_parent);
SetFocus();
break;
}
return 0;
}
LRESULT CModControlDlg::SendViewMessage(UINT uMsg, LPARAM lParam) const
{
if (m_hWndView) return ::SendMessage(m_hWndView, WM_MOD_VIEWMSG, uMsg, lParam);
return 0;
}
BOOL CModControlDlg::PostViewMessage(UINT uMsg, LPARAM lParam) const
{
if (m_hWndView) return ::PostMessage(m_hWndView, WM_MOD_VIEWMSG, uMsg, lParam);
return FALSE;
}
INT_PTR CModControlDlg::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
{
INT_PTR nHit = CDialog::OnToolHitTest(point, pTI);
if ((nHit >= 0) && (pTI))
{
if ((pTI->lpszText == LPSTR_TEXTCALLBACK) && (pTI->hwnd == m_hWnd))
{
CFrameWnd *pMDIParent = GetParentFrame();
if (pMDIParent) pTI->hwnd = pMDIParent->m_hWnd;
}
}
return nHit;
}
BOOL CModControlDlg::OnToolTipText(UINT nID, NMHDR* pNMHDR, LRESULT* pResult)
{
CChildFrame *pChildFrm = (CChildFrame *)GetParentFrame();
if (pChildFrm) return pChildFrm->OnToolTipText(nID, pNMHDR, pResult);
if (pResult) *pResult = 0;
return FALSE;
}
/////////////////////////////////////////////////////////////////////////////
// CModControlView
BOOL CModTabCtrl::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID)
{
CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
if (!pMainFrm) return FALSE;
if (!CTabCtrl::Create(dwStyle, rect, pParentWnd, nID)) return FALSE;
SendMessage(WM_SETFONT, (WPARAM)pMainFrm->GetGUIFont());
SetImageList(&pMainFrm->m_MiscIcons);
return TRUE;
}
BOOL CModTabCtrl::InsertItem(int nIndex, LPCTSTR pszText, LPARAM lParam, int iImage)
{
TC_ITEM tci;
tci.mask = TCIF_TEXT | TCIF_PARAM | TCIF_IMAGE;
tci.pszText = const_cast<LPTSTR>(pszText);
tci.lParam = lParam;
tci.iImage = iImage;
return CTabCtrl::InsertItem(nIndex, &tci);
}
LPARAM CModTabCtrl::GetItemData(int nIndex)
{
TC_ITEM tci;
tci.mask = TCIF_PARAM;
tci.lParam = 0;
if (!GetItem(nIndex, &tci)) return 0;
return tci.lParam;
}
/////////////////////////////////////////////////////////////////////////////////
// CModControlView
IMPLEMENT_DYNCREATE(CModControlView, CView)
BEGIN_MESSAGE_MAP(CModControlView, CView)
//{{AFX_MSG_MAP(CModControlView)
ON_WM_SIZE()
ON_WM_DESTROY()
ON_NOTIFY(TCN_SELCHANGE, IDC_TABCTRL1, &CModControlView::OnTabSelchange)
ON_MESSAGE(WM_MOD_ACTIVATEVIEW, &CModControlView::OnActivateModView)
ON_MESSAGE(WM_MOD_CTRLMSG, &CModControlView::OnModCtrlMsg)
ON_MESSAGE(WM_MOD_GETTOOLTIPTEXT, &CModControlView::OnGetToolTipText)
ON_COMMAND(ID_EDIT_CUT, &CModControlView::OnEditCut)
ON_COMMAND(ID_EDIT_COPY, &CModControlView::OnEditCopy)
ON_COMMAND(ID_EDIT_PASTE, &CModControlView::OnEditPaste)
ON_COMMAND(ID_EDIT_MIXPASTE, &CModControlView::OnEditMixPaste)
ON_COMMAND(ID_EDIT_MIXPASTE_ITSTYLE, &CModControlView::OnEditMixPasteITStyle)
ON_COMMAND(ID_EDIT_FIND, &CModControlView::OnEditFind)
ON_COMMAND(ID_EDIT_FINDNEXT, &CModControlView::OnEditFindNext)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
CModControlView::CModControlView()
{
MemsetZero(m_Pages);
m_nActiveDlg = -1;
m_nInstrumentChanged = -1;
m_hWndView = NULL;
m_hWndMDI = NULL;
}
BOOL CModControlView::PreCreateWindow(CREATESTRUCT& cs)
{
return CView::PreCreateWindow(cs);
}
void CModControlView::OnInitialUpdate() // called first time after construct
{
CView::OnInitialUpdate();
CRect rect;
CChildFrame *pParentFrame = (CChildFrame *)GetParentFrame();
if (pParentFrame) m_hWndView = pParentFrame->GetHwndView();
GetClientRect(&rect);
m_TabCtrl.Create(WS_CHILD|WS_VISIBLE|TCS_FOCUSNEVER|TCS_FORCELABELLEFT, rect, this, IDC_TABCTRL1);
UpdateView(UpdateHint().ModType());
SetActivePage(0);
}
void CModControlView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
if (((nType == SIZE_RESTORED) || (nType == SIZE_MAXIMIZED)) && (cx > 0) && (cy > 0))
{
RecalcLayout();
}
}
void CModControlView::RecalcLayout()
{
CRect rcClient;
if (m_TabCtrl.m_hWnd == NULL) return;
GetClientRect(&rcClient);
if ((m_nActiveDlg >= 0) && (m_nActiveDlg < MAX_PAGES) && (m_Pages[m_nActiveDlg]))
{
CWnd *pDlg = m_Pages[m_nActiveDlg];
CRect rect = rcClient;
m_TabCtrl.AdjustRect(FALSE, &rect);
HDWP hdwp = BeginDeferWindowPos(2);
DeferWindowPos(hdwp, m_TabCtrl.m_hWnd, NULL, rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(), SWP_NOZORDER);
DeferWindowPos(hdwp, pDlg->m_hWnd, NULL, rect.left, rect.top, rect.Width(), rect.Height(), SWP_NOZORDER);
EndDeferWindowPos(hdwp);
} else
{
m_TabCtrl.MoveWindow(&rcClient);
}
}
void CModControlView::OnUpdate(CView *, LPARAM lHint, CObject *pHint)
{
UpdateView(UpdateHint::FromLPARAM(lHint), pHint);
}
void CModControlView::ForceRefresh()
{
SetActivePage(GetActivePage());
}
BOOL CModControlView::SetActivePage(int nIndex, LPARAM lParam)
{
CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
CModControlDlg *pDlg = NULL;
if (nIndex == -1) nIndex = m_TabCtrl.GetCurSel();
const UINT nID = static_cast<UINT>(m_TabCtrl.GetItemData(nIndex));
if(nID == 0) return FALSE;
switch(nID)
{
//rewbs.graph
case IDD_CONTROL_GRAPH:
nIndex = 5;
break;
//end rewbs.graph
case IDD_CONTROL_COMMENTS:
nIndex = 4;
break;
case IDD_CONTROL_GLOBALS:
nIndex = 0;
break;
case IDD_CONTROL_PATTERNS:
nIndex = 1;
break;
case IDD_CONTROL_SAMPLES:
nIndex = 2;
break;
case IDD_CONTROL_INSTRUMENTS:
nIndex = 3;
break;
default:
return FALSE;
}
if ((nIndex < 0) || (nIndex >= MAX_PAGES) || (!pMainFrm)) return FALSE;
if (m_Pages[m_nActiveDlg])
m_Pages[m_nActiveDlg]->GetSplitPosRef() = ((CChildFrame *)GetParentFrame())->GetSplitterHeight();
if (nIndex == m_nActiveDlg)
{
pDlg = m_Pages[m_nActiveDlg];
PostMessage(WM_MOD_CTRLMSG, CTRLMSG_ACTIVATEPAGE, lParam);
return TRUE;
}
if ((m_nActiveDlg >= 0) && (m_nActiveDlg < MAX_PAGES))
{
if (m_Pages[m_nActiveDlg])
{
OnModCtrlMsg(CTRLMSG_DEACTIVATEPAGE, 0);
m_Pages[m_nActiveDlg]->ShowWindow(SW_HIDE);
}
m_nActiveDlg = -1;
}
if (m_Pages[nIndex]) //Ctrl window already created?
{
m_nActiveDlg = nIndex;
pDlg = m_Pages[nIndex];
} else //Ctrl window is not created yet - creating one.
{
MPT_ASSERT_ALWAYS(GetDocument() != nullptr);
switch(nID)
{
//rewbs.graph
case IDD_CONTROL_GRAPH:
//pDlg = new CCtrlGraph();
break;
//end rewbs.graph
case IDD_CONTROL_COMMENTS:
pDlg = new CCtrlComments(*this, *GetDocument());
break;
case IDD_CONTROL_GLOBALS:
pDlg = new CCtrlGeneral(*this, *GetDocument());
break;
case IDD_CONTROL_PATTERNS:
pDlg = new CCtrlPatterns(*this, *GetDocument());
break;
case IDD_CONTROL_SAMPLES:
pDlg = new CCtrlSamples(*this, *GetDocument());
break;
case IDD_CONTROL_INSTRUMENTS:
pDlg = new CCtrlInstruments(*this, *GetDocument());
break;
default:
return FALSE;
}
if (!pDlg) return FALSE;
pDlg->SetViewWnd(m_hWndView);
BOOL bStatus = pDlg->Create(nID, this);
if(bStatus == 0) // Creation failed.
{
delete pDlg;
return FALSE;
}
m_nActiveDlg = nIndex;
m_Pages[nIndex] = pDlg;
}
RecalcLayout();
pMainFrm->SetUserText(_T(""));
pMainFrm->SetInfoText(_T(""));
pMainFrm->SetXInfoText(_T("")); //rewbs.xinfo
pDlg->ShowWindow(SW_SHOW);
((CChildFrame *)GetParentFrame())->SetSplitterHeight(pDlg->GetSplitPosRef());
if (m_hWndMDI) ::PostMessage(m_hWndMDI, WM_MOD_CHANGEVIEWCLASS, (WPARAM)lParam, (LPARAM)pDlg);
return TRUE;
}
void CModControlView::OnDestroy()
{
m_nActiveDlg = -1;
for (UINT nIndex=0; nIndex<MAX_PAGES; nIndex++)
{
CModControlDlg *pDlg = m_Pages[nIndex];
if (pDlg)
{
m_Pages[nIndex] = NULL;
pDlg->DestroyWindow();
delete pDlg;
}
}
CView::OnDestroy();
}
void CModControlView::UpdateView(UpdateHint lHint, CObject *pObject)
{
CWnd *pActiveDlg = NULL;
CModDoc *pDoc = GetDocument();
if (!pDoc) return;
// Module type changed: update tabs
if (lHint.GetType()[HINT_MODTYPE])
{
UINT nCount = 4;
UINT mask = 1 | 2 | 4 | 16;
if(pDoc->GetSoundFile().GetModSpecifications().instrumentsMax > 0 || pDoc->GetNumInstruments() > 0)
{
mask |= 8;
//mask |= 32; //rewbs.graph
nCount++;
}
if (nCount != (UINT)m_TabCtrl.GetItemCount())
{
UINT count = 0;
if ((m_nActiveDlg >= 0) && (m_nActiveDlg < MAX_PAGES))
{
pActiveDlg = m_Pages[m_nActiveDlg];
if (pActiveDlg) pActiveDlg->ShowWindow(SW_HIDE);
}
m_TabCtrl.DeleteAllItems();
if (mask & 1) m_TabCtrl.InsertItem(count++, _T("General"), IDD_CONTROL_GLOBALS, IMAGE_GENERAL);
if (mask & 2) m_TabCtrl.InsertItem(count++, _T("Patterns"), IDD_CONTROL_PATTERNS, IMAGE_PATTERNS);
if (mask & 4) m_TabCtrl.InsertItem(count++, _T("Samples"), IDD_CONTROL_SAMPLES, IMAGE_SAMPLES);
if (mask & 8) m_TabCtrl.InsertItem(count++, _T("Instruments"), IDD_CONTROL_INSTRUMENTS, IMAGE_INSTRUMENTS);
//if (mask & 32) m_TabCtrl.InsertItem(count++, _T("Graph"), IDD_CONTROL_GRAPH, IMAGE_GRAPH); //rewbs.graph
if (mask & 16) m_TabCtrl.InsertItem(count++, _T("Comments"), IDD_CONTROL_COMMENTS, IMAGE_COMMENTS);
}
}
// Update child dialogs
for (UINT nIndex=0; nIndex<MAX_PAGES; nIndex++)
{
CModControlDlg *pDlg = m_Pages[nIndex];
if ((pDlg) && (pObject != pDlg)) pDlg->UpdateView(UpdateHint(lHint), pObject);
}
// Restore the displayed child dialog
if (pActiveDlg) pActiveDlg->ShowWindow(SW_SHOW);
}
void CModControlView::OnTabSelchange(NMHDR*, LRESULT* pResult)
{
SetActivePage(m_TabCtrl.GetCurSel());
if (pResult) *pResult = 0;
}
LRESULT CModControlView::OnActivateModView(WPARAM nIndex, LPARAM lParam)
{
if(::GetActiveWindow() != CMainFrame::GetMainFrame()->m_hWnd)
{
// If we are in a dialog (e.g. Amplify Sample), do not allow to switch to a different tab. Otherwise, watch the tracker crash!
return 0;
}
if (m_TabCtrl.m_hWnd)
{
if (nIndex < 100)
{
m_TabCtrl.SetCurSel(static_cast<int>(nIndex));
SetActivePage(static_cast<int>(nIndex), lParam);
} else
// Might be a dialog id IDD_XXXX
{
int nItems = m_TabCtrl.GetItemCount();
for (int i=0; i<nItems; i++)
{
if ((WPARAM)m_TabCtrl.GetItemData(i) == nIndex)
{
m_TabCtrl.SetCurSel(i);
SetActivePage(i, lParam);
break;
}
}
}
}
return 0;
}
LRESULT CModControlView::OnModCtrlMsg(WPARAM wParam, LPARAM lParam)
{
if ((m_nActiveDlg >= 0) && (m_nActiveDlg < MAX_PAGES))
{
CModControlDlg *pActiveDlg = m_Pages[m_nActiveDlg];
if (pActiveDlg)
{
switch(wParam)
{
case CTRLMSG_SETVIEWWND:
{
m_hWndView = (HWND)lParam;
for (UINT i=0; i<MAX_PAGES; i++)
{
if (m_Pages[i]) m_Pages[i]->SetViewWnd(m_hWndView);
}
}
break;
}
return pActiveDlg->OnModCtrlMsg(wParam, lParam);
}
}
return 0;
}
LRESULT CModControlView::OnGetToolTipText(WPARAM uId, LPARAM pszText)
{
if ((m_nActiveDlg >= 0) && (m_nActiveDlg < MAX_PAGES))
{
CModControlDlg *pActiveDlg = m_Pages[m_nActiveDlg];
if (pActiveDlg) return (LRESULT)pActiveDlg->GetToolTipText(static_cast<UINT>(uId), (LPTSTR)pszText);
}
return 0;
}
void CModControlView::SampleChanged(SAMPLEINDEX smp)
{
const CModDoc *modDoc = GetDocument();
if(modDoc && modDoc->GetNumInstruments())
{
INSTRUMENTINDEX k = static_cast<INSTRUMENTINDEX>(GetInstrumentChange());
if(!modDoc->IsChildSample(k, smp))
{
INSTRUMENTINDEX nins = modDoc->FindSampleParent(smp);
if(nins != INSTRUMENTINDEX_INVALID)
{
InstrumentChanged(nins);
}
}
} else
{
InstrumentChanged(smp);
}
}
//////////////////////////////////////////////////////////////////
// CModScrollView
#ifndef WM_MOUSEHWHEEL
#define WM_MOUSEHWHEEL 0x20E // Only available on Vista and newer
#endif
IMPLEMENT_SERIAL(CModScrollView, CScrollView, 0)
BEGIN_MESSAGE_MAP(CModScrollView, CScrollView)
//{{AFX_MSG_MAP(CModScrollView)
ON_WM_DESTROY()
ON_WM_MOUSEWHEEL()
ON_WM_MOUSEHWHEEL()
#if !defined(MPT_BUILD_RETRO)
ON_MESSAGE(WM_DPICHANGED, &CModScrollView::OnDPIChanged)
#endif
ON_MESSAGE(WM_MOD_VIEWMSG, &CModScrollView::OnReceiveModViewMsg)
ON_MESSAGE(WM_MOD_DRAGONDROPPING, &CModScrollView::OnDragonDropping)
ON_MESSAGE(WM_MOD_UPDATEPOSITION, &CModScrollView::OnUpdatePosition)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
LRESULT CModScrollView::SendCtrlMessage(UINT uMsg, LPARAM lParam) const
{
if (m_hWndCtrl) return ::SendMessage(m_hWndCtrl, WM_MOD_CTRLMSG, uMsg, lParam);
return 0;
}
void CModScrollView::SendCtrlCommand(int id) const
{
::SendMessage(m_hWndCtrl, WM_COMMAND, id, 0);
}
BOOL CModScrollView::PostCtrlMessage(UINT uMsg, LPARAM lParam) const
{
if (m_hWndCtrl) return ::PostMessage(m_hWndCtrl, WM_MOD_CTRLMSG, uMsg, lParam);
return FALSE;
}
LRESULT CModScrollView::OnReceiveModViewMsg(WPARAM wParam, LPARAM lParam)
{
return OnModViewMsg(wParam, lParam);
}
void CModScrollView::OnUpdate(CView* pView, LPARAM lHint, CObject*pHint)
{
if (pView != this) UpdateView(UpdateHint::FromLPARAM(lHint), pHint);
}
LRESULT CModScrollView::OnModViewMsg(WPARAM wParam, LPARAM lParam)
{
switch(wParam)
{
case VIEWMSG_SETCTRLWND:
m_hWndCtrl = (HWND)lParam;
break;
case VIEWMSG_SETFOCUS:
case VIEWMSG_SETACTIVE:
GetParentFrame()->SetActiveView(this);
SetFocus();
break;
}
return 0;
}
void CModScrollView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
m_nDPIx = Util::GetDPIx(m_hWnd);
m_nDPIy = Util::GetDPIy(m_hWnd);
}
LRESULT CModScrollView::OnDPIChanged(WPARAM wParam, LPARAM)
{
m_nDPIx = LOWORD(wParam);
m_nDPIy = HIWORD(wParam);
return 0;
}
void CModScrollView::UpdateIndicator(LPCTSTR lpszText)
{
CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
if (pMainFrm) pMainFrm->SetUserText((lpszText) ? lpszText : _T(""));
}
BOOL CModScrollView::OnMouseWheel(UINT fFlags, short zDelta, CPoint point)
{
// we don't handle anything but scrolling just now
if (fFlags & (MK_SHIFT | MK_CONTROL)) return FALSE;
//if the parent is a splitter, it will handle the message
//if (GetParentSplitter(this, TRUE)) return FALSE;
// we can't get out of it--perform the scroll ourselves
return DoMouseWheel(fFlags, zDelta, point);
}
void CModScrollView::OnMouseHWheel(UINT fFlags, short zDelta, CPoint point)
{
// we don't handle anything but scrolling just now
if (fFlags & (MK_SHIFT | MK_CONTROL))
{
CScrollView::OnMouseHWheel(fFlags, zDelta, point);
return;
}
if (OnScrollBy(CSize(zDelta * m_lineDev.cx / WHEEL_DELTA, 0), TRUE))
UpdateWindow();
}
void CModScrollView::OnDestroy()
{
CModDoc *pModDoc = GetDocument();
CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
if ((pMainFrm) && (pModDoc))
{
if (pMainFrm->GetFollowSong(pModDoc) == m_hWnd)
{
pModDoc->SetNotifications(Notification::Default);
pModDoc->SetFollowWnd(NULL);
}
if (pMainFrm->GetMidiRecordWnd() == m_hWnd)
{
pMainFrm->SetMidiRecordWnd(NULL);
}
}
CScrollView::OnDestroy();
}
LRESULT CModScrollView::OnUpdatePosition(WPARAM, LPARAM lParam)
{
Notification *pnotify = (Notification *)lParam;
if (pnotify) return OnPlayerNotify(pnotify);
return 0;
}
BOOL CModScrollView::OnScroll(UINT nScrollCode, UINT nPos, BOOL bDoScroll)
{
SCROLLINFO info;
if(LOBYTE(nScrollCode) == SB_THUMBTRACK)
{
if(GetScrollInfo(SB_HORZ, &info, SIF_TRACKPOS))
nPos = info.nTrackPos;
m_nScrollPosX = nPos;
} else if(HIBYTE(nScrollCode) == SB_THUMBTRACK)
{
if(GetScrollInfo(SB_VERT, &info, SIF_TRACKPOS))
nPos = info.nTrackPos;
m_nScrollPosY = nPos;
}
return CScrollView::OnScroll(nScrollCode, nPos, bDoScroll);
}
BOOL CModScrollView::OnScrollBy(CSize sizeScroll, BOOL bDoScroll)
{
BOOL ret = CScrollView::OnScrollBy(sizeScroll, bDoScroll);
if(ret)
{
SCROLLINFO info;
if(sizeScroll.cx)
{
if(GetScrollInfo(SB_HORZ, &info, SIF_POS))
m_nScrollPosX = info.nPos;
}
if(sizeScroll.cy)
{
if(GetScrollInfo(SB_VERT, &info, SIF_POS))
m_nScrollPosY = info.nPos;
}
}
return ret;
}
int CModScrollView::SetScrollPos(int nBar, int nPos, BOOL bRedraw)
{
if(nBar == SB_HORZ)
m_nScrollPosX = nPos;
else if(nBar == SB_VERT)
m_nScrollPosY = nPos;
return CScrollView::SetScrollPos(nBar, nPos, bRedraw);
}
void CModScrollView::SetScrollSizes(int nMapMode, SIZE sizeTotal, const SIZE& sizePage, const SIZE& sizeLine)
{
CScrollView::SetScrollSizes(nMapMode, sizeTotal, sizePage, sizeLine);
// Fix scroll positions
SCROLLINFO info;
if(GetScrollInfo(SB_HORZ, &info, SIF_POS))
m_nScrollPosX = info.nPos;
if(GetScrollInfo(SB_VERT, &info, SIF_POS))
m_nScrollPosY = info.nPos;
}
BOOL CModScrollView::OnGesturePan(CPoint ptFrom, CPoint ptTo)
{
// On Windows 8 and later, panning with touch gestures does not generate sensible WM_*SCROLL messages.
// OnScrollBy is only ever called with a size of 0/0 in this case.
// WM_GESTURE on the other hand gives us sensible data to work with.
OnScrollBy(ptTo - ptFrom, TRUE);
return TRUE;
}
////////////////////////////////////////////////////////////////////////////
// CModControlBar
BEGIN_MESSAGE_MAP(CModControlBar, CToolBarCtrl)
ON_MESSAGE(WM_HELPHITTEST, &CModControlBar::OnHelpHitTest)
END_MESSAGE_MAP()
BOOL CModControlBar::Init(CImageList &icons, CImageList &disabledIcons)
{
const int imgSize = Util::ScalePixels(16, m_hWnd), btnSizeX = Util::ScalePixels(26, m_hWnd), btnSizeY = Util::ScalePixels(24, m_hWnd);
SetButtonStructSize(sizeof(TBBUTTON));
SetBitmapSize(CSize(imgSize, imgSize));
SetButtonSize(CSize(btnSizeX, btnSizeY));
// Add bitmaps
SetImageList(&icons);
SetDisabledImageList(&disabledIcons);
UpdateStyle();
return TRUE;
}
BOOL CModControlBar::AddButton(UINT nID, int iImage, UINT nStyle, UINT nState)
{
TBBUTTON btn;
btn.iBitmap = iImage;
btn.idCommand = nID;
btn.fsStyle = (BYTE)nStyle;
btn.fsState = (BYTE)nState;
btn.dwData = 0;
btn.iString = 0;
return AddButtons(1, &btn);
}
void CModControlBar::UpdateStyle()
{
if (m_hWnd)
{
LONG lStyleOld = GetWindowLong(m_hWnd, GWL_STYLE);
if (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_FLATBUTTONS)
lStyleOld |= TBSTYLE_FLAT;
else
lStyleOld &= ~TBSTYLE_FLAT;
lStyleOld |= CCS_NORESIZE | CCS_NOPARENTALIGN | CCS_NODIVIDER | TBSTYLE_TOOLTIPS;
SetWindowLong(m_hWnd, GWL_STYLE, lStyleOld);
Invalidate();
}
}
LRESULT CModControlBar::OnHelpHitTest(WPARAM, LPARAM lParam)
{
TBBUTTON tbbn;
POINT point;
point.x = GET_X_LPARAM(lParam);
point.y = GET_Y_LPARAM(lParam);
int ndx = HitTest(&point);
if ((ndx >= 0) && (GetButton(ndx, &tbbn)))
{
return HID_BASE_COMMAND + tbbn.idCommand;
}
return 0;
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,252 @@
/*
* Globals.h
* ---------
* Purpose: Implementation of various views of the tracker interface.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
OPENMPT_NAMESPACE_BEGIN
#ifndef WM_HELPHITTEST
#define WM_HELPHITTEST 0x366
#endif
#ifndef HID_BASE_COMMAND
#define HID_BASE_COMMAND 0x10000
#endif
#define ID_EDIT_MIXPASTE ID_EDIT_PASTE_SPECIAL //rewbs.mixPaste
class CModControlView;
class CModControlBar;
class CModDoc;
class CModControlBar: public CToolBarCtrl
{
public:
BOOL Init(CImageList &icons, CImageList &disabledIcons);
void UpdateStyle();
BOOL AddButton(UINT nID, int iImage=0, UINT nStyle=TBSTYLE_BUTTON, UINT nState=TBSTATE_ENABLED);
afx_msg LRESULT OnHelpHitTest(WPARAM, LPARAM);
DECLARE_MESSAGE_MAP()
};
class CModControlDlg: public CDialog
{
protected:
CModDoc &m_modDoc;
CSoundFile &m_sndFile;
CModControlView &m_parent;
HWND m_hWndView;
LONG m_nLockCount;
int m_nDPIx, m_nDPIy; // Cached DPI settings
BOOL m_bInitialized;
public:
CModControlDlg(CModControlView &parent, CModDoc &document);
virtual ~CModControlDlg();
public:
void SetViewWnd(HWND hwndView) { m_hWndView = hwndView; }
HWND GetViewWnd() const { return m_hWndView; }
LRESULT SendViewMessage(UINT uMsg, LPARAM lParam=0) const;
BOOL PostViewMessage(UINT uMsg, LPARAM lParam=0) const;
LRESULT SwitchToView() const { return SendViewMessage(VIEWMSG_SETACTIVE); }
void LockControls() { m_nLockCount++; }
void UnlockControls() { PostMessage(WM_MOD_UNLOCKCONTROLS); }
bool IsLocked() const { return (m_nLockCount > 0); }
virtual Setting<LONG> &GetSplitPosRef() = 0; //rewbs.varWindowSize
afx_msg void OnEditCut() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_CUT, 0); }
afx_msg void OnEditCopy() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_COPY, 0); }
afx_msg void OnEditPaste() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_PASTE, 0); }
afx_msg void OnEditMixPaste() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_MIXPASTE, 0); }
afx_msg void OnEditMixPasteITStyle() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_MIXPASTE_ITSTYLE, 0); }
afx_msg void OnEditPasteFlood() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_PASTEFLOOD, 0); }
afx_msg void OnEditPushForwardPaste() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_PUSHFORWARDPASTE, 0); }
afx_msg void OnEditFind() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_FIND, 0); }
afx_msg void OnEditFindNext() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_FINDNEXT, 0); }
afx_msg void OnSwitchToView() { if (m_hWndView) ::PostMessage(m_hWndView, WM_MOD_VIEWMSG, VIEWMSG_SETFOCUS, 0); }
//{{AFX_VIRTUAL(CModControlDlg)
void OnOK() override {}
void OnCancel() override {}
virtual void RecalcLayout() = 0;
virtual void UpdateView(UpdateHint, CObject *) = 0;
virtual CRuntimeClass *GetAssociatedViewClass() { return nullptr; }
virtual LRESULT OnModCtrlMsg(WPARAM wParam, LPARAM lParam);
virtual void OnActivatePage(LPARAM) {}
virtual void OnDeactivatePage() {}
BOOL OnInitDialog() override;
INT_PTR OnToolHitTest(CPoint point, TOOLINFO *pTI) const override;
virtual BOOL GetToolTipText(UINT, LPTSTR) { return FALSE; }
//}}AFX_VIRTUAL
//{{AFX_MSG(CModControlDlg)
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg LRESULT OnUnlockControls(WPARAM, LPARAM) { if (m_nLockCount > 0) m_nLockCount--; return 0; }
afx_msg BOOL OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult);
afx_msg LRESULT OnDPIChanged(WPARAM = 0, LPARAM = 0);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
class CModTabCtrl: public CTabCtrl
{
public:
BOOL InsertItem(int nIndex, LPCTSTR pszText, LPARAM lParam=0, int iImage=-1);
BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID);
LPARAM GetItemData(int nIndex);
};
class CModControlView: public CView
{
public:
enum Views
{
VIEW_UNKNOWN = -1,
VIEW_GLOBALS = 0,
VIEW_PATTERNS,
VIEW_SAMPLES,
VIEW_INSTRUMENTS,
VIEW_COMMENTS,
VIEW_PLUGINS,
MAX_PAGES
};
protected:
CModTabCtrl m_TabCtrl;
CModControlDlg *m_Pages[MAX_PAGES];
int m_nActiveDlg, m_nInstrumentChanged;
HWND m_hWndView, m_hWndMDI;
protected: // create from serialization only
CModControlView();
DECLARE_DYNCREATE(CModControlView)
public:
virtual ~CModControlView() {}
CModDoc* GetDocument() const { return (CModDoc *)m_pDocument; }
void SampleChanged(SAMPLEINDEX smp);
void InstrumentChanged(int nInstr=-1) { m_nInstrumentChanged = nInstr; }
int GetInstrumentChange() const { return m_nInstrumentChanged; }
void SetMDIParentFrame(HWND hwnd) { m_hWndMDI = hwnd; }
void ForceRefresh();
CModControlDlg *GetCurrentControlDlg() { return m_Pages[m_nActiveDlg]; }
protected:
void RecalcLayout();
void UpdateView(UpdateHint hint, CObject *pHint = nullptr);
BOOL SetActivePage(int nIndex = -1, LPARAM lParam=-1);
public:
int GetActivePage() const { return m_nActiveDlg; }
protected:
//{{AFX_VIRTUAL(CModControlView)
public:
BOOL PreCreateWindow(CREATESTRUCT& cs) override;
protected:
void OnInitialUpdate() override; // called first time after construct
void OnDraw(CDC *) override {}
void OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) override;
//}}AFX_VIRTUAL
protected:
//{{AFX_MSG(CModControlView)
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnDestroy();
afx_msg void OnTabSelchange(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnEditCut() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_CUT, 0); }
afx_msg void OnEditCopy() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_COPY, 0); }
afx_msg void OnEditPaste() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_PASTE, 0); }
afx_msg void OnEditMixPaste() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_MIXPASTE, 0); } //rewbs.mixPaste
afx_msg void OnEditMixPasteITStyle() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_MIXPASTE_ITSTYLE, 0); }
afx_msg void OnEditFind() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_FIND, 0); }
afx_msg void OnEditFindNext() { if (m_hWndView) ::SendMessage(m_hWndView, WM_COMMAND, ID_EDIT_FINDNEXT, 0); }
afx_msg void OnSwitchToView() { if (m_hWndView) ::PostMessage(m_hWndView, WM_MOD_VIEWMSG, VIEWMSG_SETFOCUS, 0); }
afx_msg LRESULT OnActivateModView(WPARAM, LPARAM);
afx_msg LRESULT OnModCtrlMsg(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnGetToolTipText(WPARAM, LPARAM);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
// Non-client button attributes
#define NCBTNS_MOUSEOVER 0x01
#define NCBTNS_CHECKED 0x02
#define NCBTNS_DISABLED 0x04
#define NCBTNS_PUSHED 0x08
class CModScrollView: public CScrollView
{
protected:
HWND m_hWndCtrl;
int m_nScrollPosX, m_nScrollPosY;
int m_nDPIx, m_nDPIy; // Cached DPI settings
public:
DECLARE_SERIAL(CModScrollView)
CModScrollView() : m_hWndCtrl(nullptr), m_nScrollPosX(0), m_nScrollPosY(0) { }
virtual ~CModScrollView() {}
public:
CModDoc* GetDocument() const { return (CModDoc *)m_pDocument; }
LRESULT SendCtrlMessage(UINT uMsg, LPARAM lParam=0) const;
void SendCtrlCommand(int id) const;
BOOL PostCtrlMessage(UINT uMsg, LPARAM lParam=0) const;
void UpdateIndicator(LPCTSTR lpszText = nullptr);
public:
//{{AFX_VIRTUAL(CModScrollView)
void OnInitialUpdate() override;
void OnDraw(CDC *) override {}
void OnPrepareDC(CDC*, CPrintInfo*) override {}
void OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) override;
virtual void UpdateView(UpdateHint, CObject *) {}
virtual LRESULT OnModViewMsg(WPARAM wParam, LPARAM lParam);
virtual BOOL OnDragonDrop(BOOL, const DRAGONDROP *) { return FALSE; }
virtual LRESULT OnPlayerNotify(Notification *) { return 0; }
//}}AFX_VIRTUAL
CModControlDlg *GetControlDlg() { return static_cast<CModControlView *>(CWnd::FromHandle(m_hWndCtrl))->GetCurrentControlDlg(); }
protected:
//{{AFX_MSG(CModScrollView)
afx_msg void OnDestroy();
afx_msg LRESULT OnReceiveModViewMsg(WPARAM wParam, LPARAM lParam);
afx_msg BOOL OnMouseWheel(UINT fFlags, short zDelta, CPoint point);
afx_msg void OnMouseHWheel(UINT fFlags, short zDelta, CPoint point);
afx_msg LRESULT OnDragonDropping(WPARAM bDoDrop, LPARAM lParam) { return OnDragonDrop((BOOL)bDoDrop, (const DRAGONDROP *)lParam); }
afx_msg LRESULT OnUpdatePosition(WPARAM, LPARAM);
afx_msg LRESULT OnDPIChanged(WPARAM = 0, LPARAM = 0);
// Fixes for 16-bit limitation in MFC's CScrollView
BOOL OnScroll(UINT nScrollCode, UINT nPos, BOOL bDoScroll = TRUE) override;
BOOL OnScrollBy(CSize sizeScroll, BOOL bDoScroll = TRUE) override;
int SetScrollPos(int nBar, int nPos, BOOL bRedraw = TRUE);
void SetScrollSizes(int nMapMode, SIZE sizeTotal, const SIZE& sizePage = CScrollView::sizeDefault, const SIZE& sizeLine = CScrollView::sizeDefault);
BOOL OnGesturePan(CPoint ptFrom, CPoint ptTo) override;
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.
OPENMPT_NAMESPACE_END
@@ -0,0 +1,519 @@
/*
* HTTP.cpp
* --------
* Purpose: Simple HTTP client interface.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "HTTP.h"
#include "mpt/system_error/system_error.hpp"
#include <WinInet.h>
#include "mpt/io/io.hpp"
#include "mpt/io/io_stdstream.hpp"
OPENMPT_NAMESPACE_BEGIN
URI ParseURI(mpt::ustring str)
{
URI uri;
std::size_t scheme_delim_pos = str.find(':');
if(scheme_delim_pos == mpt::ustring::npos)
{
throw bad_uri("no scheme delimiter");
}
if(scheme_delim_pos == 0)
{
throw bad_uri("no scheme");
}
uri.scheme = str.substr(0, scheme_delim_pos);
str = str.substr(scheme_delim_pos + 1);
if(str.substr(0, 2) == U_("//"))
{
str = str.substr(2);
std::size_t authority_delim_pos = str.find_first_of(U_("/?#"));
mpt::ustring authority = str.substr(0, authority_delim_pos);
std::size_t userinfo_delim_pos = authority.find(U_("@"));
if(userinfo_delim_pos != mpt::ustring::npos)
{
mpt::ustring userinfo = authority.substr(0, userinfo_delim_pos);
authority = authority.substr(userinfo_delim_pos + 1);
std::size_t username_delim_pos = userinfo.find(U_(":"));
uri.username = userinfo.substr(0, username_delim_pos);
if(username_delim_pos != mpt::ustring::npos)
{
uri.password = userinfo.substr(username_delim_pos + 1);
}
}
std::size_t beg_bracket_pos = authority.find(U_("["));
std::size_t end_bracket_pos = authority.find(U_("]"));
std::size_t port_delim_pos = authority.find_last_of(U_(":"));
if(beg_bracket_pos != mpt::ustring::npos && end_bracket_pos != mpt::ustring::npos)
{
if(port_delim_pos != mpt::ustring::npos && port_delim_pos > end_bracket_pos)
{
uri.host = authority.substr(0, port_delim_pos);
uri.port = authority.substr(port_delim_pos + 1);
} else
{
uri.host = authority;
}
} else
{
uri.host = authority.substr(0, port_delim_pos);
if(port_delim_pos != mpt::ustring::npos)
{
uri.port = authority.substr(port_delim_pos + 1);
}
}
if(authority_delim_pos != mpt::ustring::npos)
{
str = str.substr(authority_delim_pos);
} else
{
str = U_("");
}
}
std::size_t path_delim_pos = str.find_first_of(U_("?#"));
uri.path = str.substr(0, path_delim_pos);
if(path_delim_pos != mpt::ustring::npos)
{
str = str.substr(path_delim_pos);
std::size_t query_delim_pos = str.find(U_("#"));
if(query_delim_pos != mpt::ustring::npos)
{
if(query_delim_pos > 0)
{
uri.query = str.substr(1, query_delim_pos - 1);
uri.fragment = str.substr(query_delim_pos + 1);
} else
{
uri.fragment = str.substr(query_delim_pos + 1);
}
} else
{
uri.query = str.substr(1);
}
}
return uri;
}
namespace HTTP
{
exception::exception(const mpt::ustring &m)
: std::runtime_error(std::string("HTTP error: ") + mpt::ToCharset(mpt::CharsetException, m))
{
message = m;
}
mpt::ustring exception::GetMessage() const
{
return message;
}
class LastErrorException
: public exception
{
public:
LastErrorException()
: exception(mpt::windows::GetErrorMessage(GetLastError(), GetModuleHandle(TEXT("wininet.dll"))))
{
}
};
struct NativeHandle
{
HINTERNET native_handle;
NativeHandle(HINTERNET h)
: native_handle(h)
{
}
operator HINTERNET() const
{
return native_handle;
}
};
Handle::Handle()
: handle(std::make_unique<NativeHandle>(HINTERNET(NULL)))
{
}
Handle::operator bool() const
{
return handle->native_handle != HINTERNET(NULL);
}
bool Handle::operator!() const
{
return handle->native_handle == HINTERNET(NULL);
}
Handle::Handle(NativeHandle h)
: handle(std::make_unique<NativeHandle>(HINTERNET(NULL)))
{
handle->native_handle = h.native_handle;
}
Handle & Handle::operator=(NativeHandle h)
{
if(handle->native_handle)
{
InternetCloseHandle(handle->native_handle);
handle->native_handle = HINTERNET(NULL);
}
handle->native_handle = h.native_handle;
return *this;
}
Handle::operator NativeHandle ()
{
return *handle;
}
Handle::~Handle()
{
if(handle->native_handle)
{
InternetCloseHandle(handle->native_handle);
handle->native_handle = HINTERNET(NULL);
}
}
InternetSession::InternetSession(mpt::ustring userAgent)
{
internet = NativeHandle(InternetOpen(mpt::ToWin(userAgent).c_str(), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0));
if(!internet)
{
throw HTTP::LastErrorException();
}
}
InternetSession::operator NativeHandle ()
{
return internet;
}
static mpt::winstring Verb(Method method)
{
mpt::winstring result;
switch(method)
{
case Method::Get:
result = _T("GET");
break;
case Method::Head:
result = _T("HEAD");
break;
case Method::Post:
result = _T("POST");
break;
case Method::Put:
result = _T("PUT");
break;
case Method::Delete:
result = _T("DELETE");
break;
case Method::Trace:
result = _T("TRACE");
break;
case Method::Options:
result = _T("OPTIONS");
break;
case Method::Connect:
result = _T("CONNECT");
break;
case Method::Patch:
result = _T("PATCH");
break;
}
return result;
}
static bool IsCachable(Method method)
{
return method == Method::Get || method == Method::Head;
}
namespace
{
class AcceptMimeTypesWrapper
{
private:
std::vector<mpt::winstring> strings;
std::vector<LPCTSTR> array;
public:
AcceptMimeTypesWrapper(AcceptMimeTypes acceptMimeTypes)
{
for(const auto &mimeType : acceptMimeTypes)
{
strings.push_back(mpt::ToWin(mpt::Charset::ASCII, mimeType));
}
array.resize(strings.size() + 1);
for(std::size_t i = 0; i < strings.size(); ++i)
{
array[i] = strings[i].c_str();
}
array[strings.size()] = NULL;
}
operator LPCTSTR*()
{
return strings.empty() ? NULL : array.data();
}
};
}
void Request::progress(Progress progress, uint64 transferred, std::optional<uint64> expectedSize) const
{
if(progressCallback)
{
progressCallback(progress, transferred, expectedSize);
}
}
Result Request::operator()(InternetSession &internet) const
{
progress(Progress::Start, 0, std::nullopt);
Port actualPort = port;
if(actualPort == Port::Default)
{
actualPort = (protocol != Protocol::HTTP) ? Port::HTTPS : Port::HTTP;
}
Handle connection = NativeHandle(InternetConnect(
NativeHandle(internet),
mpt::ToWin(host).c_str(),
static_cast<uint16>(actualPort),
!username.empty() ? mpt::ToWin(username).c_str() : NULL,
!password.empty() ? mpt::ToWin(password).c_str() : NULL,
INTERNET_SERVICE_HTTP,
0,
0));
if(!connection)
{
throw HTTP::LastErrorException();
}
progress(Progress::ConnectionEstablished, 0, std::nullopt);
mpt::ustring queryPath = path;
if(!query.empty())
{
std::vector<mpt::ustring> arguments;
for(const auto &[key, value] : query)
{
if(!value.empty())
{
arguments.push_back(MPT_UFORMAT("{}={}")(key, value));
} else
{
arguments.push_back(MPT_UFORMAT("{}")(key));
}
}
queryPath += U_("?") + mpt::String::Combine(arguments, U_("&"));
}
Handle request = NativeHandle(HttpOpenRequest(
NativeHandle(connection),
Verb(method).c_str(),
mpt::ToWin(path).c_str(),
NULL,
!referrer.empty() ? mpt::ToWin(referrer).c_str() : NULL,
AcceptMimeTypesWrapper(acceptMimeTypes),
0
| ((protocol != Protocol::HTTP) ? INTERNET_FLAG_SECURE : 0)
| (IsCachable(method) ? 0 : INTERNET_FLAG_NO_CACHE_WRITE)
| ((flags & NoCache) ? (INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE) : 0)
| ((flags & AutoRedirect) ? 0 : INTERNET_FLAG_NO_AUTO_REDIRECT)
,
NULL));
if(!request)
{
throw HTTP::LastErrorException();
}
progress(Progress::RequestOpened, 0, std::nullopt);
{
std::string headersString;
if(!dataMimeType.empty())
{
headersString += MPT_AFORMAT("Content-type: {}\r\n")(dataMimeType);
}
if(!headers.empty())
{
for(const auto &[key, value] : headers)
{
headersString += MPT_AFORMAT("{}: {}\r\n")(key, value);
}
}
if(HttpSendRequest(
NativeHandle(request),
!headersString.empty() ? mpt::ToWin(mpt::Charset::ASCII, headersString).c_str() : NULL,
!headersString.empty() ? mpt::saturate_cast<DWORD>(mpt::ToWin(mpt::Charset::ASCII, headersString).length()) : 0,
!data.empty() ? (LPVOID)data.data() : NULL,
!data.empty() ? mpt::saturate_cast<DWORD>(data.size()) : 0)
== FALSE)
{
throw HTTP::LastErrorException();
}
}
progress(Progress::RequestSent, 0, std::nullopt);
Result result;
{
DWORD statusCode = 0;
DWORD length = sizeof(statusCode);
if(HttpQueryInfo(NativeHandle(request), HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &statusCode, &length, NULL) == FALSE)
{
throw HTTP::LastErrorException();
}
result.Status = statusCode;
}
progress(Progress::ResponseReceived, 0, std::nullopt);
DWORD contentLength = static_cast<DWORD>(-1);
{
DWORD length = sizeof(contentLength);
if(HttpQueryInfo(NativeHandle(request), HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &contentLength, &length, NULL) == FALSE)
{
contentLength = static_cast<DWORD>(-1);
}
}
uint64 transferred = 0;
if(contentLength != static_cast<DWORD>(-1))
{
result.ContentLength = contentLength;
}
std::optional<uint64> expectedSize = result.ContentLength;
progress(Progress::TransferBegin, transferred, expectedSize);
{
std::vector<std::byte> resultBuffer;
DWORD bytesRead = 0;
do
{
std::array<std::byte, mpt::IO::BUFFERSIZE_TINY> downloadBuffer;
DWORD availableSize = 0;
if(InternetQueryDataAvailable(NativeHandle(request), &availableSize, 0, NULL) == FALSE)
{
throw HTTP::LastErrorException();
}
availableSize = std::clamp(availableSize, DWORD(0), mpt::saturate_cast<DWORD>(mpt::IO::BUFFERSIZE_TINY));
if(InternetReadFile(NativeHandle(request), downloadBuffer.data(), availableSize, &bytesRead) == FALSE)
{
throw HTTP::LastErrorException();
}
if(outputStream)
{
if(!mpt::IO::WriteRaw(*outputStream, mpt::as_span(downloadBuffer).first(bytesRead)))
{
throw HTTP::exception(U_("Writing output file failed."));
}
} else
{
mpt::append(resultBuffer, downloadBuffer.data(), downloadBuffer.data() + bytesRead);
}
transferred += bytesRead;
progress(Progress::TransferRunning, transferred, expectedSize);
} while(bytesRead != 0);
result.Data = std::move(resultBuffer);
}
progress(Progress::TransferDone, transferred, expectedSize);
return result;
}
Request &Request::SetURI(const URI &uri)
{
if(uri.scheme == U_(""))
{
throw bad_uri("no scheme");
} else if(uri.scheme == U_("http"))
{
protocol = HTTP::Protocol::HTTP;
} else if(uri.scheme == U_("https"))
{
protocol = HTTP::Protocol::HTTPS;
} else
{
throw bad_uri("wrong scheme");
}
host = uri.host;
if(!uri.port.empty())
{
port = HTTP::Port(ConvertStrTo<uint16>(uri.port));
} else
{
port = HTTP::Port::Default;
}
username = uri.username;
password = uri.password;
if(uri.path.empty())
{
path = U_("/");
} else
{
path = uri.path;
}
query.clear();
auto keyvals = mpt::String::Split<mpt::ustring>(uri.query, U_("&"));
for(const auto &keyval : keyvals)
{
std::size_t delim_pos = keyval.find(U_("="));
mpt::ustring key = keyval.substr(0, delim_pos);
mpt::ustring val;
if(delim_pos != mpt::ustring::npos)
{
val = keyval.substr(delim_pos + 1);
}
query.push_back(std::make_pair(key, val));
}
// ignore fragment
return *this;
}
#if defined(MPT_BUILD_RETRO)
Request &Request::InsecureTLSDowngradeWindowsXP()
{
if(mpt::OS::Windows::IsOriginal() && mpt::OS::Windows::Version::Current().IsBefore(mpt::OS::Windows::Version::WinVista))
{
// TLS 1.0 is not enabled by default until IE7. Since WinInet won't let us override this setting, we cannot assume that HTTPS
// is going to work on older systems. Besides... Windows XP is already enough of a security risk by itself. :P
if(protocol == Protocol::HTTPS)
{
protocol = Protocol::HTTP;
}
if(port == Port::HTTPS)
{
port = Port::HTTP;
}
}
return *this;
}
#endif // MPT_BUILD_RETRO
Result SimpleGet(InternetSession &internet, Protocol protocol, const mpt::ustring &host, const mpt::ustring &path)
{
HTTP::Request request;
request.protocol = protocol;
request.host = host;
request.method = HTTP::Method::Get;
request.path = path;
return internet(request);
}
} // namespace HTTP
OPENMPT_NAMESPACE_END
@@ -0,0 +1,257 @@
/*
* HTTP.h
* ------
* Purpose: Simple HTTP client interface.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include <functional>
#include <iosfwd>
#include <optional>
OPENMPT_NAMESPACE_BEGIN
struct URI
{
mpt::ustring scheme;
mpt::ustring username;
mpt::ustring password;
mpt::ustring host;
mpt::ustring port;
mpt::ustring path;
mpt::ustring query;
mpt::ustring fragment;
};
class bad_uri
: public std::runtime_error
{
public:
bad_uri(const std::string &msg)
: std::runtime_error(msg)
{
}
};
URI ParseURI(mpt::ustring str);
namespace HTTP
{
class exception
: public std::runtime_error
{
private:
mpt::ustring message;
public:
exception(const mpt::ustring &m);
mpt::ustring GetMessage() const;
};
class status_exception
: public std::runtime_error
{
public:
status_exception(uint64 status)
: std::runtime_error(MPT_AFORMAT("HTTP status {}")(status))
{
return;
}
};
class Abort
: public exception
{
public:
Abort() :
exception(U_("Operation aborted."))
{
return;
}
};
struct NativeHandle;
class Handle
{
private:
std::unique_ptr<NativeHandle> handle;
public:
Handle();
Handle(const Handle &) = delete;
Handle & operator=(const Handle &) = delete;
explicit operator bool() const;
bool operator!() const;
Handle(NativeHandle h);
Handle & operator=(NativeHandle h);
operator NativeHandle ();
~Handle();
};
class InternetSession
{
private:
Handle internet;
public:
InternetSession(mpt::ustring userAgent);
operator NativeHandle ();
template <typename TRequest>
auto operator()(const TRequest &request) -> decltype(request(*this))
{
return request(*this);
}
template <typename TRequest>
auto Request(const TRequest &request) -> decltype(request(*this))
{
return request(*this);
}
};
enum class Protocol
{
HTTP,
HTTPS,
};
enum class Port : uint16
{
Default = 0,
HTTP = 80,
HTTPS = 443,
};
enum class Method
{
Get,
Head,
Post,
Put,
Delete,
Trace,
Options,
Connect,
Patch,
};
using Query = std::vector<std::pair<mpt::ustring, mpt::ustring>>;
namespace MimeType {
inline std::string Text() { return "text/plain"; }
inline std::string JSON() { return "application/json"; }
inline std::string Binary() { return "application/octet-stream"; }
}
using AcceptMimeTypes = std::vector<std::string>;
namespace MimeTypes {
inline AcceptMimeTypes Text() { return {"text/*"}; }
inline AcceptMimeTypes JSON() { return {MimeType::JSON()}; }
inline AcceptMimeTypes Binary() { return {MimeType::Binary()}; }
}
using Headers = std::vector<std::pair<std::string, std::string>>;
enum Flags
{
None = 0x00u,
NoCache = 0x01u,
AutoRedirect = 0x02u,
};
struct Result
{
uint64 Status = 0;
std::optional<uint64> ContentLength;
std::vector<std::byte> Data;
void CheckStatus(uint64 expected) const
{
if(Status != expected)
{
throw status_exception(Status);
}
}
};
enum class Progress
{
Start = 1,
ConnectionEstablished = 2,
RequestOpened = 3,
RequestSent = 4,
ResponseReceived = 5,
TransferBegin = 6,
TransferRunning = 7,
TransferDone = 8,
};
struct Request
{
Protocol protocol = Protocol::HTTPS;
mpt::ustring host;
Port port = Port::Default;
mpt::ustring username;
mpt::ustring password;
Method method = Method::Get;
mpt::ustring path = U_("/");
Query query;
mpt::ustring referrer;
AcceptMimeTypes acceptMimeTypes;
Flags flags = None;
Headers headers;
std::string dataMimeType;
mpt::const_byte_span data;
std::ostream *outputStream = nullptr;
std::function<void(Progress, uint64, std::optional<uint64>)> progressCallback = nullptr;
Request &SetURI(const URI &uri);
#if defined(MPT_BUILD_RETRO)
Request &InsecureTLSDowngradeWindowsXP();
#endif // MPT_BUILD_RETRO
Result operator()(InternetSession &internet) const;
private:
void progress(Progress progress, uint64 transferred, std::optional<uint64> expectedSize) const;
};
Result SimpleGet(InternetSession &internet, Protocol protocol, const mpt::ustring &host, const mpt::ustring &path);
} // namespace HTTP
OPENMPT_NAMESPACE_END
@@ -0,0 +1,235 @@
/*
* IPCWindow.cpp
* -------------
* Purpose: Hidden window to receive file open commands from another OpenMPT instance
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "IPCWindow.h"
#include "../common/version.h"
#include "Mptrack.h"
OPENMPT_NAMESPACE_BEGIN
namespace IPCWindow
{
static constexpr TCHAR ClassName[] = _T("OpenMPT_IPC_Wnd");
static HWND ipcWindow = nullptr;
static LRESULT CALLBACK IPCWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if(uMsg == WM_COPYDATA)
{
const auto &copyData = *reinterpret_cast<const COPYDATASTRUCT *>(lParam);
LRESULT result = 0;
switch(static_cast<Function>(copyData.dwData))
{
case Function::Open:
{
std::size_t count = copyData.cbData / sizeof(WCHAR);
const WCHAR* data = static_cast<const WCHAR *>(copyData.lpData);
const std::wstring name = std::wstring(data, data + count);
result = theApp.OpenDocumentFile(mpt::PathString::FromWide(name).AsNative().c_str()) ? 1 : 2;
}
break;
case Function::SetWindowForeground:
{
auto mainWnd = theApp.GetMainWnd();
if(mainWnd)
{
if(mainWnd->IsIconic())
{
mainWnd->ShowWindow(SW_RESTORE);
}
mainWnd->SetForegroundWindow();
result = 1;
} else
{
result = 0;
}
}
break;
case Function::GetVersion:
{
result = Version::Current().GetRawVersion();
}
break;
case Function::GetArchitecture:
{
#if MPT_OS_WINDOWS
result = static_cast<int32>(mpt::OS::Windows::GetProcessArchitecture());
#else
result = -1;
#endif
}
break;
case Function::HasSameBinaryPath:
{
std::size_t count = copyData.cbData / sizeof(WCHAR);
const WCHAR* data = static_cast<const WCHAR *>(copyData.lpData);
const std::wstring path = std::wstring(data, data + count);
result = (theApp.GetInstallBinArchPath().ToWide() == path) ? 1 : 0;
}
break;
case Function::HasSameSettingsPath:
{
std::size_t count = copyData.cbData / sizeof(WCHAR);
const WCHAR* data = static_cast<const WCHAR *>(copyData.lpData);
const std::wstring path = std::wstring(data, data + count);
result = (theApp.GetConfigPath().ToWide() == path) ? 1 : 0;
}
break;
default:
result = 0;
break;
}
return result;
}
return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
}
void Open(HINSTANCE hInstance)
{
WNDCLASS ipcWindowClass =
{
0,
IPCWindowProc,
0,
0,
hInstance,
nullptr,
nullptr,
nullptr,
nullptr,
ClassName
};
auto ipcAtom = RegisterClass(&ipcWindowClass);
ipcWindow = CreateWindow(MAKEINTATOM(ipcAtom), _T("OpenMPT IPC Window"), 0, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, 0);
}
void Close()
{
::DestroyWindow(ipcWindow);
ipcWindow = nullptr;
}
LRESULT SendIPC(HWND ipcWnd, Function function, mpt::const_byte_span data)
{
if(!ipcWnd)
{
return 0;
}
if(!mpt::in_range<DWORD>(data.size()))
{
return 0;
}
COPYDATASTRUCT copyData{};
copyData.dwData = static_cast<ULONG>(function);
copyData.cbData = mpt::saturate_cast<DWORD>(data.size());
copyData.lpData = const_cast<void*>(mpt::void_cast<const void*>(data.data()));
return ::SendMessage(ipcWnd, WM_COPYDATA, 0, reinterpret_cast<LPARAM>(&copyData));
}
HWND FindIPCWindow()
{
return ::FindWindow(ClassName, nullptr);
}
struct EnumWindowState
{
FlagSet<InstanceRequirements> require;
HWND result = nullptr;
};
static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
EnumWindowState &state = *reinterpret_cast<EnumWindowState*>(lParam);
if(hwnd)
{
TCHAR className[256];
MemsetZero(className);
if(::GetClassName(hwnd, className, 256) > 0)
{
if(!_tcscmp(className, IPCWindow::ClassName))
{
if(state.require[SameVersion])
{
if(Version(static_cast<uint32>(SendIPC(hwnd, Function::GetVersion))) != Version::Current())
{
return TRUE; // continue
}
}
if(state.require[SameArchitecture])
{
if(SendIPC(hwnd, Function::GetArchitecture) != static_cast<int>(mpt::OS::Windows::GetProcessArchitecture()))
{
return TRUE; // continue
}
}
if(state.require[SamePath])
{
if(SendIPC(hwnd, Function::HasSameBinaryPath, mpt::as_span(theApp.GetInstallBinArchPath().ToWide())) != 1)
{
return TRUE; // continue
}
}
if(state.require[SameSettings])
{
if(SendIPC(hwnd, Function::HasSameSettingsPath, mpt::as_span(theApp.GetConfigPath().ToWide())) != 1)
{
return TRUE; // continue
}
}
state.result = hwnd;
return TRUE; // continue
//return FALSE; // done
}
}
}
return TRUE; // continue
}
HWND FindIPCWindow(FlagSet<InstanceRequirements> require)
{
EnumWindowState state;
state.require = require;
if(::EnumWindows(&EnumWindowsProc, reinterpret_cast<LPARAM>(&state)) == 0)
{
return nullptr;
}
return state.result;
}
bool SendToIPC(const std::vector<mpt::PathString> &filenames)
{
HWND ipcWnd = FindIPCWindow();
if(!ipcWnd)
{
return false;
}
DWORD processID = 0;
GetWindowThreadProcessId(ipcWnd, &processID);
AllowSetForegroundWindow(processID);
SendIPC(ipcWnd, Function::SetWindowForeground);
for(const auto &filename : filenames)
{
if(SendIPC(ipcWnd, Function::Open, mpt::as_span(filename.ToWide())) == 0)
{
return false;
}
}
return true;
}
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,55 @@
/*
* IPCWindow.h
* -----------
* Purpose: Hidden window to receive file open commands from another OpenMPT instance
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
OPENMPT_NAMESPACE_BEGIN
namespace IPCWindow
{
enum class Function : ULONG
{
Open = 0x01,
SetWindowForeground = 0x02,
GetVersion = 0x03, // returns Version::GewRawVersion()
GetArchitecture = 0x04, // returns mpt::OS::Windows::Architecture
HasSameBinaryPath = 0x05,
HasSameSettingsPath = 0x06
};
void Open(HINSTANCE hInstance);
void Close();
LRESULT SendIPC(HWND ipcWnd, Function function, mpt::const_byte_span data = mpt::const_byte_span());
template <typename Tdata> LRESULT SendIPC(HWND ipcWnd, Function function, mpt::span<const Tdata> data) { return SendIPC(ipcWnd, function, mpt::const_byte_span(reinterpret_cast<const std::byte*>(data.data()), data.size() * sizeof(Tdata))); }
enum InstanceRequirements
{
SamePath = 0x01u,
SameSettings = 0x02u,
SameArchitecture = 0x04u,
SameVersion = 0x08u
};
MPT_DECLARE_ENUM(InstanceRequirements)
HWND FindIPCWindow();
HWND FindIPCWindow(FlagSet<InstanceRequirements> require);
// Send file open requests to other OpenMPT instance, if there is one
bool SendToIPC(const std::vector<mpt::PathString> &filenames);
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,337 @@
/*
* Image.cpp
* ---------
* Purpose: Bitmap and Vector image file handling using GDI+.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "MPTrackUtil.h"
#include "Image.h"
#include "../common/FileReader.h"
#include "../common/ComponentManager.h"
// GDI+
#include <atlbase.h>
#define max(a, b) (((a) > (b)) ? (a) : (b))
#define min(a, b) (((a) < (b)) ? (a) : (b))
#if MPT_COMPILER_MSVC
#pragma warning(push)
#pragma warning(disable : 4458) // declaration of 'x' hides class member
#endif
#include <gdiplus.h>
#if MPT_COMPILER_MSVC
#pragma warning(pop)
#endif
#undef min
#undef max
OPENMPT_NAMESPACE_BEGIN
GdiplusRAII::GdiplusRAII()
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
}
GdiplusRAII::~GdiplusRAII()
{
Gdiplus::GdiplusShutdown(gdiplusToken);
gdiplusToken = 0;
}
RawGDIDIB::RawGDIDIB(uint32 width, uint32 height)
: width(width)
, height(height)
, pixels(width * height)
{
MPT_ASSERT(width > 0);
MPT_ASSERT(height > 0);
}
namespace GDIP
{
static CComPtr<IStream> GetStream(mpt::const_byte_span data)
{
CComPtr<IStream> stream;
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
stream.Attach(SHCreateMemStream(mpt::byte_cast<const unsigned char *>(data.data()), mpt::saturate_cast<UINT>(data.size())));
#else
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, data.size());
if(hGlobal == NULL)
{
throw bad_image();
}
void * mem = GlobalLock(hGlobal);
if(!mem)
{
hGlobal = GlobalFree(hGlobal);
throw bad_image();
}
std::memcpy(mem, data.data(), data.size());
GlobalUnlock(hGlobal);
if(CreateStreamOnHGlobal(hGlobal, TRUE, &stream) != S_OK)
{
hGlobal = GlobalFree(hGlobal);
throw bad_image();
}
hGlobal = NULL;
#endif
if(!stream)
{
throw bad_image();
}
return stream;
}
std::unique_ptr<Gdiplus::Bitmap> LoadPixelImage(mpt::const_byte_span file)
{
CComPtr<IStream> stream = GetStream(file);
std::unique_ptr<Gdiplus::Bitmap> result = std::make_unique<Gdiplus::Bitmap>(stream, FALSE);
if(result->GetLastStatus() != Gdiplus::Ok)
{
throw bad_image();
}
if(result->GetWidth() == 0 || result->GetHeight() == 0)
{
throw bad_image();
}
return result;
}
std::unique_ptr<Gdiplus::Bitmap> LoadPixelImage(FileReader file)
{
FileReader::PinnedView view = file.GetPinnedView();
return LoadPixelImage(view.span());
}
std::unique_ptr<Gdiplus::Metafile> LoadVectorImage(mpt::const_byte_span file)
{
CComPtr<IStream> stream = GetStream(file);
std::unique_ptr<Gdiplus::Metafile> result = std::make_unique<Gdiplus::Metafile>(stream);
if(result->GetLastStatus() != Gdiplus::Ok)
{
throw bad_image();
}
if(result->GetWidth() == 0 || result->GetHeight() == 0)
{
throw bad_image();
}
return result;
}
std::unique_ptr<Gdiplus::Metafile> LoadVectorImage(FileReader file)
{
FileReader::PinnedView view = file.GetPinnedView();
return LoadVectorImage(view.span());
}
static std::unique_ptr<Gdiplus::Bitmap> DoResize(Gdiplus::Image &src, double scaling, int spriteWidth, int spriteHeight)
{
const int width = src.GetWidth(), height = src.GetHeight();
int newWidth = 0, newHeight = 0, newSpriteWidth = 0, newSpriteHeight = 0;
if(spriteWidth <= 0 || spriteHeight <= 0)
{
newWidth = mpt::saturate_round<int>(width * scaling);
newHeight = mpt::saturate_round<int>(height * scaling);
} else
{
// Sprite mode: Source images consists of several sprites / icons that should be scaled individually
newSpriteWidth = mpt::saturate_round<int>(spriteWidth * scaling);
newSpriteHeight = mpt::saturate_round<int>(spriteHeight * scaling);
newWidth = width * newSpriteWidth / spriteWidth;
newHeight = height * newSpriteHeight / spriteHeight;
}
std::unique_ptr<Gdiplus::Bitmap> resizedImage = std::make_unique<Gdiplus::Bitmap>(newWidth, newHeight, PixelFormat32bppARGB);
std::unique_ptr<Gdiplus::Graphics> resizedGraphics(Gdiplus::Graphics::FromImage(resizedImage.get()));
if(scaling >= 1.5)
{
// Prefer crisp look on real high-DPI devices
resizedGraphics->SetInterpolationMode(Gdiplus::InterpolationModeNearestNeighbor);
resizedGraphics->SetPixelOffsetMode(Gdiplus::PixelOffsetModeHalf);
resizedGraphics->SetSmoothingMode(Gdiplus::SmoothingModeNone);
} else
{
resizedGraphics->SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBicubic);
resizedGraphics->SetPixelOffsetMode(Gdiplus::PixelOffsetModeHighQuality);
resizedGraphics->SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
}
if(spriteWidth <= 0 || spriteHeight <= 0)
{
resizedGraphics->DrawImage(&src, 0, 0, newWidth, newHeight);
} else
{
// Draw each source sprite individually into separate image to avoid neighbouring source sprites bleeding in
std::unique_ptr<Gdiplus::Bitmap> spriteImage = std::make_unique<Gdiplus::Bitmap>(spriteWidth, spriteHeight, PixelFormat32bppARGB);
std::unique_ptr<Gdiplus::Graphics> spriteGraphics(Gdiplus::Graphics::FromImage(spriteImage.get()));
for(int srcY = 0, destY = 0; srcY < height; srcY += spriteHeight, destY += newSpriteHeight)
{
for(int srcX = 0, destX = 0; srcX < width; srcX += spriteWidth, destX += newSpriteWidth)
{
spriteGraphics->Clear({0, 0, 0, 0});
spriteGraphics->DrawImage(&src, Gdiplus::Rect(0, 0, spriteWidth, spriteHeight), srcX, srcY, spriteWidth, spriteHeight, Gdiplus::UnitPixel);
resizedGraphics->DrawImage(spriteImage.get(), destX, destY, newSpriteWidth, newSpriteHeight);
}
}
}
return resizedImage;
}
std::unique_ptr<Gdiplus::Image> ResizeImage(Gdiplus::Image &src, double scaling, int spriteWidth, int spriteHeight)
{
return DoResize(src, scaling, spriteWidth, spriteHeight);
}
std::unique_ptr<Gdiplus::Bitmap> ResizeImage(Gdiplus::Bitmap &src, double scaling, int spriteWidth, int spriteHeight)
{
return DoResize(src, scaling, spriteWidth, spriteHeight);
}
} // namespace GDIP
std::unique_ptr<RawGDIDIB> ToRawGDIDIB(Gdiplus::Bitmap &bitmap)
{
Gdiplus::BitmapData bitmapData;
Gdiplus::Rect rect{Gdiplus::Point{0, 0}, Gdiplus::Size{static_cast<INT>(bitmap.GetWidth()), static_cast<INT>(bitmap.GetHeight())}};
std::unique_ptr<RawGDIDIB> result = std::make_unique<RawGDIDIB>(bitmap.GetWidth(), bitmap.GetHeight());
if(bitmap.LockBits(&rect, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &bitmapData) != Gdiplus::Ok)
{
throw bad_image();
}
RawGDIDIB::Pixel *dst = result->Pixels().data();
for(uint32 y = 0; y < result->Height(); ++y)
{
const GDIP::Pixel *src = GDIP::GetScanline(bitmapData, y);
for(uint32 x = 0; x < result->Width(); ++x)
{
*dst = GDIP::ToRawGDIDIB(*src);
src++;
dst++;
}
}
bitmap.UnlockBits(&bitmapData);
return result;
}
std::unique_ptr<RawGDIDIB> LoadPixelImage(mpt::const_byte_span file, double scaling, int spriteWidth, int spriteHeight)
{
auto bitmap = GDIP::LoadPixelImage(file);
if(scaling != 1.0)
bitmap = GDIP::ResizeImage(*bitmap, scaling, spriteWidth, spriteHeight);
return ToRawGDIDIB(*bitmap);
}
std::unique_ptr<RawGDIDIB> LoadPixelImage(FileReader file, double scaling, int spriteWidth, int spriteHeight)
{
auto bitmap = GDIP::LoadPixelImage(file);
if(scaling != 1.0)
bitmap = GDIP::ResizeImage(*bitmap, scaling, spriteWidth, spriteHeight);
return ToRawGDIDIB(*bitmap);
}
// Create a DIB for the current device from our PNG.
bool CopyToCompatibleBitmap(CBitmap &dst, CDC &dc, const RawGDIDIB &src)
{
BITMAPINFOHEADER bi;
MemsetZero(bi);
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = src.Width();
bi.biHeight = -static_cast<LONG>(src.Height());
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = BI_RGB;
bi.biSizeImage = src.Width() * src.Height() * 4;
if(!dst.CreateCompatibleBitmap(&dc, src.Width(), src.Height()))
{
return false;
}
if(!SetDIBits(dc.GetSafeHdc(), dst, 0, src.Height(), src.Pixels().data(), reinterpret_cast<BITMAPINFO *>(&bi), DIB_RGB_COLORS))
{
return false;
}
return true;
}
bool CopyToCompatibleBitmap(CBitmap &dst, CDC &dc, Gdiplus::Image &src)
{
if(!dst.CreateCompatibleBitmap(&dc, src.GetWidth(), src.GetHeight()))
{
return false;
}
CDC memdc;
if(!memdc.CreateCompatibleDC(&dc))
{
return false;
}
memdc.SelectObject(dst);
Gdiplus::Graphics gfx(memdc);
if(gfx.DrawImage(&src, 0, 0) != Gdiplus::Ok)
{
return false;
}
return true;
}
bool LoadCompatibleBitmapFromPixelImage(CBitmap &dst, CDC &dc, mpt::const_byte_span file)
{
try
{
std::unique_ptr<Gdiplus::Bitmap> pBitmap = GDIP::LoadPixelImage(file);
if(!CopyToCompatibleBitmap(dst, dc, *pBitmap))
{
return false;
}
} catch(...)
{
return false;
}
return true;
}
bool LoadCompatibleBitmapFromPixelImage(CBitmap &dst, CDC &dc, FileReader file)
{
try
{
std::unique_ptr<Gdiplus::Bitmap> pBitmap = GDIP::LoadPixelImage(file);
if(!CopyToCompatibleBitmap(dst, dc, *pBitmap))
{
return false;
}
} catch(...)
{
return false;
}
return true;
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,137 @@
/*
* Image.h
* -------
* Purpose: Bitmap and Vector image file handling.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "../common/FileReaderFwd.h"
// GDI+
namespace Gdiplus {
#include <gdipluspixelformats.h>
class Image;
class Bitmap;
class Metafile;
}
OPENMPT_NAMESPACE_BEGIN
class bad_image : public std::runtime_error { public: bad_image() : std::runtime_error("") { } };
class RawGDIDIB
{
public:
struct Pixel
{
// Component order must be compatible with Microsoft DIBs!
uint8 b;
uint8 g;
uint8 r;
uint8 a;
constexpr Pixel() noexcept
: b(0), g(0), r(0), a(0) {}
constexpr Pixel(uint8 r, uint8 g, uint8 b, uint8 a) noexcept
: b(b), g(g), r(r), a(a) {}
constexpr Pixel(COLORREF color) noexcept
: b(GetBValue(color)), g(GetGValue(color)), r(GetRValue(color)), a(0) {}
};
private:
uint32 width;
uint32 height;
std::vector<Pixel> pixels;
public:
RawGDIDIB(uint32 width, uint32 height);
public:
constexpr uint32 Width() const noexcept { return width; }
constexpr uint32 Height() const noexcept { return height; }
MPT_FORCEINLINE Pixel &operator()(uint32 x, uint32 y) noexcept { return pixels[y * width + x]; }
MPT_FORCEINLINE const Pixel &operator()(uint32 x, uint32 y) const noexcept { return pixels[y * width + x]; }
std::vector<Pixel> &Pixels() { return pixels; }
const std::vector<Pixel> &Pixels() const { return pixels; }
};
class GdiplusRAII
{
private:
ULONG_PTR gdiplusToken = 0;
public:
GdiplusRAII();
~GdiplusRAII();
};
namespace GDIP
{
std::unique_ptr<Gdiplus::Bitmap> LoadPixelImage(mpt::const_byte_span file);
std::unique_ptr<Gdiplus::Bitmap> LoadPixelImage(FileReader file);
std::unique_ptr<Gdiplus::Metafile> LoadVectorImage(mpt::const_byte_span file);
std::unique_ptr<Gdiplus::Metafile> LoadVectorImage(FileReader file);
std::unique_ptr<Gdiplus::Image> ResizeImage(Gdiplus::Image &src, double scaling, int spriteWidth = 0, int spriteHeight = 0);
std::unique_ptr<Gdiplus::Bitmap> ResizeImage(Gdiplus::Bitmap &src, double scaling, int spriteWidth = 0, int spriteHeight = 0);
using Pixel = Gdiplus::ARGB;
template <typename TBitmapData>
inline Pixel * GetScanline(const TBitmapData &bitmapData, std::size_t y) noexcept
{
if(bitmapData.Stride >= 0)
{
return reinterpret_cast<Pixel*>(mpt::void_cast<void*>(mpt::void_cast<std::byte*>(bitmapData.Scan0) + y * bitmapData.Stride));
} else
{
return reinterpret_cast<Pixel*>(mpt::void_cast<void*>(mpt::void_cast<std::byte*>(bitmapData.Scan0) + (bitmapData.Height - 1 - y) * (-bitmapData.Stride)));
}
}
constexpr Pixel AsPixel(uint8 r, uint8 g, uint8 b, uint8 a) noexcept
{
return Pixel(0)
| (static_cast<Pixel>(r) << RED_SHIFT)
| (static_cast<Pixel>(g) << GREEN_SHIFT)
| (static_cast<Pixel>(b) << BLUE_SHIFT)
| (static_cast<Pixel>(a) << ALPHA_SHIFT)
;
}
constexpr uint8 R(Pixel p) noexcept { return static_cast<uint8>(p >> RED_SHIFT); }
constexpr uint8 G(Pixel p) noexcept { return static_cast<uint8>(p >> GREEN_SHIFT); }
constexpr uint8 B(Pixel p) noexcept { return static_cast<uint8>(p >> BLUE_SHIFT); }
constexpr uint8 A(Pixel p) noexcept { return static_cast<uint8>(p >> ALPHA_SHIFT); }
constexpr RawGDIDIB::Pixel ToRawGDIDIB(Pixel p) noexcept
{
return RawGDIDIB::Pixel(GDIP::R(p), GDIP::G(p), GDIP::B(p), GDIP::A(p));
}
} // namespace GDIP
std::unique_ptr<RawGDIDIB> ToRawGDIDIB(Gdiplus::Bitmap &bitmap);
bool CopyToCompatibleBitmap(CBitmap &dst, CDC &dc, const RawGDIDIB &src);
bool CopyToCompatibleBitmap(CBitmap &dst, CDC &dc, Gdiplus::Image &src);
std::unique_ptr<RawGDIDIB> LoadPixelImage(mpt::const_byte_span file, double scaling = 1.0, int spriteWidth = 0, int spriteHeight = 0);
std::unique_ptr<RawGDIDIB> LoadPixelImage(FileReader file, double scaling = 1.0, int spriteWidth = 0, int spriteHeight = 0);
bool LoadCompatibleBitmapFromPixelImage(CBitmap &dst, CDC &dc, mpt::const_byte_span file);
bool LoadCompatibleBitmapFromPixelImage(CBitmap &dst, CDC &dc, FileReader file);
OPENMPT_NAMESPACE_END
@@ -0,0 +1,141 @@
/*
* ImageLists.h
* ------------
* Purpose: Enums for image lists
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
OPENMPT_NAMESPACE_BEGIN
// Image List index
enum
{
IMAGE_COMMENTS=0,
IMAGE_PATTERNS,
IMAGE_OPLINSTR = IMAGE_PATTERNS,
IMAGE_SAMPLES,
IMAGE_INSTRUMENTS,
IMAGE_PLUGININSTRUMENT = IMAGE_INSTRUMENTS,
IMAGE_GENERAL,
IMAGE_FOLDER,
IMAGE_OPENFOLDER,
IMAGE_PARTITION,
IMAGE_NOSAMPLE,
IMAGE_FOLDERPARENT,
IMAGE_FOLDERSONG,
IMAGE_DIRECTX,
IMAGE_WAVEOUT,
IMAGE_EFFECTPLUGIN = IMAGE_WAVEOUT,
IMAGE_ASIO,
IMAGE_CHIP,
IMAGE_SAMPLEMUTE,
IMAGE_INSTRMUTE,
IMAGE_SAMPLEACTIVE,
IMAGE_INSTRACTIVE,
IMAGE_NOPLUGIN,
IMAGE_TUX,
IMAGE_OPLINSTRACTIVE,
IMAGE_OPLINSTRMUTE,
IMAGE_EXTSAMPLEMISSING,
IMAGE_EXTSAMPLE,
IMAGE_EXTSAMPLEACTIVE,
IMAGE_EXTSAMPLEMUTE,
IMGLIST_NUMIMAGES
};
// Toolbar Image List index
enum
{
TIMAGE_PATTERN_NEW=0,
TIMAGE_PATTERN_STOP,
TIMAGE_PATTERN_PLAY,
TIMAGE_PATTERN_RESTART,
TIMAGE_PATTERN_RECORD,
TIMAGE_SAMPLE_FIXLOOP,
TIMAGE_SAMPLE_NEW,
TIMAGE_INSTR_NEW,
TIMAGE_SAMPLE_NORMALIZE,
TIMAGE_SAMPLE_AMPLIFY,
TIMAGE_SAMPLE_RESAMPLE,
TIMAGE_SAMPLE_REVERSE,
TIMAGE_OPEN,
TIMAGE_SAVE,
TIMAGE_PREVIEW,
TIMAGE_SAMPLE_AUTOTUNE,
TIMAGE_PATTERN_VUMETERS,
TIMAGE_MACROEDITOR,
TIMAGE_CHORDEDITOR,
TIMAGE_PATTERN_PROPERTIES,
TIMAGE_PATTERN_EXPAND,
TIMAGE_PATTERN_SHRINK,
TIMAGE_SAMPLE_SILENCE,
TIMAGE_PATTERN_OVERFLOWPASTE,
TIMAGE_UNDO,
TIMAGE_REDO,
TIMAGE_PATTERN_PLAYROW,
TIMAGE_SAMPLE_DOWNSAMPLE,
TIMAGE_PATTERN_DETAIL_LO,
TIMAGE_PATTERN_DETAIL_MED,
TIMAGE_PATTERN_DETAIL_HI,
TIMAGE_PATTERN_PLUGINS,
TIMAGE_CHANNELMANAGER,
TIMAGE_SAMPLE_INVERT,
TIMAGE_SAMPLE_UNSIGN,
TIMAGE_SAMPLE_DCOFFSET,
TIMAGE_SAMPLE_STEREOSEP,
PATTERNIMG_NUMIMAGES
};
// Sample editor toolbar image list index
enum
{
SIMAGE_CHECKED = 0,
SIMAGE_ZOOMUP,
SIMAGE_ZOOMDOWN,
SIMAGE_DRAW,
SIMAGE_RESIZE,
SIMAGE_GENERATE,
SIMAGE_GRID,
SAMPLEIMG_NUMIMAGES
};
// Instrument editor toolbar image list index
enum
{
IIMAGE_CHECKED = 0,
IIMAGE_VOLENV,
IIMAGE_PANENV,
IIMAGE_PITCHENV,
IIMAGE_NOPITCHENV,
IIMAGE_LOOP,
IIMAGE_SUSTAIN,
IIMAGE_CARRY,
IIMAGE_NOCARRY,
IIMAGE_VOLSWITCH,
IIMAGE_PANSWITCH,
IIMAGE_PITCHSWITCH,
IIMAGE_FILTERSWITCH,
IIMAGE_NOPITCHSWITCH,
IIMAGE_NOFILTERSWITCH,
IIMAGE_SAMPLEMAP,
IIMAGE_GRID,
IIMAGE_ZOOMIN,
IIMAGE_NOZOOMIN,
IIMAGE_ZOOMOUT,
IIMAGE_NOZOOMOUT,
IIMAGE_SAVE,
IIMAGE_LOAD,
ENVIMG_NUMIMAGES
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,632 @@
/*
* InputHandler.cpp
* ----------------
* Purpose: Implementation of keyboard input handling, keymap loading, ...
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "CommandSet.h"
#include "InputHandler.h"
#include "resource.h"
#include "Mainfrm.h"
#include "../soundlib/MIDIEvents.h"
OPENMPT_NAMESPACE_BEGIN
#define TRANSITIONBIT 0x8000
#define REPEATBIT 0x4000
CInputHandler::CInputHandler(CWnd *mainframe)
{
m_pMainFrm = mainframe;
//Init CommandSet and Load defaults
m_activeCommandSet = std::make_unique<CCommandSet>();
m_lastCommands.fill(kcNull);
mpt::PathString sDefaultPath = theApp.GetConfigPath() + P_("Keybindings.mkb");
const bool bNoExistingKbdFileSetting = TrackerSettings::Instance().m_szKbdFile.empty();
// 1. Try to load keybindings from the path saved in the settings.
// 2. If the setting doesn't exist or the loading fails, try to load from default location.
// 3. If neither one of these worked, load default keybindings from resources.
// 4. If there were no keybinding setting already, create a keybinding file to default location
// and set its path to settings.
if (bNoExistingKbdFileSetting || !(m_activeCommandSet->LoadFile(TrackerSettings::Instance().m_szKbdFile)))
{
if (bNoExistingKbdFileSetting)
TrackerSettings::Instance().m_szKbdFile = sDefaultPath;
bool bSuccess = false;
if (sDefaultPath.IsFile())
bSuccess = m_activeCommandSet->LoadFile(sDefaultPath);
if (!bSuccess)
{
// Load keybindings from resources.
MPT_LOG_GLOBAL(LogDebug, "InputHandler", U_("Loading keybindings from resources\n"));
bSuccess = m_activeCommandSet->LoadDefaultKeymap();
if (bSuccess && bNoExistingKbdFileSetting)
{
m_activeCommandSet->SaveFile(TrackerSettings::Instance().m_szKbdFile);
}
}
if (!bSuccess)
ErrorBox(IDS_UNABLE_TO_LOAD_KEYBINDINGS);
}
// We will only overwrite the default Keybindings.mkb file from now on.
TrackerSettings::Instance().m_szKbdFile = sDefaultPath;
//Get Keymap
m_activeCommandSet->GenKeyMap(m_keyMap);
SetupSpecialKeyInterception(); // Feature: use Windows keys as modifier keys, intercept special keys
}
CommandID CInputHandler::SendCommands(CWnd *wnd, const KeyMapRange &cmd)
{
CommandID executeCommand = kcNull;
if(wnd != nullptr)
{
// Some commands (e.g. open/close/document switching) may invalidate the key map and thus its iterators.
// To avoid this problem, copy over the elements we are interested in before sending commands.
std::vector<KeyMap::value_type> commands;
commands.reserve(std::distance(cmd.first, cmd.second));
for(auto i = cmd.first; i != cmd.second; i++)
{
commands.push_back(*i);
}
for(const auto &i : commands)
{
m_lastCommands[m_lastCommandPos] = i.second;
m_lastCommandPos = (m_lastCommandPos + 1) % m_lastCommands.size();
if(wnd->SendMessage(WM_MOD_KEYCOMMAND, i.second, i.first.AsLPARAM()) != kcNull)
{
// Command was handled, no need to let the OS handle the key
executeCommand = i.second;
}
}
}
return executeCommand;
}
CommandID CInputHandler::GeneralKeyEvent(InputTargetContext context, int code, WPARAM wParam, LPARAM lParam)
{
KeyMapRange cmd = { m_keyMap.end(), m_keyMap.end() };
KeyEventType keyEventType;
if(code == HC_ACTION)
{
//Get the KeyEventType (key up, key down, key repeat)
DWORD scancode = static_cast<LONG>(lParam) >> 16;
if((scancode & 0xC000) == 0xC000)
{
keyEventType = kKeyEventUp;
} else if((scancode & 0xC000) == 0x0000)
{
keyEventType = kKeyEventDown;
} else
{
keyEventType = kKeyEventRepeat;
}
// Catch modifier change (ctrl, alt, shift) - Only check on keyDown or keyUp.
// NB: we want to catch modifiers even when the input handler is locked
if(keyEventType == kKeyEventUp || keyEventType == kKeyEventDown)
{
scancode = (static_cast<LONG>(lParam) >> 16) & 0x1FF;
CatchModifierChange(wParam, keyEventType, scancode);
}
if(!InterceptSpecialKeys(static_cast<UINT>(wParam), static_cast<LONG>(lParam), true) && !IsBypassed())
{
// only execute command when the input handler is not locked
// and the input is not a consequence of special key interception.
cmd = m_keyMap.equal_range(KeyCombination(context, m_modifierMask, static_cast<UINT>(wParam), keyEventType));
}
}
if(code == HC_MIDI)
{
cmd = m_keyMap.equal_range(KeyCombination(context, ModMidi, static_cast<UINT>(wParam), static_cast<KeyEventType>(lParam)));
}
return SendCommands(m_pMainFrm, cmd);
}
CommandID CInputHandler::KeyEvent(InputTargetContext context, UINT &nChar, UINT &/*nRepCnt*/, UINT &nFlags, KeyEventType keyEventType, CWnd *pSourceWnd)
{
if(InterceptSpecialKeys(nChar, nFlags, false))
return kcDummyShortcut;
KeyMapRange cmd = m_keyMap.equal_range(KeyCombination(context, m_modifierMask, nChar, keyEventType));
if(pSourceWnd == nullptr)
pSourceWnd = m_pMainFrm; // By default, send command message to main frame.
return SendCommands(pSourceWnd, cmd);
}
// Feature: use Windows keys as modifier keys, intercept special keys
bool CInputHandler::InterceptSpecialKeys(UINT nChar, UINT nFlags, bool generateMsg)
{
KeyEventType keyEventType = GetKeyEventType(HIWORD(nFlags));
enum { VK_NonExistentKey = VK_F24+1 };
if(nChar == VK_NonExistentKey)
{
return true;
} else if(m_bInterceptWindowsKeys && (nChar == VK_LWIN || nChar == VK_RWIN))
{
if(keyEventType == kKeyEventDown)
{
INPUT inp[2];
inp[0].type = inp[1].type = INPUT_KEYBOARD;
inp[0].ki.time = inp[1].ki.time = 0;
inp[0].ki.dwExtraInfo = inp[1].ki.dwExtraInfo = 0;
inp[0].ki.wVk = inp[1].ki.wVk = VK_NonExistentKey;
inp[0].ki.wScan = inp[1].ki.wScan = 0;
inp[0].ki.dwFlags = 0;
inp[1].ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(2, inp, sizeof(INPUT));
}
}
if((nChar == VK_NUMLOCK && m_bInterceptNumLock)
|| (nChar == VK_CAPITAL && m_bInterceptCapsLock)
|| (nChar == VK_SCROLL && m_bInterceptScrollLock))
{
if(GetMessageExtraInfo() == 0xC0FFEE)
{
SetMessageExtraInfo(0);
return true;
} else if(keyEventType == kKeyEventDown && generateMsg)
{
// Prevent keys from lighting up by simulating a second press.
INPUT inp[2];
inp[0].type = inp[1].type = INPUT_KEYBOARD;
inp[0].ki.time = inp[1].ki.time = 0;
inp[0].ki.dwExtraInfo = inp[1].ki.dwExtraInfo = 0xC0FFEE;
inp[0].ki.wVk = inp[1].ki.wVk = static_cast<WORD>(nChar);
inp[0].ki.wScan = inp[1].ki.wScan = 0;
inp[0].ki.dwFlags = KEYEVENTF_KEYUP;
inp[1].ki.dwFlags = 0;
SendInput(2, inp, sizeof(INPUT));
}
}
return false;
};
void CInputHandler::SetupSpecialKeyInterception()
{
m_bInterceptWindowsKeys = m_bInterceptNumLock = m_bInterceptCapsLock = m_bInterceptScrollLock = false;
for(const auto &i : m_keyMap)
{
ASSERT(i.second != kcNull);
if(i.first.Modifier() == ModWin)
m_bInterceptWindowsKeys = true;
if(i.first.KeyCode() == VK_NUMLOCK)
m_bInterceptNumLock = true;
if(i.first.KeyCode() == VK_CAPITAL)
m_bInterceptCapsLock = true;
if(i.first.KeyCode() == VK_SCROLL)
m_bInterceptScrollLock = true;
}
};
//Deal with Modifier keypresses. Private surouting used above.
bool CInputHandler::CatchModifierChange(WPARAM wParam, KeyEventType keyEventType, int scancode)
{
FlagSet<Modifiers> modifierMask = ModNone;
// Scancode for right modifier keys should have bit 8 set, but Right Shift is actually 0x36.
const bool isRight = ((scancode & 0x100) || scancode == 0x36) && TrackerSettings::Instance().MiscDistinguishModifiers;
switch(wParam)
{
case VK_CONTROL:
modifierMask.set(isRight ? ModRCtrl : ModCtrl);
break;
case VK_SHIFT:
modifierMask.set(isRight ? ModRShift : ModShift);
break;
case VK_MENU:
modifierMask.set(isRight ? ModRAlt : ModAlt);
break;
case VK_LWIN: case VK_RWIN: // Feature: use Windows keys as modifier keys
modifierMask.set(ModWin);
break;
}
if (modifierMask) // This keypress just changed the modifier mask
{
if (keyEventType == kKeyEventDown)
{
m_modifierMask.set(modifierMask);
// Right Alt is registered as Ctrl+Alt.
// Left Ctrl + Right Alt seems like a pretty difficult to use key combination anyway, so just ignore Ctrl.
if(scancode == 0x138)
m_modifierMask.reset(ModCtrl);
#ifdef _DEBUG
LogModifiers();
#endif
} else if (keyEventType == kKeyEventUp)
m_modifierMask.reset(modifierMask);
return true;
}
return false;
}
// Translate MIDI messages to shortcut commands
CommandID CInputHandler::HandleMIDIMessage(InputTargetContext context, uint32 message)
{
auto byte1 = MIDIEvents::GetDataByte1FromEvent(message), byte2 = MIDIEvents::GetDataByte2FromEvent(message);
switch(MIDIEvents::GetTypeFromEvent(message))
{
case MIDIEvents::evControllerChange:
if(byte2 != 0)
{
// Only capture MIDI CCs for now. Some controllers constantly send some MIDI CCs with value 0
// (e.g. the Roland D-50 sends CC123 whenenver all notes have been released), so we will ignore those.
return GeneralKeyEvent(context, HC_MIDI, byte1, kKeyEventDown);
}
break;
case MIDIEvents::evNoteOff:
byte2 = 0;
[[fallthrough]];
case MIDIEvents::evNoteOn:
if(byte2 != 0)
{
return GeneralKeyEvent(context, HC_MIDI, byte1 | 0x80, kKeyEventDown);
} else
{
// If the key-down triggered a note, we still want that note to be stopped. So we always pretend that no key was assigned to this event
GeneralKeyEvent(context, HC_MIDI, byte1 | 0x80, kKeyEventUp);
}
break;
}
return kcNull;
}
int CInputHandler::GetKeyListSize(CommandID cmd) const
{
return m_activeCommandSet->GetKeyListSize(cmd);
}
//----------------------- Misc
void CInputHandler::LogModifiers()
{
MPT_LOG_GLOBAL(LogDebug, "InputHandler", U_("----------------------------------\n"));
MPT_LOG_GLOBAL(LogDebug, "InputHandler", m_modifierMask[ModCtrl] ? U_("Ctrl On") : U_("Ctrl --"));
MPT_LOG_GLOBAL(LogDebug, "InputHandler", m_modifierMask[ModShift] ? U_("\tShft On") : U_("\tShft --"));
MPT_LOG_GLOBAL(LogDebug, "InputHandler", m_modifierMask[ModAlt] ? U_("\tAlt On") : U_("\tAlt --"));
MPT_LOG_GLOBAL(LogDebug, "InputHandler", m_modifierMask[ModWin] ? U_("\tWin On\n") : U_("\tWin --\n"));
}
KeyEventType CInputHandler::GetKeyEventType(UINT nFlags)
{
if (nFlags & TRANSITIONBIT)
{
// Key released
return kKeyEventUp;
} else if (nFlags & REPEATBIT)
{
// Key repeated
return kKeyEventRepeat;
} else
{
// New key down
return kKeyEventDown;
}
}
bool CInputHandler::SelectionPressed() const
{
int nSelectionKeys = m_activeCommandSet->GetKeyListSize(kcSelect);
KeyCombination key;
for (int k=0; k<nSelectionKeys; k++)
{
key = m_activeCommandSet->GetKey(kcSelect, k);
if (m_modifierMask & key.Modifier())
{
return true;
}
}
return false;
}
bool CInputHandler::ShiftPressed() const
{
return m_modifierMask[ModShift | ModRShift];
}
bool CInputHandler::CtrlPressed() const
{
return m_modifierMask[ModCtrl | ModRCtrl];
}
bool CInputHandler::AltPressed() const
{
return m_modifierMask[ModAlt | ModRAlt];
}
void CInputHandler::Bypass(bool b)
{
if(b)
m_bypassCount++;
else
m_bypassCount--;
ASSERT(m_bypassCount >= 0);
}
bool CInputHandler::IsBypassed() const
{
return m_bypassCount > 0;
}
FlagSet<Modifiers> CInputHandler::GetModifierMask() const
{
return m_modifierMask;
}
void CInputHandler::SetModifierMask(FlagSet<Modifiers> mask)
{
m_modifierMask = mask;
}
CString CInputHandler::GetKeyTextFromCommand(CommandID c, const TCHAR *prependText) const
{
CString s;
if(prependText != nullptr)
{
s = prependText;
s.AppendChar(_T('\t'));
}
s += m_activeCommandSet->GetKeyTextFromCommand(c, 0);
return s;
}
CString CInputHandler::GetMenuText(UINT id) const
{
static constexpr std::tuple<UINT, CommandID, const TCHAR *> MenuItems[] =
{
{ ID_FILE_NEW, kcFileNew, _T("&New") },
{ ID_FILE_OPEN, kcFileOpen, _T("&Open...") },
{ ID_FILE_OPENTEMPLATE, kcNull, _T("Open &Template") },
{ ID_FILE_CLOSE, kcFileClose, _T("&Close") },
{ ID_FILE_CLOSEALL, kcFileCloseAll, _T("C&lose All") },
{ ID_FILE_APPENDMODULE, kcFileAppend, _T("Appen&d Module...") },
{ ID_FILE_SAVE, kcFileSave, _T("&Save") },
{ ID_FILE_SAVE_AS, kcFileSaveAs, _T("Save &As...") },
{ ID_FILE_SAVE_COPY, kcFileSaveCopy, _T("Save Cop&y...") },
{ ID_FILE_SAVEASTEMPLATE, kcFileSaveTemplate, _T("Sa&ve as Template") },
{ ID_FILE_SAVEASWAVE, kcFileSaveAsWave, _T("Stream Export (&WAV, FLAC, MP3, etc.)...") },
{ ID_FILE_SAVEMIDI, kcFileSaveMidi, _T("Export as M&IDI...") },
{ ID_FILE_SAVEOPL, kcFileSaveOPL, _T("Export O&PL Register Dump...") },
{ ID_FILE_SAVECOMPAT, kcFileExportCompat, _T("Compatibility &Export...") },
{ ID_IMPORT_MIDILIB, kcFileImportMidiLib, _T("Import &MIDI Library...") },
{ ID_ADD_SOUNDBANK, kcFileAddSoundBank, _T("Add Sound &Bank...") },
{ ID_PLAYER_PLAY, kcPlayPauseSong, _T("Pause / &Resume") },
{ ID_PLAYER_PLAYFROMSTART, kcPlaySongFromStart, _T("&Play from Start") },
{ ID_PLAYER_STOP, kcStopSong, _T("&Stop") },
{ ID_PLAYER_PAUSE, kcPauseSong, _T("P&ause") },
{ ID_MIDI_RECORD, kcMidiRecord, _T("&MIDI Record") },
{ ID_ESTIMATESONGLENGTH, kcEstimateSongLength, _T("&Estimate Song Length") },
{ ID_APPROX_BPM, kcApproxRealBPM, _T("Approximate Real &BPM") },
{ ID_EDIT_UNDO, kcEditUndo, _T("&Undo") },
{ ID_EDIT_REDO, kcEditRedo, _T("&Redo") },
{ ID_EDIT_CUT, kcEditCut, _T("Cu&t") },
{ ID_EDIT_COPY, kcEditCopy, _T("&Copy") },
{ ID_EDIT_PASTE, kcEditPaste, _T("&Paste") },
{ ID_EDIT_SELECT_ALL, kcEditSelectAll, _T("Select &All") },
{ ID_EDIT_CLEANUP, kcNull, _T("C&leanup") },
{ ID_EDIT_FIND, kcEditFind, _T("&Find / Replace") },
{ ID_EDIT_FINDNEXT, kcEditFindNext, _T("Find &Next") },
{ ID_EDIT_GOTO_MENU, kcPatternGoto, _T("&Goto") },
{ ID_EDIT_SPLITKEYBOARDSETTINGS, kcShowSplitKeyboardSettings, _T("Split &Keyboard Settings") },
// "Paste Special" sub menu
{ ID_EDIT_PASTE_SPECIAL, kcEditMixPaste, _T("&Mix Paste") },
{ ID_EDIT_MIXPASTE_ITSTYLE, kcEditMixPasteITStyle, _T("M&ix Paste (IT Style)") },
{ ID_EDIT_PASTEFLOOD, kcEditPasteFlood, _T("Paste Fl&ood") },
{ ID_EDIT_PUSHFORWARDPASTE, kcEditPushForwardPaste, _T("&Push Forward Paste (Insert)") },
{ ID_VIEW_GLOBALS, kcViewGeneral, _T("&General") },
{ ID_VIEW_SAMPLES, kcViewSamples, _T("&Samples") },
{ ID_VIEW_PATTERNS, kcViewPattern, _T("&Patterns") },
{ ID_VIEW_INSTRUMENTS, kcViewInstruments, _T("&Instruments") },
{ ID_VIEW_COMMENTS, kcViewComments, _T("&Comments") },
{ ID_VIEW_OPTIONS, kcViewOptions, _T("S&etup") },
{ ID_VIEW_TOOLBAR, kcViewMain, _T("&Main") },
{ IDD_TREEVIEW, kcViewTree, _T("&Tree") },
{ ID_PLUGIN_SETUP, kcViewAddPlugin, _T("Pl&ugin Manager") },
{ ID_CHANNEL_MANAGER, kcViewChannelManager, _T("Ch&annel Manager") },
{ ID_CLIPBOARD_MANAGER, kcToggleClipboardManager, _T("C&lipboard Manager") },
{ ID_VIEW_SONGPROPERTIES, kcViewSongProperties, _T("Song P&roperties") },
{ ID_PATTERN_MIDIMACRO, kcShowMacroConfig, _T("&Zxx Macro Configuration") },
{ ID_VIEW_MIDIMAPPING, kcViewMIDImapping, _T("&MIDI Mapping") },
{ ID_VIEW_EDITHISTORY, kcViewEditHistory, _T("Edit &History") },
// Help submenu
{ ID_HELPSHOW, kcHelp, _T("&Help") },
{ ID_EXAMPLE_MODULES, kcNull, _T("&Example Modules") },
};
for(const auto & [cmdID, command, text] : MenuItems)
{
if(id == cmdID)
{
if(command != kcNull)
return GetKeyTextFromCommand(command, text);
else
return text;
}
}
MPT_ASSERT_NOTREACHED();
return _T("Unknown Item");
}
void CInputHandler::UpdateMainMenu()
{
CMenu *pMenu = (CMainFrame::GetMainFrame())->GetMenu();
if (!pMenu) return;
pMenu->GetSubMenu(0)->ModifyMenu(0, MF_BYPOSITION | MF_STRING, 0, GetMenuText(ID_FILE_NEW));
static constexpr int MenuItems[] =
{
ID_FILE_OPEN,
ID_FILE_APPENDMODULE,
ID_FILE_CLOSE,
ID_FILE_SAVE,
ID_FILE_SAVE_AS,
ID_FILE_SAVEASWAVE,
ID_FILE_SAVEMIDI,
ID_FILE_SAVECOMPAT,
ID_IMPORT_MIDILIB,
ID_ADD_SOUNDBANK,
ID_PLAYER_PLAY,
ID_PLAYER_PLAYFROMSTART,
ID_PLAYER_STOP,
ID_PLAYER_PAUSE,
ID_MIDI_RECORD,
ID_ESTIMATESONGLENGTH,
ID_APPROX_BPM,
ID_EDIT_UNDO,
ID_EDIT_REDO,
ID_EDIT_CUT,
ID_EDIT_COPY,
ID_EDIT_PASTE,
ID_EDIT_PASTE_SPECIAL,
ID_EDIT_MIXPASTE_ITSTYLE,
ID_EDIT_PASTEFLOOD,
ID_EDIT_PUSHFORWARDPASTE,
ID_EDIT_SELECT_ALL,
ID_EDIT_FIND,
ID_EDIT_FINDNEXT,
ID_EDIT_GOTO_MENU,
ID_EDIT_SPLITKEYBOARDSETTINGS,
ID_VIEW_GLOBALS,
ID_VIEW_SAMPLES,
ID_VIEW_PATTERNS,
ID_VIEW_INSTRUMENTS,
ID_VIEW_COMMENTS,
ID_VIEW_TOOLBAR,
IDD_TREEVIEW,
ID_VIEW_OPTIONS,
ID_PLUGIN_SETUP,
ID_CHANNEL_MANAGER,
ID_CLIPBOARD_MANAGER,
ID_VIEW_SONGPROPERTIES,
ID_VIEW_SONGPROPERTIES,
ID_PATTERN_MIDIMACRO,
ID_VIEW_EDITHISTORY,
ID_HELPSHOW,
};
for(const auto id : MenuItems)
{
pMenu->ModifyMenu(id, MF_BYCOMMAND | MF_STRING, id, GetMenuText(id));
}
}
void CInputHandler::SetNewCommandSet(const CCommandSet *newSet)
{
m_activeCommandSet->Copy(newSet);
m_activeCommandSet->GenKeyMap(m_keyMap);
SetupSpecialKeyInterception(); // Feature: use Windows keys as modifier keys, intercept special keys
UpdateMainMenu();
}
bool CInputHandler::SetEffectLetters(const CModSpecifications &modSpecs)
{
MPT_LOG_GLOBAL(LogDebug, "InputHandler", U_("Changing command set."));
bool retval = m_activeCommandSet->QuickChange_SetEffects(modSpecs);
if(retval) m_activeCommandSet->GenKeyMap(m_keyMap);
return retval;
}
bool CInputHandler::IsKeyPressHandledByTextBox(DWORD key, HWND hWnd) const
{
if(hWnd == nullptr)
return false;
TCHAR activeWindowClassName[6];
GetClassName(hWnd, activeWindowClassName, mpt::saturate_cast<int>(std::size(activeWindowClassName)));
const bool textboxHasFocus = _tcsicmp(activeWindowClassName, _T("Edit")) == 0;
if(!textboxHasFocus)
return false;
//Alpha-numerics (only shift or no modifier):
if(!GetModifierMask().test_any_except(ModShift)
&& ((key>='A'&&key<='Z') || (key>='0'&&key<='9') ||
key==VK_DIVIDE || key==VK_MULTIPLY || key==VK_SPACE || key==VK_RETURN ||
key==VK_CAPITAL || (key>=VK_OEM_1 && key<=VK_OEM_3) || (key>=VK_OEM_4 && key<=VK_OEM_8)))
return true;
//navigation (any modifier):
if(key == VK_LEFT || key == VK_RIGHT || key == VK_UP || key == VK_DOWN ||
key == VK_HOME || key == VK_END || key == VK_DELETE || key == VK_INSERT || key == VK_BACK)
return true;
//Copy paste etc..
if(GetModifierMask() == ModCtrl &&
(key == 'Y' || key == 'Z' || key == 'X' || key == 'C' || key == 'V' || key == 'A'))
return true;
return false;
}
BypassInputHandler::BypassInputHandler()
{
if(CMainFrame::GetInputHandler())
{
bypassed = true;
CMainFrame::GetInputHandler()->Bypass(true);
}
}
BypassInputHandler::~BypassInputHandler()
{
if(bypassed)
{
CMainFrame::GetInputHandler()->Bypass(false);
bypassed = false;
}
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,84 @@
/*
* InputHandler.h
* --------------
* Purpose: Implementation of keyboard input handling, keymap loading, ...
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "CommandSet.h"
OPENMPT_NAMESPACE_BEGIN
// Hook codes
enum
{
HC_MIDI = 0x8000,
};
class CInputHandler
{
protected:
CWnd *m_pMainFrm;
KeyMap m_keyMap;
FlagSet<Modifiers> m_modifierMask = ModNone;
int m_bypassCount = 0;
bool m_bInterceptWindowsKeys : 1, m_bInterceptNumLock : 1, m_bInterceptCapsLock : 1, m_bInterceptScrollLock : 1;
public:
std::unique_ptr<CCommandSet> m_activeCommandSet;
std::array<CommandID, 10> m_lastCommands;
size_t m_lastCommandPos = 0;
public:
CInputHandler(CWnd *mainframe);
CommandID GeneralKeyEvent(InputTargetContext context, int code, WPARAM wParam , LPARAM lParam);
CommandID KeyEvent(InputTargetContext context, UINT &nChar, UINT &nRepCnt, UINT &nFlags, KeyEventType keyEventType, CWnd *pSourceWnd = nullptr);
static KeyEventType GetKeyEventType(UINT nFlags);
bool IsKeyPressHandledByTextBox(DWORD wparam, HWND hWnd) const;
CommandID HandleMIDIMessage(InputTargetContext context, uint32 message);
int GetKeyListSize(CommandID cmd) const;
protected:
void LogModifiers();
bool CatchModifierChange(WPARAM wParam, KeyEventType keyEventType, int scancode);
bool InterceptSpecialKeys(UINT nChar, UINT nFlags, bool generateMsg);
void SetupSpecialKeyInterception();
CommandID SendCommands(CWnd *wnd, const KeyMapRange &cmd);
public:
bool ShiftPressed() const;
bool SelectionPressed() const;
bool CtrlPressed() const;
bool AltPressed() const;
bool IsBypassed() const;
void Bypass(bool);
FlagSet<Modifiers> GetModifierMask() const;
void SetModifierMask(FlagSet<Modifiers> mask);
CString GetKeyTextFromCommand(CommandID c, const TCHAR *prependText = nullptr) const;
CString GetMenuText(UINT id) const;
void UpdateMainMenu();
void SetNewCommandSet(const CCommandSet *newSet);
bool SetEffectLetters(const CModSpecifications &modSpecs);
};
// RAII object for temporarily bypassing the input handler
class BypassInputHandler
{
private:
bool bypassed = false;
public:
BypassInputHandler();
~BypassInputHandler();
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,816 @@
/*
* KeyConfigDlg.cpp
* ----------------
* Purpose: Implementation of OpenMPT's keyboard configuration dialog.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "KeyConfigDlg.h"
#include "FileDialog.h"
#include "../soundlib/mod_specifications.h"
#include "../soundlib/MIDIEvents.h"
OPENMPT_NAMESPACE_BEGIN
//***************************************************************************************//
// CCustEdit: customised CEdit control to catch keypresses.
// (does what CHotKeyCtrl does,but better)
//***************************************************************************************//
BEGIN_MESSAGE_MAP(CCustEdit, CEdit)
ON_WM_SETFOCUS()
ON_WM_KILLFOCUS()
ON_MESSAGE(WM_MOD_MIDIMSG, &CCustEdit::OnMidiMsg)
END_MESSAGE_MAP()
LRESULT CCustEdit::OnMidiMsg(WPARAM dwMidiDataParam, LPARAM)
{
if(!m_isFocussed)
return 1;
uint32 midiData = static_cast<uint32>(dwMidiDataParam);
const auto byte1 = MIDIEvents::GetDataByte1FromEvent(midiData), byte2 = MIDIEvents::GetDataByte2FromEvent(midiData);
switch(MIDIEvents::GetTypeFromEvent(midiData))
{
case MIDIEvents::evControllerChange:
if(byte2 != 0)
{
SetKey(ModMidi, byte1);
if(!m_isDummy)
m_pOptKeyDlg->OnSetKeyChoice();
}
break;
case MIDIEvents::evNoteOn:
case MIDIEvents::evNoteOff:
SetKey(ModMidi, byte1 | 0x80);
if(!m_isDummy)
m_pOptKeyDlg->OnSetKeyChoice();
break;
}
return 1;
}
BOOL CCustEdit::PreTranslateMessage(MSG *pMsg)
{
if(pMsg)
{
if(pMsg->message == WM_KEYDOWN || pMsg->message == WM_SYSKEYDOWN)
{
SetKey(CMainFrame::GetInputHandler()->GetModifierMask(), static_cast<UINT>(pMsg->wParam));
return -1; // Keypress handled, don't pass on message.
} else if(pMsg->message == WM_KEYUP || pMsg->message == WM_SYSKEYUP)
{
//if a key has been released but custom edit box is empty, we have probably just
//navigated into the box with TAB or SHIFT-TAB. No need to set keychoice.
if(code != 0 && !m_isDummy)
m_pOptKeyDlg->OnSetKeyChoice();
}
}
return CEdit::PreTranslateMessage(pMsg);
}
void CCustEdit::SetKey(FlagSet<Modifiers> inMod, UINT inCode)
{
mod = inMod;
code = inCode;
//Setup display
SetWindowText(KeyCombination::GetKeyText(mod, code));
}
void CCustEdit::OnSetFocus(CWnd *pOldWnd)
{
CEdit::OnSetFocus(pOldWnd);
// Lock the input handler
CMainFrame::GetInputHandler()->Bypass(true);
// Accept MIDI input
CMainFrame::GetMainFrame()->SetMidiRecordWnd(m_hWnd);
m_isFocussed = true;
}
void CCustEdit::OnKillFocus(CWnd *pNewWnd)
{
CEdit::OnKillFocus(pNewWnd);
//unlock the input handler
CMainFrame::GetInputHandler()->Bypass(false);
m_isFocussed = false;
}
//***************************************************************************************//
// COptionsKeyboard:
//
//***************************************************************************************//
// Initialisation
BEGIN_MESSAGE_MAP(COptionsKeyboard, CPropertyPage)
ON_LBN_SELCHANGE(IDC_CHOICECOMBO, &COptionsKeyboard::OnKeyChoiceSelect)
ON_LBN_SELCHANGE(IDC_COMMAND_LIST, &COptionsKeyboard::OnCommandKeySelChanged)
ON_LBN_SELCHANGE(IDC_KEYCATEGORY, &COptionsKeyboard::OnCategorySelChanged)
ON_EN_UPDATE(IDC_CHORDDETECTWAITTIME, &COptionsKeyboard::OnChordWaitTimeChanged) //rewbs.autochord
ON_COMMAND(IDC_DELETE, &COptionsKeyboard::OnDeleteKeyChoice)
ON_COMMAND(IDC_RESTORE, &COptionsKeyboard::OnRestoreKeyChoice)
ON_COMMAND(IDC_LOAD, &COptionsKeyboard::OnLoad)
ON_COMMAND(IDC_SAVE, &COptionsKeyboard::OnSave)
ON_COMMAND(IDC_CHECKKEYDOWN, &COptionsKeyboard::OnCheck)
ON_COMMAND(IDC_CHECKKEYHOLD, &COptionsKeyboard::OnCheck)
ON_COMMAND(IDC_CHECKKEYUP, &COptionsKeyboard::OnCheck)
ON_COMMAND(IDC_NOTESREPEAT, &COptionsKeyboard::OnNotesRepeat)
ON_COMMAND(IDC_NONOTESREPEAT, &COptionsKeyboard::OnNoNotesRepeat)
ON_COMMAND(IDC_CLEARLOG, &COptionsKeyboard::OnClearLog)
ON_COMMAND(IDC_RESTORE_KEYMAP, &COptionsKeyboard::OnRestoreDefaultKeymap)
ON_EN_CHANGE(IDC_FIND, &COptionsKeyboard::OnSearchTermChanged)
ON_EN_CHANGE(IDC_FINDHOTKEY, &COptionsKeyboard::OnFindHotKey)
ON_EN_SETFOCUS(IDC_FINDHOTKEY, &COptionsKeyboard::OnClearHotKey)
ON_WM_DESTROY()
END_MESSAGE_MAP()
void COptionsKeyboard::DoDataExchange(CDataExchange *pDX)
{
CPropertyPage::DoDataExchange(pDX);
DDX_Control(pDX, IDC_KEYCATEGORY, m_cmbCategory);
DDX_Control(pDX, IDC_COMMAND_LIST, m_lbnCommandKeys);
DDX_Control(pDX, IDC_CHOICECOMBO, m_cmbKeyChoice);
DDX_Control(pDX, IDC_CHORDDETECTWAITTIME, m_eChordWaitTime);//rewbs.autochord
DDX_Control(pDX, IDC_KEYREPORT, m_eReport);
DDX_Control(pDX, IDC_CUSTHOTKEY, m_eCustHotKey);
DDX_Control(pDX, IDC_FINDHOTKEY, m_eFindHotKey);
DDX_Control(pDX, IDC_CHECKKEYDOWN, m_bKeyDown);
DDX_Control(pDX, IDC_CHECKKEYHOLD, m_bKeyHold);
DDX_Control(pDX, IDC_CHECKKEYUP, m_bKeyUp);
DDX_Control(pDX, IDC_FIND, m_eFind);
}
BOOL COptionsKeyboard::OnSetActive()
{
CMainFrame::m_nLastOptionsPage = OPTIONS_PAGE_KEYBOARD;
return CPropertyPage::OnSetActive();
}
BOOL COptionsKeyboard::OnInitDialog()
{
CPropertyPage::OnInitDialog();
m_fullPathName = TrackerSettings::Instance().m_szKbdFile;
m_localCmdSet = std::make_unique<CCommandSet>();
m_localCmdSet->Copy(CMainFrame::GetInputHandler()->m_activeCommandSet.get());
//Fill category combo and automatically selects first category
DefineCommandCategories();
for(size_t c = 0; c < commandCategories.size(); c++)
{
if(commandCategories[c].name && !commandCategories[c].commands.empty())
m_cmbCategory.SetItemData(m_cmbCategory.AddString(commandCategories[c].name), c);
}
m_cmbCategory.SetCurSel(0);
UpdateDialog();
m_eCustHotKey.SetParent(m_hWnd, IDC_CUSTHOTKEY, this);
m_eFindHotKey.SetParent(m_hWnd, IDC_FINDHOTKEY, this);
m_eReport.FmtLines(TRUE);
m_eReport.SetWindowText(_T(""));
m_eChordWaitTime.SetWindowText(mpt::cfmt::val(TrackerSettings::Instance().gnAutoChordWaitTime));
return TRUE;
}
void CommandCategory::AddCommands(CommandID first, CommandID last, bool addSeparatorAtEnd)
{
int count = last - first + 1, val = first;
commands.insert(commands.end(), count, kcNull);
std::generate(commands.end() - count, commands.end(), [&val] { return static_cast<CommandID>(val++); });
if(addSeparatorAtEnd)
separators.push_back(last);
}
// Filter commands: We only need user to see a select set off commands
// for each category
void COptionsKeyboard::DefineCommandCategories()
{
{
CommandCategory newCat(_T("Global keys"), kCtxAllContexts);
newCat.AddCommands(kcStartFile, kcEndFile, true);
newCat.AddCommands(kcStartPlayCommands, kcEndPlayCommands, true);
newCat.AddCommands(kcStartEditCommands, kcEndEditCommands, true);
newCat.AddCommands(kcStartView, kcEndView, true);
newCat.AddCommands(kcStartMisc, kcEndMisc, true);
newCat.commands.push_back(kcDummyShortcut);
commandCategories.push_back(newCat);
}
commandCategories.emplace_back(_T(" General [Top]"), kCtxCtrlGeneral);
commandCategories.emplace_back(_T(" General [Bottom]"), kCtxViewGeneral);
commandCategories.emplace_back(_T(" Pattern Editor [Top]"), kCtxCtrlPatterns);
{
CommandCategory newCat(_T(" Pattern Editor - Order List"), kCtxCtrlOrderlist);
newCat.AddCommands(kcStartOrderlistCommands, kcEndOrderlistCommands);
newCat.separators.push_back(kcEndOrderlistNavigation);
newCat.separators.push_back(kcEndOrderlistEdit);
newCat.separators.push_back(kcEndOrderlistNum);
commandCategories.push_back(newCat);
}
{
CommandCategory newCat(_T(" Pattern Editor - Quick Channel Settings"), kCtxChannelSettings);
newCat.AddCommands(kcStartChnSettingsCommands, kcEndChnSettingsCommands);
commandCategories.push_back(newCat);
}
{
CommandCategory newCat(_T(" Pattern Editor - General"), kCtxViewPatterns);
newCat.AddCommands(kcStartPlainNavigate, kcEndPlainNavigate, true);
newCat.AddCommands(kcStartJumpSnap, kcEndJumpSnap, true);
newCat.AddCommands(kcStartHomeEnd, kcEndHomeEnd, true);
newCat.AddCommands(kcPrevPattern, kcNextSequence, true);
newCat.AddCommands(kcStartSelect, kcEndSelect, true);
newCat.AddCommands(kcStartPatternClipboard, kcEndPatternClipboard, true);
newCat.AddCommands(kcClearRow, kcInsertWholeRowGlobal, true);
newCat.AddCommands(kcStartChannelKeys, kcEndChannelKeys, true);
newCat.AddCommands(kcBeginTranspose, kcEndTranspose, true);
newCat.AddCommands(kcPatternAmplify, kcPatternShrinkSelection, true);
newCat.AddCommands(kcStartPatternEditMisc, kcEndPatternEditMisc, true);
commandCategories.push_back(newCat);
}
{
CommandCategory newCat(_T(" Pattern Editor - Note Column"), kCtxViewPatternsNote);
newCat.AddCommands(kcVPStartNotes, kcVPEndNotes, true);
newCat.AddCommands(kcSetOctave0, kcSetOctave9, true);
newCat.AddCommands(kcStartNoteMisc, kcEndNoteMisc);
commandCategories.push_back(newCat);
}
{
CommandCategory newCat(_T(" Pattern Editor - Instrument Column"), kCtxViewPatternsIns);
newCat.AddCommands(kcSetIns0, kcSetIns9);
commandCategories.push_back(newCat);
}
{
CommandCategory newCat(_T(" Pattern Editor - Volume Column"), kCtxViewPatternsVol);
newCat.AddCommands(kcSetVolumeStart, kcSetVolumeEnd);
commandCategories.push_back(newCat);
}
{
CommandCategory newCat(_T(" Pattern Editor - Effect Column"), kCtxViewPatternsFX);
newCat.AddCommands(kcSetFXStart, kcSetFXEnd);
commandCategories.push_back(newCat);
}
{
CommandCategory newCat(_T(" Pattern Editor - Effect Parameter Column"), kCtxViewPatternsFXparam);
newCat.AddCommands(kcSetFXParam0, kcSetFXParamF);
commandCategories.push_back(newCat);
}
{
CommandCategory newCat(_T(" Sample [Top]"), kCtxCtrlSamples);
commandCategories.push_back(newCat);
}
{
CommandCategory newCat(_T(" Sample Editor"), kCtxViewSamples);
newCat.AddCommands(kcStartSampleEditing, kcEndSampleEditing, true);
newCat.AddCommands(kcStartSampleMisc, kcEndSampleMisc, true);
newCat.AddCommands(kcStartSampleCues, kcEndSampleCueGroup);
commandCategories.push_back(newCat);
}
{
CommandCategory newCat(_T(" Instrument Editor"), kCtxCtrlInstruments);
newCat.AddCommands(kcStartInstrumentCtrlMisc, kcEndInstrumentCtrlMisc);
commandCategories.push_back(newCat);
}
{
CommandCategory newCat(_T(" Envelope Editor"), kCtxViewInstruments);
newCat.AddCommands(kcStartInstrumentMisc, kcEndInstrumentMisc);
commandCategories.push_back(newCat);
}
commandCategories.emplace_back(_T(" Comments [Top]"), kCtxCtrlComments);
{
CommandCategory newCat(_T(" Comments [Bottom]"), kCtxViewComments);
newCat.AddCommands(kcStartCommentsCommands, kcEndCommentsCommands);
commandCategories.push_back(newCat);
}
{
CommandCategory newCat(_T(" Plugin Editor"), kCtxVSTGUI);
newCat.AddCommands(kcStartVSTGUICommands, kcEndVSTGUICommands);
commandCategories.push_back(newCat);
}
}
// Pure GUI methods
void COptionsKeyboard::UpdateDialog()
{
OnCategorySelChanged(); // Fills command list and automatically selects first command.
OnCommandKeySelChanged(); // Fills command key choice list for that command and automatically selects first choice.
}
void COptionsKeyboard::OnKeyboardChanged()
{
OnSettingsChanged();
UpdateDialog();
}
void COptionsKeyboard::OnCategorySelChanged()
{
int cat = static_cast<int>(m_cmbCategory.GetItemData(m_cmbCategory.GetCurSel()));
if(cat >= 0 && cat != m_curCategory)
{
// Changed category
UpdateShortcutList(cat);
}
}
// Force last active category to be selected in dropdown menu.
void COptionsKeyboard::UpdateCategory()
{
for(int i = 0; i < m_cmbCategory.GetCount(); i++)
{
if((int)m_cmbCategory.GetItemData(i) == m_curCategory)
{
m_cmbCategory.SetCurSel(i);
break;
}
}
}
void COptionsKeyboard::OnSearchTermChanged()
{
CString findString;
m_eFind.GetWindowText(findString);
if(findString.IsEmpty())
{
UpdateCategory();
}
UpdateShortcutList(findString.IsEmpty() ? m_curCategory : -1);
}
void COptionsKeyboard::OnFindHotKey()
{
if(m_eFindHotKey.code == 0)
{
UpdateCategory();
}
UpdateShortcutList(m_eFindHotKey.code == 0 ? m_curCategory : -1);
}
void COptionsKeyboard::OnClearHotKey()
{
// Focus key search: Clear input
m_eFindHotKey.SetKey(ModNone, 0);
}
// Fills command list and automatically selects first command.
void COptionsKeyboard::UpdateShortcutList(int category)
{
CString findString;
m_eFind.GetWindowText(findString);
findString.MakeLower();
const bool searchByName = !findString.IsEmpty(), searchByKey = (m_eFindHotKey.code != 0);
const bool doSearch = (searchByName || searchByKey);
int firstCat = category, lastCat = category;
if(category == -1)
{
// We will search in all categories
firstCat = 0;
lastCat = static_cast<int>(commandCategories.size()) - 1;
}
CommandID curCommand = static_cast<CommandID>(m_lbnCommandKeys.GetItemData(m_lbnCommandKeys.GetCurSel()));
m_lbnCommandKeys.ResetContent();
for(int cat = firstCat; cat <= lastCat; cat++)
{
// When searching, we also add the category names to the list.
bool addCategoryName = (firstCat != lastCat);
for(size_t cmd = 0; cmd < commandCategories[cat].commands.size(); cmd++)
{
CommandID com = (CommandID)commandCategories[cat].commands[cmd];
CString cmdText = m_localCmdSet->GetCommandText(com);
bool addKey = true;
if(searchByKey)
{
addKey = false;
int numChoices = m_localCmdSet->GetKeyListSize(com);
for(int choice = 0; choice < numChoices; choice++)
{
const KeyCombination &kc = m_localCmdSet->GetKey(com, choice);
if(kc.KeyCode() == m_eFindHotKey.code && kc.Modifier() == m_eFindHotKey.mod)
{
addKey = true;
break;
}
}
}
if(searchByName && addKey)
{
addKey = (cmdText.MakeLower().Find(findString) >= 0);
}
if(addKey)
{
m_curCategory = cat;
if(!m_localCmdSet->isHidden(com))
{
if(doSearch && addCategoryName)
{
const CString catName = _T("------ ") + commandCategories[cat].name.Trim() + _T(" ------");
m_lbnCommandKeys.SetItemData(m_lbnCommandKeys.AddString(catName), DWORD_PTR(-1));
addCategoryName = false;
}
int item = m_lbnCommandKeys.AddString(m_localCmdSet->GetCommandText(com));
m_lbnCommandKeys.SetItemData(item, com);
if(curCommand == com)
{
// Keep selection on previously selected string
m_lbnCommandKeys.SetCurSel(item);
}
}
if(commandCategories[cat].SeparatorAt(com))
m_lbnCommandKeys.SetItemData(m_lbnCommandKeys.AddString(_T("------------------------------------------------------")), DWORD_PTR(-1));
}
}
}
if(m_lbnCommandKeys.GetCurSel() == -1)
{
m_lbnCommandKeys.SetCurSel(0);
}
OnCommandKeySelChanged();
}
// Fills key choice list and automatically selects first key choice
void COptionsKeyboard::OnCommandKeySelChanged()
{
const CommandID cmd = static_cast<CommandID>(m_lbnCommandKeys.GetItemData(m_lbnCommandKeys.GetCurSel()));
CString str;
//Separator
if(cmd == kcNull)
{
m_cmbKeyChoice.SetWindowText(_T(""));
m_cmbKeyChoice.EnableWindow(FALSE);
m_eCustHotKey.SetWindowText(_T(""));
m_eCustHotKey.EnableWindow(FALSE);
m_bKeyDown.SetCheck(0);
m_bKeyDown.EnableWindow(FALSE);
m_bKeyHold.SetCheck(0);
m_bKeyHold.EnableWindow(FALSE);
m_bKeyUp.SetCheck(0);
m_bKeyUp.EnableWindow(FALSE);
m_curCommand = kcNull;
}
//Fill "choice" list
else if((cmd >= 0) && (cmd != m_curCommand) || m_forceUpdate) // Have we changed command?
{
m_forceUpdate = false;
m_cmbKeyChoice.EnableWindow(TRUE);
m_eCustHotKey.EnableWindow(TRUE);
m_bKeyDown.EnableWindow(TRUE);
m_bKeyHold.EnableWindow(TRUE);
m_bKeyUp.EnableWindow(TRUE);
m_curCommand = cmd;
m_curCategory = GetCategoryFromCommandID(cmd);
m_cmbKeyChoice.ResetContent();
int numChoices = m_localCmdSet->GetKeyListSize(cmd);
if((cmd < kcNumCommands) && (numChoices > 0))
{
for(int i = 0; i < numChoices; i++)
{
CString s = MPT_CFORMAT("Choice {} (of {})")(i + 1, numChoices);
m_cmbKeyChoice.SetItemData(m_cmbKeyChoice.AddString(s), i);
}
}
m_cmbKeyChoice.SetItemData(m_cmbKeyChoice.AddString(_T("<new>")), numChoices);
m_cmbKeyChoice.SetCurSel(0);
m_curKeyChoice = -1;
OnKeyChoiceSelect();
}
}
//Fills or clears key choice info
void COptionsKeyboard::OnKeyChoiceSelect()
{
int choice = static_cast<int>(m_cmbKeyChoice.GetItemData(m_cmbKeyChoice.GetCurSel()));
CommandID cmd = m_curCommand;
//If nothing there, clear
if(cmd == kcNull || choice >= m_localCmdSet->GetKeyListSize(cmd) || choice < 0)
{
m_curKeyChoice = choice;
m_forceUpdate = true;
m_eCustHotKey.SetKey(ModNone, 0);
m_bKeyDown.SetCheck(0);
m_bKeyHold.SetCheck(0);
m_bKeyUp.SetCheck(0);
return;
}
//else, if changed, Fill
if(choice != m_curKeyChoice || m_forceUpdate)
{
m_curKeyChoice = choice;
m_forceUpdate = false;
KeyCombination kc = m_localCmdSet->GetKey(cmd, choice);
m_eCustHotKey.SetKey(kc.Modifier(), kc.KeyCode());
m_bKeyDown.SetCheck((kc.EventType() & kKeyEventDown) ? BST_CHECKED : BST_UNCHECKED);
m_bKeyHold.SetCheck((kc.EventType() & kKeyEventRepeat) ? BST_CHECKED : BST_UNCHECKED);
m_bKeyUp.SetCheck((kc.EventType() & kKeyEventUp) ? BST_CHECKED : BST_UNCHECKED);
}
}
void COptionsKeyboard::OnChordWaitTimeChanged()
{
CString s;
UINT val;
m_eChordWaitTime.GetWindowText(s);
val = _tstoi(s);
if(val > 5000)
{
val = 5000;
m_eChordWaitTime.SetWindowText(_T("5000"));
}
OnSettingsChanged();
}
// Change handling
void COptionsKeyboard::OnRestoreKeyChoice()
{
KeyCombination kc;
CommandID cmd = m_curCommand;
CInputHandler *ih = CMainFrame::GetInputHandler();
// Do nothing if there's nothing to restore
if(cmd == kcNull || m_curKeyChoice < 0 || m_curKeyChoice >= ih->GetKeyListSize(cmd))
{
::MessageBeep(MB_ICONWARNING);
return;
}
// Restore current key combination choice for currently selected command.
kc = ih->m_activeCommandSet->GetKey(cmd, m_curKeyChoice);
m_localCmdSet->Remove(m_curKeyChoice, cmd);
m_localCmdSet->Add(kc, cmd, true, m_curKeyChoice);
ForceUpdateGUI();
return;
}
void COptionsKeyboard::OnDeleteKeyChoice()
{
CommandID cmd = m_curCommand;
// Do nothing if there's no key defined for this slot.
if(m_curCommand == kcNull || m_curKeyChoice < 0 || m_curKeyChoice >= m_localCmdSet->GetKeyListSize(cmd))
{
::MessageBeep(MB_ICONWARNING);
return;
}
// Delete current key combination choice for currently selected command.
m_localCmdSet->Remove(m_curKeyChoice, cmd);
ForceUpdateGUI();
return;
}
void COptionsKeyboard::OnSetKeyChoice()
{
CommandID cmd = m_curCommand;
if(cmd == kcNull)
{
Reporting::Warning("Invalid slot.", "Invalid key data", this);
return;
}
FlagSet<KeyEventType> event = kKeyEventNone;
if(m_bKeyDown.GetCheck() != BST_UNCHECKED)
event |= kKeyEventDown;
if(m_bKeyHold.GetCheck() != BST_UNCHECKED)
event |= kKeyEventRepeat;
if(m_bKeyUp.GetCheck() != BST_UNCHECKED)
event |= kKeyEventUp;
KeyCombination kc((commandCategories[m_curCategory]).id, m_eCustHotKey.mod, m_eCustHotKey.code, event);
//detect invalid input
if(!kc.KeyCode())
{
Reporting::Warning("You need to say to which key you'd like to map this command to.", "Invalid key data", this);
return;
}
if(!kc.EventType())
{
::MessageBeep(MB_ICONWARNING);
kc.EventType(kKeyEventDown);
}
bool add = true;
std::pair<CommandID, KeyCombination> conflictCmd;
if((conflictCmd = m_localCmdSet->IsConflicting(kc, cmd)).first != kcNull
&& conflictCmd.first != cmd
&& !m_localCmdSet->IsCrossContextConflict(kc, conflictCmd.second))
{
ConfirmAnswer delOld = Reporting::Confirm(_T("New shortcut (") + kc.GetKeyText() + _T(") has the same key combination as ") + m_localCmdSet->GetCommandText(conflictCmd.first) + _T(" in ") + conflictCmd.second.GetContextText() + _T(".\nDo you want to delete the other shortcut, only keeping the new one?"), _T("Shortcut Conflict"), true, false, this);
if(delOld == cnfYes)
{
m_localCmdSet->Remove(conflictCmd.second, conflictCmd.first);
} else if(delOld == cnfCancel)
{
// Cancel altogther; restore original choice
add = false;
if(m_curKeyChoice >= 0 && m_curKeyChoice < m_localCmdSet->GetKeyListSize(cmd))
{
KeyCombination origKc = m_localCmdSet->GetKey(cmd, m_curKeyChoice);
m_eCustHotKey.SetKey(origKc.Modifier(), origKc.KeyCode());
} else
{
m_eCustHotKey.SetWindowText(_T(""));
}
}
}
if(add)
{
CString report, reportHistory;
//process valid input
m_localCmdSet->Remove(m_curKeyChoice, cmd);
report = m_localCmdSet->Add(kc, cmd, true, m_curKeyChoice);
//Update log
m_eReport.GetWindowText(reportHistory);
m_eReport.SetWindowText(report + reportHistory);
ForceUpdateGUI();
}
}
void COptionsKeyboard::OnOK()
{
CMainFrame::GetInputHandler()->SetNewCommandSet(m_localCmdSet.get());
CString cs;
m_eChordWaitTime.GetWindowText(cs);
TrackerSettings::Instance().gnAutoChordWaitTime = _tstoi(cs);
CPropertyPage::OnOK();
}
void COptionsKeyboard::OnDestroy()
{
CPropertyPage::OnDestroy();
m_localCmdSet = nullptr;
}
void COptionsKeyboard::OnLoad()
{
auto dlg = OpenFileDialog()
.DefaultExtension("mkb")
.DefaultFilename(m_fullPathName)
.ExtensionFilter("OpenMPT Key Bindings (*.mkb)|*.mkb||")
.AddPlace(theApp.GetInstallPkgPath() + P_("extraKeymaps"))
.WorkingDirectory(TrackerSettings::Instance().m_szKbdFile);
if(!dlg.Show(this))
return;
m_fullPathName = dlg.GetFirstFile();
m_localCmdSet->LoadFile(m_fullPathName);
ForceUpdateGUI();
}
void COptionsKeyboard::OnSave()
{
auto dlg = SaveFileDialog()
.DefaultExtension("mkb")
.DefaultFilename(m_fullPathName)
.ExtensionFilter("OpenMPT Key Bindings (*.mkb)|*.mkb||")
.WorkingDirectory(TrackerSettings::Instance().m_szKbdFile);
if(!dlg.Show(this))
return;
m_fullPathName = dlg.GetFirstFile();
m_localCmdSet->SaveFile(m_fullPathName);
}
void COptionsKeyboard::OnNotesRepeat()
{
m_localCmdSet->QuickChange_NotesRepeat(true);
ForceUpdateGUI();
}
void COptionsKeyboard::OnNoNotesRepeat()
{
m_localCmdSet->QuickChange_NotesRepeat(false);
ForceUpdateGUI();
}
void COptionsKeyboard::ForceUpdateGUI()
{
m_forceUpdate = true; // m_nCurKeyChoice and m_nCurHotKey haven't changed, yet we still want to update.
int ntmpChoice = m_curKeyChoice; // next call will overwrite m_nCurKeyChoice
OnCommandKeySelChanged(); // update keychoice list
m_cmbKeyChoice.SetCurSel(ntmpChoice); // select fresh keychoice (thus restoring m_nCurKeyChoice)
OnKeyChoiceSelect(); // update key data
OnSettingsChanged(); // Enable "apply" button
}
void COptionsKeyboard::OnClearLog()
{
m_eReport.SetWindowText(_T(""));
ForceUpdateGUI();
}
void COptionsKeyboard::OnRestoreDefaultKeymap()
{
if(Reporting::Confirm("Discard all custom changes and restore default key configuration?", false, true, this) == cnfYes)
{
m_localCmdSet->LoadDefaultKeymap();
ForceUpdateGUI();
}
}
int COptionsKeyboard::GetCategoryFromCommandID(CommandID command) const
{
for(size_t cat = 0; cat < commandCategories.size(); cat++)
{
const auto &cmds = commandCategories[cat].commands;
if(mpt::contains(cmds, command))
return static_cast<int>(cat);
}
return -1;
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,133 @@
/*
* KeyConfigDlg.h
* --------------
* Purpose: Implementation of OpenMPT's keyboard configuration dialog.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "Mainfrm.h"
#include "InputHandler.h"
OPENMPT_NAMESPACE_BEGIN
class COptionsKeyboard;
// Might promote to class so we can add rules
// (eg automatically do note off stuff, generate chord keybindings from notes based just on modifier.
// Would need GUI rules too as options would be different for each category
class CommandCategory
{
public:
CommandCategory(const TCHAR *n, InputTargetContext d) : name(n), id(d) { }
bool SeparatorAt(CommandID c) const
{
return mpt::contains(separators, c);
}
void AddCommands(CommandID first, CommandID last, bool addSeparatorAtEnd = false);
CString name;
InputTargetContext id;
std::vector<CommandID> separators;
std::vector<CommandID> commands;
};
class CCustEdit: public CEdit
{
protected:
COptionsKeyboard *m_pOptKeyDlg;
HWND m_hParent = nullptr;
UINT m_nCtrlId = 0;
bool m_isFocussed = false, m_isDummy = false;
public:
FlagSet<Modifiers> mod = ModNone;
UINT code = 0;
CCustEdit(bool dummyField) : m_isDummy(dummyField) { }
void SetParent(HWND h, UINT nID, COptionsKeyboard *pOKD)
{
m_hParent = h;
m_nCtrlId = nID;
m_pOptKeyDlg = pOKD;
}
void SetKey(FlagSet<Modifiers> mod, UINT code);
BOOL PreTranslateMessage(MSG *pMsg) override;
afx_msg void OnSetFocus(CWnd* pOldWnd);
afx_msg void OnKillFocus(CWnd* pNewWnd);
afx_msg LRESULT OnMidiMsg(WPARAM, LPARAM);
DECLARE_MESSAGE_MAP()
};
class COptionsKeyboard: public CPropertyPage
{
protected:
CListBox m_lbnHotKeys;
CListBox m_lbnCommandKeys;
CComboBox m_cmbKeyChoice;
CComboBox m_cmbCategory;
CButton m_bKeyDown, m_bKeyHold, m_bKeyUp;
CButton m_bnReset;
CCustEdit m_eCustHotKey, m_eFindHotKey;
CEdit m_eFind;
CEdit m_eReport, m_eChordWaitTime;
CommandID m_curCommand = kcNull;
int m_curCategory = -1, m_curKeyChoice = -1;
mpt::PathString m_fullPathName;
std::unique_ptr<CCommandSet> m_localCmdSet;
bool m_forceUpdate = false;
void ForceUpdateGUI();
void UpdateShortcutList(int category = -1);
void UpdateCategory();
int GetCategoryFromCommandID(CommandID command) const;
public:
COptionsKeyboard() : CPropertyPage(IDD_OPTIONS_KEYBOARD), m_eCustHotKey(false), m_eFindHotKey(true) { }
std::vector<CommandCategory> commandCategories;
void DefineCommandCategories();
void OnSetKeyChoice();
protected:
BOOL OnInitDialog() override;
void OnOK() override;
BOOL OnSetActive() override;
void DoDataExchange(CDataExchange* pDX) override;
afx_msg void UpdateDialog();
afx_msg void OnKeyboardChanged();
afx_msg void OnKeyChoiceSelect();
afx_msg void OnCommandKeySelChanged();
afx_msg void OnCategorySelChanged();
afx_msg void OnSearchTermChanged();
afx_msg void OnChordWaitTimeChanged();
afx_msg void OnSettingsChanged() { SetModified(TRUE); }
afx_msg void OnCheck() { OnSetKeyChoice(); };
afx_msg void OnNotesRepeat();
afx_msg void OnNoNotesRepeat();
afx_msg void OnDeleteKeyChoice();
afx_msg void OnRestoreKeyChoice();
afx_msg void OnLoad();
afx_msg void OnSave();
afx_msg void OnClearLog();
afx_msg void OnRestoreDefaultKeymap();
afx_msg void OnClearHotKey();
afx_msg void OnFindHotKey();
afx_msg void OnDestroy();
DECLARE_MESSAGE_MAP()
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,62 @@
/*
* LinkResolver.cpp
* ----------------
* Purpose: Resolve Windows shell link targets
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "LinkResolver.h"
#include <atlconv.h>
OPENMPT_NAMESPACE_BEGIN
LinkResolver::LinkResolver()
{
HRESULT result = CoCreateInstance(CLSID_ShellLink,
0,
CLSCTX_INPROC_SERVER,
IID_IShellLink,
reinterpret_cast<LPVOID *>(&psl));
if(SUCCEEDED(result) && psl != nullptr)
{
psl->QueryInterface(IID_IPersistFile, reinterpret_cast<LPVOID *>(&ppf));
}
}
LinkResolver::~LinkResolver()
{
if(ppf != nullptr)
ppf->Release();
if(psl != nullptr)
psl->Release();
}
mpt::PathString LinkResolver::Resolve(const TCHAR *inPath)
{
if(ppf == nullptr)
return {};
SHFILEINFO info;
Clear(info);
if((SHGetFileInfo(inPath, 0, &info, sizeof(info), SHGFI_ATTRIBUTES) == 0) || !(info.dwAttributes & SFGAO_LINK))
return {};
USES_CONVERSION; // T2COLE needs this
if(ppf != nullptr && SUCCEEDED(ppf->Load(T2COLE(inPath), STGM_READ)))
{
if(SUCCEEDED(psl->Resolve(AfxGetMainWnd()->m_hWnd, MAKELONG(SLR_ANY_MATCH | SLR_NO_UI | SLR_NOSEARCH, 100))))
{
TCHAR outPath[MAX_PATH];
psl->GetPath(outPath, mpt::saturate_cast<int>(std::size(outPath)), nullptr, 0);
return mpt::PathString::FromNative(outPath);
}
}
return {};
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,33 @@
/*
* LinkResolver.h
* --------------
* Purpose: Resolve Windows shell link targets
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "../common/mptPathString.h"
#include <ShObjIdl.h>
OPENMPT_NAMESPACE_BEGIN
class LinkResolver
{
IShellLink *psl = nullptr;
IPersistFile *ppf = nullptr;
public:
LinkResolver();
~LinkResolver();
mpt::PathString Resolve(const TCHAR *inPath);
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,503 @@
/*
* MIDIMacroDialog.cpp
* -------------------
* Purpose: MIDI Macro Configuration Dialog
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "../mptrack/Reporting.h"
#include "../common/mptStringBuffer.h"
#include "Mainfrm.h"
#include "Mptrack.h"
#include "resource.h"
#include "MIDIMacroDialog.h"
#include "../soundlib/MIDIEvents.h"
#include "../soundlib/plugins/PlugInterface.h"
OPENMPT_NAMESPACE_BEGIN
BEGIN_MESSAGE_MAP(CMidiMacroSetup, CDialog)
ON_COMMAND(IDC_BUTTON1, &CMidiMacroSetup::OnSetAsDefault)
ON_COMMAND(IDC_BUTTON2, &CMidiMacroSetup::OnResetCfg)
ON_COMMAND(IDC_BUTTON3, &CMidiMacroSetup::OnMacroHelp)
ON_CBN_SELCHANGE(IDC_COMBO1, &CMidiMacroSetup::OnSFxChanged)
ON_CBN_SELCHANGE(IDC_COMBO2, &CMidiMacroSetup::OnSFxPresetChanged)
ON_CBN_SELCHANGE(IDC_COMBO3, &CMidiMacroSetup::OnZxxPresetChanged)
ON_CBN_SELCHANGE(IDC_COMBO4, &CMidiMacroSetup::UpdateZxxSelection)
ON_CBN_SELCHANGE(IDC_MACROPLUG, &CMidiMacroSetup::OnPlugChanged)
ON_CBN_SELCHANGE(IDC_MACROPARAM,&CMidiMacroSetup::OnPlugParamChanged)
ON_CBN_SELCHANGE(IDC_MACROCC, &CMidiMacroSetup::OnCCChanged)
ON_EN_CHANGE(IDC_EDIT1, &CMidiMacroSetup::OnSFxEditChanged)
ON_EN_CHANGE(IDC_EDIT2, &CMidiMacroSetup::OnZxxEditChanged)
ON_COMMAND_RANGE(ID_PLUGSELECT, ID_PLUGSELECT + kSFxMacros - 1, &CMidiMacroSetup::OnViewAllParams)
ON_COMMAND_RANGE(ID_PLUGSELECT + kSFxMacros, ID_PLUGSELECT + kSFxMacros + kSFxMacros - 1, &CMidiMacroSetup::OnSetSFx)
END_MESSAGE_MAP()
void CMidiMacroSetup::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_COMBO1, m_CbnSFx);
DDX_Control(pDX, IDC_COMBO2, m_CbnSFxPreset);
DDX_Control(pDX, IDC_COMBO3, m_CbnZxxPreset);
DDX_Control(pDX, IDC_COMBO4, m_CbnZxx);
DDX_Control(pDX, IDC_EDIT1, m_EditSFx);
DDX_Control(pDX, IDC_EDIT2, m_EditZxx);
DDX_Control(pDX, IDC_MACROPLUG, m_CbnMacroPlug);
DDX_Control(pDX, IDC_MACROPARAM, m_CbnMacroParam);
DDX_Control(pDX, IDC_MACROCC, m_CbnMacroCC);
}
BOOL CMidiMacroSetup::OnInitDialog()
{
CString s;
CDialog::OnInitDialog();
m_EditSFx.SetLimitText(kMacroLength - 1);
m_EditZxx.SetLimitText(kMacroLength - 1);
// Parametered macro selection
for(int i = 0; i < 16; i++)
{
s.Format(_T("%d (SF%X)"), i, i);
m_CbnSFx.AddString(s);
}
// Parametered macro presets
m_CbnSFx.SetCurSel(0);
for(int i = 0; i < kSFxMax; i++)
{
m_CbnSFxPreset.SetItemData(m_CbnSFxPreset.AddString(m_MidiCfg.GetParameteredMacroName(static_cast<ParameteredMacro>(i))), i);
}
OnSFxChanged();
// MIDI CC selection box
for (int cc = MIDIEvents::MIDICC_start; cc <= MIDIEvents::MIDICC_end; cc++)
{
s.Format(_T("CC %02d "), cc);
s += mpt::ToCString(mpt::Charset::UTF8, MIDIEvents::MidiCCNames[cc]);
m_CbnMacroCC.SetItemData(m_CbnMacroCC.AddString(s), cc);
}
// Z80...ZFF box
for(int zxx = 0x80; zxx <= 0xFF; zxx++)
{
s.Format(_T("Z%02X"), zxx);
m_CbnZxx.AddString(s);
}
// Fixed macro presets
m_CbnZxx.SetCurSel(0);
for(int i = 0; i < kZxxMax; i++)
{
m_CbnZxxPreset.SetItemData(m_CbnZxxPreset.AddString(m_MidiCfg.GetFixedMacroName(static_cast<FixedMacro>(i))), i);
}
m_CbnZxxPreset.SetCurSel(m_MidiCfg.GetFixedMacroType());
UpdateDialog();
auto ScalePixels = [&](auto x) { return Util::ScalePixels(x, m_hWnd); };
int offsetx = ScalePixels(19), offsety = ScalePixels(30), separatorx = ScalePixels(4), separatory = ScalePixels(2);
int height = ScalePixels(18), widthMacro = ScalePixels(30), widthVal = ScalePixels(179), widthType = ScalePixels(135), widthBtn = ScalePixels(70);
for(UINT m = 0; m < kSFxMacros; m++)
{
m_EditMacro[m].Create(_T(""), WS_CHILD | WS_VISIBLE | WS_TABSTOP,
CRect(offsetx, offsety + m * (separatory + height), offsetx + widthMacro, offsety + m * (separatory + height) + height), this, ID_PLUGSELECT + kSFxMacros + m);
m_EditMacro[m].SetFont(GetFont());
m_EditMacroType[m].Create(ES_READONLY | WS_CHILD| WS_VISIBLE | WS_TABSTOP | WS_BORDER,
CRect(offsetx + separatorx + widthMacro, offsety + m * (separatory + height), offsetx + widthMacro + widthType, offsety + m * (separatory + height) + height), this, ID_PLUGSELECT + kSFxMacros + m);
m_EditMacroType[m].SetFont(GetFont());
m_EditMacroValue[m].Create(ES_CENTER | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER,
CRect(offsetx + separatorx + widthType + widthMacro, offsety + m * (separatory + height), offsetx + widthMacro + widthType + widthVal, offsety + m * (separatory + height) + height), this, ID_PLUGSELECT + kSFxMacros + m);
m_EditMacroValue[m].SetFont(GetFont());
m_BtnMacroShowAll[m].Create(_T("Show All..."), WS_CHILD | WS_TABSTOP | WS_VISIBLE,
CRect(offsetx + separatorx + widthType + widthMacro + widthVal, offsety + m * (separatory + height), offsetx + widthMacro + widthType + widthVal + widthBtn, offsety + m * (separatory + height) + height), this, ID_PLUGSELECT + m);
m_BtnMacroShowAll[m].SetFont(GetFont());
}
UpdateMacroList();
#ifndef NO_PLUGINS
for(PLUGINDEX i = 0; i < MAX_MIXPLUGINS; i++)
{
const SNDMIXPLUGIN &plugin = m_SndFile.m_MixPlugins[i];
if(plugin.IsValidPlugin())
{
s.Format(_T("FX%d: "), i + 1);
s += mpt::ToCString(plugin.GetName());
m_CbnMacroPlug.SetItemData(m_CbnMacroPlug.AddString(s), i);
}
}
m_CbnMacroPlug.SetCurSel(0);
OnPlugChanged();
#endif // NO_PLUGINS
return FALSE;
}
// macro == -1 for updating all macros at once
void CMidiMacroSetup::UpdateMacroList(int macro)
{
if(!m_EditMacro[0])
{
// GUI not yet initialized
return;
}
int start, end;
if(macro >= 0 && macro < kSFxMacros)
{
start = end = macro;
} else
{
start = 0;
end = kSFxMacros - 1;
}
CString s;
const int selectedMacro = m_CbnSFx.GetCurSel();
for(int m = start; m <= end; m++)
{
// SFx
s.Format(_T("SF%X"), static_cast<unsigned int>(m));
m_EditMacro[m].SetWindowText(s);
// Macro value:
m_EditMacroValue[m].SetWindowText(mpt::ToCString(mpt::Charset::ASCII, m_MidiCfg.SFx[m]));
m_EditMacroValue[m].SetBackColor(m == selectedMacro ? RGB(200, 200, 225) : RGB(245, 245, 245));
// Macro Type:
const ParameteredMacro macroType = m_MidiCfg.GetParameteredMacroType(m);
switch(macroType)
{
case kSFxPlugParam:
s.Format(_T("Control Plugin Param %u"), static_cast<unsigned int>(m_MidiCfg.MacroToPlugParam(m)));
break;
default:
s = m_MidiCfg.GetParameteredMacroName(m);
break;
}
m_EditMacroType[m].SetWindowText(s);
m_EditMacroType[m].SetBackColor(m == selectedMacro ? RGB(200,200,225) : RGB(245,245,245));
// Param details button:
m_BtnMacroShowAll[m].ShowWindow((macroType == kSFxPlugParam) ? SW_SHOW : SW_HIDE);
}
}
void CMidiMacroSetup::UpdateDialog()
{
UINT sfx = m_CbnSFx.GetCurSel();
UINT sfx_preset = static_cast<UINT>(m_CbnSFxPreset.GetItemData(m_CbnSFxPreset.GetCurSel()));
if(sfx < m_MidiCfg.SFx.size())
{
ToggleBoxes(sfx_preset, sfx);
m_EditSFx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, m_MidiCfg.SFx[sfx]));
}
UpdateZxxSelection();
UpdateMacroList();
}
void CMidiMacroSetup::OnSetAsDefault()
{
theApp.SetDefaultMidiMacro(m_MidiCfg);
}
void CMidiMacroSetup::OnResetCfg()
{
theApp.GetDefaultMidiMacro(m_MidiCfg);
m_CbnZxxPreset.SetCurSel(0);
OnSFxChanged();
}
void CMidiMacroSetup::OnMacroHelp()
{
Reporting::Information(_T("Valid characters in macros:\n\n"
"0-9, A-F - Raw hex data (4-Bit value)\n"
"c - MIDI channel (4-Bit value)\n"
"n - Note value\n\n"
"v - Note velocity\n"
"u - Computed note volume (including envelopes)\n\n"
"x - Note panning\n"
"y - Computed panning (including envelopes)\n\n"
"a - High byte of bank select\n"
"b - Low byte of bank select\n"
"p - Program select\n\n"
"h - Pattern channel\n"
"m - Sample loop direction\n"
"o - Last sample offset (Oxx / 9xx)\n"
"s - SysEx checksum (Roland)\n\n"
"z - Zxx parameter (00-7F)\n\n"
"Macros can be up to 31 characters long and contain multiple MIDI messages. SysEx messages are automatically terminated if not specified by the user."),
_T("OpenMPT MIDI Macro quick reference"));
}
void CMidiMacroSetup::OnSFxChanged()
{
UINT sfx = m_CbnSFx.GetCurSel();
if (sfx < 16)
{
int preset = m_MidiCfg.GetParameteredMacroType(sfx);
m_CbnSFxPreset.SetCurSel(preset);
}
UpdateDialog();
}
void CMidiMacroSetup::OnSFxPresetChanged()
{
UINT sfx = m_CbnSFx.GetCurSel();
ParameteredMacro sfx_preset = static_cast<ParameteredMacro>(m_CbnSFxPreset.GetItemData(m_CbnSFxPreset.GetCurSel()));
if (sfx < kSFxMacros)
{
if(sfx_preset != kSFxCustom)
{
m_MidiCfg.CreateParameteredMacro(sfx, sfx_preset);
}
UpdateDialog();
}
}
void CMidiMacroSetup::OnZxxPresetChanged()
{
FixedMacro zxxPreset = static_cast<FixedMacro>(m_CbnZxxPreset.GetItemData(m_CbnZxxPreset.GetCurSel()));
if (zxxPreset != kZxxCustom)
{
m_MidiCfg.CreateFixedMacro(zxxPreset);
UpdateDialog();
}
}
void CMidiMacroSetup::UpdateZxxSelection()
{
UINT zxx = m_CbnZxx.GetCurSel();
if(zxx < m_MidiCfg.Zxx.size())
{
m_EditZxx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, m_MidiCfg.Zxx[zxx]));
}
}
void CMidiMacroSetup::OnSFxEditChanged()
{
UINT sfx = m_CbnSFx.GetCurSel();
if(sfx < m_MidiCfg.SFx.size())
{
if(ValidateMacroString(m_EditSFx, m_MidiCfg.SFx[sfx], true))
{
CString s;
m_EditSFx.GetWindowText(s);
m_MidiCfg.SFx[sfx] = mpt::ToCharset(mpt::Charset::ASCII, s);
int sfx_preset = m_MidiCfg.GetParameteredMacroType(sfx);
m_CbnSFxPreset.SetCurSel(sfx_preset);
ToggleBoxes(sfx_preset, sfx);
UpdateMacroList(sfx);
}
}
}
void CMidiMacroSetup::OnZxxEditChanged()
{
UINT zxx = m_CbnZxx.GetCurSel();
if(zxx < m_MidiCfg.Zxx.size())
{
if(ValidateMacroString(m_EditZxx, m_MidiCfg.Zxx[zxx], false))
{
CString s;
m_EditZxx.GetWindowText(s);
m_MidiCfg.Zxx[zxx] = mpt::ToCharset(mpt::Charset::ASCII, s);
m_CbnZxxPreset.SetCurSel(m_MidiCfg.GetFixedMacroType());
}
}
}
void CMidiMacroSetup::OnSetSFx(UINT id)
{
m_CbnSFx.SetCurSel(id - (ID_PLUGSELECT + kSFxMacros));
OnSFxChanged();
}
void CMidiMacroSetup::OnViewAllParams(UINT id)
{
#ifndef NO_PLUGINS
CString message, plugName;
int sfx = id - ID_PLUGSELECT;
PlugParamIndex param = m_MidiCfg.MacroToPlugParam(sfx);
message.Format(_T("These are the parameters that can be controlled by macro SF%X:\n\n"), sfx);
for(PLUGINDEX plug = 0; plug < MAX_MIXPLUGINS; plug++)
{
IMixPlugin *pVstPlugin = m_SndFile.m_MixPlugins[plug].pMixPlugin;
if(pVstPlugin && param < pVstPlugin->GetNumParameters())
{
plugName = mpt::ToCString(m_SndFile.m_MixPlugins[plug].GetName());
message.AppendFormat(_T("FX%d: "), plug + 1);
message += plugName + _T("\t") + pVstPlugin->GetFormattedParamName(param) + _T("\n");
}
}
Reporting::Notification(message, _T("Macro -> Parameters"));
#endif // NO_PLUGINS
}
void CMidiMacroSetup::OnPlugChanged()
{
#ifndef NO_PLUGINS
DWORD_PTR plug = m_CbnMacroPlug.GetItemData(m_CbnMacroPlug.GetCurSel());
if(plug >= MAX_MIXPLUGINS)
return;
IMixPlugin *pVstPlugin = m_SndFile.m_MixPlugins[plug].pMixPlugin;
if (pVstPlugin != nullptr)
{
m_CbnMacroParam.SetRedraw(FALSE);
m_CbnMacroParam.Clear();
m_CbnMacroParam.ResetContent();
AddPluginParameternamesToCombobox(m_CbnMacroParam, *pVstPlugin);
m_CbnMacroParam.SetRedraw(TRUE);
int param = m_MidiCfg.MacroToPlugParam(m_CbnSFx.GetCurSel());
m_CbnMacroParam.SetCurSel(param);
}
#endif // NO_PLUGINS
}
void CMidiMacroSetup::OnPlugParamChanged()
{
int param = static_cast<int>(m_CbnMacroParam.GetItemData(m_CbnMacroParam.GetCurSel()));
if(param < 384)
{
const std::string macroText = m_MidiCfg.CreateParameteredMacro(kSFxPlugParam, param);
m_EditSFx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, macroText));
} else
{
Reporting::Notification("Only parameters 0 to 383 can be controlled using MIDI Macros. Use Parameter Control Events to automate higher parameters.");
}
}
void CMidiMacroSetup::OnCCChanged()
{
int cc = static_cast<int>(m_CbnMacroCC.GetItemData(m_CbnMacroCC.GetCurSel()));
const std::string macroText = m_MidiCfg.CreateParameteredMacro(kSFxCC, cc);
m_EditSFx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, macroText));
}
void CMidiMacroSetup::ToggleBoxes(UINT sfxPreset, UINT sfx)
{
if (sfxPreset == kSFxPlugParam)
{
m_CbnMacroCC.ShowWindow(FALSE);
m_CbnMacroPlug.ShowWindow(TRUE);
m_CbnMacroParam.ShowWindow(TRUE);
m_CbnMacroPlug.EnableWindow(TRUE);
m_CbnMacroParam.EnableWindow(TRUE);
SetDlgItemText(IDC_GENMACROLABEL, _T("Plugin/Param"));
m_CbnMacroParam.SetCurSel(m_MidiCfg.MacroToPlugParam(sfx));
} else
{
m_CbnMacroPlug.EnableWindow(FALSE);
m_CbnMacroParam.EnableWindow(FALSE);
}
if (sfxPreset == kSFxCC)
{
m_CbnMacroCC.EnableWindow(TRUE);
m_CbnMacroCC.ShowWindow(TRUE);
m_CbnMacroPlug.ShowWindow(FALSE);
m_CbnMacroParam.ShowWindow(FALSE);
SetDlgItemText(IDC_GENMACROLABEL, _T("MIDI CC"));
m_CbnMacroCC.SetCurSel(m_MidiCfg.MacroToMidiCC(sfx));
} else
{
m_CbnMacroCC.EnableWindow(FALSE);
}
}
bool CMidiMacroSetup::ValidateMacroString(CEdit &wnd, const MIDIMacroConfig::Macro &prevMacro, bool isParametric)
{
CString macroStrT;
wnd.GetWindowText(macroStrT);
std::string macroStr = mpt::ToCharset(mpt::Charset::ASCII, macroStrT);
bool allowed = true, caseChange = false;
for(char &c : macroStr)
{
if(c == 'k' || c == 'K') // Previously, 'K' was used for MIDI channel
{
caseChange = true;
c = 'c';
} else if(c >= 'd' && c <= 'f') // abc have special meanings, but def can be fixed
{
caseChange = true;
c = c - 'a' + 'A';
} else if(c == 'M' || c == 'N' || c == 'O' || c == 'P' || c == 'S' || c == 'U' || c == 'V' || c == 'X' || c == 'Y' || c == 'Z')
{
caseChange = true;
c = c - 'A' + 'a';
} else if(!(
(c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'c') ||
(c == 'h' || c == 'm' || c == 'n' || c == 'o' || c == 'p' || c == 's' ||c == 'u' || c == 'v' || c == 'x' || c == 'y' || c == ' ') ||
(c == 'z' && isParametric)))
{
allowed = false;
break;
}
}
if(!allowed)
{
// Replace text and keep cursor position if we just typed in an invalid character
if(prevMacro != std::string_view{macroStr})
{
int start, end;
wnd.GetSel(start, end);
wnd.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, prevMacro));
wnd.SetSel(start - 1, end - 1, true);
MessageBeep(MB_OK);
}
return false;
} else
{
if(caseChange)
{
// Replace text and keep cursor position if there was a case conversion
int start, end;
wnd.GetSel(start, end);
wnd.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, macroStr));
wnd.SetSel(start, end, true);
}
return true;
}
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,66 @@
/*
* MIDIMacroDialog.h
* -----------------
* Purpose: MIDI Macro Configuration Dialog
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "ColourEdit.h"
#include "../common/misc_util.h"
#include "../soundlib/MIDIMacros.h"
#include "mpt/base/alloc.hpp"
OPENMPT_NAMESPACE_BEGIN
class CMidiMacroSetup: public CDialog
{
protected:
CComboBox m_CbnSFx, m_CbnSFxPreset, m_CbnZxx, m_CbnZxxPreset, m_CbnMacroPlug, m_CbnMacroParam, m_CbnMacroCC;
CEdit m_EditSFx, m_EditZxx;
CColourEdit m_EditMacroValue[kSFxMacros], m_EditMacroType[kSFxMacros];
CButton m_EditMacro[kSFxMacros], m_BtnMacroShowAll[kSFxMacros];
CSoundFile &m_SndFile;
public:
CMidiMacroSetup(CSoundFile &sndFile, CWnd *parent = nullptr) : CDialog(IDD_MIDIMACRO, parent), m_SndFile(sndFile), m_vMidiCfg(sndFile.m_MidiCfg), m_MidiCfg(*m_vMidiCfg) { }
private:
mpt::heap_value<MIDIMacroConfig> m_vMidiCfg;
public:
MIDIMacroConfig & m_MidiCfg;
protected:
BOOL OnInitDialog() override;
void DoDataExchange(CDataExchange* pDX) override;
bool ValidateMacroString(CEdit &wnd, const MIDIMacroConfig::Macro &prevMacro, bool isParametric);
void UpdateMacroList(int macro=-1);
void ToggleBoxes(UINT preset, UINT sfx);
afx_msg void UpdateDialog();
afx_msg void OnSetAsDefault();
afx_msg void OnResetCfg();
afx_msg void OnMacroHelp();
afx_msg void OnSFxChanged();
afx_msg void OnSFxPresetChanged();
afx_msg void OnZxxPresetChanged();
afx_msg void OnSFxEditChanged();
afx_msg void OnZxxEditChanged();
afx_msg void UpdateZxxSelection();
afx_msg void OnPlugChanged();
afx_msg void OnPlugParamChanged();
afx_msg void OnCCChanged();
afx_msg void OnViewAllParams(UINT id);
afx_msg void OnSetSFx(UINT id);
DECLARE_MESSAGE_MAP()
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,172 @@
/*
* MIDIMapping.cpp
* ---------------
* Purpose: MIDI Mapping management classes
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "Moddoc.h"
#include "MIDIMapping.h"
#include "../common/FileReader.h"
#include "../soundlib/MIDIEvents.h"
#include "../soundlib/plugins/PlugInterface.h"
#include "mpt/io/io.hpp"
#include "mpt/io/io_stdstream.hpp"
OPENMPT_NAMESPACE_BEGIN
size_t CMIDIMapper::Serialize(std::ostream *file) const
{
//Bytes: 1 Flags, 2 key, 1 plugindex, 1,2,4,8 plug/etc.
size_t size = 0;
for(const auto &d : m_Directives)
{
uint16 temp16 = (d.GetChnEvent() << 1) + (d.GetController() << 9);
if(d.GetAnyChannel()) temp16 |= 1;
uint32 temp32 = d.GetParamIndex();
uint8 temp8 = d.IsActive(); //bit 0
if(d.GetCaptureMIDI()) temp8 |= (1 << 1); //bit 1
//bits 2-4: Mapping type: 0 for plug param control.
//bit 5:
if(d.GetAllowPatternEdit()) temp8 |= (1 << 5);
//bits 6-7: Size: 5, 6, 8, 12
uint8 parambytes = 4;
if(temp32 <= uint16_max)
{
if(temp32 <= uint8_max) parambytes = 1;
else {parambytes = 2; temp8 |= (1 << 6);}
}
else temp8 |= (2 << 6);
if(file)
{
std::ostream & f = *file;
mpt::IO::WriteIntLE<uint8>(f, temp8);
mpt::IO::WriteIntLE<uint16>(f, temp16);
mpt::IO::WriteIntLE<uint8>(f, d.GetPlugIndex());
mpt::IO::WritePartial<uint32le>(f, mpt::as_le(temp32), parambytes);
}
size += sizeof(temp8) + sizeof(temp16) + sizeof(temp8) + parambytes;
}
return size;
}
bool CMIDIMapper::Deserialize(FileReader &file)
{
m_Directives.clear();
while(file.CanRead(1))
{
uint8 i8 = file.ReadUint8();
uint8 psize = 0;
// Determine size of this event (depends on size of plugin parameter index)
switch(i8 >> 6)
{
case 0: psize = 4; break;
case 1: psize = 5; break;
case 2: psize = 7; break;
case 3: default: psize = 11; break;
}
if(!file.CanRead(psize)) return false;
if(((i8 >> 2) & 7) != 0) { file.Skip(psize); continue;} //Skipping unrecognised mapping types.
CMIDIMappingDirective s;
s.SetActive((i8 & 1) != 0);
s.SetCaptureMIDI((i8 & (1 << 1)) != 0);
s.SetAllowPatternEdit((i8 & (1 << 5)) != 0);
uint16 i16 = file.ReadUint16LE(); //Channel, event, MIDIbyte1.
i8 = file.ReadUint8(); //Plugindex
uint32le i32;
file.ReadStructPartial(i32, psize - 3);
s.SetChannel(((i16 & 1) != 0) ? 0 : 1 + ((i16 >> 1) & 0xF));
s.SetEvent(static_cast<uint8>((i16 >> 5) & 0xF));
s.SetController(i16 >> 9);
s.SetPlugIndex(i8);
s.SetParamIndex(i32);
AddDirective(s);
}
return true;
}
bool CMIDIMapper::OnMIDImsg(const DWORD midimsg, PLUGINDEX &mappedIndex, PlugParamIndex &paramindex, uint16 &paramval)
{
const MIDIEvents::EventType eventType = MIDIEvents::GetTypeFromEvent(midimsg);
const uint8 controller = MIDIEvents::GetDataByte1FromEvent(midimsg);
const uint8 channel = MIDIEvents::GetChannelFromEvent(midimsg) & 0x7F;
const uint8 controllerVal = MIDIEvents::GetDataByte2FromEvent(midimsg) & 0x7F;
for(const auto &d : m_Directives)
{
if(!d.IsActive()) continue;
if(d.GetEvent() != eventType) continue;
if(eventType == MIDIEvents::evControllerChange
&& d.GetController() != controller
&& (d.GetController() >= 32 || d.GetController() + 32 != controller))
continue;
if(!d.GetAnyChannel() && channel + 1 != d.GetChannel()) continue;
const PLUGINDEX plugindex = d.GetPlugIndex();
const uint32 param = d.GetParamIndex();
uint16 val = (d.GetEvent() == MIDIEvents::evChannelAftertouch ? controller : controllerVal) << 7;
if(eventType == MIDIEvents::evControllerChange)
{
// Fine (0...31) / Coarse (32...63) controller pairs - Fine should be sent first.
if(controller == m_lastCC + 32 && m_lastCC < 32)
{
val = (val >> 7) | m_lastCCvalue;
}
m_lastCC = controller;
m_lastCCvalue = val;
}
if(d.GetAllowPatternEdit())
{
mappedIndex = plugindex;
paramindex = param;
paramval = val;
}
if(plugindex > 0 && plugindex <= MAX_MIXPLUGINS)
{
#ifndef NO_PLUGINS
IMixPlugin *pPlug = m_rSndFile.m_MixPlugins[plugindex - 1].pMixPlugin;
if(!pPlug) continue;
pPlug->SetParameter(param, val / 16383.0f);
if(m_rSndFile.GetpModDoc() != nullptr)
m_rSndFile.GetpModDoc()->SetModified();
#endif // NO_PLUGINS
}
if(d.GetCaptureMIDI())
{
return true;
}
}
return false;
}
void CMIDIMapper::Swap(const size_t a, const size_t b)
{
if(a < m_Directives.size() && b < m_Directives.size())
{
std::swap(m_Directives[a], m_Directives[b]);
Sort();
}
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,128 @@
/*
* MIDIMapping.h
* -------------
* Purpose: MIDI Mapping management classes
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include <vector>
#include <algorithm>
#include "../common/FileReaderFwd.h"
OPENMPT_NAMESPACE_BEGIN
class CMIDIMappingDirective
{
public:
CMIDIMappingDirective()
: m_Active(true), m_CaptureMIDI(false), m_AllowPatternEdit(true), m_AnyChannel(true)
{
}
void SetActive(const bool b) { m_Active = b; }
bool IsActive() const { return m_Active; }
void SetCaptureMIDI(const bool b) { m_CaptureMIDI = b; }
bool GetCaptureMIDI() const { return m_CaptureMIDI; }
void SetAllowPatternEdit(const bool b) { m_AllowPatternEdit = b; }
bool GetAllowPatternEdit() const { return m_AllowPatternEdit; }
bool GetAnyChannel() const { return m_AnyChannel; }
//Note: In these functions, channel value is in range [1,16],
//GetChannel() returns 0 on 'any channel'.
void SetChannel(const int c) { if(c < 1 || c > 16) m_AnyChannel = true; else { m_ChnEvent &= ~0x0F; m_ChnEvent |= c - 1; m_AnyChannel = false; } }
uint8 GetChannel() const {return (m_AnyChannel) ? 0 : (m_ChnEvent & 0xF) + 1;}
void SetEvent(uint8 e) { if(e > 15) e = 15; m_ChnEvent &= ~0xF0; m_ChnEvent |= (e << 4); }
uint8 GetEvent() const {return (m_ChnEvent >> 4) & 0x0F;}
void SetController(int controller) { if(controller > 127) controller = 127; m_MIDIByte1 = static_cast<uint8>(controller); }
uint8 GetController() const { return m_MIDIByte1; }
//Note: Plug index starts from 1.
void SetPlugIndex(const int i) { m_PluginIndex = static_cast<PLUGINDEX>(i); }
PLUGINDEX GetPlugIndex() const { return m_PluginIndex; }
void SetParamIndex(const int i) { m_Parameter = i; }
uint32 GetParamIndex() const { return m_Parameter; }
bool IsDefault() const { return *this == CMIDIMappingDirective{}; }
bool operator==(const CMIDIMappingDirective &other) const { return memcmp(this, &other, sizeof(*this)) == 0; }
bool operator<(const CMIDIMappingDirective &other) const { return GetController() < other.GetController(); }
uint8 GetChnEvent() const {return m_ChnEvent;}
private:
uint32 m_Parameter = 0;
PLUGINDEX m_PluginIndex = 1;
uint8 m_MIDIByte1 = 0;
uint8 m_ChnEvent = (0xB << 4); // 0-3 channel, 4-7 event
bool m_Active : 1;
bool m_CaptureMIDI : 1; // When true, MIDI data should not be processed beyond this directive
bool m_AllowPatternEdit : 1; // When true, the mapping can be used for modifying pattern.
bool m_AnyChannel : 1;
};
class CSoundFile;
class CMIDIMapper
{
public:
CMIDIMapper(CSoundFile& sndfile) : m_rSndFile(sndfile) {}
// If mapping found:
// - mappedIndex is set to mapped value(plug index)
// - paramindex to mapped parameter
// - paramvalue to parameter value.
// In case of multiple mappings, these get the values from the last mapping found.
// Returns true if MIDI was 'captured' by some directive, false otherwise.
bool OnMIDImsg(const DWORD midimsg, PLUGINDEX &mappedIndex, PlugParamIndex &paramindex, uint16 &paramvalue);
// Swaps the positions of two elements.
void Swap(const size_t a, const size_t b);
// Return the index after sorting for the added element
size_t SetDirective(const size_t i, const CMIDIMappingDirective& d) { m_Directives[i] = d; Sort(); return std::find(m_Directives.begin(), m_Directives.end(), d) - m_Directives.begin(); }
// Return the index after sorting for the added element
size_t AddDirective(const CMIDIMappingDirective& d) { m_Directives.push_back(d); Sort(); return std::find(m_Directives.begin(), m_Directives.end(), d) - m_Directives.begin(); }
void RemoveDirective(const size_t i) { m_Directives.erase(m_Directives.begin() + i); }
const CMIDIMappingDirective &GetDirective(const size_t i) const { return m_Directives[i]; }
size_t GetCount() const { return m_Directives.size(); }
// Serialize to file, or just return the serialization size if no file handle is provided.
size_t Serialize(std::ostream *file = nullptr) const;
// Deserialize MIDI Mappings from file. Returns true if no errors were encountered.
bool Deserialize(FileReader &file);
bool AreOrderEqual(const size_t a, const size_t b) const { return !(m_Directives[a] < m_Directives[b] || m_Directives[b] < m_Directives[a]); }
private:
void Sort() { std::stable_sort(m_Directives.begin(), m_Directives.end()); }
private:
CSoundFile &m_rSndFile;
std::vector<CMIDIMappingDirective> m_Directives;
uint16 m_lastCCvalue = 0;
uint8 m_lastCC = uint8_max;
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,506 @@
/*
* MIDIMappingDialog.cpp
* ---------------------
* Purpose: Implementation of OpenMPT's MIDI mapping dialog, for mapping incoming MIDI messages to plugin parameters.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "Mainfrm.h"
#include "Moddoc.h"
#include "MIDIMappingDialog.h"
#include "InputHandler.h"
#include "../soundlib/MIDIEvents.h"
#include "../soundlib/mod_specifications.h"
#include "../soundlib/plugins/PlugInterface.h"
#include "../common/mptStringBuffer.h"
#ifndef NO_PLUGINS
OPENMPT_NAMESPACE_BEGIN
CMIDIMappingDialog::CMIDIMappingDialog(CWnd *pParent, CSoundFile &rSndfile)
: CDialog(IDD_MIDIPARAMCONTROL, pParent)
, m_sndFile(rSndfile)
, m_rMIDIMapper(m_sndFile.GetMIDIMapper())
{
CMainFrame::GetInputHandler()->Bypass(true);
oldMIDIRecondWnd = CMainFrame::GetMainFrame()->GetMidiRecordWnd();
}
CMIDIMappingDialog::~CMIDIMappingDialog()
{
CMainFrame::GetMainFrame()->SetMidiRecordWnd(oldMIDIRecondWnd);
CMainFrame::GetInputHandler()->Bypass(false);
}
void CMIDIMappingDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_COMBO_CONTROLLER, m_ControllerCBox);
DDX_Control(pDX, IDC_COMBO_PLUGIN, m_PluginCBox);
DDX_Control(pDX, IDC_COMBO_PARAM, m_PlugParamCBox);
DDX_Control(pDX, IDC_LIST1, m_List);
DDX_Control(pDX, IDC_COMBO_CHANNEL, m_ChannelCBox);
DDX_Control(pDX, IDC_COMBO_EVENT, m_EventCBox);
DDX_Control(pDX, IDC_SPINMOVEMAPPING, m_SpinMoveMapping);
}
BEGIN_MESSAGE_MAP(CMIDIMappingDialog, CDialog)
ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST1, &CMIDIMappingDialog::OnSelectionChanged)
ON_BN_CLICKED(IDC_CHECKACTIVE, &CMIDIMappingDialog::OnBnClickedCheckactive)
ON_BN_CLICKED(IDC_CHECKCAPTURE, &CMIDIMappingDialog::OnBnClickedCheckCapture)
ON_CBN_SELCHANGE(IDC_COMBO_CONTROLLER, &CMIDIMappingDialog::OnCbnSelchangeComboController)
ON_CBN_SELCHANGE(IDC_COMBO_CHANNEL, &CMIDIMappingDialog::OnCbnSelchangeComboChannel)
ON_CBN_SELCHANGE(IDC_COMBO_PLUGIN, &CMIDIMappingDialog::OnCbnSelchangeComboPlugin)
ON_CBN_SELCHANGE(IDC_COMBO_PARAM, &CMIDIMappingDialog::OnCbnSelchangeComboParam)
ON_CBN_SELCHANGE(IDC_COMBO_EVENT, &CMIDIMappingDialog::OnCbnSelchangeComboEvent)
ON_BN_CLICKED(IDC_BUTTON_ADD, &CMIDIMappingDialog::OnBnClickedButtonAdd)
ON_BN_CLICKED(IDC_BUTTON_REPLACE, &CMIDIMappingDialog::OnBnClickedButtonReplace)
ON_BN_CLICKED(IDC_BUTTON_REMOVE, &CMIDIMappingDialog::OnBnClickedButtonRemove)
ON_MESSAGE(WM_MOD_MIDIMSG, &CMIDIMappingDialog::OnMidiMsg)
ON_NOTIFY(UDN_DELTAPOS, IDC_SPINMOVEMAPPING, &CMIDIMappingDialog::OnDeltaposSpinmovemapping)
ON_BN_CLICKED(IDC_CHECK_PATRECORD, &CMIDIMappingDialog::OnBnClickedCheckPatRecord)
ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &CMIDIMappingDialog::OnToolTipNotify)
END_MESSAGE_MAP()
LRESULT CMIDIMappingDialog::OnMidiMsg(WPARAM dwMidiDataParam, LPARAM)
{
uint32 midiData = static_cast<uint32>(dwMidiDataParam);
if(IsDlgButtonChecked(IDC_CHECK_MIDILEARN))
{
for(int i = 0; i < m_EventCBox.GetCount(); i++)
{
if(static_cast<MIDIEvents::EventType>(m_EventCBox.GetItemData(i)) == MIDIEvents::GetTypeFromEvent(midiData))
{
m_ChannelCBox.SetCurSel(1 + MIDIEvents::GetChannelFromEvent(midiData));
m_EventCBox.SetCurSel(i);
if(MIDIEvents::GetTypeFromEvent(midiData) == MIDIEvents::evControllerChange)
{
uint8 cc = MIDIEvents::GetDataByte1FromEvent(midiData);
if(m_lastCC >= 32 || cc != m_lastCC + 32)
{
// Ignore second CC message of 14-bit CC.
m_ControllerCBox.SetCurSel(cc);
}
m_lastCC = cc;
}
OnCbnSelchangeComboChannel();
OnCbnSelchangeComboEvent();
OnCbnSelchangeComboController();
break;
}
}
}
return 1;
}
BOOL CMIDIMappingDialog::OnInitDialog()
{
CDialog::OnInitDialog();
// Add events
m_EventCBox.SetItemData(m_EventCBox.AddString(_T("Controller Change")), MIDIEvents::evControllerChange);
m_EventCBox.SetItemData(m_EventCBox.AddString(_T("Polyphonic Aftertouch")), MIDIEvents::evPolyAftertouch);
m_EventCBox.SetItemData(m_EventCBox.AddString(_T("Channel Aftertouch")), MIDIEvents::evChannelAftertouch);
// Add controller names
CString s;
for(uint8 i = MIDIEvents::MIDICC_start; i <= MIDIEvents::MIDICC_end; i++)
{
s.Format(_T("%3u "), i);
s += mpt::ToCString(mpt::Charset::UTF8, MIDIEvents::MidiCCNames[i]);
m_ControllerCBox.AddString(s);
}
// Add plugin names
AddPluginNamesToCombobox(m_PluginCBox, m_sndFile.m_MixPlugins);
// Initialize mapping table
static constexpr CListCtrlEx::Header headers[] =
{
{ _T("Channel"), 58, LVCFMT_LEFT },
{ _T("Event / Controller"), 176, LVCFMT_LEFT },
{ _T("Plugin"), 120, LVCFMT_LEFT },
{ _T("Parameter"), 120, LVCFMT_LEFT },
{ _T("Capture"), 40, LVCFMT_LEFT },
{ _T("Pattern Record"), 40, LVCFMT_LEFT }
};
m_List.SetHeaders(headers);
m_List.SetExtendedStyle(m_List.GetExtendedStyle() | LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT);
// Add directives to list
for(size_t i = 0; i < m_rMIDIMapper.GetCount(); i++)
{
InsertItem(m_rMIDIMapper.GetDirective(i), int(i));
}
if(m_rMIDIMapper.GetCount() > 0 && m_Setting.IsDefault())
{
SelectItem(0);
OnSelectionChanged();
} else
{
UpdateDialog();
}
GetDlgItem(IDC_CHECK_PATRECORD)->EnableWindow((m_sndFile.GetType() == MOD_TYPE_MPT) ? TRUE : FALSE);
CMainFrame::GetMainFrame()->SetMidiRecordWnd(GetSafeHwnd());
CheckDlgButton(IDC_CHECK_MIDILEARN, BST_CHECKED);
EnableToolTips(TRUE);
return TRUE; // return TRUE unless you set the focus to a control
}
int CMIDIMappingDialog::InsertItem(const CMIDIMappingDirective &m, int insertAt)
{
CString s;
if(m.GetAnyChannel())
s = _T("Any");
else
s.Format(_T("Ch %u"), m.GetChannel());
insertAt = m_List.InsertItem(insertAt, s);
if(insertAt == -1)
return -1;
m_List.SetCheck(insertAt, m.IsActive() ? TRUE : FALSE);
switch(m.GetEvent())
{
case MIDIEvents::evControllerChange:
s.Format(_T("CC %u: "), m.GetController());
if(m.GetController() <= MIDIEvents::MIDICC_end) s += mpt::ToCString(mpt::Charset::UTF8, MIDIEvents::MidiCCNames[m.GetController()]);
break;
case MIDIEvents::evPolyAftertouch:
s = _T("Polyphonic Aftertouch"); break;
case MIDIEvents::evChannelAftertouch:
s = _T("Channel Aftertouch"); break;
default:
s.Format(_T("0x%02X"), m.GetEvent()); break;
}
m_List.SetItemText(insertAt, 1, s);
const PLUGINDEX plugindex = m.GetPlugIndex();
if(plugindex > 0 && plugindex < MAX_MIXPLUGINS)
{
const SNDMIXPLUGIN &plug = m_sndFile.m_MixPlugins[plugindex - 1];
s.Format(_T("FX%u: "), plugindex);
s += mpt::ToCString(plug.GetName());
m_List.SetItemText(insertAt, 2, s);
if(plug.pMixPlugin != nullptr)
s = plug.pMixPlugin->GetFormattedParamName(m.GetParamIndex());
else
s.Empty();
m_List.SetItemText(insertAt, 3, s);
}
m_List.SetItemText(insertAt, 4, m.GetCaptureMIDI() ? _T("Capt") : _T(""));
m_List.SetItemText(insertAt, 5, m.GetAllowPatternEdit() ? _T("Rec") : _T(""));
return insertAt;
}
void CMIDIMappingDialog::SelectItem(int i)
{
m_List.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
m_List.SetSelectionMark(i);
}
void CMIDIMappingDialog::UpdateDialog(int selItem)
{
CheckDlgButton(IDC_CHECKACTIVE, m_Setting.IsActive() ? BST_CHECKED : BST_UNCHECKED);
CheckDlgButton(IDC_CHECKCAPTURE, m_Setting.GetCaptureMIDI() ? BST_CHECKED : BST_UNCHECKED);
CheckDlgButton(IDC_CHECK_PATRECORD, m_Setting.GetAllowPatternEdit() ? BST_CHECKED : BST_UNCHECKED);
m_ChannelCBox.SetCurSel(m_Setting.GetChannel());
m_EventCBox.SetCurSel(-1);
for(int i = 0; i < m_EventCBox.GetCount(); i++)
{
if(m_EventCBox.GetItemData(i) == m_Setting.GetEvent())
{
m_EventCBox.SetCurSel(i);
break;
}
}
m_ControllerCBox.SetCurSel(m_Setting.GetController());
m_PluginCBox.SetCurSel(m_Setting.GetPlugIndex() - 1);
m_PlugParamCBox.SetCurSel(m_Setting.GetParamIndex());
UpdateEvent();
UpdateParameters();
bool enableMover = selItem >= 0;
if(enableMover)
{
const bool previousEqual = (selItem > 0 && m_rMIDIMapper.AreOrderEqual(selItem - 1, selItem));
const bool nextEqual = (selItem + 1 < m_List.GetItemCount() && m_rMIDIMapper.AreOrderEqual(selItem, selItem + 1));
enableMover = previousEqual || nextEqual;
}
m_SpinMoveMapping.EnableWindow(enableMover);
}
void CMIDIMappingDialog::UpdateEvent()
{
m_ControllerCBox.EnableWindow(m_Setting.GetEvent() == MIDIEvents::evControllerChange ? TRUE : FALSE);
if(m_Setting.GetEvent() != MIDIEvents::evControllerChange)
m_ControllerCBox.SetCurSel(0);
}
void CMIDIMappingDialog::UpdateParameters()
{
m_PlugParamCBox.SetRedraw(FALSE);
m_PlugParamCBox.ResetContent();
AddPluginParameternamesToCombobox(m_PlugParamCBox, m_sndFile.m_MixPlugins[m_Setting.GetPlugIndex() - 1]);
m_PlugParamCBox.SetCurSel(m_Setting.GetParamIndex());
m_PlugParamCBox.SetRedraw(TRUE);
m_PlugParamCBox.Invalidate();
}
void CMIDIMappingDialog::OnSelectionChanged(NMHDR *pNMHDR, LRESULT * /*pResult*/)
{
int i;
if(pNMHDR != nullptr)
{
NMLISTVIEW *nmlv = (NMLISTVIEW *)pNMHDR;
if(((nmlv->uOldState ^ nmlv->uNewState) & INDEXTOSTATEIMAGEMASK(3)) != 0 && nmlv->uOldState != 0)
{
// Check box status changed
CMIDIMappingDirective m = m_rMIDIMapper.GetDirective(nmlv->iItem);
m.SetActive(nmlv->uNewState == INDEXTOSTATEIMAGEMASK(2));
m_rMIDIMapper.SetDirective(nmlv->iItem, m);
SetModified();
if(nmlv->iItem == m_List.GetSelectionMark())
CheckDlgButton(IDC_CHECKACTIVE, nmlv->uNewState == INDEXTOSTATEIMAGEMASK(2) ? BST_CHECKED : BST_UNCHECKED);
}
if(nmlv->uNewState & LVIS_SELECTED)
i = nmlv->iItem;
else
return;
} else
{
i = m_List.GetSelectionMark();
}
if(i < 0 || (size_t)i >= m_rMIDIMapper.GetCount()) return;
m_Setting = m_rMIDIMapper.GetDirective(i);
UpdateDialog(i);
}
void CMIDIMappingDialog::OnBnClickedCheckactive()
{
m_Setting.SetActive(IsDlgButtonChecked(IDC_CHECKACTIVE) == BST_CHECKED);
}
void CMIDIMappingDialog::OnBnClickedCheckCapture()
{
m_Setting.SetCaptureMIDI(IsDlgButtonChecked(IDC_CHECKCAPTURE) == BST_CHECKED);
}
void CMIDIMappingDialog::OnBnClickedCheckPatRecord()
{
m_Setting.SetAllowPatternEdit(IsDlgButtonChecked(IDC_CHECK_PATRECORD) == BST_CHECKED);
}
void CMIDIMappingDialog::OnCbnSelchangeComboController()
{
m_Setting.SetController(m_ControllerCBox.GetCurSel());
}
void CMIDIMappingDialog::OnCbnSelchangeComboChannel()
{
m_Setting.SetChannel(m_ChannelCBox.GetCurSel());
}
void CMIDIMappingDialog::OnCbnSelchangeComboPlugin()
{
int i = m_PluginCBox.GetCurSel();
if(i < 0 || i >= MAX_MIXPLUGINS) return;
m_Setting.SetPlugIndex(i+1);
UpdateParameters();
}
void CMIDIMappingDialog::OnCbnSelchangeComboParam()
{
m_Setting.SetParamIndex(m_PlugParamCBox.GetCurSel());
}
void CMIDIMappingDialog::OnCbnSelchangeComboEvent()
{
uint8 eventType = static_cast<uint8>(m_EventCBox.GetItemData(m_EventCBox.GetCurSel()));
m_Setting.SetEvent(eventType);
UpdateEvent();
}
void CMIDIMappingDialog::OnBnClickedButtonAdd()
{
if(m_sndFile.GetModSpecifications().MIDIMappingDirectivesMax <= m_rMIDIMapper.GetCount())
{
Reporting::Information("Maximum amount of MIDI Mapping directives reached.");
} else
{
const size_t i = m_rMIDIMapper.AddDirective(m_Setting);
SetModified();
SelectItem(InsertItem(m_Setting, static_cast<int>(i)));
OnSelectionChanged();
}
}
void CMIDIMappingDialog::OnBnClickedButtonReplace()
{
const int i = m_List.GetSelectionMark();
if(i >= 0 && (size_t)i < m_rMIDIMapper.GetCount())
{
const size_t newIndex = m_rMIDIMapper.SetDirective(i, m_Setting);
SetModified();
m_List.DeleteItem(i);
SelectItem(InsertItem(m_Setting, static_cast<int>(newIndex)));
OnSelectionChanged();
}
}
void CMIDIMappingDialog::OnBnClickedButtonRemove()
{
int i = m_List.GetSelectionMark();
if(i >= 0 && (size_t)i < m_rMIDIMapper.GetCount())
{
m_rMIDIMapper.RemoveDirective(i);
SetModified();
m_List.DeleteItem(i);
if(m_List.GetItemCount() > 0)
{
if(i < m_List.GetItemCount())
SelectItem(i);
else
SelectItem(i - 1);
}
i = m_List.GetSelectionMark();
if(i >= 0 && (size_t)i < m_rMIDIMapper.GetCount())
m_Setting = m_rMIDIMapper.GetDirective(i);
OnSelectionChanged();
}
}
void CMIDIMappingDialog::OnDeltaposSpinmovemapping(NMHDR *pNMHDR, LRESULT *pResult)
{
const int index = m_List.GetSelectionMark();
if(index < 0 || index >= m_List.GetItemCount()) return;
LPNMUPDOWN pNMUpDown = reinterpret_cast<LPNMUPDOWN>(pNMHDR);
int newIndex = -1;
if(pNMUpDown->iDelta < 0) //Up
{
if(index - 1 >= 0 && m_rMIDIMapper.AreOrderEqual(index-1, index))
{
newIndex = index - 1;
}
} else //Down
{
if(index + 1 < m_List.GetItemCount() && m_rMIDIMapper.AreOrderEqual(index, index+1))
{
newIndex = index + 1;
}
}
if(newIndex != -1)
{
m_rMIDIMapper.Swap(size_t(newIndex), size_t(index));
m_List.DeleteItem(index);
InsertItem(m_rMIDIMapper.GetDirective(newIndex), newIndex);
SelectItem(newIndex);
}
*pResult = 0;
}
BOOL CMIDIMappingDialog::OnToolTipNotify(UINT, NMHDR * pNMHDR, LRESULT *)
{
TOOLTIPTEXT *pTTT = (TOOLTIPTEXT*)pNMHDR;
const TCHAR *text = _T("");
UINT_PTR nID = pNMHDR->idFrom;
if(pTTT->uFlags & TTF_IDISHWND)
{
// idFrom is actually the HWND of the tool
nID = ::GetDlgCtrlID((HWND)nID);
}
switch(nID)
{
case IDC_CHECKCAPTURE:
text = _T("The event is not passed to any further MIDI mappings or recording facilities.");
break;
case IDC_CHECKACTIVE:
text = _T("The MIDI mapping is enabled and can be processed.");
break;
case IDC_CHECK_PATRECORD:
text = _T("Parameter changes are recorded into patterns as Parameter Control events.");
break;
case IDC_CHECK_MIDILEARN:
text = _T("Listens to incoming MIDI data to automatically fill in the appropriate data.");
break;
case IDC_SPINMOVEMAPPING:
text = _T("Change the processing order of the current selected MIDI mapping.");
break;
case IDC_COMBO_CHANNEL:
text = _T("The MIDI channel to listen on for this event.");
break;
case IDC_COMBO_EVENT:
text = _T("The MIDI event to listen for.");
break;
case IDC_COMBO_CONTROLLER:
text = _T("The MIDI controler to listen for.");
break;
}
mpt::String::WriteWinBuf(pTTT->szText) = mpt::winstring(text);
return TRUE;
}
void CMIDIMappingDialog::SetModified()
{
if(m_sndFile.GetpModDoc() != nullptr)
m_sndFile.GetpModDoc()->SetModified();
}
OPENMPT_NAMESPACE_END
#endif // NO_PLUGINS
@@ -0,0 +1,85 @@
/*
* MIDIMappingDialog.h
* -------------------
* Purpose: Implementation of OpenMPT's MIDI mapping dialog, for mapping incoming MIDI messages to plugin parameters.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#ifndef NO_PLUGINS
#include "MIDIMapping.h"
#include "CListCtrl.h"
OPENMPT_NAMESPACE_BEGIN
class CSoundFile;
class CMIDIMapper;
class CMIDIMappingDialog : public CDialog
{
public:
CMIDIMappingDirective m_Setting;
protected:
CSoundFile &m_sndFile;
CMIDIMapper &m_rMIDIMapper;
HWND oldMIDIRecondWnd;
// Dialog Data
CComboBox m_ControllerCBox;
CComboBox m_PluginCBox;
CComboBox m_PlugParamCBox;
CComboBox m_ChannelCBox;
CComboBox m_EventCBox;
CListCtrlEx m_List;
CSpinButtonCtrl m_SpinMoveMapping;
uint8 m_lastCC = uint8_max;
public:
CMIDIMappingDialog(CWnd *pParent, CSoundFile &rSndfile);
~CMIDIMappingDialog();
protected:
void UpdateDialog(int selItem = -1);
void UpdateEvent();
void UpdateParameters();
int InsertItem(const CMIDIMappingDirective &m, int insertAt);
void SelectItem(int i);
void SetModified();
BOOL OnInitDialog() override;
void DoDataExchange(CDataExchange* pDX) override; // DDX/DDV support
DECLARE_MESSAGE_MAP()
afx_msg void OnSelectionChanged(NMHDR *pNMHDR = nullptr, LRESULT *pResult = nullptr);
afx_msg BOOL OnToolTipNotify(UINT id, NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnBnClickedCheckactive();
afx_msg void OnBnClickedCheckCapture();
afx_msg void OnCbnSelchangeComboController();
afx_msg void OnCbnSelchangeComboChannel();
afx_msg void OnCbnSelchangeComboPlugin();
afx_msg void OnCbnSelchangeComboParam();
afx_msg void OnCbnSelchangeComboEvent();
afx_msg void OnBnClickedButtonAdd();
afx_msg void OnBnClickedButtonReplace();
afx_msg void OnBnClickedButtonRemove();
afx_msg LRESULT OnMidiMsg(WPARAM, LPARAM);
afx_msg void OnDeltaposSpinmovemapping(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnBnClickedCheckPatRecord();
};
OPENMPT_NAMESPACE_END
#endif // NO_PLUGINS
@@ -0,0 +1,482 @@
/*
* MPTHacks.cpp
* ------------
* Purpose: Find out if MOD/XM/S3M/IT modules have MPT-specific hacks and fix them.
* Notes : This is not finished yet. Still need to handle:
* - Out-of-range sample pre-amp settings
* - Comments in XM files
* - Many auto-fix actions (so that the auto-fix mode can actually be used at some point!)
* Maybe there should be two options if hacks are found: Convert the song to MPTM or remove hacks.
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "Moddoc.h"
#include "../soundlib/modsmp_ctrl.h"
#include "../soundlib/mod_specifications.h"
OPENMPT_NAMESPACE_BEGIN
// Find and fix envelopes where two nodes are on the same tick.
bool FindIncompatibleEnvelopes(InstrumentEnvelope &env, bool autofix)
{
bool found = false;
for(uint32 i = 1; i < env.size(); i++)
{
if(env[i].tick <= env[i - 1].tick) // "<=" so we can fix envelopes "on the fly"
{
found = true;
if(autofix)
{
env[i].tick = env[i - 1].tick + 1;
}
}
}
return found;
}
// Go through the module to find out if it contains any hacks introduced by (Open)MPT
bool CModDoc::HasMPTHacks(const bool autofix)
{
const CModSpecifications *originalSpecs = &m_SndFile.GetModSpecifications();
// retrieve original (not hacked) specs.
MODTYPE modType = m_SndFile.GetBestSaveFormat();
switch(modType)
{
case MOD_TYPE_MOD:
originalSpecs = &ModSpecs::mod;
break;
case MOD_TYPE_XM:
originalSpecs = &ModSpecs::xm;
break;
case MOD_TYPE_S3M:
originalSpecs = &ModSpecs::s3m;
break;
case MOD_TYPE_IT:
originalSpecs = &ModSpecs::it;
break;
}
bool foundHacks = false, foundHere = false;
ClearLog();
// Check for plugins
#ifndef NO_PLUGINS
foundHere = false;
for(const auto &plug : m_SndFile.m_MixPlugins)
{
if(plug.IsValidPlugin())
{
foundHere = foundHacks = true;
break;
}
// REQUIRES AUTOFIX
}
if(foundHere)
AddToLog("Found plugins");
#endif // NO_PLUGINS
// Check for invalid order items
if(!originalSpecs->hasIgnoreIndex && mpt::contains(m_SndFile.Order(), m_SndFile.Order.GetIgnoreIndex()))
{
foundHacks = true;
AddToLog("This format does not support separator (+++) patterns");
if(autofix)
{
m_SndFile.Order().RemovePattern(m_SndFile.Order.GetIgnoreIndex());
}
}
if(!originalSpecs->hasStopIndex && m_SndFile.Order().GetLengthFirstEmpty() != m_SndFile.Order().GetLengthTailTrimmed())
{
foundHacks = true;
AddToLog("The pattern sequence should end after the first stop (---) index in this format.");
if(autofix)
{
m_SndFile.Order().RemovePattern(m_SndFile.Order.GetInvalidPatIndex());
}
}
// Global volume
if(modType == MOD_TYPE_XM && m_SndFile.m_nDefaultGlobalVolume != MAX_GLOBAL_VOLUME)
{
foundHacks = true;
AddToLog("XM format does not support default global volume");
if(autofix)
{
GlobalVolumeToPattern();
}
}
// Pattern count
if(m_SndFile.Patterns.GetNumPatterns() > originalSpecs->patternsMax)
{
AddToLog(MPT_AFORMAT("Found too many patterns ({} allowed)")(originalSpecs->patternsMax));
foundHacks = true;
// REQUIRES (INTELLIGENT) AUTOFIX
}
// Check for too big/small patterns
foundHere = false;
for(auto &pat : m_SndFile.Patterns)
{
if(pat.IsValid())
{
const ROWINDEX patSize = pat.GetNumRows();
if(patSize > originalSpecs->patternRowsMax)
{
foundHacks = foundHere = true;
if(autofix)
{
// REQUIRES (INTELLIGENT) AUTOFIX
} else
{
break;
}
} else if(patSize < originalSpecs->patternRowsMin)
{
foundHacks = foundHere = true;
if(autofix)
{
pat.Resize(originalSpecs->patternRowsMin);
pat.WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(patSize - 1).RetryNextRow());
} else
{
break;
}
}
}
}
if(foundHere)
{
AddToLog(MPT_AFORMAT("Found incompatible pattern lengths (must be between {} and {} rows)")(originalSpecs->patternRowsMin, originalSpecs->patternRowsMax));
}
// Check for invalid pattern commands
foundHere = false;
m_SndFile.Patterns.ForEachModCommand([originalSpecs, &foundHere, autofix, modType] (ModCommand &m)
{
// definitely not perfect yet. :)
// Probably missing: Some extended effect parameters
if(!originalSpecs->HasNote(m.note))
{
foundHere = true;
if(autofix)
m.note = NOTE_NONE;
}
if(!originalSpecs->HasCommand(m.command))
{
foundHere = true;
if(autofix)
m.command = CMD_NONE;
}
if(!originalSpecs->HasVolCommand(m.volcmd))
{
foundHere = true;
if(autofix)
m.volcmd = VOLCMD_NONE;
}
if(modType == MOD_TYPE_XM) // ModPlug XM extensions
{
if(m.command == CMD_XFINEPORTAUPDOWN && m.param >= 0x30)
{
foundHere = true;
if(autofix)
m.command = CMD_NONE;
}
} else if(modType == MOD_TYPE_IT) // ModPlug IT extensions
{
if((m.command == CMD_S3MCMDEX) && ((m.param & 0xF0) == 0x90) && (m.param != 0x91))
{
foundHere = true;
if(autofix)
m.command = CMD_NONE;
}
}
});
if(foundHere)
{
AddToLog("Found invalid pattern commands");
foundHacks = true;
}
// Check for pattern names
const PATTERNINDEX numNamedPatterns = m_SndFile.Patterns.GetNumNamedPatterns();
if(numNamedPatterns > 0 && !originalSpecs->hasPatternNames)
{
AddToLog("Found pattern names");
foundHacks = true;
if(autofix)
{
for(PATTERNINDEX i = 0; i < numNamedPatterns; i++)
{
m_SndFile.Patterns[i].SetName("");
}
}
}
// Check for too many channels
if(m_SndFile.GetNumChannels() > originalSpecs->channelsMax || m_SndFile.GetNumChannels() < originalSpecs->channelsMin)
{
AddToLog(MPT_AFORMAT("Found incompatible channel count (must be between {} and {} channels)")(originalSpecs->channelsMin, originalSpecs->channelsMax));
foundHacks = true;
if(autofix)
{
std::vector<bool> usedChannels;
CheckUsedChannels(usedChannels);
RemoveChannels(usedChannels);
// REQUIRES (INTELLIGENT) AUTOFIX
}
}
// Check for channel names
foundHere = false;
for(CHANNELINDEX i = 0; i < m_SndFile.GetNumChannels(); i++)
{
if(!m_SndFile.ChnSettings[i].szName.empty())
{
foundHere = foundHacks = true;
if(autofix)
m_SndFile.ChnSettings[i].szName = "";
else
break;
}
}
if(foundHere)
AddToLog("Found channel names");
// Check for too many samples
if(m_SndFile.GetNumSamples() > originalSpecs->samplesMax)
{
AddToLog(MPT_AFORMAT("Found too many samples ({} allowed)")(originalSpecs->samplesMax));
foundHacks = true;
// REQUIRES (INTELLIGENT) AUTOFIX
}
// Check for sample extensions
foundHere = false;
for(SAMPLEINDEX i = 1; i <= m_SndFile.GetNumSamples(); i++)
{
ModSample &smp = m_SndFile.GetSample(i);
if(modType == MOD_TYPE_XM && smp.GetNumChannels() > 1)
{
foundHere = foundHacks = true;
if(autofix)
{
ctrlSmp::ConvertToMono(smp, m_SndFile, ctrlSmp::mixChannels);
} else
{
break;
}
}
}
if(foundHere)
AddToLog("Stereo samples are not supported in the original XM format");
// Check for too many instruments
if(m_SndFile.GetNumInstruments() > originalSpecs->instrumentsMax)
{
AddToLog(MPT_AFORMAT("Found too many instruments ({} allowed)")(originalSpecs->instrumentsMax));
foundHacks = true;
// REQUIRES (INTELLIGENT) AUTOFIX
}
// Check for instrument extensions
foundHere = false;
bool foundEnvelopes = false;
for(INSTRUMENTINDEX i = 1; i <= m_SndFile.GetNumInstruments(); i++)
{
ModInstrument *instr = m_SndFile.Instruments[i];
if(instr == nullptr) continue;
// Extended instrument attributes
if(instr->filterMode != FilterMode::Unchanged || instr->nVolRampUp != 0 || instr->resampling != SRCMODE_DEFAULT ||
instr->nCutSwing != 0 || instr->nResSwing != 0 || instr->nMixPlug != 0 || instr->pitchToTempoLock.GetRaw() != 0 ||
instr->nDCT == DuplicateCheckType::Plugin ||
instr->VolEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET ||
instr->PanEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET ||
instr->PitchEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET
)
{
foundHere = foundHacks = true;
if(autofix)
{
instr->filterMode = FilterMode::Unchanged;
instr->nVolRampUp = 0;
instr->resampling = SRCMODE_DEFAULT;
instr->nCutSwing = 0;
instr->nResSwing = 0;
instr->nMixPlug = 0;
instr->pitchToTempoLock.Set(0);
if(instr->nDCT == DuplicateCheckType::Plugin) instr->nDCT = DuplicateCheckType::None;
instr->VolEnv.nReleaseNode = instr->PanEnv.nReleaseNode = instr->PitchEnv.nReleaseNode = ENV_RELEASE_NODE_UNSET;
}
}
// Incompatible envelope shape
foundEnvelopes |= FindIncompatibleEnvelopes(instr->VolEnv, autofix);
foundEnvelopes |= FindIncompatibleEnvelopes(instr->PanEnv, autofix);
foundEnvelopes |= FindIncompatibleEnvelopes(instr->PitchEnv, autofix);
foundHacks |= foundEnvelopes;
}
if(foundHere)
AddToLog("Found MPT instrument extensions");
if(foundEnvelopes)
AddToLog("Two envelope points may not share the same tick.");
// Check for too many orders
if(m_SndFile.Order().GetLengthTailTrimmed() > originalSpecs->ordersMax)
{
AddToLog(MPT_AFORMAT("Found too many orders ({} allowed)")(originalSpecs->ordersMax));
foundHacks = true;
if(autofix)
{
// Can we be more intelligent here and maybe remove stop patterns and such?
m_SndFile.Order().resize(originalSpecs->ordersMax);
}
}
// Check for invalid default tempo
if(m_SndFile.m_nDefaultTempo > originalSpecs->GetTempoMax() || m_SndFile.m_nDefaultTempo < originalSpecs->GetTempoMin())
{
AddToLog(MPT_AFORMAT("Found incompatible default tempo (must be between {} and {})")(originalSpecs->GetTempoMin().GetInt(), originalSpecs->GetTempoMax().GetInt()));
foundHacks = true;
if(autofix)
m_SndFile.m_nDefaultTempo = Clamp(m_SndFile.m_nDefaultTempo, originalSpecs->GetTempoMin(), originalSpecs->GetTempoMax());
}
// Check for invalid default speed
if(m_SndFile.m_nDefaultSpeed > originalSpecs->speedMax || m_SndFile.m_nDefaultSpeed < originalSpecs->speedMin)
{
AddToLog(MPT_AFORMAT("Found incompatible default speed (must be between {} and {})")(originalSpecs->speedMin, originalSpecs->speedMax));
foundHacks = true;
if(autofix)
m_SndFile.m_nDefaultSpeed = Clamp(m_SndFile.m_nDefaultSpeed, originalSpecs->speedMin, originalSpecs->speedMax);
}
// Check for invalid rows per beat / measure values
if(m_SndFile.m_nDefaultRowsPerBeat >= originalSpecs->patternRowsMax || m_SndFile.m_nDefaultRowsPerMeasure >= originalSpecs->patternRowsMax)
{
AddToLog("Found incompatible rows per beat / measure");
foundHacks = true;
if(autofix)
{
m_SndFile.m_nDefaultRowsPerBeat = Clamp(m_SndFile.m_nDefaultRowsPerBeat, 1u, (originalSpecs->patternRowsMax - 1));
m_SndFile.m_nDefaultRowsPerMeasure = Clamp(m_SndFile.m_nDefaultRowsPerMeasure, m_SndFile.m_nDefaultRowsPerBeat, (originalSpecs->patternRowsMax - 1));
}
}
// Find pattern-specific time signatures
if(!originalSpecs->hasPatternSignatures)
{
foundHere = false;
for(auto &pat : m_SndFile.Patterns)
{
if(pat.GetOverrideSignature())
{
if(!foundHere)
AddToLog("Found pattern-specific time signatures");
if(autofix)
pat.RemoveSignature();
foundHacks = foundHere = true;
if(!autofix)
break;
}
}
}
// Check for new tempo modes
if(m_SndFile.m_nTempoMode != TempoMode::Classic)
{
AddToLog("Found incompatible tempo mode (only classic tempo mode allowed)");
foundHacks = true;
if(autofix)
m_SndFile.m_nTempoMode = TempoMode::Classic;
}
// Check for extended filter range flag
if(m_SndFile.m_SongFlags[SONG_EXFILTERRANGE])
{
AddToLog("Found extended filter range");
foundHacks = true;
if(autofix)
m_SndFile.m_SongFlags.reset(SONG_EXFILTERRANGE);
}
// Player flags
if((modType & (MOD_TYPE_XM|MOD_TYPE_IT)) && !m_SndFile.m_playBehaviour[MSF_COMPATIBLE_PLAY])
{
AddToLog("Compatible play is deactivated");
foundHacks = true;
if(autofix)
m_SndFile.SetDefaultPlaybackBehaviour(modType);
}
// Check for restart position where it should not be
for(SEQUENCEINDEX seq = 0; seq < m_SndFile.Order.GetNumSequences(); seq++)
{
if(m_SndFile.Order(seq).GetRestartPos() > 0 && !originalSpecs->hasRestartPos)
{
AddToLog("Found restart position");
foundHacks = true;
if(autofix)
{
m_SndFile.Order.RestartPosToPattern(seq);
}
}
}
if(!originalSpecs->hasArtistName && !m_SndFile.m_songArtist.empty() && !(modType & (MOD_TYPE_MOD | MOD_TYPE_S3M)))
{
AddToLog("Found artist name");
foundHacks = true;
if(autofix)
{
m_SndFile.m_songArtist.clear();
}
}
if(m_SndFile.GetMixLevels() != MixLevels::Compatible && m_SndFile.GetMixLevels() != MixLevels::CompatibleFT2)
{
AddToLog("Found incorrect mix levels (only compatible mix levels allowed)");
foundHacks = true;
if(autofix)
m_SndFile.SetMixLevels(modType == MOD_TYPE_XM ? MixLevels::CompatibleFT2 : MixLevels::Compatible);
}
// Check for extended MIDI macros
if(modType == MOD_TYPE_IT)
{
for(const auto &macro : m_SndFile.m_MidiCfg)
{
for(const auto c : std::string_view{macro})
{
if(c == 's')
{
foundHacks = true;
AddToLog("Found SysEx checksum variable in MIDI macro");
break;
}
}
}
}
if(autofix && foundHacks)
SetModified();
return foundHacks;
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,71 @@
/*
* MPTrackLink.cpp
* ---------------
* Purpose: Consolidated linking against MSVC/Windows libraries.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
OPENMPT_NAMESPACE_BEGIN
#if defined(MPT_BUILD_MSVC)
#if MPT_COMPILER_MSVC || MPT_COMPILER_CLANG
#if !defined(MPT_BUILD_RETRO)
#pragma comment(lib, "delayimp.lib")
#endif // !MPT_BUILD_RETRO
#pragma comment(lib, "version.lib")
#pragma comment(lib, "rpcrt4.lib")
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "wininet.lib")
#pragma comment(lib, "htmlhelp.lib")
#pragma comment(lib, "uxtheme.lib")
#pragma comment(lib, "bcrypt.lib")
#pragma comment(lib, "ncrypt.lib")
#pragma comment(lib, "gdiplus.lib")
#pragma comment(lib, "dmoguids.lib")
#pragma comment(lib, "strmiids.lib")
#if (_WIN32_WINNT >= 0x600)
#pragma comment(lib, "avrt.lib")
#endif
#if defined(MPT_WITH_DIRECTSOUND)
#pragma comment(lib, "dsound.lib")
#endif // MPT_WITH_DIRECTSOUND
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "ksuser.lib")
#ifdef MPT_WITH_MEDIAFOUNDATION
#pragma comment(lib, "mf.lib")
#pragma comment(lib, "mfplat.lib")
#pragma comment(lib, "mfreadwrite.lib")
#pragma comment(lib, "mfuuid.lib") // static lib
#pragma comment(lib, "propsys.lib")
#endif
// work-around VS2019 16.8.1 bug on ARM and ARM64
#if MPT_COMPILER_MSVC
#if defined(_M_ARM) || defined(_M_ARM64)
#pragma comment(lib, "Synchronization.lib")
#endif
#endif
#if MPT_COMPILER_MSVC
#pragma comment( linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df'\"" )
#endif // MPT_COMPILER_MSVC
#endif // MPT_COMPILER_MSVC || MPT_COMPILER_CLANG
#endif // MPT_BUILD_MSVC
OPENMPT_NAMESPACE_END
@@ -0,0 +1,85 @@
/*
* MPTrackUtil.cpp
* ---------------
* Purpose: Various useful utility functions.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "MPTrackUtil.h"
OPENMPT_NAMESPACE_BEGIN
static bool CreateShellLink(const IID &type, const mpt::PathString &path, const mpt::PathString &target, const mpt::ustring &description)
{
HRESULT hres = 0;
IShellLink *psl = nullptr;
hres = CoCreateInstance(type, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
if(SUCCEEDED(hres))
{
IPersistFile *ppf = nullptr;
psl->SetPath(target.AsNative().c_str());
psl->SetDescription(mpt::ToWin(description).c_str());
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
if(SUCCEEDED(hres))
{
hres = ppf->Save(path.ToWide().c_str(), TRUE);
ppf->Release();
ppf = nullptr;
}
psl->Release();
psl = nullptr;
}
return SUCCEEDED(hres);
}
bool CreateShellFolderLink(const mpt::PathString &path, const mpt::PathString &target, const mpt::ustring &description)
{
return CreateShellLink(CLSID_FolderShortcut, path, target, description);
}
bool CreateShellFileLink(const mpt::PathString &path, const mpt::PathString &target, const mpt::ustring &description)
{
return CreateShellLink(CLSID_ShellLink, path, target, description);
}
mpt::const_byte_span GetResource(LPCTSTR lpName, LPCTSTR lpType)
{
HINSTANCE hInstance = AfxGetInstanceHandle();
HRSRC hRsrc = FindResource(hInstance, lpName, lpType);
if(hRsrc == NULL)
{
return mpt::const_byte_span();
}
HGLOBAL hGlob = LoadResource(hInstance, hRsrc);
if(hGlob == NULL)
{
return mpt::const_byte_span();
}
return mpt::const_byte_span(mpt::void_cast<const std::byte *>(LockResource(hGlob)), SizeofResource(hInstance, hRsrc));
// no need to call FreeResource(hGlob) or free hRsrc, according to MSDN
}
CString LoadResourceString(UINT nID)
{
CString str;
BOOL resourceLoaded = str.LoadString(nID);
MPT_ASSERT(resourceLoaded);
if(!resourceLoaded)
{
return _T("");
}
return str;
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,85 @@
/*
* MPTrackUtil.h
* -------------
* Purpose: Various useful utility functions.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include <string>
OPENMPT_NAMESPACE_BEGIN
bool CreateShellFolderLink(const mpt::PathString &path, const mpt::PathString &target, const mpt::ustring &description = mpt::ustring());
bool CreateShellFileLink(const mpt::PathString &path, const mpt::PathString &target, const mpt::ustring &description = mpt::ustring());
/*
* Gets resource as raw byte data.
* [in] lpName and lpType: parameters passed to FindResource().
* Return: span representing the resource data, valid as long as hInstance is valid.
*/
mpt::const_byte_span GetResource(LPCTSTR lpName, LPCTSTR lpType);
CString LoadResourceString(UINT nID);
namespace Util
{
// Get horizontal DPI resolution
MPT_FORCEINLINE int GetDPIx(HWND hwnd)
{
HDC dc = ::GetDC(hwnd);
int dpi = ::GetDeviceCaps(dc, LOGPIXELSX);
::ReleaseDC(hwnd, dc);
return dpi;
}
// Get vertical DPI resolution
MPT_FORCEINLINE int GetDPIy(HWND hwnd)
{
HDC dc = ::GetDC(hwnd);
int dpi = ::GetDeviceCaps(dc, LOGPIXELSY);
::ReleaseDC(hwnd, dc);
return dpi;
}
// Applies DPI scaling factor to some given size
MPT_FORCEINLINE int ScalePixels(int pixels, HWND hwnd)
{
return MulDiv(pixels, GetDPIx(hwnd), 96);
}
// Removes DPI scaling factor from some given size
MPT_FORCEINLINE int ScalePixelsInv(int pixels, HWND hwnd)
{
return MulDiv(pixels, 96, GetDPIx(hwnd));
}
}
namespace Util
{
inline DWORD64 GetTickCount64()
{
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
return ::GetTickCount64();
#else
return ::GetTickCount();
#endif
}
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,313 @@
/*
* MPTrackUtilWine.cpp
* -------------------
* Purpose: Wine utility functions.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "MPTrackUtilWine.h"
#include "Mptrack.h"
#include "../common/misc_util.h"
#include "../misc/mptWine.h"
OPENMPT_NAMESPACE_BEGIN
namespace Util
{
namespace Wine
{
class CExecutePosixShellScriptProgressDialog
: public CDialog
{
protected:
mpt::Wine::Context & wine;
std::string m_Title;
std::string m_Status;
bool m_bAbort;
std::string m_script;
FlagSet<mpt::Wine::ExecFlags> m_Flags;
std::map<std::string, std::vector<char> > m_Filetree;
mpt::Wine::ExecResult m_ExecResult;
std::string m_ExceptionString;
public:
CExecutePosixShellScriptProgressDialog(mpt::Wine::Context & wine, std::string script, FlagSet<mpt::Wine::ExecFlags> flags, std::map<std::string, std::vector<char> > filetree, std::string title, std::string status, CWnd *parent = NULL);
BOOL OnInitDialog();
void OnCancel();
afx_msg void OnButton1();
static mpt::Wine::ExecuteProgressResult ProgressCancelCallback(void *userdata);
static void ProgressCallback(void *userdata);
mpt::Wine::ExecuteProgressResult Progress(bool allowCancel);
void MessageLoop();
mpt::Wine::ExecResult GetExecResult() const;
std::string GetExceptionString() const;
private:
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CExecutePosixShellScriptProgressDialog, CDialog)
ON_COMMAND(IDC_BUTTON1, &CExecutePosixShellScriptProgressDialog::OnButton1)
END_MESSAGE_MAP()
CExecutePosixShellScriptProgressDialog::CExecutePosixShellScriptProgressDialog(mpt::Wine::Context & wine, std::string script, FlagSet<mpt::Wine::ExecFlags> flags, std::map<std::string, std::vector<char> > filetree, std::string title, std::string status, CWnd *parent)
: CDialog(IDD_PROGRESS, parent)
, wine(wine)
, m_Title(title)
, m_Status(status)
, m_bAbort(false)
, m_script(script)
, m_Flags(flags)
, m_Filetree(filetree)
, m_ExecResult(mpt::Wine::ExecResult::Error())
{
return;
}
void CExecutePosixShellScriptProgressDialog::OnCancel()
{
m_bAbort = true;
}
mpt::Wine::ExecuteProgressResult CExecutePosixShellScriptProgressDialog::ProgressCancelCallback(void *userdata)
{
return reinterpret_cast<CExecutePosixShellScriptProgressDialog*>(userdata)->Progress(true);
}
void CExecutePosixShellScriptProgressDialog::ProgressCallback(void *userdata)
{
reinterpret_cast<CExecutePosixShellScriptProgressDialog*>(userdata)->Progress(false);
}
void CExecutePosixShellScriptProgressDialog::MessageLoop()
{
MSG msg;
while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
mpt::Wine::ExecResult CExecutePosixShellScriptProgressDialog::GetExecResult() const
{
return m_ExecResult;
}
std::string CExecutePosixShellScriptProgressDialog::GetExceptionString() const
{
return m_ExceptionString;
}
BOOL CExecutePosixShellScriptProgressDialog::OnInitDialog()
{
CDialog::OnInitDialog();
SetWindowText(mpt::ToCString(mpt::Charset::UTF8, m_Title));
SetDlgItemText(IDCANCEL, _T("Cancel"));
SetWindowLong(::GetDlgItem(m_hWnd, IDC_PROGRESS1), GWL_STYLE, GetWindowLong(::GetDlgItem(m_hWnd, IDC_PROGRESS1), GWL_STYLE) | PBS_MARQUEE);
::SendMessage(::GetDlgItem(m_hWnd, IDC_PROGRESS1), PBM_SETMARQUEE, 1, 30); // 30 is Windows default, but Wine < 1.7.15 defaults to 0
PostMessage(WM_COMMAND, IDC_BUTTON1);
return TRUE;
}
mpt::Wine::ExecuteProgressResult CExecutePosixShellScriptProgressDialog::Progress(bool allowCancel)
{
if(m_bAbort)
{
return mpt::Wine::ExecuteProgressAsyncCancel;
}
::ShowWindow(::GetDlgItem(m_hWnd, IDCANCEL), allowCancel ? SW_SHOW : SW_HIDE);
MessageLoop();
if(m_bAbort)
{
return mpt::Wine::ExecuteProgressAsyncCancel;
}
::Sleep(10);
return mpt::Wine::ExecuteProgressContinueWaiting;
}
void CExecutePosixShellScriptProgressDialog::OnButton1()
{
if(m_script.empty())
{
EndDialog(IDCANCEL);
return;
}
SetDlgItemText(IDC_TEXT1, mpt::ToCString(mpt::Charset::UTF8, m_Status));
MessageLoop();
if(m_bAbort)
{
EndDialog(IDCANCEL);
return;
}
::Sleep(10);
try
{
m_ExecResult = wine.ExecutePosixShellScript(m_script, m_Flags, m_Filetree, m_Title, &ProgressCallback, &ProgressCancelCallback, this);
} catch(const mpt::Wine::Exception &e)
{
m_ExceptionString = mpt::get_exception_text<std::string>(e);
EndDialog(IDCANCEL);
return;
}
MessageLoop();
if(m_bAbort)
{
EndDialog(IDCANCEL);
return;
}
SetDlgItemText(IDC_TEXT1, _T("Done."));
::ShowWindow(::GetDlgItem(m_hWnd, IDCANCEL), SW_HIDE);
for(int i = 0; i < 10; ++i)
{
MessageLoop();
::Sleep(10);
}
MessageLoop();
EndDialog(IDOK);
}
static void ProgressCallback(void * /*userdata*/ )
{
MSG msg;
while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
::Sleep(10);
}
static mpt::Wine::ExecuteProgressResult ProgressCancelCallback(void *userdata)
{
ProgressCallback(userdata);
return mpt::Wine::ExecuteProgressContinueWaiting;
}
mpt::Wine::ExecResult ExecutePosixShellScript(mpt::Wine::Context & wine, std::string script, FlagSet<mpt::Wine::ExecFlags> flags, std::map<std::string, std::vector<char> > filetree, std::string title, std::string status)
{
if(flags[mpt::Wine::ExecFlagProgressWindow])
{
CExecutePosixShellScriptProgressDialog dlg(wine, script, flags, filetree, title, status, theApp.GetMainWnd());
if(dlg.DoModal() != IDOK)
{
if(!dlg.GetExceptionString().empty())
{
throw mpt::Wine::Exception(dlg.GetExceptionString());
}
throw mpt::Wine::Exception("Canceled.");
}
return dlg.GetExecResult();
} else
{
return wine.ExecutePosixShellScript(script, flags, filetree, title, &ProgressCallback, &ProgressCancelCallback, nullptr);
}
}
Dialog::Dialog(std::string title, bool terminal)
: m_Terminal(terminal)
, m_Title(title)
{
return;
}
std::string Dialog::Detect() const
{
std::string script;
script += std::string() + "chmod u+x ./build/wine/dialog.sh" + "\n";
return script;
}
std::string Dialog::DialogVar() const
{
if(m_Terminal)
{
return "./build/wine/dialog.sh tui";
} else
{
return "./build/wine/dialog.sh gui";
}
}
std::string Dialog::Title() const
{
return m_Title;
}
std::string Dialog::Status(std::string text) const
{
return std::string() + DialogVar() + " --infobox \"" + Title() + "\" \"" + text + "\"";
}
std::string Dialog::MessageBox(std::string text) const
{
return std::string() + DialogVar() + " --msgbox \"" + Title() + "\" \"" + text + "\"";
}
std::string Dialog::YesNo(std::string text) const
{
return std::string() + DialogVar() + " --yesno \"" + Title() + "\" \"" + text + "\"";
}
std::string Dialog::TextBox(std::string text) const
{
return std::string() + DialogVar() + " --textbox \"" + Title() + "\" \"" + text + "\"";
}
std::string Dialog::Progress(std::string text) const
{
return std::string() + DialogVar() + " --gauge \"" + Title() + "\" \"" + text + "\"";
}
} // namespace Wine
} // namespace Util
OPENMPT_NAMESPACE_END
@@ -0,0 +1,58 @@
/*
* MPTrackUtilWine.h
* -----------------
* Purpose: Wine utility functions.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "../misc/mptWine.h"
OPENMPT_NAMESPACE_BEGIN
namespace Util
{
namespace Wine
{
mpt::Wine::ExecResult ExecutePosixShellScript(mpt::Wine::Context & wine, std::string script, FlagSet<mpt::Wine::ExecFlags> flags, std::map<std::string, std::vector<char> > filetree, std::string title, std::string status);
class Dialog
{
private:
bool m_Terminal;
std::string m_Title;
private:
std::string DialogVar() const;
public:
Dialog(std::string title, bool terminal);
std::string Title() const;
std::string Detect() const;
std::string Status(std::string text) const;
std::string MessageBox(std::string text) const;
std::string YesNo(std::string text) const;
std::string TextBox(std::string filename) const;
std::string Progress(std::string text) const;
};
} // namespace Wine
} // namespace Util
OPENMPT_NAMESPACE_END
@@ -0,0 +1,847 @@
/*
* MPTrackWine.cpp
* ---------------
* Purpose: OpenMPT Wine support functions.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#if MPT_COMPILER_MSVC
#pragma warning(disable:4800) // 'T' : forcing value to bool 'true' or 'false' (performance warning)
#endif // MPT_COMPILER_MSVC
#include "MPTrackWine.h"
#include "mpt/uuid/uuid.hpp"
#include "Mptrack.h"
#include "Mainfrm.h"
#include "AboutDialog.h"
#include "TrackerSettings.h"
#include "../common/ComponentManager.h"
#include "../common/mptFileIO.h"
#include "../misc/mptOS.h"
#include "mpt/crc/crc.hpp"
#include "../common/FileReader.h"
#include "../misc/mptWine.h"
#include "MPTrackUtilWine.h"
#include "wine/NativeSoundDevice.h"
#include "openmpt/sounddevice/SoundDevice.hpp"
#include "wine/NativeSoundDeviceMarshalling.h"
#include "openmpt/sounddevice/SoundDeviceManager.hpp"
#include <ios>
#include <contrib/minizip/unzip.h>
#include <contrib/minizip/iowin32.h>
OPENMPT_NAMESPACE_BEGIN
static mpt::ustring WineGetWindowTitle()
{
return U_("OpenMPT Wine integration");
}
static std::string WineGetWindowTitleUTF8()
{
return mpt::ToCharset(mpt::Charset::UTF8, WineGetWindowTitle());
}
static mpt::PathString WineGetSupportZipFilename()
{
return P_("openmpt-wine-support.zip");
}
static char SanitizeBuildIdChar(char c)
{
char result = c;
if (c == '\0') result = '_';
else if (c >= 'a' && c <= 'z') result = c;
else if (c >= 'A' && c <= 'Z') result = c;
else if (c >= '0' && c <= '9') result = c;
else if (c == '!') result = c;
else if (c == '+') result = c;
else if (c == '-') result = c;
else if (c == '.') result = c;
else if (c == '~') result = c;
else if (c == '_') result = c;
else result = '_';
return result;
}
static std::string SanitizeBuildID(std::string id)
{
for(auto & c : id)
{
c = SanitizeBuildIdChar(c);
}
return id;
}
namespace WineIntegration {
static mpt::crc64_jones WineHashVersion(mpt::crc64_jones crc)
{
std::string s;
s += mpt::ToCharset(mpt::Charset::UTF8, Build::GetVersionStringExtended());
s += " ";
s += mpt::ToCharset(mpt::Charset::UTF8, mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture()));
s += " ";
s += mpt::ToCharset(mpt::Charset::UTF8, SourceInfo::Current().GetUrlWithRevision());
s += " ";
s += mpt::ToCharset(mpt::Charset::UTF8, SourceInfo::Current().GetStateString());
crc(s.begin(), s.end());
return crc;
}
static mpt::crc64_jones WineHashFile(mpt::crc64_jones crc, mpt::PathString filename)
{
InputFile file(filename, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
if(!file.IsValid())
{
return crc;
}
FileReader f = GetFileReader(file);
FileReader::PinnedView view = f.ReadPinnedView();
crc(view.begin(), view.end());
return crc;
}
static mpt::crc64_jones WineHashSettings(mpt::crc64_jones crc)
{
std::string result;
result += std::string() + "-c";
result += std::string() + "-" + mpt::afmt::dec(TrackerSettings::Instance().WineSupportEnablePulseAudio.Get());
result += std::string() + "-" + mpt::afmt::dec(TrackerSettings::Instance().WineSupportEnablePortAudio.Get());
crc(result.begin(), result.end());
return crc;
}
mpt::ustring WineGetSystemInfoString(mpt::OS::Wine::VersionContext & wineVersion)
{
mpt::ustring msg;
msg += CAboutDlg::GetTabText(5);
msg += U_("\n");
msg += MPT_UFORMAT("OpenMPT detected Wine {} running on {}.\n")
( wineVersion.Version().AsString()
, wineVersion.HostClass() == mpt::osinfo::osclass::Linux ? U_("Linux") : U_("unknown system")
);
return msg;
}
bool WineSetupIsSupported(mpt::OS::Wine::VersionContext & wineVersion)
{
bool supported = true;
if(wineVersion.RawBuildID().empty()) supported = false;
if(!TrackerSettings::Instance().WineSupportAllowUnknownHost)
{
if((wineVersion.HostClass() == mpt::osinfo::osclass::Linux) || ((wineVersion.HostClass() == mpt::osinfo::osclass::BSD) && wineVersion.RawHostSysName() == "FreeBSD"))
{
// ok
} else
{
supported = false;
}
}
if(!wineVersion.Version().IsValid()) supported = false;
return supported;
}
bool WineSetupIsSupported(mpt::Wine::Context & wine)
{
bool supported = true;
if(theApp.GetInstallPath().empty()) supported = false;
if(wine.PathToPosix(theApp.GetInstallPath()).empty()) supported = false;
if(wine.PathToPosix(theApp.GetConfigPath()).empty()) supported = false;
if(wine.PathToWindows("/").empty()) supported = false;
if(supported)
{
if(wine.HOME().empty()) supported = false;
}
if(supported)
{
if(wine.Uname_m() == "x86_64" && mpt::pointer_size != 8) supported = false;
}
return supported;
}
static std::map<std::string, std::vector<char> > UnzipToMap(mpt::PathString filename)
{
std::map<std::string, std::vector<char> > filetree;
{
zlib_filefunc64_def zipfilefuncs;
MemsetZero(zipfilefuncs);
fill_win32_filefunc64W(&zipfilefuncs);
unzFile zipfile = unzOpen2_64(filename.ToWide().c_str(), &zipfilefuncs);
if(!zipfile)
{
throw mpt::Wine::Exception("Archive is not a zip file");
}
for(int status = unzGoToFirstFile(zipfile); status == UNZ_OK; status = unzGoToNextFile(zipfile))
{
int openstatus = UNZ_OK;
openstatus = unzOpenCurrentFile(zipfile);
if(openstatus != UNZ_OK)
{
unzClose(zipfile);
throw mpt::Wine::Exception("Archive is corrupted.");
}
unz_file_info info;
MemsetZero(info);
char name[1024];
MemsetZero(name);
openstatus = unzGetCurrentFileInfo(zipfile, &info, name, sizeof(name) - 1, nullptr, 0, nullptr, 0);
if(openstatus != UNZ_OK)
{
unzCloseCurrentFile(zipfile);
unzClose(zipfile);
throw mpt::Wine::Exception("Archive is corrupted.");
}
std::vector<char> data(info.uncompressed_size);
unzReadCurrentFile(zipfile, &data[0], info.uncompressed_size);
unzCloseCurrentFile(zipfile);
data = mpt::buffer_cast<std::vector<char>>(mpt::replace(mpt::buffer_cast<std::string>(data), std::string("\r\n"), std::string("\n")));
filetree[mpt::replace(mpt::ToCharset(mpt::Charset::UTF8, mpt::Charset::CP437, name), std::string("\\"), std::string("/"))] = data;
}
unzClose(zipfile);
}
return filetree;
}
bool IsSupported()
{
return theApp.GetWine() ? true : false;
}
bool IsCompiled()
{
return !theApp.GetWineWrapperDllFilename().empty();
}
void Initialize()
{
if(!mpt::OS::Windows::IsWine())
{
return;
}
mpt::ustring lf = U_("\n");
if(!TrackerSettings::Instance().WineSupportEnabled)
{
return;
}
mpt::OS::Wine::VersionContext wineVersion = *theApp.GetWineVersion();
if(!WineSetupIsSupported(wineVersion))
{
mpt::ustring msg;
msg += U_("OpenMPT does not support Wine integration on your current Wine setup.") + lf;
Reporting::Notification(msg, WineGetWindowTitle());
return;
}
try
{
mpt::Wine::Context wine = mpt::Wine::Context(wineVersion);
if(!WineSetupIsSupported(wine))
{
mpt::ustring msg;
msg += U_("OpenMPT does not support Wine integration on your current Wine setup.") + lf;
Reporting::Notification(msg, WineGetWindowTitle());
return;
}
theApp.SetWine(std::make_shared<mpt::Wine::Context>(wine));
} catch(const std::exception & e)
{
mpt::ustring msg;
msg += U_("OpenMPT was not able to determine Wine configuration details on your current Wine setup:") + lf;
msg += mpt::get_exception_text<mpt::ustring>(e) + lf;
msg += U_("OpenMPT native Wine Integration will not be available.") + lf;
Reporting::Error(msg, WineGetWindowTitle());
return;
}
mpt::Wine::Context wine = *theApp.GetWine();
try
{
struct Paths
{
mpt::PathString AppData;
mpt::PathString AppData_Wine;
mpt::PathString AppData_Wine_WineVersion;
mpt::PathString AppData_Wine_WineVersion_OpenMPTVersion;
std::string Host_AppData;
std::string Host_AppData_Wine;
std::string Host_AppData_Wine_WineVersion;
std::string Host_AppData_Wine_WineVersion_OpenMPTVersion;
std::string Host_Native_OpenMPT_Wine_WineVersion_OpenMPTVersion;
static void CreatePath(mpt::PathString path)
{
if(path.IsDirectory())
{
return;
}
if(CreateDirectory(path.AsNative().c_str(), NULL) == 0)
{
throw mpt::Wine::Exception(std::string() + "Failed to create directory: " + path.ToUTF8());
}
}
std::string GetOpenMPTVersion() const
{
std::string ver;
ver += mpt::ToCharset(mpt::Charset::UTF8, Build::GetVersionStringPure() + U_("_") + mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture()));
mpt::crc64_jones crc;
crc = WineHashVersion(crc);
crc = WineHashFile(crc, theApp.GetInstallPath() + WineGetSupportZipFilename());
crc = WineHashSettings(crc);
ver += std::string("-") + mpt::afmt::hex0<16>(crc.result());
return ver;
}
Paths(mpt::Wine::Context & wine)
{
AppData = theApp.GetConfigPath().WithoutTrailingSlash();
AppData_Wine = AppData.WithTrailingSlash() + P_("Wine");
AppData_Wine_WineVersion = AppData_Wine.WithTrailingSlash() + mpt::PathString::FromUTF8(SanitizeBuildID(wine.VersionContext().RawBuildID()));
AppData_Wine_WineVersion_OpenMPTVersion = AppData_Wine_WineVersion.WithTrailingSlash() + mpt::PathString::FromUTF8(GetOpenMPTVersion());
CreatePath(AppData);
CreatePath(AppData_Wine);
CreatePath(AppData_Wine_WineVersion);
CreatePath(AppData_Wine_WineVersion_OpenMPTVersion);
Host_AppData = wine.PathToPosixCanonical(AppData);
Host_AppData_Wine = wine.PathToPosixCanonical(AppData_Wine);
Host_AppData_Wine_WineVersion = wine.PathToPosixCanonical(AppData_Wine_WineVersion);
Host_AppData_Wine_WineVersion_OpenMPTVersion = wine.PathToPosixCanonical(AppData_Wine_WineVersion_OpenMPTVersion);
Host_Native_OpenMPT_Wine_WineVersion_OpenMPTVersion = wine.XDG_DATA_HOME() + "/OpenMPT/Wine/" + SanitizeBuildID(wine.VersionContext().RawBuildID()) + "/" + GetOpenMPTVersion();
}
};
const Paths paths(wine);
const std::string nativeSearchPath = paths.Host_Native_OpenMPT_Wine_WineVersion_OpenMPTVersion;
if(!TrackerSettings::Instance().WineSupportAlwaysRecompile)
{
if((paths.AppData_Wine_WineVersion_OpenMPTVersion.WithTrailingSlash() + P_("success.txt")).IsFile())
{
theApp.SetWineWrapperDllFilename(paths.AppData_Wine_WineVersion_OpenMPTVersion.WithTrailingSlash() + P_("openmpt_wine_wrapper.dll"));
return;
}
}
if(TrackerSettings::Instance().WineSupportAskCompile)
{
mpt::ustring msg;
msg += U_("OpenMPT Wine integration requires recompilation and will not work otherwise.\n");
msg += U_("Recompile now?\n");
if(Reporting::Confirm(msg, WineGetWindowTitle(), false, false) != cnfYes)
{
return;
}
}
std::map<std::string, std::vector<char> > filetree;
filetree = UnzipToMap(theApp.GetInstallPath() + WineGetSupportZipFilename());
Util::Wine::Dialog dialog(WineGetWindowTitleUTF8(), TrackerSettings::Instance().WineSupportCompileVerbosity < 6);
std::string script;
script += std::string() + "#!/usr/bin/env sh" + "\n";
script += std::string() + "\n";
script += std::string() + "touch message.txt" + "\n";
script += std::string() + "\n";
script += dialog.Detect();
if(TrackerSettings::Instance().WineSupportCompileVerbosity >= 6)
{
script += std::string() + "echo Working directory:" + "\n";
script += std::string() + "pwd" + "\n";
}
script += std::string() + "\n";
if(TrackerSettings::Instance().WineSupportCompileVerbosity >= 3)
{
script += std::string() + "echo " + WineGetWindowTitleUTF8() + "\n";
} else
{
if(TrackerSettings::Instance().WineSupportCompileVerbosity == 2)
{
script += std::string() + "{" + "\n";
script += std::string() + " echo 0" + "\n";
script += std::string() + " echo 100" + "\n";
script += std::string() + "} | " + dialog.Progress("[>>] Prepare OpenMPT Wine Integration\\n[ ] Compile native support\\n[ ] Compile Wine wrapper\\n\\n[1/3] Preparing OpenMPT Wine Integration ...") + "\n";
} else
{
script += std::string() + dialog.Status("Preparing OpenMPT Wine Integration.") + "\n";
}
}
script += std::string() + "\n";
script += std::string() + "printf \"#pragma once\\n\" >> common/svn_version.h" + "\n";
script += std::string() + "printf \"#define OPENMPT_VERSION_URL \\\"" + mpt::ToCharset(mpt::Charset::ASCII, SourceInfo::Current().Url()) + "\\\"\\n\" >> common/svn_version.h" + "\n";
script += std::string() + "printf \"#define OPENMPT_VERSION_DATE \\\"" + mpt::ToCharset(mpt::Charset::ASCII, SourceInfo::Current().Date()) + "\\\"\\n\" >> common/svn_version.h" + "\n";
script += std::string() + "printf \"#define OPENMPT_VERSION_REVISION " + mpt::afmt::dec(SourceInfo::Current().Revision()) + "\\n\" >> common/svn_version.h" + "\n";
script += std::string() + "printf \"#define OPENMPT_VERSION_DIRTY " + mpt::afmt::dec(SourceInfo::Current().IsDirty()) + "\\n\" >> common/svn_version.h" + "\n";
script += std::string() + "printf \"#define OPENMPT_VERSION_MIXEDREVISIONS " + mpt::afmt::dec(SourceInfo::Current().HasMixedRevisions()) + "\\n\" >> common/svn_version.h" + "\n";
script += std::string() + "printf \"#define OPENMPT_VERSION_IS_PACKAGE " + mpt::afmt::dec(SourceInfo::Current().IsPackage()) + "\\n\" >> common/svn_version.h" + "\n";
script += std::string() + "\n";
script += std::string() + "missing=" + "\n";
script += std::string() + "\n";
const std::string make = ((wineVersion.HostClass() == mpt::osinfo::osclass::BSD) ? "gmake" : "make");
std::vector<std::string> commands;
commands.push_back(make);
commands.push_back("pkg-config");
commands.push_back("cpp");
commands.push_back("cc");
commands.push_back("c++");
commands.push_back("ld");
commands.push_back("ccache");
for(const auto &command : commands)
{
script += std::string() + "command -v " + command + " 2>/dev/null 1>/dev/null" + "\n";
script += std::string() + "if [ \"$?\" -ne \"0\" ] ; then" + "\n";
script += std::string() + " missing=\"$missing " + command + "\"" + "\n";
script += std::string() + "fi" + "\n";
}
script += std::string() + "if [ \"x$missing\" = \"x\" ] ; then" + "\n";
script += std::string() + " printf \"\"" + "\n";
script += std::string() + "else" + "\n";
#if 0
if(!TrackerSettings::Instance().WineSupportSilentCompile >= 1)
{
script += std::string() + " " + dialog.YesNo("The following commands are missing:\\n\\n$missing\\n\\nDo you want OpenMPT to try installing those now?") + "\n";
}
script += std::string() + " if [ \"$?\" -ne \"0\" ] ; then" + "\n";
script += std::string() + " exit 1" + "\n";
script += std::string() + " fi" + "\n";
#else
if(TrackerSettings::Instance().WineSupportCompileVerbosity >= 1)
{
script += std::string() + " " + dialog.MessageBox("The following commands are missing:\\n\\n$missing\\n\\nPlease install them with your system package installer.") + "\n";
}
script += std::string() + " exit 1" + "\n";
#endif
script += std::string() + "fi" + "\n";
script += std::string() + "\n";
script += std::string() + "mkdir -p " + wine.EscapePosixShell(wine.XDG_DATA_HOME()) + "/OpenMPT/Wine" + "\n";
script += std::string() + "mkdir -p " + wine.EscapePosixShell(wine.XDG_CACHE_HOME()) + "/OpenMPT/Wine" + "\n";
script += std::string() + "mkdir -p " + wine.EscapePosixShell(wine.XDG_CONFIG_HOME()) + "/OpenMPT/Wine" + "\n";
script += std::string() + "mkdir -p " + wine.EscapePosixShell(paths.Host_Native_OpenMPT_Wine_WineVersion_OpenMPTVersion) + "\n";
script += std::string() + "\n";
script += std::string() + "CCACHE_DIR=" + wine.EscapePosixShell(wine.XDG_CACHE_HOME()) + "/OpenMPT/Wine/ccache" + "\n";
script += std::string() + "CCACHE_COMPRESS=1" + " \n";
script += std::string() + "export CCACHE_DIR" + " \n";
script += std::string() + "export CCACHE_COMPRESS" + " \n";
script += std::string() + "\n";
std::vector<std::string> winegcc;
if constexpr(mpt::arch_bits == 32)
{ // 32bit winegcc probably cannot compile to 64bit
winegcc.push_back("winegcc32-development");
}
MPT_MAYBE_CONSTANT_IF(TrackerSettings::Instance().WineSupportForeignOpenMPT || (mpt::arch_bits == 64))
{
winegcc.push_back("winegcc64-development");
}
winegcc.push_back("winegcc-development");
if(wineVersion.HostClass() != mpt::osinfo::osclass::BSD)
{ // avoid C++ compiler on *BSD because libc++ Win32 support tends to be missing there.
if constexpr(mpt::arch_bits == 32)
{ // 32bit winegcc probably cannot compile to 64bit
winegcc.push_back("wineg++32-development");
}
MPT_MAYBE_CONSTANT_IF(TrackerSettings::Instance().WineSupportForeignOpenMPT || (mpt::arch_bits == 64))
{
winegcc.push_back("wineg++64-development");
}
winegcc.push_back("wineg++-development");
}
if constexpr(mpt::arch_bits == 32)
{ // 32bit winegcc probably cannot compile to 64bit
winegcc.push_back("winegcc32");
}
MPT_MAYBE_CONSTANT_IF(TrackerSettings::Instance().WineSupportForeignOpenMPT || (mpt::arch_bits == 64))
{
winegcc.push_back("winegcc64");
}
winegcc.push_back("winegcc");
if(wineVersion.HostClass() != mpt::osinfo::osclass::BSD)
{ // avoid C++ compiler on *BSD because libc++ Win32 support tends to be missing there.
if constexpr(mpt::arch_bits == 32)
{ // 32bit winegcc probably cannot compile to 64bit
winegcc.push_back("wineg++32");
}
MPT_MAYBE_CONSTANT_IF(TrackerSettings::Instance().WineSupportForeignOpenMPT || (mpt::arch_bits == 64))
{
winegcc.push_back("wineg++64");
}
winegcc.push_back("wineg++");
}
for(const auto &c : winegcc)
{
script += std::string() + "if command -v " + c + " 2>/dev/null 1>/dev/null ; then" + "\n";
script += std::string() + " MPT_WINEGXX=" + c + "\n";
script += std::string() + "fi" + "\n";
}
script += std::string() + "if [ -z $MPT_WINEGXX ] ; then" + "\n";
if(TrackerSettings::Instance().WineSupportCompileVerbosity >= 1)
{
script += std::string() + " " + dialog.MessageBox("WineGCC not found.\\nPlease install it with your system package installer.") + "\n";
}
script += std::string() + " exit 1" + "\n";
script += std::string() + "fi" + "\n";
// Work-around for Debian 8, Wine 1.6.2
MPT_MAYBE_CONSTANT_IF(TrackerSettings::Instance().WineSupportForeignOpenMPT || (mpt::arch_bits == 64))
{
script += std::string() + "if [ `$MPT_WINEGXX > /dev/null 2>&1 ; echo $?` -eq 127 ] ; then" + "\n";
script += std::string() + " if command -v /usr/lib/x86_64-linux-gnu/wine/bin/winegcc 2>/dev/null 1>/dev/null ; then" + "\n";
script += std::string() + " MPT_WINEGXX=/usr/lib/x86_64-linux-gnu/wine/bin/winegcc"+ "\n";
script += std::string() + " PATH=/usr/lib/x86_64-linux-gnu/wine/bin:\"${PATH}\"" + "\n";
script += std::string() + " export PATH" + "\n";
script += std::string() + " fi" + "\n";
script += std::string() + "fi" + "\n";
}
if constexpr(mpt::arch_bits == 32)
{
script += std::string() + "if [ `$MPT_WINEGXX > /dev/null 2>&1 ; echo $?` -eq 127 ] ; then" + "\n";
script += std::string() + " if command -v /usr/lib/i386-linux-gnu/wine/bin/winegcc 2>/dev/null 1>/dev/null ; then" + "\n";
script += std::string() + " MPT_WINEGXX=/usr/lib/i386-linux-gnu/wine/bin/winegcc" + "\n";
script += std::string() + " PATH=/usr/lib/i386-linux-gnu/wine/bin:\"${PATH}\"" + "\n";
script += std::string() + " export PATH" + "\n";
script += std::string() + " fi" + "\n";
script += std::string() + "fi" + "\n";
}
std::string features;
if(TrackerSettings::Instance().WineSupportForeignOpenMPT)
{
features += std::string() + " " + "MPT_ARCH_BITS=" + mpt::afmt::dec(mpt::arch_bits);
if constexpr(mpt::arch_bits == 64)
{
features += std::string() + " " + "MPT_TARGET=" + "x86_64-linux-gnu-";
} else
{
features += std::string() + " " + "MPT_TARGET=" + "i686-linux-gnu-";
}
}
features += std::string() + " " + "MPT_TRY_PORTAUDIO=" + mpt::afmt::dec(TrackerSettings::Instance().WineSupportEnablePortAudio.Get());
features += std::string() + " " + "MPT_TRY_PULSEAUDIO=" + mpt::afmt::dec(TrackerSettings::Instance().WineSupportEnablePulseAudio.Get());
features += std::string() + " " + "MPT_TRY_RTAUDIO=" + mpt::afmt::dec(TrackerSettings::Instance().WineSupportEnableRtAudio.Get());
int makeverbosity = Clamp(TrackerSettings::Instance().WineSupportCompileVerbosity.Get(), 0, 6);
if(TrackerSettings::Instance().WineSupportCompileVerbosity == 2)
{
script += std::string() + "{" + "\n";
script += std::string() + " echo 0" + "\n";
script += std::string() + " " + make + " -j " + mpt::afmt::dec(std::max(std::thread::hardware_concurrency(), static_cast<unsigned int>(1))) + " -f build/wine/native_support.mk" + " V=" + mpt::afmt::dec(makeverbosity) + " " + features + " all MPT_PROGRESS_FILE=\"&4\" 4>&1 1>stdout.txt 2>stderr.txt" + "\n";
script += std::string() + " echo -n $? > stdexit.txt" + "\n";
script += std::string() + " echo 100" + "\n";
script += std::string() + "} | " + dialog.Progress("[OK] Prepare OpenMPT Wine Integration\\n[>>] Compile native support\\n[ ] Compile Wine wrapper\\n\\n[2/3] Compiling native support ...") + "\n";
script += std::string() + "MPT_EXITCODE=`cat stdexit.txt`" + "\n";
script += std::string() + "if [ \"$MPT_EXITCODE\" -ne \"0\" ] ; then" + "\n";
if(TrackerSettings::Instance().WineSupportCompileVerbosity >= 1)
{
script += std::string() + " " + dialog.MessageBox("OpenMPT Wine integration failed to compile.") + "\n";
script += std::string() + " " + dialog.TextBox("stderr.txt") + "\n";
}
script += std::string() + " exit 1" + "\n";
script += std::string() + "fi" + "\n";
script += std::string() + "if [ -s stderr.txt ] ; then" + "\n";
script += std::string() + " " + dialog.TextBox("stderr.txt") + "\n";
script += std::string() + "fi" + "\n";
script += std::string() + "{" + "\n";
script += std::string() + " echo 0" + "\n";
script += std::string() + " " + make + " -j " + mpt::afmt::dec(std::max(std::thread::hardware_concurrency(), static_cast<unsigned int>(1))) + " -f build/wine/wine_wrapper.mk" + " V=" + mpt::afmt::dec(makeverbosity) + " WINEGXX=$MPT_WINEGXX " + "MPT_WINEGCC_LANG=" + ((wineVersion.HostClass() == mpt::osinfo::osclass::BSD) ? "C" : "CPLUSPLUS") + " MPT_WINE_SEARCHPATH=" + wine.EscapePosixShell(nativeSearchPath) + " all MPT_PROGRESS_FILE=\"&4\" 4>&1 1>stdout.txt 2>stderr.txt" + "\n";
script += std::string() + " echo -n $? > stdexit.txt" + "\n";
script += std::string() + " echo 100" + "\n";
script += std::string() + "} | " + dialog.Progress("[OK] Prepare OpenMPT Wine Integration\\n[OK] Compile native support\\n[>>] Compile Wine wrapper\\n\\n[3/3] Compiling Wine wrapper ...") + "\n";
script += std::string() + "MPT_EXITCODE=`cat stdexit.txt`" + "\n";
script += std::string() + "if [ \"$MPT_EXITCODE\" -ne \"0\" ] ; then" + "\n";
if(TrackerSettings::Instance().WineSupportCompileVerbosity >= 1)
{
script += std::string() + " " + dialog.MessageBox("OpenMPT Wine integration failed to compile.") + "\n";
script += std::string() + " " + dialog.TextBox("stderr.txt") + "\n";
}
script += std::string() + " exit 1" + "\n";
script += std::string() + "fi" + "\n";
script += std::string() + "if [ -s stderr.txt ] ; then" + "\n";
script += std::string() + " " + dialog.TextBox("stderr.txt") + "\n";
script += std::string() + "fi" + "\n";
} else
{
script += std::string() + "" + make + " -j " + mpt::afmt::dec(std::max(std::thread::hardware_concurrency(), static_cast<unsigned int>(1))) + " -f build/wine/native_support.mk" + " V=" + mpt::afmt::dec(makeverbosity) + " " + features + " all" + "\n";
script += std::string() + "if [ \"$?\" -ne \"0\" ] ; then" + "\n";
if(TrackerSettings::Instance().WineSupportCompileVerbosity >= 1)
{
script += std::string() + " " + dialog.MessageBox("OpenMPT Wine integration failed to compile.") + "\n";
script += std::string() + " " + dialog.TextBox("stderr.txt") + "\n";
}
script += std::string() + " exit 1" + "\n";
script += std::string() + "fi" + "\n";
script += std::string() + "if [ -s stderr.txt ] ; then" + "\n";
script += std::string() + " " + dialog.TextBox("stderr.txt") + "\n";
script += std::string() + "fi" + "\n";
script += std::string() + "" + make + " -j " + mpt::afmt::dec(std::max(std::thread::hardware_concurrency(), static_cast<unsigned int>(1))) + " -f build/wine/wine_wrapper.mk" + " V=" + mpt::afmt::dec(makeverbosity) + " WINEGXX=$MPT_WINEGXX " + "MPT_WINEGCC_LANG=" + ((wineVersion.HostClass() == mpt::osinfo::osclass::BSD) ? "C" : "CPLUSPLUS") + " MPT_WINE_SEARCHPATH=" + wine.EscapePosixShell(nativeSearchPath) + " all" + "\n";
script += std::string() + "if [ \"$?\" -ne \"0\" ] ; then" + "\n";
if(TrackerSettings::Instance().WineSupportCompileVerbosity >= 1)
{
script += std::string() + " " + dialog.MessageBox("OpenMPT Wine integration failed to compile.") + "\n";
script += std::string() + " " + dialog.TextBox("stderr.txt") + "\n";
}
script += std::string() + " exit 1" + "\n";
script += std::string() + "fi" + "\n";
script += std::string() + "if [ -s stderr.txt ] ; then" + "\n";
script += std::string() + " " + dialog.TextBox("stderr.txt") + "\n";
script += std::string() + "fi" + "\n";
}
script += std::string() + "\n";
if(TrackerSettings::Instance().WineSupportCompileVerbosity >= 6)
{
script += std::string() + dialog.MessageBox("OpenMPT Wine integration compiled successfully.") + "\n";
}
script += std::string() + "\n";
script += "exit 0" "\n";
CMainFrame::GetMainFrame()->EnableWindow(FALSE);
mpt::Wine::ExecResult result;
try
{
FlagSet<mpt::Wine::ExecFlags> flags = mpt::Wine::ExecFlagNone;
if(TrackerSettings::Instance().WineSupportCompileVerbosity >= 1)
{
flags = (mpt::Wine::ExecFlagProgressWindow | mpt::Wine::ExecFlagInteractive);
} else if(TrackerSettings::Instance().WineSupportCompileVerbosity == 0)
{
flags = (mpt::Wine::ExecFlagProgressWindow | mpt::Wine::ExecFlagSilent);
} else
{
flags = (mpt::Wine::ExecFlagSilent);
}
result = Util::Wine::ExecutePosixShellScript
( wine
, script
, flags
, filetree
, WineGetWindowTitleUTF8()
, "Compiling Wine support ..."
);
} catch(const mpt::Wine::Exception & /* e */ )
{
CMainFrame::GetMainFrame()->EnableWindow(TRUE);
throw;
}
CMainFrame::GetMainFrame()->EnableWindow(TRUE);
if(result.exitcode != 0)
{
if(result.filetree["message.txt"].size() > 0)
{
throw mpt::Wine::Exception(std::string(result.filetree["message.txt"].begin(), result.filetree["message.txt"].end()));
} else
{
throw mpt::Wine::Exception("Executing Wine integration build script failed.");
}
}
{
std::string fn = "libopenmpt_native_support.so";
mpt::ofstream f(wine.PathToWindows(nativeSearchPath) + P_("\\") + mpt::PathString::FromUTF8(fn), std::ios::binary);
f.write(&result.filetree[fn][0], result.filetree[fn].size());
f.flush();
if(!f)
{
throw mpt::Wine::Exception("Writing libopenmpt_native_support.so failed.");
}
}
{
std::string fn = "openmpt_wine_wrapper.dll";
mpt::ofstream f(paths.AppData_Wine_WineVersion_OpenMPTVersion + P_("\\") + mpt::PathString::FromUTF8(fn), std::ios::binary);
f.write(&result.filetree[fn][0], result.filetree[fn].size());
f.flush();
if(!f)
{
throw mpt::Wine::Exception("Writing openmpt_wine_wrapper.dll failed.");
}
}
{
std::string fn = "success.txt";
mpt::ofstream f(paths.AppData_Wine_WineVersion_OpenMPTVersion + P_("\\") + mpt::PathString::FromUTF8(fn), std::ios::binary);
f.imbue(std::locale::classic());
f << std::string("1");
f.flush();
if(!f)
{
throw mpt::Wine::Exception("Writing success.txt failed.");
}
}
theApp.SetWineWrapperDllFilename(paths.AppData_Wine_WineVersion_OpenMPTVersion + P_("\\") + P_("openmpt_wine_wrapper.dll"));
} catch(const mpt::Wine::Exception &e)
{
Reporting::Error(U_("Setting up OpenMPT Wine integration failed: ") + mpt::get_exception_text<mpt::ustring>(e), WineGetWindowTitle());
}
}
} // namespace WineIntegration
std::string ComponentWineWrapper::result_as_string(char * str) const
{
std::string result = str;
OpenMPT_Wine_Wrapper_String_Free(str);
return result;
}
ComponentWineWrapper::ComponentWineWrapper()
: ComponentLibrary(ComponentTypeBundled)
{
return;
}
bool ComponentWineWrapper::DoInitialize()
{
if(theApp.GetWineWrapperDllFilename().empty())
{
return false;
}
AddLibrary("WineWrapper", mpt::LibraryPath::FullPath(theApp.GetWineWrapperDllFilename()));
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_Init);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_Fini);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_String_Free);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_EnumerateDevices);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_Construct);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_Destruct);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_SetMessageReceiver);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_SetCallback);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_GetDeviceInfo);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_GetDeviceCaps);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_GetDeviceDynamicCaps);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_Init);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_Open);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_Close);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_Start);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_Stop);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_GetRequestFlags);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_IsInited);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_IsOpen);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_IsAvailable);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_IsPlaying);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_IsPlayingSilence);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_StopAndAvoidPlayingSilence);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_EndPlayingSilence);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_OnIdle);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_GetSettings);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_GetActualSampleFormat);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_GetEffectiveBufferAttributes);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_GetTimeInfo);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_GetStreamPosition);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_DebugIsFragileDevice);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_DebugInRealtimeCallback);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_GetStatistics);
MPT_COMPONENT_BIND("WineWrapper", OpenMPT_Wine_Wrapper_SoundDevice_OpenDriverSettings);
if(HasBindFailed())
{
Reporting::Error("OpenMPT Wine integration failed loading.", WineGetWindowTitle());
return false;
}
if(OpenMPT_Wine_Wrapper_Init() != 0)
{
Reporting::Error("OpenMPT Wine integration initialization failed.", WineGetWindowTitle());
return false;
}
if(TrackerSettings::Instance().WineSupportCompileVerbosity >= 6)
{
Reporting::Notification(MPT_AFORMAT("OpenMPT Wine integration loaded successfully.")(), WineGetWindowTitle());
}
return true;
}
ComponentWineWrapper::~ComponentWineWrapper()
{
if(IsAvailable())
{
OpenMPT_Wine_Wrapper_Fini();
}
}
namespace WineIntegration {
void Load()
{
ReloadComponent<ComponentWineWrapper>();
}
} // namespace WineIntegration
OPENMPT_NAMESPACE_END
@@ -0,0 +1,135 @@
/*
* MPTrackWine.h
* -------------
* Purpose: OpenMPT Wine support functions.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "../common/ComponentManager.h"
extern "C" {
typedef void OpenMPT_int24;
typedef struct OpenMPT_SoundDevice_StreamPosition OpenMPT_SoundDevice_StreamPosition;
typedef struct OpenMPT_SoundDevice_TimeInfo OpenMPT_SoundDevice_TimeInfo;
typedef struct OpenMPT_SoundDevice_Flags OpenMPT_SoundDevice_Flags;
typedef struct OpenMPT_SoundDevice_BufferFormat OpenMPT_SoundDevice_BufferFormat;
typedef struct OpenMPT_SoundDevice_BufferAttributes OpenMPT_SoundDevice_BufferAttributes;
typedef struct OpenMPT_SoundDevice_RequestFlags OpenMPT_SoundDevice_RequestFlags;
typedef struct OpenMPT_SoundDevice OpenMPT_SoundDevice;
typedef struct OpenMPT_Wine_Wrapper_SoundDevice_IMessageReceiver {
void * inst;
void (__cdecl * SoundDeviceMessageFunc)( void * inst, uintptr_t level, const char * message );
} OpenMPT_Wine_Wrapper_SoundDevice_IMessageReceiver;
typedef struct OpenMPT_Wine_Wrapper_SoundDevice_ICallback {
void * inst;
// main thread
void (__cdecl * SoundCallbackGetReferenceClockNowNanosecondsFunc)( void * inst, uint64_t * result );
void (__cdecl * SoundCallbackPreStartFunc)( void * inst );
void (__cdecl * SoundCallbackPostStopFunc)( void * inst );
void (__cdecl * SoundCallbackIsLockedByCurrentThreadFunc)( void * inst, uintptr_t * result );
// audio thread
void (__cdecl * SoundCallbackLockFunc)( void * inst );
void (__cdecl * SoundCallbackLockedGetReferenceClockNowNanosecondsFunc)( void * inst, uint64_t * result );
void (__cdecl * SoundCallbackLockedProcessPrepareFunc)( void * inst, const OpenMPT_SoundDevice_TimeInfo * timeInfo );
void (__cdecl * SoundCallbackLockedProcessUint8Func)( void * inst, const OpenMPT_SoundDevice_BufferFormat * bufferFormat, uintptr_t numFrames, uint8_t * buffer, const uint8_t * inputBuffer );
void (__cdecl * SoundCallbackLockedProcessInt8Func)( void * inst, const OpenMPT_SoundDevice_BufferFormat * bufferFormat, uintptr_t numFrames, int8_t * buffer, const int8_t * inputBuffer );
void (__cdecl * SoundCallbackLockedProcessInt16Func)( void * inst, const OpenMPT_SoundDevice_BufferFormat * bufferFormat, uintptr_t numFrames, int16_t * buffer, const int16_t * inputBuffer );
void (__cdecl * SoundCallbackLockedProcessInt24Func)( void * inst, const OpenMPT_SoundDevice_BufferFormat * bufferFormat, uintptr_t numFrames, OpenMPT_int24 * buffer, const OpenMPT_int24 * inputBuffer );
void (__cdecl * SoundCallbackLockedProcessInt32Func)( void * inst, const OpenMPT_SoundDevice_BufferFormat * bufferFormat, uintptr_t numFrames, int32_t * buffer, const int32_t * inputBuffer );
void (__cdecl * SoundCallbackLockedProcessFloatFunc)( void * inst, const OpenMPT_SoundDevice_BufferFormat * bufferFormat, uintptr_t numFrames, float * buffer, const float * inputBuffer );
void (__cdecl * SoundCallbackLockedProcessDoubleFunc)( void * inst, const OpenMPT_SoundDevice_BufferFormat * bufferFormat, uintptr_t numFrames, double * buffer, const double * inputBuffer );
void (__cdecl * SoundCallbackLockedProcessDoneFunc)( void * inst, const OpenMPT_SoundDevice_TimeInfo * timeInfo );
void (__cdecl * SoundCallbackUnlockFunc)( void * inst );
} OpenMPT_Wine_Wrapper_SoundDevice_ICallback;
typedef struct OpenMPT_Wine_Wrapper_SoundDevice OpenMPT_Wine_Wrapper_SoundDevice;
};
OPENMPT_NAMESPACE_BEGIN
namespace WineIntegration {
void Initialize();
bool IsSupported();
bool IsCompiled();
void Load();
} // namespace WineIntegration
class ComponentWineWrapper
: public ComponentLibrary
{
MPT_DECLARE_COMPONENT_MEMBERS(ComponentWineWrapper, "WineWrapper")
public:
private: uintptr_t (__cdecl * OpenMPT_Wine_Wrapper_Init)(void) = nullptr;
private: uintptr_t (__cdecl * OpenMPT_Wine_Wrapper_Fini)(void) = nullptr;
private: void (__cdecl * OpenMPT_Wine_Wrapper_String_Free)(char * str) = nullptr;
private: char * (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_EnumerateDevices)(void) = nullptr;
public: std::string SoundDevice_EnumerateDevices() const { return result_as_string(OpenMPT_Wine_Wrapper_SoundDevice_EnumerateDevices()); }
public: OpenMPT_Wine_Wrapper_SoundDevice * (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_Construct)( const char * info ) = nullptr;
public: void (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_Destruct)( OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: void (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_SetMessageReceiver)( OpenMPT_Wine_Wrapper_SoundDevice * sd, const OpenMPT_Wine_Wrapper_SoundDevice_IMessageReceiver * receiver ) = nullptr;
public: void (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_SetCallback)( OpenMPT_Wine_Wrapper_SoundDevice * sd, const OpenMPT_Wine_Wrapper_SoundDevice_ICallback * callback ) = nullptr;
public: char * (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_GetDeviceInfo)( const OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: char * (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_GetDeviceCaps)( const OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: char * (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_GetDeviceDynamicCaps)( OpenMPT_Wine_Wrapper_SoundDevice * sd, const char * baseSampleRates ) = nullptr;
public: uintptr_t (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_Init)( OpenMPT_Wine_Wrapper_SoundDevice * sd, const char * appInfo ) = nullptr;
public: uintptr_t (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_Open)( OpenMPT_Wine_Wrapper_SoundDevice * sd, const char * settings ) = nullptr;
public: uintptr_t (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_Close)( OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: uintptr_t (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_Start)( OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: void (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_Stop)( OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: void (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_GetRequestFlags)( const OpenMPT_Wine_Wrapper_SoundDevice * sd, uint32_t * result ) = nullptr;
public: uintptr_t (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_IsInited)( const OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: uintptr_t (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_IsOpen)( const OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: uintptr_t (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_IsAvailable)( const OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: uintptr_t (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_IsPlaying)( const OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: uintptr_t (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_IsPlayingSilence)( const OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: void (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_StopAndAvoidPlayingSilence)( const OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: void (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_EndPlayingSilence)( const OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: uintptr_t (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_OnIdle)( OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: char * (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_GetSettings)( const OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: void (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_GetActualSampleFormat)( const OpenMPT_Wine_Wrapper_SoundDevice * sd, int32_t * result ) = nullptr;
public: void (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_GetEffectiveBufferAttributes)( const OpenMPT_Wine_Wrapper_SoundDevice * sd, OpenMPT_SoundDevice_BufferAttributes * result ) = nullptr;
public: void (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_GetTimeInfo)( const OpenMPT_Wine_Wrapper_SoundDevice * sd, OpenMPT_SoundDevice_TimeInfo * result ) = nullptr;
public: void (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_GetStreamPosition)( const OpenMPT_Wine_Wrapper_SoundDevice * sd, OpenMPT_SoundDevice_StreamPosition * result ) = nullptr;
public: uintptr_t (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_DebugIsFragileDevice)( const OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: uintptr_t (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_DebugInRealtimeCallback)( const OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: char * (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_GetStatistics)( const OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: uintptr_t (__cdecl * OpenMPT_Wine_Wrapper_SoundDevice_OpenDriverSettings)( OpenMPT_Wine_Wrapper_SoundDevice * sd ) = nullptr;
public: std::string result_as_string(char * str) const;
public:
ComponentWineWrapper();
bool DoInitialize();
virtual ~ComponentWineWrapper();
};
OPENMPT_NAMESPACE_END
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,197 @@
/*
* Mainbar.h
* ---------
* Purpose: Implementation of OpenMPT's window toolbar.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include "UpdateToolTip.h"
OPENMPT_NAMESPACE_BEGIN
class CStereoVU: public CStatic
{
protected:
uint8 numChannels;
uint32 vuMeter[4];
DWORD lastVuUpdateTime;
int lastV[4];
bool lastClip[4];
bool horizontal;
bool allowRightToLeft;
public:
CStereoVU() { numChannels = 2; MemsetZero(vuMeter); lastVuUpdateTime = timeGetTime(); horizontal = true; MemsetZero(lastV); MemsetZero(lastClip); allowRightToLeft = false; }
void SetVuMeter(uint8 validChannels, const uint32 channels[4], bool force=false);
void SetOrientation(bool h) { horizontal = h; }
protected:
void DrawVuMeters(CDC &dc, bool redraw=false);
void DrawVuMeter(CDC &dc, const CRect &rect, int index, bool redraw=false);
protected:
afx_msg void OnPaint();
afx_msg void OnLButtonDown(UINT, CPoint);
DECLARE_MESSAGE_MAP();
};
#define MIN_BASEOCTAVE 0
#define MAX_BASEOCTAVE 8
class CSoundFile;
class CModDoc;
class CModTree;
class CMainFrame;
class CToolBarEx: public CToolBar
{
protected:
bool m_bVertical = false, m_bFlatButtons = false;
public:
CToolBarEx() {}
~CToolBarEx() override {}
public:
BOOL EnableControl(CWnd &wnd, UINT nIndex, UINT nHeight=0);
void ChangeCtrlStyle(LONG lStyle, BOOL bSetStyle);
void EnableFlatButtons(BOOL bFlat);
public:
//{{AFX_VIRTUAL(CToolBarEx)
CSize CalcDynamicLayout(int nLength, DWORD dwMode) override;
virtual void SetHorizontal();
virtual void SetVertical();
//}}AFX_VIRTUAL
};
class CMainToolBar: public CToolBarEx
{
protected:
UpdateToolTip m_tooltip;
CImageListEx m_ImageList, m_ImageListDisabled;
CStatic m_EditTempo, m_EditSpeed, m_EditOctave, m_EditRowsPerBeat;
CStatic m_StaticTempo, m_StaticSpeed, m_StaticRowsPerBeat;
CSpinButtonCtrl m_SpinTempo, m_SpinSpeed, m_SpinOctave, m_SpinRowsPerBeat;
int nCurrentSpeed, nCurrentOctave, nCurrentRowsPerBeat;
TEMPO nCurrentTempo;
public:
CStereoVU m_VuMeter;
public:
CMainToolBar() {}
~CMainToolBar() override {}
protected:
void SetRowsPerBeat(ROWINDEX nNewRPB);
public:
//{{AFX_VIRTUAL(CMainToolBar)
void SetHorizontal() override;
void SetVertical() override;
//}}AFX_VIRTUAL
public:
#if MPT_COMPILER_CLANG
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Woverloaded-virtual"
#endif // MPT_COMPILER_CLANG
BOOL Create(CWnd *parent);
#if MPT_COMPILER_CLANG
#pragma clang diagnostic pop
#endif // MPT_COMPILER_CLANG
void Init(CMainFrame *);
UINT GetBaseOctave() const;
BOOL SetBaseOctave(UINT nOctave);
BOOL SetCurrentSong(CSoundFile *pModDoc);
bool ShowUpdateInfo(const CString &newVersion, const CString &infoURL, bool showHighLight);
void RemoveUpdateInfo();
protected:
//{{AFX_MSG(CMainToolBar)
afx_msg void OnVScroll(UINT, UINT, CScrollBar *);
afx_msg void OnTbnDropDownToolBar(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg BOOL OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnSelectMIDIDevice(UINT id);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
class CModTreeBar: public CDialogBar
{
protected:
enum Status
{
MTB_VERTICAL = 0x01,
MTB_CAPTURE = 0x02,
MTB_DRAGGING = 0x04,
MTB_TRACKER = 0x08,
};
DWORD m_dwStatus = 0; // MTB_XXXX
UINT m_nCursorDrag = 0;
CPoint ptDragging;
UINT m_cxOriginal = 0, m_cyOriginal = 0, m_nTrackPos = 0;
UINT m_nTreeSplitRatio = 0;
public:
CModTree *m_pModTree = nullptr, *m_pModTreeData = nullptr;
CModTreeBar();
~CModTreeBar() override;
public:
void Init();
void RecalcLayout();
void DoMouseMove(CPoint point);
void DoLButtonDown(CPoint point);
void DoLButtonUp();
void CancelTracking();
void OnInvertTracker(UINT x);
void RefreshDlsBanks();
void RefreshMidiLibrary();
void OnOptionsChanged();
void OnDocumentCreated(CModDoc *pModDoc);
void OnDocumentClosed(CModDoc *pModDoc);
void OnUpdate(CModDoc *pModDoc, UpdateHint hint, CObject *pHint = nullptr);
void UpdatePlayPos(CModDoc *pModDoc, Notification *pNotify);
HWND GetModTreeHWND(); //rewbs.customKeys
LRESULT SendMessageToModTree(UINT cmdID, WPARAM wParam, LPARAM lParam);
bool SetTreeSoundfile(FileReader &file);
protected:
//{{AFX_VIRTUAL(CModTreeBar)
CSize CalcFixedLayout(BOOL bStretch, BOOL bHorz) override;
//}}AFX_VIRTUAL
protected:
//{{AFX_MSG(CModTreeBar)
afx_msg void OnNcPaint();
afx_msg LRESULT OnNcHitTest(CPoint point);
afx_msg void OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp);
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnNcMouseMove(UINT nHitTest, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnNcLButtonDown(UINT, CPoint);
afx_msg void OnLButtonDown(UINT, CPoint);
afx_msg void OnNcLButtonUp(UINT, CPoint);
afx_msg void OnLButtonUp(UINT, CPoint);
afx_msg void OnNcRButtonDown(UINT, CPoint) { CancelTracking(); }
afx_msg void OnRButtonDown(UINT, CPoint) { CancelTracking(); }
afx_msg LRESULT OnInitDialog(WPARAM, LPARAM);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
OPENMPT_NAMESPACE_END
@@ -0,0 +1,588 @@
/*
* Mainfrm.h
* ---------
* Purpose: Implementation of OpenMPT's main window code.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include <Msctf.h>
#include "Mptrack.h"
#include "AutoSaver.h"
#include "UpdateHints.h"
#include "../soundlib/AudioCriticalSection.h"
#include "mpt/mutex/mutex.hpp"
#include "../soundlib/Sndfile.h"
#include "openmpt/soundbase/Dither.hpp"
#include "../common/Dither.h"
#include "mpt/audio/span.hpp"
#include "openmpt/sounddevice/SoundDeviceBuffer.hpp"
OPENMPT_NAMESPACE_BEGIN
class CDLSBank;
class CInputHandler;
class CModDoc;
class CAutoSaver;
struct UpdateCheckResult;
namespace SoundDevice {
class Base;
class ICallback;
} // namerspace SoundDevice
#define MAINFRAME_TITLE _T("Open ModPlug Tracker")
// Custom window messages
enum
{
WM_MOD_UPDATEPOSITION = (WM_USER+1973),
WM_MOD_INVALIDATEPATTERNS,
WM_MOD_ACTIVATEVIEW,
WM_MOD_CHANGEVIEWCLASS,
WM_MOD_UNLOCKCONTROLS,
WM_MOD_CTRLMSG,
WM_MOD_VIEWMSG,
WM_MOD_MIDIMSG,
WM_MOD_GETTOOLTIPTEXT,
WM_MOD_DRAGONDROPPING,
WM_MOD_KBDNOTIFY,
WM_MOD_INSTRSELECTED,
WM_MOD_KEYCOMMAND,
WM_MOD_RECORDPARAM,
WM_MOD_PLUGPARAMAUTOMATE,
WM_MOD_MIDIMAPPING,
WM_MOD_UPDATEVIEWS,
WM_MOD_SETMODIFIED,
WM_MOD_MDIACTIVATE,
WM_MOD_MDIDEACTIVATE,
WM_MOD_UPDATENOTIFY,
WM_MOD_PLUGINDRYWETRATIOCHANGED,
};
enum
{
MPT_WM_APP_UPDATECHECK_START = WM_APP + 1,
MPT_WM_APP_UPDATECHECK_PROGRESS = WM_APP + 2,
MPT_WM_APP_UPDATECHECK_CANCELED = WM_APP + 3,
MPT_WM_APP_UPDATECHECK_FAILURE = WM_APP + 4,
MPT_WM_APP_UPDATECHECK_SUCCESS = WM_APP + 5,
};
enum
{
CTRLMSG_BASE = 0,
CTRLMSG_SETVIEWWND,
CTRLMSG_ACTIVATEPAGE,
CTRLMSG_DEACTIVATEPAGE,
CTRLMSG_SETFOCUS,
// Pattern-Specific
CTRLMSG_GETCURRENTPATTERN,
CTRLMSG_NOTIFYCURRENTORDER,
CTRLMSG_SETCURRENTORDER,
CTRLMSG_GETCURRENTORDER,
CTRLMSG_FORCEREFRESH,
CTRLMSG_PAT_PREVINSTRUMENT,
CTRLMSG_PAT_NEXTINSTRUMENT,
CTRLMSG_PAT_SETINSTRUMENT,
CTRLMSG_PAT_FOLLOWSONG,
CTRLMSG_PAT_LOOP,
CTRLMSG_PAT_NEWPATTERN,
CTRLMSG_PAT_SETSEQUENCE,
CTRLMSG_GETCURRENTINSTRUMENT,
CTRLMSG_SETCURRENTINSTRUMENT,
CTRLMSG_SETSPACING,
CTRLMSG_PATTERNCHANGED,
CTRLMSG_PREVORDER,
CTRLMSG_NEXTORDER,
CTRLMSG_SETRECORD,
CTRLMSG_PAT_DUPPATTERN,
// Sample-Specific
CTRLMSG_SMP_PREVINSTRUMENT,
CTRLMSG_SMP_NEXTINSTRUMENT,
CTRLMSG_SMP_OPENFILE,
CTRLMSG_SMP_SETZOOM,
CTRLMSG_SMP_GETZOOM,
CTRLMSG_SMP_SONGDROP,
CTRLMSG_SMP_INITOPL,
CTRLMSG_SMP_NEWSAMPLE,
// Instrument-Specific
CTRLMSG_INS_PREVINSTRUMENT,
CTRLMSG_INS_NEXTINSTRUMENT,
CTRLMSG_INS_OPENFILE,
CTRLMSG_INS_NEWINSTRUMENT,
CTRLMSG_INS_SONGDROP,
CTRLMSG_INS_SAMPLEMAP,
};
enum
{
VIEWMSG_BASE=0,
VIEWMSG_SETCTRLWND,
VIEWMSG_SETACTIVE,
VIEWMSG_SETFOCUS,
VIEWMSG_SAVESTATE,
VIEWMSG_LOADSTATE,
// Pattern-Specific
VIEWMSG_SETCURRENTPATTERN,
VIEWMSG_GETCURRENTPATTERN,
VIEWMSG_FOLLOWSONG,
VIEWMSG_PATTERNLOOP,
VIEWMSG_GETCURRENTPOS,
VIEWMSG_SETRECORD,
VIEWMSG_SETSPACING,
VIEWMSG_PATTERNPROPERTIES,
VIEWMSG_SETVUMETERS,
VIEWMSG_SETPLUGINNAMES, //rewbs.patPlugNames
VIEWMSG_DOMIDISPACING,
VIEWMSG_EXPANDPATTERN,
VIEWMSG_SHRINKPATTERN,
VIEWMSG_COPYPATTERN,
VIEWMSG_PASTEPATTERN,
VIEWMSG_AMPLIFYPATTERN,
VIEWMSG_SETDETAIL,
// Sample-Specific
VIEWMSG_SETCURRENTSAMPLE,
VIEWMSG_SETMODIFIED,
VIEWMSG_PREPAREUNDO,
// Instrument-Specific
VIEWMSG_SETCURRENTINSTRUMENT,
VIEWMSG_DOSCROLL,
};
#define NUM_VUMETER_PENS 32
// Tab Order
enum OptionsPage
{
OPTIONS_PAGE_DEFAULT = 0,
OPTIONS_PAGE_GENERAL = OPTIONS_PAGE_DEFAULT,
OPTIONS_PAGE_SOUNDCARD,
OPTIONS_PAGE_MIXER,
OPTIONS_PAGE_PLAYER,
OPTIONS_PAGE_SAMPLEDITOR,
OPTIONS_PAGE_KEYBOARD,
OPTIONS_PAGE_COLORS,
OPTIONS_PAGE_MIDI,
OPTIONS_PAGE_PATHS,
OPTIONS_PAGE_UPDATE,
OPTIONS_PAGE_ADVANCED,
OPTIONS_PAGE_WINE,
};
/////////////////////////////////////////////////////////////////////////
// Player position notification
#define MAX_UPDATE_HISTORY 2000 // 2 seconds with 1 ms updates
OPENMPT_NAMESPACE_END
#include "Notification.h"
OPENMPT_NAMESPACE_BEGIN
OPENMPT_NAMESPACE_END
#include "CImageListEx.h"
#include "Mainbar.h"
#include "TrackerSettings.h"
OPENMPT_NAMESPACE_BEGIN
struct MODPLUGDIB;
template<> inline SettingValue ToSettingValue(const WINDOWPLACEMENT &val)
{
return SettingValue(EncodeBinarySetting<WINDOWPLACEMENT>(val), "WINDOWPLACEMENT");
}
template<> inline WINDOWPLACEMENT FromSettingValue(const SettingValue &val)
{
ASSERT(val.GetTypeTag() == "WINDOWPLACEMENT");
return DecodeBinarySetting<WINDOWPLACEMENT>(val.as<std::vector<std::byte> >());
}
class VUMeter
: public IMonitorInput
, public IMonitorOutput
{
public:
static constexpr std::size_t maxChannels = 4;
static const float dynamicRange; // corresponds to the current implementation of the UI widget diplaying the result
struct Channel
{
int32 peak = 0;
bool clipped = false;
};
private:
Channel channels[maxChannels];
int32 decayParam;
void Process(Channel &channel, MixSampleInt sample);
void Process(Channel &channel, MixSampleFloat sample);
public:
VUMeter() : decayParam(0) { SetDecaySpeedDecibelPerSecond(88.0f); }
void SetDecaySpeedDecibelPerSecond(float decibelPerSecond);
public:
const Channel & operator [] (std::size_t channel) const { return channels[channel]; }
void Process(mpt::audio_span_interleaved<const MixSampleInt> buffer);
void Process(mpt::audio_span_planar<const MixSampleInt> buffer);
void Process(mpt::audio_span_interleaved<const MixSampleFloat> buffer);
void Process(mpt::audio_span_planar<const MixSampleFloat> buffer);
void Decay(int32 secondsNum, int32 secondsDen);
void ResetClipped();
};
class TfLanguageProfileNotifySink : public ITfLanguageProfileNotifySink
{
public:
TfLanguageProfileNotifySink();
~TfLanguageProfileNotifySink();
HRESULT STDMETHODCALLTYPE OnLanguageChange(LANGID langid, __RPC__out BOOL *pfAccept) override;
HRESULT STDMETHODCALLTYPE OnLanguageChanged() override;
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override;
ULONG STDMETHODCALLTYPE AddRef() override;
ULONG STDMETHODCALLTYPE Release() override;
protected:
ITfInputProcessorProfiles *m_pProfiles = nullptr;
ITfSource *m_pSource = nullptr;
DWORD m_dwCookie = TF_INVALID_COOKIE;
};
class CMainFrame
: public CMDIFrameWnd
, public SoundDevice::CallbackBufferHandler<DithersOpenMPT>
, public SoundDevice::IMessageReceiver
, public TfLanguageProfileNotifySink
{
DECLARE_DYNAMIC(CMainFrame)
// static data
public:
// Globals
static OptionsPage m_nLastOptionsPage;
static HHOOK ghKbdHook;
// GDI
static HICON m_hIcon;
static HFONT m_hGUIFont, m_hFixedFont;
static HPEN penDarkGray, penHalfDarkGray, penGray99;
static HCURSOR curDragging, curNoDrop, curArrow, curNoDrop2, curVSplit;
static MODPLUGDIB *bmpNotes, *bmpVUMeters, *bmpPluginVUMeters;
static COLORREF gcolrefVuMeter[NUM_VUMETER_PENS * 2]; // General tab VU meters
public:
// Low-Level Audio
CriticalSection m_SoundDeviceFillBufferCriticalSection;
Util::MultimediaClock m_SoundDeviceClock;
SoundDevice::IBase *gpSoundDevice = nullptr;
UINT_PTR m_NotifyTimer = 0;
VUMeter m_VUMeterInput;
VUMeter m_VUMeterOutput;
DWORD m_AudioThreadId = 0;
bool m_InNotifyHandler = false;
// Midi Input
public:
static HMIDIIN shMidiIn;
public:
CImageListEx m_MiscIcons, m_MiscIconsDisabled; // Misc Icons
CImageListEx m_PatternIcons, m_PatternIconsDisabled; // Pattern icons (includes some from sample editor as well...)
CImageListEx m_EnvelopeIcons; // Instrument editor icons
CImageListEx m_SampleIcons; // Sample editor icons
protected:
CModTreeBar m_wndTree;
CStatusBar m_wndStatusBar;
CMainToolBar m_wndToolBar;
CSoundFile *m_pSndFile = nullptr; // != NULL only when currently playing or rendering
HWND m_hWndMidi = nullptr;
CSoundFile::samplecount_t m_dwTimeSec = 0;
UINT_PTR m_nTimer = 0;
UINT m_nAvgMixChn = 0, m_nMixChn = 0;
// Misc
class COptionsSoundcard *m_SoundCardOptionsDialog = nullptr;
#if defined(MPT_ENABLE_UPDATE)
class CUpdateSetupDlg *m_UpdateOptionsDialog = nullptr;
std::unique_ptr<UpdateCheckResult> m_updateCheckResult;
#endif // MPT_ENABLE_UPDATE
DWORD helpCookie = 0;
bool m_bOptionsLocked = false;
// Notification Buffer
mpt::mutex m_NotificationBufferMutex; // to avoid deadlocks, this mutex should only be taken as a innermost lock, i.e. do not block on anything while holding this mutex
Util::fixed_size_queue<Notification,MAX_UPDATE_HISTORY> m_NotifyBuffer;
// Instrument preview in tree view
CSoundFile m_WaveFile;
TCHAR m_szUserText[512], m_szInfoText[512], m_szXInfoText[512];
CAutoSaver m_AutoSaver;
public:
CWnd *m_pNoteMapHasFocus = nullptr;
CWnd *m_pOrderlistHasFocus = nullptr;
bool m_bModTreeHasFocus = false;
public:
CMainFrame(/*CString regKeyExtension*/);
void Initialize();
// Low-Level Audio
public:
static void UpdateDspEffects(CSoundFile &sndFile, bool reset=false);
static void UpdateAudioParameters(CSoundFile &sndFile, bool reset=false);
// from SoundDevice::IBufferHandler
uint64 SoundCallbackGetReferenceClockNowNanoseconds() const override;
void SoundCallbackPreStart() override;
void SoundCallbackPostStop() override;
bool SoundCallbackIsLockedByCurrentThread() const override;
void SoundCallbackLock() override;
uint64 SoundCallbackLockedGetReferenceClockNowNanoseconds() const override;
void SoundCallbackLockedProcessPrepare(SoundDevice::TimeInfo timeInfo) override;
void SoundCallbackLockedCallback(SoundDevice::CallbackBuffer<DithersOpenMPT> &buffer) override;
void SoundCallbackLockedProcessDone(SoundDevice::TimeInfo timeInfo) override;
void SoundCallbackUnlock() override;
// from SoundDevice::IMessageReceiver
void SoundDeviceMessage(LogLevel level, const mpt::ustring &str) override;
bool InGuiThread() const { return theApp.InGuiThread(); }
bool InAudioThread() const { return GetCurrentThreadId() == m_AudioThreadId; }
bool InNotifyHandler() const { return m_InNotifyHandler; }
bool audioOpenDevice();
void audioCloseDevice();
bool IsAudioDeviceOpen() const;
bool DoNotification(DWORD dwSamplesRead, int64 streamPosition);
// Midi Input Functions
public:
bool midiOpenDevice(bool showSettings = true);
void midiCloseDevice();
void SetMidiRecordWnd(HWND hwnd) { m_hWndMidi = hwnd; }
HWND GetMidiRecordWnd() const { return m_hWndMidi; }
static int ApplyVolumeRelatedSettings(const DWORD &dwParam1, const BYTE midivolume);
// static functions
public:
static CMainFrame *GetMainFrame() { return (CMainFrame *)theApp.m_pMainWnd; }
static void UpdateColors();
static HICON GetModIcon() { return m_hIcon; }
static HFONT GetGUIFont() { return m_hGUIFont; }
static HFONT &GetCommentsFont() { return m_hFixedFont; }
static void UpdateAllViews(UpdateHint hint, CObject *pHint=NULL);
static LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam);
static CInputHandler *m_InputHandler;
// Misc functions
public:
void SetUserText(LPCTSTR lpszText);
void SetInfoText(LPCTSTR lpszText);
void SetXInfoText(LPCTSTR lpszText);
void SetHelpText(LPCTSTR lpszText);
UINT GetBaseOctave() const;
CModDoc *GetActiveDoc() const;
CView *GetActiveView() const;
void OnDocumentCreated(CModDoc *pModDoc);
void OnDocumentClosed(CModDoc *pModDoc);
void UpdateTree(CModDoc *pModDoc, UpdateHint hint, CObject *pHint = nullptr);
void RefreshDlsBanks();
static CInputHandler* GetInputHandler() { return m_InputHandler; }
void SetElapsedTime(double t) { m_dwTimeSec = static_cast<CSoundFile::samplecount_t>(t); }
#if defined(MPT_ENABLE_UPDATE)
bool ShowUpdateIndicator(const UpdateCheckResult &result, const CString &releaseVersion, const CString &infoURL, bool showHighlight);
#endif // MPT_ENABLE_UPDATE
CModTree *GetUpperTreeview() { return m_wndTree.m_pModTree; }
CModTree *GetLowerTreeview() { return m_wndTree.m_pModTreeData; }
bool SetTreeSoundfile(FileReader &file) { return m_wndTree.SetTreeSoundfile(file); }
void CreateExampleModulesMenu();
void CreateTemplateModulesMenu();
CMenu *GetFileMenu() const;
// Creates submenu whose items are filenames of files in both
// AppDirectory\folderName\ (usually C:\Program Files\OpenMPT\folderName\)
// and
// ConfigDirectory\folderName (usually %appdata%\OpenMPT\folderName\)
// [in] maxCount: Maximum number of items allowed in the menu
// [out] paths: Receives the full paths of the files added to the menu.
// [in] folderName: Name of the folder
// [in] idRangeBegin: First ID for the menu item.
static HMENU CreateFileMenu(const size_t maxCount, std::vector<mpt::PathString>& paths, const mpt::PathString &folderName, const uint16 idRangeBegin);
// Player functions
public:
// High-level synchronous playback functions, do not hold AudioCriticalSection while calling these
bool PreparePlayback();
bool StartPlayback();
void StopPlayback();
bool RestartPlayback();
bool PausePlayback();
static bool IsValidSoundFile(CSoundFile &sndFile) { return sndFile.GetType() ? true : false; }
static bool IsValidSoundFile(CSoundFile *pSndFile) { return pSndFile && pSndFile->GetType(); }
void SetPlaybackSoundFile(CSoundFile *pSndFile);
void UnsetPlaybackSoundFile();
void GenerateStopNotification();
bool PlayMod(CModDoc *);
bool StopMod(CModDoc *pDoc = nullptr);
bool PauseMod(CModDoc *pDoc = nullptr);
bool StopSoundFile(CSoundFile *);
bool PlaySoundFile(CSoundFile *);
bool PlaySoundFile(const mpt::PathString &filename, ModCommand::NOTE note, int volume = -1);
bool PlaySoundFile(CSoundFile &sndFile, INSTRUMENTINDEX nInstrument, SAMPLEINDEX nSample, ModCommand::NOTE note, int volume = -1);
bool PlayDLSInstrument(const CDLSBank &bank, UINT instr, UINT region, ModCommand::NOTE note, int volume = -1);
void InitPreview();
void PreparePreview(ModCommand::NOTE note, int volume);
void StopPreview() { StopSoundFile(&m_WaveFile); }
void PlayPreview() { PlaySoundFile(&m_WaveFile); }
inline bool IsPlaying() const { return m_pSndFile != nullptr; }
// Return currently playing module (nullptr if none is playing)
inline CModDoc *GetModPlaying() const { return m_pSndFile ? m_pSndFile->GetpModDoc() : nullptr; }
// Return currently playing module (nullptr if none is playing)
inline CSoundFile *GetSoundFilePlaying() const { return m_pSndFile; }
BOOL InitRenderer(CSoundFile*);
BOOL StopRenderer(CSoundFile*);
void SwitchToActiveView();
void IdleHandlerSounddevice();
BOOL ResetSoundCard();
BOOL SetupSoundCard(SoundDevice::Settings deviceSettings, SoundDevice::Identifier deviceIdentifier, SoundDeviceStopMode stoppedMode, bool forceReset = false);
BOOL SetupMiscOptions();
BOOL SetupPlayer();
void SetupMidi(DWORD d, UINT n);
HWND GetFollowSong() const;
HWND GetFollowSong(const CModDoc *pDoc) const { return (pDoc == GetModPlaying()) ? GetFollowSong() : nullptr; }
void ResetNotificationBuffer();
// Notify accessbility software that it should read out updated UI elements
void NotifyAccessibilityUpdate(CWnd &source);
// Overrides
protected:
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMainFrame)
BOOL PreCreateWindow(CREATESTRUCT& cs) override;
BOOL PreTranslateMessage(MSG *pMsg) override;
BOOL DestroyWindow() override;
void OnUpdateFrameTitle(BOOL bAddToTitle) override;
//}}AFX_VIRTUAL
/// Opens either template or example menu item.
void OpenMenuItemFile(const UINT nId, const bool isTemplateFile);
public:
void UpdateMRUList();
// Implementation
public:
~CMainFrame() override;
#ifdef _DEBUG
void AssertValid() const override;
void Dump(CDumpContext& dc) const override;
#endif
void OnTimerGUI();
void OnTimerNotify();
// Message map functions
//{{AFX_MSG(CMainFrame)
public:
afx_msg void OnAddDlsBank();
afx_msg void OnImportMidiLib();
afx_msg void OnViewOptions();
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnRButtonDown(UINT, CPoint);
afx_msg void OnClose();
afx_msg void OnTimer(UINT_PTR);
afx_msg void OnPluginManager();
afx_msg void OnClipboardManager();
afx_msg LRESULT OnViewMIDIMapping(WPARAM wParam, LPARAM lParam);
afx_msg void OnUpdateTime(CCmdUI *pCmdUI);
afx_msg void OnUpdateUser(CCmdUI *pCmdUI);
afx_msg void OnUpdateInfo(CCmdUI *pCmdUI);
afx_msg void OnUpdateXInfo(CCmdUI *pCmdUI);
afx_msg void OnUpdateMidiRecord(CCmdUI *pCmdUI);
afx_msg void OnPlayerPause();
afx_msg void OnMidiRecord();
afx_msg void OnPrevOctave();
afx_msg void OnNextOctave();
afx_msg void OnPanic();
afx_msg void OnReportBug();
afx_msg BOOL OnInternetLink(UINT nID);
afx_msg LRESULT OnUpdatePosition(WPARAM, LPARAM lParam);
afx_msg LRESULT OnUpdateViews(WPARAM modDoc, LPARAM hint);
afx_msg LRESULT OnSetModified(WPARAM modDoc, LPARAM);
afx_msg void OnOpenTemplateModule(UINT nId);
afx_msg void OnExampleSong(UINT nId);
afx_msg void OnOpenMRUItem(UINT nId);
afx_msg void OnUpdateMRUItem(CCmdUI *cmd);
afx_msg LRESULT OnInvalidatePatterns(WPARAM, LPARAM);
afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM);
afx_msg void OnInternetUpdate();
afx_msg void OnUpdateAvailable();
afx_msg void OnShowSettingsFolder();
#if defined(MPT_ENABLE_UPDATE)
afx_msg LRESULT OnUpdateCheckStart(WPARAM wparam, LPARAM lparam);
afx_msg LRESULT OnUpdateCheckProgress(WPARAM wparam, LPARAM lparam);
afx_msg LRESULT OnUpdateCheckCanceled(WPARAM wparam, LPARAM lparam);
afx_msg LRESULT OnUpdateCheckFailure(WPARAM wparam, LPARAM lparam);
afx_msg LRESULT OnUpdateCheckSuccess(WPARAM wparam, LPARAM lparam);
afx_msg LRESULT OnToolbarUpdateIndicatorClick(WPARAM wparam, LPARAM lparam);
#endif // MPT_ENABLE_UPDATE
afx_msg void OnHelp();
afx_msg BOOL OnDeviceChange(UINT nEventType, DWORD_PTR dwData);
afx_msg void OnDropFiles(HDROP hDropInfo);
afx_msg BOOL OnQueryEndSession();
afx_msg void OnActivateApp(BOOL active, DWORD threadID);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnInitMenu(CMenu *pMenu);
bool UpdateEffectKeys(const CModDoc *modDoc);
afx_msg void OnKillFocus(CWnd* pNewWnd);
afx_msg void OnShowWindow(BOOL bShow, UINT nStatus);
// Defines maximum number of items in example modules menu.
static constexpr size_t nMaxItemsInExampleModulesMenu = 50;
static constexpr size_t nMaxItemsInTemplateModulesMenu = 50;
private:
/// Array of paths of example modules that are available from help menu.
std::vector<mpt::PathString> m_ExampleModulePaths;
/// Array of paths of template modules that are available from file menu.
std::vector<mpt::PathString> m_TemplateModulePaths;
};
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.
OPENMPT_NAMESPACE_END
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,675 @@
/*
* ModConvert.cpp
* --------------
* Purpose: Converting between various module formats.
* Notes : Incomplete list of MPTm-only features and extensions in the old formats:
* Features only available for MPTm:
* - User definable tunings.
* - Extended pattern range
* - Extended sequence
* - Multiple sequences ("songs")
* - Pattern-specific time signatures
* - Pattern effects :xy, S7D, S7E
* - Long instrument envelopes
* - Envelope release node (this was previously also usable in the IT format, but is now deprecated in that format)
* - Fractional tempo
* - Song-specific resampling
* - Alternative tempo modes (only usable in legacy XM / IT files)
*
* Extended features in IT/XM/S3M (not all listed below are available in all of those formats):
* - Plugins
* - Extended ranges for
* - Sample count
* - Instrument count
* - Pattern count
* - Sequence size
* - Row count
* - Channel count
* - Tempo limits
* - Extended sample/instrument properties.
* - MIDI mapping directives
* - Version info
* - Channel names
* - Pattern names
* - For more info, see e.g. SaveExtendedSongProperties(), SaveExtendedInstrumentProperties()
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "Moddoc.h"
#include "Mainfrm.h"
#include "InputHandler.h"
#include "../tracklib/SampleEdit.h"
#include "../soundlib/modsmp_ctrl.h"
#include "../soundlib/mod_specifications.h"
#include "ModConvert.h"
OPENMPT_NAMESPACE_BEGIN
// Trim envelopes and remove release nodes.
static void UpdateEnvelopes(InstrumentEnvelope &mptEnv, const CModSpecifications &specs, std::bitset<wNumWarnings> &warnings)
{
// shorten instrument envelope if necessary (for mod conversion)
const uint8 envMax = specs.envelopePointsMax;
#define TRIMENV(envLen) if(envLen >= envMax) { envLen = envMax - 1; warnings.set(wTrimmedEnvelopes); }
if(mptEnv.size() > envMax)
{
mptEnv.resize(envMax);
warnings.set(wTrimmedEnvelopes);
}
TRIMENV(mptEnv.nLoopStart);
TRIMENV(mptEnv.nLoopEnd);
TRIMENV(mptEnv.nSustainStart);
TRIMENV(mptEnv.nSustainEnd);
if(mptEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET)
{
if(specs.hasReleaseNode)
{
TRIMENV(mptEnv.nReleaseNode);
} else
{
mptEnv.nReleaseNode = ENV_RELEASE_NODE_UNSET;
warnings.set(wReleaseNode);
}
}
#undef TRIMENV
}
bool CModDoc::ChangeModType(MODTYPE nNewType)
{
std::bitset<wNumWarnings> warnings;
warnings.reset();
PATTERNINDEX nResizedPatterns = 0;
const MODTYPE nOldType = m_SndFile.GetType();
if(nNewType == nOldType)
return true;
const bool oldTypeIsXM = (nOldType == MOD_TYPE_XM),
oldTypeIsS3M = (nOldType == MOD_TYPE_S3M), oldTypeIsIT = (nOldType == MOD_TYPE_IT),
oldTypeIsMPT = (nOldType == MOD_TYPE_MPT),
oldTypeIsS3M_IT_MPT = (oldTypeIsS3M || oldTypeIsIT || oldTypeIsMPT),
oldTypeIsIT_MPT = (oldTypeIsIT || oldTypeIsMPT);
const bool newTypeIsMOD = (nNewType == MOD_TYPE_MOD), newTypeIsXM = (nNewType == MOD_TYPE_XM),
newTypeIsS3M = (nNewType == MOD_TYPE_S3M), newTypeIsIT = (nNewType == MOD_TYPE_IT),
newTypeIsMPT = (nNewType == MOD_TYPE_MPT), newTypeIsMOD_XM = (newTypeIsMOD || newTypeIsXM),
newTypeIsIT_MPT = (newTypeIsIT || newTypeIsMPT);
const CModSpecifications &specs = m_SndFile.GetModSpecifications(nNewType);
// Check if conversion to 64 rows is necessary
for(const auto &pat : m_SndFile.Patterns)
{
if(pat.IsValid() && pat.GetNumRows() != 64)
nResizedPatterns++;
}
if((m_SndFile.GetNumInstruments() || nResizedPatterns) && (nNewType & (MOD_TYPE_MOD|MOD_TYPE_S3M)))
{
if(Reporting::Confirm(
"This operation will convert all instruments to samples,\n"
"and resize all patterns to 64 rows.\n"
"Do you want to continue?", "Warning") != cnfYes) return false;
BeginWaitCursor();
CriticalSection cs;
// Converting instruments to samples
if(m_SndFile.GetNumInstruments())
{
ConvertInstrumentsToSamples();
warnings.set(wInstrumentsToSamples);
}
// Resizing all patterns to 64 rows
for(auto &pat : m_SndFile.Patterns) if(pat.IsValid() && pat.GetNumRows() != 64)
{
ROWINDEX origRows = pat.GetNumRows();
pat.Resize(64);
if(origRows < 64)
{
// Try to save short patterns by inserting a pattern break.
pat.WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(origRows - 1).RetryNextRow());
}
warnings.set(wResizedPatterns);
}
// Removing all instrument headers from channels
for(auto &chn : m_SndFile.m_PlayState.Chn)
{
chn.pModInstrument = nullptr;
}
for(INSTRUMENTINDEX nIns = 0; nIns <= m_SndFile.GetNumInstruments(); nIns++)
{
delete m_SndFile.Instruments[nIns];
m_SndFile.Instruments[nIns] = nullptr;
}
m_SndFile.m_nInstruments = 0;
EndWaitCursor();
} //End if (((m_SndFile.m_nInstruments) || (b64)) && (nNewType & (MOD_TYPE_MOD|MOD_TYPE_S3M)))
BeginWaitCursor();
/////////////////////////////
// Converting pattern data
// When converting to MOD, get the new sample transpose setting right here so that we can compensate notes in the pattern.
if(newTypeIsMOD && !oldTypeIsXM)
{
for(SAMPLEINDEX smp = 1; smp <= m_SndFile.GetNumSamples(); smp++)
{
m_SndFile.GetSample(smp).FrequencyToTranspose();
}
}
bool onlyAmigaNotes = true;
for(auto &pat : m_SndFile.Patterns) if(pat.IsValid())
{
// This is used for -> MOD/XM conversion
std::vector<std::array<ModCommand::PARAM, MAX_EFFECTS>> effMemory(GetNumChannels());
std::vector<ModCommand::VOL> volMemory(GetNumChannels(), 0);
std::vector<ModCommand::INSTR> instrMemory(GetNumChannels(), 0);
bool addBreak = false; // When converting to XM, avoid the E60 bug.
CHANNELINDEX chn = 0;
ROWINDEX row = 0;
for(auto m = pat.begin(); m != pat.end(); m++, chn++)
{
if(chn >= GetNumChannels())
{
chn = 0;
row++;
}
ModCommand::INSTR instr = m->instr;
if(m->instr) instrMemory[chn] = instr;
else instr = instrMemory[chn];
// Deal with volume column slide memory (it's not shared with the effect column)
if(oldTypeIsIT_MPT && (newTypeIsMOD_XM || newTypeIsS3M))
{
switch(m->volcmd)
{
case VOLCMD_VOLSLIDEUP:
case VOLCMD_VOLSLIDEDOWN:
case VOLCMD_FINEVOLUP:
case VOLCMD_FINEVOLDOWN:
if(m->vol == 0)
m->vol = volMemory[chn];
else
volMemory[chn] = m->vol;
break;
}
}
// Deal with MOD/XM commands without effect memory
if(oldTypeIsS3M_IT_MPT && newTypeIsMOD_XM)
{
switch(m->command)
{
// No effect memory in XM / MOD
case CMD_ARPEGGIO:
case CMD_S3MCMDEX:
case CMD_MODCMDEX:
// These have effect memory in XM, but it is spread over several commands (for fine and extra-fine slides), so the easiest way to fix this is to just always use the previous value.
case CMD_PORTAMENTOUP:
case CMD_PORTAMENTODOWN:
case CMD_VOLUMESLIDE:
if(m->param == 0)
m->param = effMemory[chn][m->command];
else
effMemory[chn][m->command] = m->param;
break;
}
}
// Adjust effect memory for MOD files
if(newTypeIsMOD)
{
switch(m->command)
{
case CMD_PORTAMENTOUP:
case CMD_PORTAMENTODOWN:
case CMD_TONEPORTAVOL:
case CMD_VIBRATOVOL:
case CMD_VOLUMESLIDE:
// ProTracker doesn't have effect memory for these commands, so let's try to fix them
if(m->param == 0)
m->param = effMemory[chn][m->command];
else
effMemory[chn][m->command] = m->param;
break;
}
// Compensate for loss of transpose information
if(m->IsNote() && instr && instr <= GetNumSamples())
{
const int newNote = m->note + m_SndFile.GetSample(instr).RelativeTone;
m->note = static_cast<ModCommand::NOTE>(Clamp(newNote, specs.noteMin, specs.noteMax));
}
if(!m->IsAmigaNote())
{
onlyAmigaNotes = false;
}
}
m->Convert(nOldType, nNewType, m_SndFile);
// When converting to XM, avoid the E60 bug.
if(newTypeIsXM)
{
switch(m->command)
{
case CMD_MODCMDEX:
if(m->param == 0x60 && row > 0)
{
addBreak = true;
}
break;
case CMD_POSITIONJUMP:
case CMD_PATTERNBREAK:
addBreak = false;
break;
}
}
// Fix Row Delay commands when converting between MOD/XM and S3M/IT.
// FT2 only considers the rightmost command, ST3/IT only the leftmost...
if((nOldType & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT)) && (nNewType & (MOD_TYPE_MOD | MOD_TYPE_XM))
&& m->command == CMD_MODCMDEX && (m->param & 0xF0) == 0xE0)
{
if(oldTypeIsIT_MPT || m->param != 0xE0)
{
// If the leftmost row delay command is SE0, ST3 ignores it, IT doesn't.
// Delete all commands right of the first command
auto p = m + 1;
for(CHANNELINDEX c = chn + 1; c < m_SndFile.GetNumChannels(); c++, p++)
{
if(p->command == CMD_S3MCMDEX && (p->param & 0xF0) == 0xE0)
{
p->command = CMD_NONE;
}
}
}
} else if((nOldType & (MOD_TYPE_MOD | MOD_TYPE_XM)) && (nNewType & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT))
&& m->command == CMD_S3MCMDEX && (m->param & 0xF0) == 0xE0)
{
// Delete all commands left of the last command
auto p = m - 1;
for(CHANNELINDEX c = 0; c < chn; c++, p--)
{
if(p->command == CMD_S3MCMDEX && (p->param & 0xF0) == 0xE0)
{
p->command = CMD_NONE;
}
}
}
}
if(addBreak)
{
pat.WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(pat.GetNumRows() - 1));
}
}
////////////////////////////////////////////////
// Converting instrument / sample / etc. data
// Do some sample conversion
const bool newTypeHasPingPongLoops = !(newTypeIsMOD || newTypeIsS3M);
for(SAMPLEINDEX smp = 1; smp <= m_SndFile.GetNumSamples(); smp++)
{
ModSample &sample = m_SndFile.GetSample(smp);
GetSampleUndo().PrepareUndo(smp, sundo_none, "Song Conversion");
// Too many samples? Only 31 samples allowed in MOD format...
if(newTypeIsMOD && smp > 31 && sample.nLength > 0)
{
warnings.set(wMOD31Samples);
}
// No auto-vibrato in MOD/S3M
if((newTypeIsMOD || newTypeIsS3M) && (sample.nVibDepth | sample.nVibRate | sample.nVibSweep) != 0)
{
warnings.set(wSampleAutoVibrato);
}
// No sustain loops for MOD/S3M/XM
bool ignoreLoopConversion = false;
if(newTypeIsMOD_XM || newTypeIsS3M)
{
// Sustain loops - convert to normal loops
if(sample.uFlags[CHN_SUSTAINLOOP])
{
warnings.set(wSampleSustainLoops);
// Prepare conversion to regular loop
if(!newTypeHasPingPongLoops)
{
ignoreLoopConversion = true;
if(!SampleEdit::ConvertPingPongLoop(sample, m_SndFile, true))
warnings.set(wSampleBidiLoops);
}
}
}
// No ping-pong loops in MOD/S3M
if(!ignoreLoopConversion && !newTypeHasPingPongLoops && sample.HasPingPongLoop())
{
if(!SampleEdit::ConvertPingPongLoop(sample, m_SndFile, false))
warnings.set(wSampleBidiLoops);
}
if(newTypeIsMOD && sample.RelativeTone != 0)
{
warnings.set(wMODSampleFrequency);
}
if(!CSoundFile::SupportsOPL(nNewType) && sample.uFlags[CHN_ADLIB])
{
warnings.set(wAdlibInstruments);
}
sample.Convert(nOldType, nNewType);
}
for(INSTRUMENTINDEX ins = 1; ins <= m_SndFile.GetNumInstruments(); ins++)
{
ModInstrument *pIns = m_SndFile.Instruments[ins];
if(pIns == nullptr)
{
continue;
}
// Convert IT/MPT to XM (fix instruments)
if(newTypeIsXM)
{
for(size_t i = 0; i < std::size(pIns->NoteMap); i++)
{
if (pIns->NoteMap[i] && pIns->NoteMap[i] != (i + 1))
{
warnings.set(wBrokenNoteMap);
break;
}
}
// Convert sustain loops to sustain "points"
if(pIns->VolEnv.nSustainStart != pIns->VolEnv.nSustainEnd)
{
warnings.set(wInstrumentSustainLoops);
}
if(pIns->PanEnv.nSustainStart != pIns->PanEnv.nSustainEnd)
{
warnings.set(wInstrumentSustainLoops);
}
}
// Convert MPT to anything - remove instrument tunings, Pitch/Tempo Lock, filter variation
if(oldTypeIsMPT)
{
if(pIns->pTuning != nullptr)
{
warnings.set(wInstrumentTuning);
}
if(pIns->pitchToTempoLock.GetRaw() != 0)
{
warnings.set(wPitchToTempoLock);
}
if((pIns->nCutSwing | pIns->nResSwing) != 0)
{
warnings.set(wFilterVariation);
}
}
pIns->Convert(nOldType, nNewType);
}
if(newTypeIsMOD)
{
// Not supported in MOD format
auto firstPat = std::find_if(m_SndFile.Order().cbegin(), m_SndFile.Order().cend(), [this](PATTERNINDEX pat) { return m_SndFile.Patterns.IsValidPat(pat); });
bool firstPatValid = firstPat != m_SndFile.Order().cend();
bool lossy = false;
if(m_SndFile.m_nDefaultSpeed != 6)
{
if(firstPatValid)
{
m_SndFile.Patterns[*firstPat].WriteEffect(EffectWriter(CMD_SPEED, ModCommand::PARAM(m_SndFile.m_nDefaultSpeed)).RetryNextRow());
}
m_SndFile.m_nDefaultSpeed = 6;
lossy = true;
}
if(m_SndFile.m_nDefaultTempo != TEMPO(125, 0))
{
if(firstPatValid)
{
m_SndFile.Patterns[*firstPat].WriteEffect(EffectWriter(CMD_TEMPO, ModCommand::PARAM(m_SndFile.m_nDefaultTempo.GetInt())).RetryNextRow());
}
m_SndFile.m_nDefaultTempo.Set(125);
lossy = true;
}
if(m_SndFile.m_nDefaultGlobalVolume != MAX_GLOBAL_VOLUME || m_SndFile.m_nSamplePreAmp != 48 || m_SndFile.m_nVSTiVolume != 48)
{
m_SndFile.m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME;
m_SndFile.m_nSamplePreAmp = 48;
m_SndFile.m_nVSTiVolume = 48;
lossy = true;
}
if(lossy)
{
warnings.set(wMODGlobalVars);
}
}
// Is the "restart position" value allowed in this format?
for(SEQUENCEINDEX seq = 0; seq < m_SndFile.Order.GetNumSequences(); seq++)
{
if(m_SndFile.Order(seq).GetRestartPos() > 0 && !specs.hasRestartPos)
{
// Try to fix it by placing a pattern jump command in the pattern.
if(!m_SndFile.Order.RestartPosToPattern(seq))
{
// Couldn't fix it! :(
warnings.set(wRestartPos);
}
}
}
// Fix channel settings (pan/vol)
for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++)
{
if(newTypeIsMOD_XM || newTypeIsS3M)
{
if(m_SndFile.ChnSettings[nChn].nVolume != 64 || m_SndFile.ChnSettings[nChn].dwFlags[CHN_SURROUND])
{
m_SndFile.ChnSettings[nChn].nVolume = 64;
m_SndFile.ChnSettings[nChn].dwFlags.reset(CHN_SURROUND);
warnings.set(wChannelVolSurround);
}
}
if(newTypeIsXM)
{
if(m_SndFile.ChnSettings[nChn].nPan != 128)
{
m_SndFile.ChnSettings[nChn].nPan = 128;
warnings.set(wChannelPanning);
}
}
}
// Check for patterns with custom time signatures (fixing will be applied in the pattern container)
if(!specs.hasPatternSignatures)
{
for(const auto &pat: m_SndFile.Patterns)
{
if(pat.GetOverrideSignature())
{
warnings.set(wPatternSignatures);
break;
}
}
}
// Check whether the new format supports embedding the edit history in the file.
if(oldTypeIsIT_MPT && !newTypeIsIT_MPT && GetSoundFile().GetFileHistory().size() > 0)
{
warnings.set(wEditHistory);
}
if((nOldType & MOD_TYPE_XM) && m_SndFile.m_playBehaviour[kFT2VolumeRamping])
{
warnings.set(wVolRamp);
}
CriticalSection cs;
m_SndFile.ChangeModTypeTo(nNewType);
// In case we need to update IT bidi loop handling pre-computation or loops got changed...
m_SndFile.PrecomputeSampleLoops(false);
// Song flags
if(!(specs.songFlags & SONG_LINEARSLIDES) && m_SndFile.m_SongFlags[SONG_LINEARSLIDES])
{
warnings.set(wLinearSlides);
}
if(oldTypeIsXM && newTypeIsIT_MPT)
{
m_SndFile.m_SongFlags.set(SONG_ITCOMPATGXX);
} else if(newTypeIsMOD && GetNumChannels() == 4 && onlyAmigaNotes)
{
m_SndFile.m_SongFlags.set(SONG_ISAMIGA);
m_SndFile.InitAmigaResampler();
}
m_SndFile.m_SongFlags &= (specs.songFlags | SONG_PLAY_FLAGS);
// Adjust mix levels
if(newTypeIsMOD || newTypeIsS3M)
{
m_SndFile.SetMixLevels(MixLevels::Compatible);
}
if(oldTypeIsMPT && m_SndFile.GetMixLevels() != MixLevels::Compatible && m_SndFile.GetMixLevels() != MixLevels::CompatibleFT2)
{
warnings.set(wMixmode);
}
if(!specs.hasFractionalTempo && m_SndFile.m_nDefaultTempo.GetFract() != 0)
{
m_SndFile.m_nDefaultTempo.Set(m_SndFile.m_nDefaultTempo.GetInt(), 0);
warnings.set(wFractionalTempo);
}
ChangeFileExtension(nNewType);
// Check mod specifications
Limit(m_SndFile.m_nDefaultTempo, specs.GetTempoMin(), specs.GetTempoMax());
Limit(m_SndFile.m_nDefaultSpeed, specs.speedMin, specs.speedMax);
for(INSTRUMENTINDEX i = 1; i <= m_SndFile.GetNumInstruments(); i++) if(m_SndFile.Instruments[i] != nullptr)
{
UpdateEnvelopes(m_SndFile.Instruments[i]->VolEnv, specs, warnings);
UpdateEnvelopes(m_SndFile.Instruments[i]->PanEnv, specs, warnings);
UpdateEnvelopes(m_SndFile.Instruments[i]->PitchEnv, specs, warnings);
}
// XM requires instruments, so we create them right away.
if(newTypeIsXM && GetNumInstruments() == 0)
{
ConvertSamplesToInstruments();
}
// XM has no global volume
if(newTypeIsXM && m_SndFile.m_nDefaultGlobalVolume != MAX_GLOBAL_VOLUME)
{
if(!GlobalVolumeToPattern())
{
warnings.set(wGlobalVolumeNotSupported);
}
}
// Resampling is only saved in MPTM
if(!newTypeIsMPT && m_SndFile.m_nResampling != SRCMODE_DEFAULT)
{
warnings.set(wResamplingMode);
m_SndFile.m_nResampling = SRCMODE_DEFAULT;
}
cs.Leave();
if(warnings[wResizedPatterns])
{
AddToLog(LogInformation, MPT_UFORMAT("{} patterns have been resized to 64 rows")(nResizedPatterns));
}
static constexpr struct
{
ConversionWarning warning;
const char *mesage;
} messages[] =
{
// Pattern warnings
{ wRestartPos, "Restart position is not supported by the new format." },
{ wPatternSignatures, "Pattern-specific time signatures are not supported by the new format." },
{ wChannelVolSurround, "Channel volume and surround are not supported by the new format." },
{ wChannelPanning, "Channel panning is not supported by the new format." },
// Sample warnings
{ wSampleBidiLoops, "Sample bidi loops are not supported by the new format." },
{ wSampleSustainLoops, "New format doesn't support sample sustain loops." },
{ wSampleAutoVibrato, "New format doesn't support sample autovibrato." },
{ wMODSampleFrequency, "Sample C-5 frequencies will be lost." },
{ wMOD31Samples, "Samples above 31 will be lost when saving as MOD. Consider rearranging samples if there are unused slots available." },
{ wAdlibInstruments, "OPL instruments are not supported by this format." },
// Instrument warnings
{ wInstrumentsToSamples, "All instruments have been converted to samples." },
{ wTrimmedEnvelopes, "Instrument envelopes have been shortened." },
{ wInstrumentSustainLoops, "Sustain loops were converted to sustain points." },
{ wInstrumentTuning, "Instrument tunings will be lost." },
{ wPitchToTempoLock, "Pitch / Tempo Lock instrument property is not supported by the new format." },
{ wBrokenNoteMap, "Instrument Note Mapping is not supported by the new format." },
{ wReleaseNode, "Instrument envelope release nodes are not supported by the new format." },
{ wFilterVariation, "Random filter variation is not supported by the new format." },
// General warnings
{ wMODGlobalVars, "Default speed, tempo and global volume will be lost." },
{ wLinearSlides, "Linear Frequency Slides not supported by the new format." },
{ wEditHistory, "Edit history will not be saved in the new format." },
{ wMixmode, "Consider setting the mix levels to \"Compatible\" in the song properties when working with legacy formats." },
{ wVolRamp, "Fasttracker 2 compatible super soft volume ramping gets lost when converting XM files to another type." },
{ wGlobalVolumeNotSupported, "Default global volume is not supported by the new format." },
{ wResamplingMode, "Song-specific resampling mode is not supported by the new format." },
{ wFractionalTempo, "Fractional tempo is not supported by the new format." },
};
for(const auto &msg : messages)
{
if(warnings[msg.warning])
AddToLog(LogInformation, mpt::ToUnicode(mpt::Charset::UTF8, msg.mesage));
}
SetModified();
GetPatternUndo().ClearUndo();
UpdateAllViews(nullptr, GeneralHint().General().ModType());
EndWaitCursor();
// Update effect key commands
CMainFrame::GetInputHandler()->SetEffectLetters(m_SndFile.GetModSpecifications());
return true;
}
OPENMPT_NAMESPACE_END
@@ -0,0 +1,50 @@
/*
* ModConvert.h
* ------------
* Purpose: Converting between various module formats.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
OPENMPT_NAMESPACE_BEGIN
// Warning types
enum ConversionWarning
{
wInstrumentsToSamples = 0,
wResizedPatterns,
wSampleBidiLoops,
wSampleSustainLoops,
wSampleAutoVibrato,
wMODSampleFrequency,
wBrokenNoteMap,
wInstrumentSustainLoops,
wInstrumentTuning,
wMODGlobalVars,
wMOD31Samples,
wAdlibInstruments,
wRestartPos,
wChannelVolSurround,
wChannelPanning,
wPatternSignatures,
wLinearSlides,
wTrimmedEnvelopes,
wReleaseNode,
wEditHistory,
wMixmode,
wVolRamp,
wPitchToTempoLock,
wGlobalVolumeNotSupported,
wFilterVariation,
wResamplingMode,
wFractionalTempo,
wNumWarnings
};
OPENMPT_NAMESPACE_END

Some files were not shown because too many files have changed in this diff Show More