Initial community commit
This commit is contained in:
@@ -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 §ion = 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 == ¶mScroller)
|
||||
{
|
||||
// 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 ©Data = *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>(©Data));
|
||||
}
|
||||
|
||||
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 ¶mindex, uint16 ¶mval)
|
||||
{
|
||||
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 ¶mindex, uint16 ¶mvalue);
|
||||
|
||||
// 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 ¯o : 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
Reference in New Issue
Block a user