diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/mptrack/Mptrack.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/mptrack/Mptrack.cpp')
-rw-r--r-- | Src/external_dependencies/openmpt-trunk/mptrack/Mptrack.cpp | 2413 |
1 files changed, 2413 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/mptrack/Mptrack.cpp b/Src/external_dependencies/openmpt-trunk/mptrack/Mptrack.cpp new file mode 100644 index 00000000..2e84bc81 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/mptrack/Mptrack.cpp @@ -0,0 +1,2413 @@ +/* + * MPTrack.cpp + * ----------- + * Purpose: OpenMPT core application class. + * 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 "IPCWindow.h" +#include "InputHandler.h" +#include "Childfrm.h" +#include "Moddoc.h" +#include "ModDocTemplate.h" +#include "Globals.h" +#include "../soundlib/Dlsbank.h" +#include "../common/version.h" +#include "../test/test.h" +#include "UpdateCheck.h" +#include "../common/mptStringBuffer.h" +#include "ExceptionHandler.h" +#include "CloseMainDialog.h" +#include "PlugNotFoundDlg.h" +#include "AboutDialog.h" +#include "AutoSaver.h" +#include "FileDialog.h" +#include "Image.h" +#include "BuildVariants.h" +#include "../common/ComponentManager.h" +#include "WelcomeDialog.h" +#include "openmpt/sounddevice/SoundDeviceManager.hpp" +#include "WineSoundDeviceStub.h" +#include "../soundlib/plugins/PluginManager.h" +#include "MPTrackWine.h" +#include "MPTrackUtil.h" +#if MPT_MSVC_AT_LEAST(2022, 2) && MPT_MSVC_BEFORE(2022, 3) +// Work-around <https://developercommunity.visualstudio.com/t/warning-C4311-in-MFC-header-afxrecovery/10041328>, +// see <https://developercommunity.visualstudio.com/t/Compiler-warnings-after-upgrading-to-17/10036311#T-N10061908>. +template <class ARG_KEY> +AFX_INLINE UINT AFXAPI HashKey(ARG_KEY key); +template <> +AFX_INLINE UINT AFXAPI HashKey<CDocument*>(CDocument *key) +{ + // (algorithm copied from STL hash in xfunctional) +#pragma warning(suppress: 4302) // 'type cast' : truncation +#pragma warning(suppress: 4311) // pointer truncation + ldiv_t HashVal = ldiv((long)(CDocument*)key, 127773); + HashVal.rem = 16807 * HashVal.rem - 2836 * HashVal.quot; + if(HashVal.rem < 0) + HashVal.rem += 2147483647; + return ((UINT)HashVal.rem); +} +#endif +#include <afxdatarecovery.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 + +#if MPT_COMPILER_MSVC +#define _CRTDBG_MAP_ALLOC +#include <stdlib.h> +#include <crtdbg.h> +#endif + + +OPENMPT_NAMESPACE_BEGIN + +///////////////////////////////////////////////////////////////////////////// +// The one and only CTrackApp object + +CTrackApp theApp; + +const TCHAR *szSpecialNoteNamesMPT[] = {_T("PCs"), _T("PC"), _T("~~ (Note Fade)"), _T("^^ (Note Cut)"), _T("== (Note Off)")}; +const TCHAR *szSpecialNoteShortDesc[] = {_T("Param Control (Smooth)"), _T("Param Control"), _T("Note Fade"), _T("Note Cut"), _T("Note Off")}; + +// Make sure that special note arrays include string for every note. +static_assert(NOTE_MAX_SPECIAL - NOTE_MIN_SPECIAL + 1 == mpt::array_size<decltype(szSpecialNoteNamesMPT)>::size); +static_assert(mpt::array_size<decltype(szSpecialNoteShortDesc)>::size == mpt::array_size<decltype(szSpecialNoteNamesMPT)>::size); + +const char *szHexChar = "0123456789ABCDEF"; + + +#ifdef MPT_WITH_ASIO +class ComponentASIO + : public ComponentBuiltin +{ + MPT_DECLARE_COMPONENT_MEMBERS(ComponentASIO, "ASIO") +public: + ComponentASIO() = default; + virtual ~ComponentASIO() = default; +}; +#endif // MPT_WITH_ASIO + +#if defined(MPT_WITH_DIRECTSOUND) +class ComponentDirectSound + : public ComponentBuiltin +{ + MPT_DECLARE_COMPONENT_MEMBERS(ComponentDirectSound, "DirectSound") +public: + ComponentDirectSound() = default; + virtual ~ComponentDirectSound() = default; +}; +#endif // MPT_WITH_DIRECTSOUND + +#if defined(MPT_WITH_PORTAUDIO) +class ComponentPortAudio + : public ComponentBuiltin +{ + MPT_DECLARE_COMPONENT_MEMBERS(ComponentPortAudio, "PortAudio") +public: + ComponentPortAudio() = default; + virtual ~ComponentPortAudio() = default; +}; +#endif // MPT_WITH_PORTAUDIO + +#if defined(MPT_WITH_PULSEAUDIO) +class ComponentPulseaudio + : public ComponentBuiltin +{ + MPT_DECLARE_COMPONENT_MEMBERS(ComponentPulseaudio, "Pulseaudio") +public: + ComponentPulseaudio() = default; + virtual ~ComponentPulseaudio() = default; +}; +#endif // MPT_WITH_PULSEAUDIO + +#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_WITH_PULSEAUDIOSIMPLE) +class ComponentPulseaudioSimple + : public ComponentBuiltin +{ + MPT_DECLARE_COMPONENT_MEMBERS(ComponentPulseaudioSimple, "PulseaudioSimple") +public: + ComponentPulseaudioSimple() = default; + virtual ~ComponentPulseaudioSimple() = default; +}; +#endif // MPT_WITH_PULSEAUDIO && MPT_WITH_PULSEAUDIOSIMPLE + +#if defined(MPT_WITH_RTAUDIO) +class ComponentRtAudio + : public ComponentBuiltin +{ + MPT_DECLARE_COMPONENT_MEMBERS(ComponentRtAudio, "RtAudio") +public: + ComponentRtAudio() = default; + virtual ~ComponentRtAudio() = default; +}; +#endif // MPT_WITH_RTAUDIO + +#if MPT_OS_WINDOWS +class ComponentWaveOut + : public ComponentBuiltin +{ + MPT_DECLARE_COMPONENT_MEMBERS(ComponentWaveOut, "WaveOut") +public: + ComponentWaveOut() = default; + virtual ~ComponentWaveOut() = default; +}; +#endif // MPT_OS_WINDOWS + +struct AllSoundDeviceComponents +{ +#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_ENABLE_PULSEAUDIO_FULL) + ComponentHandle<ComponentPulseaudio> m_Pulseaudio; +#endif // MPT_WITH_PULSEAUDIO && MPT_ENABLE_PULSEAUDIO_FULL +#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_WITH_PULSEAUDIOSIMPLE) + ComponentHandle<ComponentPulseaudioSimple> m_PulseaudioSimple; +#endif // MPT_WITH_PULSEAUDIO && MPT_WITH_PULSEAUDIOSIMPLE +#if MPT_OS_WINDOWS + ComponentHandle<ComponentWaveOut> m_WaveOut; +#endif // MPT_OS_WINDOWS +#if defined(MPT_WITH_DIRECTSOUND) + ComponentHandle<ComponentDirectSound> m_DirectSound; +#endif // MPT_WITH_DIRECTSOUND +#ifdef MPT_WITH_ASIO + ComponentHandle<ComponentASIO> m_ASIO; +#endif // MPT_WITH_ASIO +#ifdef MPT_WITH_PORTAUDIO + ComponentHandle<ComponentPortAudio> m_PortAudio; +#endif // MPT_WITH_PORTAUDIO +#ifdef MPT_WITH_RTAUDIO + ComponentHandle<ComponentRtAudio> m_RtAudio; +#endif // MPT_WITH_RTAUDIO + operator SoundDevice::EnabledBackends() const + { + SoundDevice::EnabledBackends result; +#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_ENABLE_PULSEAUDIO_FULL) + result.Pulseaudio = IsComponentAvailable(m_PulseAudio); +#endif // MPT_WITH_PULSEAUDIO && MPT_ENABLE_PULSEAUDIO_FULL +#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_WITH_PULSEAUDIOSIMPLE) + result.PulseaudioSimple = IsComponentAvailable(m_PulseAudioSimple); +#endif // MPT_WITH_PULSEAUDIO && MPT_WITH_PULSEAUDIOSIMPLE +#if MPT_OS_WINDOWS + result.WaveOut = IsComponentAvailable(m_WaveOut); +#endif // MPT_OS_WINDOWS +#if defined(MPT_WITH_DIRECTSOUND) + result.DirectSound = IsComponentAvailable(m_DirectSound); +#endif // MPT_WITH_DIRECTSOUND +#ifdef MPT_WITH_ASIO + result.ASIO = IsComponentAvailable(m_ASIO); +#endif // MPT_WITH_ASIO +#ifdef MPT_WITH_PORTAUDIO + result.PortAudio = IsComponentAvailable(m_PortAudio); +#endif // MPT_WITH_PORTAUDIO +#ifdef MPT_WITH_RTAUDIO + result.RtAudio = IsComponentAvailable(m_RtAudio); +#endif // MPT_WITH_RTAUDIO + return result; + } +}; + + +void CTrackApp::OnFileCloseAll() +{ + if(!(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_NOCLOSEDIALOG)) + { + // Show modified documents window + CloseMainDialog dlg; + if(dlg.DoModal() != IDOK) + { + return; + } + } + + for(auto &doc : GetOpenDocuments()) + { + doc->SafeFileClose(); + } +} + + +void CTrackApp::OnUpdateAnyDocsOpen(CCmdUI *cmd) +{ + cmd->Enable(!GetModDocTemplate()->empty()); +} + + +int CTrackApp::GetOpenDocumentCount() const +{ + return static_cast<int>(GetModDocTemplate()->size()); +} + + +// Retrieve a list of all open modules. +std::vector<CModDoc *> CTrackApp::GetOpenDocuments() const +{ + std::vector<CModDoc *> documents; + if(auto *pDocTmpl = GetModDocTemplate()) + { + POSITION pos = pDocTmpl->GetFirstDocPosition(); + CDocument *pDoc; + while((pos != nullptr) && ((pDoc = pDocTmpl->GetNextDoc(pos)) != nullptr)) + { + documents.push_back(dynamic_cast<CModDoc *>(pDoc)); + } + } + + return documents; +} + + +///////////////////////////////////////////////////////////////////////////// +// Command Line options + +class CMPTCommandLineInfo: public CCommandLineInfo +{ +public: + std::vector<mpt::PathString> m_fileNames; + bool m_noDls = false, m_noPlugins = false, m_noAssembly = false, m_noSysCheck = false, m_noWine = false, + m_portable = false, m_noCrashHandler = false, m_debugCrashHandler = false, m_sharedInstance = false; +#ifdef ENABLE_TESTS + bool m_noTests = false; +#endif + +public: + void ParseParam(LPCTSTR param, BOOL isFlag, BOOL isLast) override + { + if(isFlag) + { + if(!lstrcmpi(param, _T("nologo"))) { m_bShowSplash = FALSE; return; } + if(!lstrcmpi(param, _T("nodls"))) { m_noDls = true; return; } + if(!lstrcmpi(param, _T("noplugs"))) { m_noPlugins = true; return; } + if(!lstrcmpi(param, _T("portable"))) { m_portable = true; return; } + if(!lstrcmpi(param, _T("fullMemDump"))) { ExceptionHandler::fullMemDump = true; return; } + if(!lstrcmpi(param, _T("noAssembly"))) { m_noAssembly = true; return; } + if(!lstrcmpi(param, _T("noSysCheck"))) { m_noSysCheck = true; return; } + if(!lstrcmpi(param, _T("noWine"))) { m_noWine = true; return; } + if(!lstrcmpi(param, _T("noCrashHandler"))) { m_noCrashHandler = true; return; } + if(!lstrcmpi(param, _T("DebugCrashHandler"))) { m_debugCrashHandler = true; return; } + if(!lstrcmpi(param, _T("shared"))) { m_sharedInstance = true; return; } +#ifdef ENABLE_TESTS + if (!lstrcmpi(param, _T("noTests"))) { m_noTests = true; return; } +#endif + } else + { + m_fileNames.push_back(mpt::PathString::FromNative(param)); + if(m_nShellCommand == FileNew) m_nShellCommand = FileOpen; + } + CCommandLineInfo::ParseParam(param, isFlag, isLast); + } +}; + + +// Splash Screen + +static void StartSplashScreen(); +static void StopSplashScreen(); +static void TimeoutSplashScreen(); + + +///////////////////////////////////////////////////////////////////////////// +// Midi Library + +MidiLibrary CTrackApp::midiLibrary; + +void CTrackApp::ImportMidiConfig(const mpt::PathString &filename, bool hideWarning) +{ + if(filename.empty()) return; + + if(CDLSBank::IsDLSBank(filename)) + { + ConfirmAnswer result = cnfYes; + if(!hideWarning) + { + result = Reporting::Confirm("You are about to replace the current MIDI library:\n" + "Do you want to replace only the missing instruments? (recommended)", + "Warning", true); + } + if(result == cnfCancel) return; + const bool replaceAll = (result == cnfNo); + CDLSBank dlsbank; + if (dlsbank.Open(filename)) + { + for(uint32 ins = 0; ins < 256; ins++) + { + if(replaceAll || midiLibrary[ins].empty()) + { + uint32 prog = (ins < 128) ? ins : 0xFF; + uint32 key = (ins < 128) ? 0xFF : ins & 0x7F; + uint32 bank = (ins < 128) ? 0 : F_INSTRUMENT_DRUMS; + if (dlsbank.FindInstrument(ins >= 128, bank, prog, key)) + { + midiLibrary[ins] = filename; + } + } + } + } + return; + } + + IniFileSettingsContainer file(filename); + ImportMidiConfig(file, filename.GetPath()); +} + + +static mpt::PathString GetUltraSoundPatchDir(SettingsContainer &file, const mpt::ustring &iniSection, const mpt::PathString &path, bool forgetSettings) +{ + mpt::PathString patchDir = file.Read<mpt::PathString>(iniSection, U_("PatchDir"), {}); + if(forgetSettings) + file.Forget(U_("Ultrasound"), U_("PatchDir")); + if(patchDir.empty() || patchDir == P_(".\\")) + patchDir = path; + if(!patchDir.empty()) + patchDir.EnsureTrailingSlash(); + return patchDir; +} + +void CTrackApp::ImportMidiConfig(SettingsContainer &file, const mpt::PathString &path, bool forgetSettings) +{ + const mpt::PathString patchDir = GetUltraSoundPatchDir(file, U_("Ultrasound"), path, forgetSettings); + for(uint32 prog = 0; prog < 256; prog++) + { + mpt::ustring key = MPT_UFORMAT("{}{}")((prog < 128) ? U_("Midi") : U_("Perc"), prog & 0x7F); + mpt::PathString filename = file.Read<mpt::PathString>(U_("Midi Library"), key, mpt::PathString()); + // Check for ULTRASND.INI + if(filename.empty()) + { + mpt::ustring section = (prog < 128) ? UL_("Melodic Patches") : UL_("Drum Patches"); + key = mpt::ufmt::val(prog & 0x7f); + filename = file.Read<mpt::PathString>(section, key, mpt::PathString()); + if(forgetSettings) file.Forget(section, key); + if(filename.empty()) + { + section = (prog < 128) ? UL_("Melodic Bank 0") : UL_("Drum Bank 0"); + filename = file.Read<mpt::PathString>(section, key, mpt::PathString()); + if(forgetSettings) file.Forget(section, key); + } + const mpt::PathString localPatchDir = GetUltraSoundPatchDir(file, section, patchDir, forgetSettings); + if(!filename.empty()) + { + filename = localPatchDir + filename + P_(".pat"); + } + } + if(!filename.empty()) + { + filename = theApp.PathInstallRelativeToAbsolute(filename); + midiLibrary[prog] = filename; + } + } +} + + +void CTrackApp::ExportMidiConfig(const mpt::PathString &filename) +{ + if(filename.empty()) return; + IniFileSettingsContainer file(filename); + ExportMidiConfig(file); +} + +void CTrackApp::ExportMidiConfig(SettingsContainer &file) +{ + for(uint32 prog = 0; prog < 256; prog++) if (!midiLibrary[prog].empty()) + { + mpt::PathString szFileName = midiLibrary[prog]; + + if(!szFileName.empty()) + { + if(theApp.IsPortableMode()) + szFileName = theApp.PathAbsoluteToInstallRelative(szFileName); + + mpt::ustring key = MPT_UFORMAT("{}{}")((prog < 128) ? U_("Midi") : U_("Perc"), prog & 0x7F); + file.Write<mpt::PathString>(U_("Midi Library"), key, szFileName); + } + } +} + + +///////////////////////////////////////////////////////////////////////////// +// DLS Banks support + +std::vector<std::unique_ptr<CDLSBank>> CTrackApp::gpDLSBanks; + + +struct CompareLessPathStringNoCase +{ + inline bool operator()(const mpt::PathString &l, const mpt::PathString &r) const + { + return mpt::PathString::CompareNoCase(l, r) < 0; + } +}; + +std::future<std::vector<std::unique_ptr<CDLSBank>>> CTrackApp::LoadDefaultDLSBanks() +{ + std::set<mpt::PathString, CompareLessPathStringNoCase> paths; + + uint32 numBanks = theApp.GetSettings().Read<uint32>(U_("DLS Banks"), U_("NumBanks"), 0); + for(uint32 i = 0; i < numBanks; i++) + { + mpt::PathString path = theApp.GetSettings().Read<mpt::PathString>(U_("DLS Banks"), MPT_UFORMAT("Bank{}")(i + 1), mpt::PathString()); + paths.insert(theApp.PathInstallRelativeToAbsolute(path)); + } + + HKEY key; + if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\DirectMusic"), 0, KEY_READ, &key) == ERROR_SUCCESS) + { + DWORD dwRegType = REG_SZ; + DWORD dwSize = 0; + if(RegQueryValueEx(key, _T("GMFilePath"), NULL, &dwRegType, nullptr, &dwSize) == ERROR_SUCCESS && dwSize > 0) + { + std::vector<TCHAR> filenameT(dwSize / sizeof(TCHAR)); + if(RegQueryValueEx(key, _T("GMFilePath"), NULL, &dwRegType, reinterpret_cast<LPBYTE>(filenameT.data()), &dwSize) == ERROR_SUCCESS) + { + mpt::winstring filenamestr = ParseMaybeNullTerminatedStringFromBufferWithSizeInBytes<mpt::winstring>(filenameT.data(), dwSize); + std::vector<TCHAR> filenameExpanded(::ExpandEnvironmentStrings(filenamestr.c_str(), nullptr, 0)); + ::ExpandEnvironmentStrings(filenamestr.c_str(), filenameExpanded.data(), static_cast<DWORD>(filenameExpanded.size())); + auto filename = mpt::PathString::FromNative(filenameExpanded.data()); + ImportMidiConfig(filename, true); + paths.insert(std::move(filename)); + } + } + RegCloseKey(key); + } + + if(paths.empty()) + return {}; + + return std::async(std::launch::async, [paths = std::move(paths)]() + { + std::vector<std::unique_ptr<CDLSBank>> banks; + banks.reserve(paths.size()); + for(const auto &filename : paths) + { + if(filename.empty() || !CDLSBank::IsDLSBank(filename)) + continue; + try + { + auto bank = std::make_unique<CDLSBank>(); + if(bank->Open(filename)) + { + banks.push_back(std::move(bank)); + continue; + } + } catch(mpt::out_of_memory e) + { + mpt::delete_out_of_memory(e); + } catch(const std::exception &) + { + } + } + // Avoid the overhead of future::wait_for(0) until future::is_ready is finally non-experimental + theApp.m_scannedDlsBanksAvailable = true; + return banks; + }); +} + + +void CTrackApp::SaveDefaultDLSBanks() +{ + uint32 nBanks = 0; + for(const auto &bank : gpDLSBanks) + { + if(!bank || bank->GetFileName().empty()) + continue; + + mpt::PathString path = bank->GetFileName(); + if(theApp.IsPortableMode()) + { + path = theApp.PathAbsoluteToInstallRelative(path); + } + + mpt::ustring key = MPT_UFORMAT("Bank{}")(nBanks + 1); + theApp.GetSettings().Write<mpt::PathString>(U_("DLS Banks"), key, path); + nBanks++; + + } + theApp.GetSettings().Write<uint32>(U_("DLS Banks"), U_("NumBanks"), nBanks); +} + + +void CTrackApp::RemoveDLSBank(UINT nBank) +{ + if(nBank < gpDLSBanks.size()) + gpDLSBanks[nBank] = nullptr; +} + + +bool CTrackApp::AddDLSBank(const mpt::PathString &filename) +{ + if(filename.empty() || !CDLSBank::IsDLSBank(filename)) return false; + // Check for dupes + for(const auto &bank : gpDLSBanks) + { + if(bank && !mpt::PathString::CompareNoCase(filename, bank->GetFileName())) + return true; + } + try + { + auto bank = std::make_unique<CDLSBank>(); + if(bank->Open(filename)) + { + gpDLSBanks.push_back(std::move(bank)); + return true; + } + } catch(mpt::out_of_memory e) + { + mpt::delete_out_of_memory(e); + } catch(const std::exception &) + { + } + return false; +} + + +size_t CTrackApp::AddScannedDLSBanks() +{ + if(!m_scannedDlsBanks.valid()) + return 0; + + size_t numAdded = 0; + auto scannedBanks = m_scannedDlsBanks.get(); + gpDLSBanks.reserve(gpDLSBanks.size() + scannedBanks.size()); + const size_t existingBanks = gpDLSBanks.size(); + for(auto &bank : scannedBanks) + { + if(std::find_if(gpDLSBanks.begin(), gpDLSBanks.begin() + existingBanks, [&bank](const auto &other) { return other && *bank == *other; }) == gpDLSBanks.begin() + existingBanks) + { + gpDLSBanks.push_back(std::move(bank)); + numAdded++; + } + } + return numAdded; +} + + +///////////////////////////////////////////////////////////////////////////// +// CTrackApp + +MODTYPE CTrackApp::m_nDefaultDocType = MOD_TYPE_IT; + +BEGIN_MESSAGE_MAP(CTrackApp, CWinApp) + //{{AFX_MSG_MAP(CTrackApp) + ON_COMMAND(ID_FILE_NEW, &CTrackApp::OnFileNew) + ON_COMMAND(ID_FILE_NEWMOD, &CTrackApp::OnFileNewMOD) + ON_COMMAND(ID_FILE_NEWS3M, &CTrackApp::OnFileNewS3M) + ON_COMMAND(ID_FILE_NEWXM, &CTrackApp::OnFileNewXM) + ON_COMMAND(ID_FILE_NEWIT, &CTrackApp::OnFileNewIT) + ON_COMMAND(ID_NEW_MPT, &CTrackApp::OnFileNewMPT) + ON_COMMAND(ID_FILE_OPEN, &CTrackApp::OnFileOpen) + ON_COMMAND(ID_FILE_CLOSEALL, &CTrackApp::OnFileCloseAll) + ON_COMMAND(ID_APP_ABOUT, &CTrackApp::OnAppAbout) + ON_UPDATE_COMMAND_UI(ID_FILE_CLOSEALL, &CTrackApp::OnUpdateAnyDocsOpen) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CTrackApp construction + +CTrackApp::CTrackApp() +{ + m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART | AFX_RESTART_MANAGER_REOPEN_PREVIOUS_FILES; +} + + +class OpenMPTDataRecoveryHandler + : public CDataRecoveryHandler +{ +public: + OpenMPTDataRecoveryHandler(_In_ DWORD dwRestartManagerSupportFlags, _In_ int nAutosaveInterval) + : CDataRecoveryHandler(dwRestartManagerSupportFlags, nAutosaveInterval) + { + return; + } + ~OpenMPTDataRecoveryHandler() override = default; + + BOOL SaveOpenDocumentList() override + { + BOOL bRet = TRUE; // return FALSE if document list non-empty and not saved + + POSITION posAutosave = m_mapDocNameToAutosaveName.GetStartPosition(); + if (posAutosave != NULL) + { + bRet = FALSE; + + // Save the open document list and associated autosave info to the registry + IniFileSettingsBackend ini(theApp.GetConfigPath() + P_("restart.") + mpt::PathString::FromCString(GetRestartIdentifier()) + P_(".ini")); + ini.ConvertToUnicode(); + int32 count = 0; + while (posAutosave != NULL) + { + CString strDocument, strAutosave; + m_mapDocNameToAutosaveName.GetNextAssoc(posAutosave, strDocument, strAutosave); + + ini.WriteSetting({ U_("RestartDocument"), mpt::ufmt::val(count) }, SettingValue(mpt::ToUnicode(strDocument))); + ini.WriteSetting({ U_("RestartAutosave"), mpt::ufmt::val(count) }, SettingValue(mpt::ToUnicode(strAutosave))); + count++; + } + ini.WriteSetting({ U_("Restart"), U_("Count") }, SettingValue(count)); + + return TRUE; + } + + return bRet; + } + + BOOL ReadOpenDocumentList() override + { + BOOL bRet = FALSE; // return TRUE only if at least one document was found + + { + IniFileSettingsBackend ini(theApp.GetConfigPath() + P_("restart.") + mpt::PathString::FromCString(GetRestartIdentifier()) + P_(".ini")); + int32 count = ini.ReadSetting({ U_("Restart"), U_("Count") }, SettingValue(0)); + + for(int32 index = 0; index < count; ++index) + { + mpt::ustring document = ini.ReadSetting({ U_("RestartDocument"), mpt::ufmt::val(index) }, SettingValue(U_(""))); + mpt::ustring autosave = ini.ReadSetting({ U_("RestartAutosave"), mpt::ufmt::val(index) }, SettingValue(U_(""))); + if(!document.empty()) + { + m_mapDocNameToAutosaveName[mpt::ToCString(document)] = mpt::ToCString(autosave); + bRet = TRUE; + } + } + + ini.RemoveSection(U_("Restart")); + ini.RemoveSection(U_("RestartDocument")); + ini.RemoveSection(U_("RestartAutosave")); + + } + + DeleteFile((theApp.GetConfigPath() + P_("restart.") + mpt::PathString::FromCString(GetRestartIdentifier()) + P_(".ini")).AsNative().c_str()); + + return bRet; + } + +}; + + +CDataRecoveryHandler *CTrackApp::GetDataRecoveryHandler() +{ + static BOOL bTriedOnce = FALSE; + + // Since the application restart and application recovery are supported only on Windows + // Vista and above, we don't need a recovery handler on Windows versions less than Vista. + if (SupportsRestartManager() || SupportsApplicationRecovery()) + { + if (!bTriedOnce && m_pDataRecoveryHandler == NULL) + { + m_pDataRecoveryHandler = new OpenMPTDataRecoveryHandler(m_dwRestartManagerSupportFlags, m_nAutosaveInterval); + if (!m_pDataRecoveryHandler->Initialize()) + { + delete m_pDataRecoveryHandler; + m_pDataRecoveryHandler = NULL; + } + } + } + + bTriedOnce = TRUE; + return m_pDataRecoveryHandler; +} + + +void CTrackApp::AddToRecentFileList(LPCTSTR lpszPathName) +{ + AddToRecentFileList(mpt::PathString::FromCString(lpszPathName)); +} + + +void CTrackApp::AddToRecentFileList(const mpt::PathString &path) +{ + RemoveMruItem(path); + TrackerSettings::Instance().mruFiles.insert(TrackerSettings::Instance().mruFiles.begin(), path); + if(TrackerSettings::Instance().mruFiles.size() > TrackerSettings::Instance().mruListLength) + { + TrackerSettings::Instance().mruFiles.resize(TrackerSettings::Instance().mruListLength); + } + CMainFrame::GetMainFrame()->UpdateMRUList(); +} + + +void CTrackApp::RemoveMruItem(const size_t item) +{ + if(item < TrackerSettings::Instance().mruFiles.size()) + { + TrackerSettings::Instance().mruFiles.erase(TrackerSettings::Instance().mruFiles.begin() + item); + CMainFrame::GetMainFrame()->UpdateMRUList(); + } +} + + +void CTrackApp::RemoveMruItem(const mpt::PathString &path) +{ + auto &mruFiles = TrackerSettings::Instance().mruFiles; + for(auto i = mruFiles.begin(); i != mruFiles.end(); i++) + { + if(!mpt::PathString::CompareNoCase(*i, path)) + { + mruFiles.erase(i); + break; + } + } +} + + +///////////////////////////////////////////////////////////////////////////// +// CTrackApp initialization + + +namespace Tracker +{ +mpt::recursive_mutex_with_lock_count & GetGlobalMutexRef() +{ + return theApp.GetGlobalMutexRef(); +} +} // namespace Tracker + + +class ComponentManagerSettings + : public IComponentManagerSettings +{ +private: + TrackerSettings &conf; + mpt::PathString configPath; +public: + ComponentManagerSettings(TrackerSettings &conf, const mpt::PathString &configPath) + : conf(conf) + , configPath(configPath) + { + return; + } + bool LoadOnStartup() const override + { + return conf.ComponentsLoadOnStartup; + } + bool KeepLoaded() const override + { + return conf.ComponentsKeepLoaded; + } + bool IsBlocked(const std::string &key) const override + { + return conf.IsComponentBlocked(key); + } + mpt::PathString Path() const override + { + if(mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture()).empty()) + { + return mpt::PathString(); + } + return configPath + P_("Components\\") + mpt::PathString::FromUnicode(mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture())) + P_("\\"); + } +}; + + +// Move a config file called fileName from the App's directory (or one of its sub directories specified by subDir) to +// %APPDATA%. If specified, it will be renamed to newFileName. Existing files are never overwritten. +// Returns true on success. +bool CTrackApp::MoveConfigFile(const mpt::PathString &fileName, mpt::PathString subDir, mpt::PathString newFileName) +{ + const mpt::PathString oldPath = GetInstallPath() + subDir + fileName; + mpt::PathString newPath = GetConfigPath() + subDir; + if(!newFileName.empty()) + newPath += newFileName; + else + newPath += fileName; + + if(!newPath.IsFile() && oldPath.IsFile()) + { + return MoveFile(oldPath.AsNative().c_str(), newPath.AsNative().c_str()) != 0; + } + return false; +} + + +// Set up paths were configuration data is written to. Set overridePortable to true if application's own directory should always be used. +void CTrackApp::SetupPaths(bool overridePortable) +{ + + // First, determine if the executable is installed in multi-arch mode or in the old standard mode. + bool modeMultiArch = false; + bool modeSourceProject = false; + const mpt::PathString exePath = mpt::GetExecutablePath(); + auto exePathComponents = mpt::String::Split<mpt::ustring>(exePath.GetDir().WithoutTrailingSlash().ToUnicode(), P_("\\").ToUnicode()); + if(exePathComponents.size() >= 2) + { + if(exePathComponents[exePathComponents.size()-1] == mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture())) + { + if(exePathComponents[exePathComponents.size()-2] == U_("bin")) + { + modeMultiArch = true; + } + } + } + // Check if we are running from the source tree. + if(!modeMultiArch && exePathComponents.size() >= 4) + { + if(exePathComponents[exePathComponents.size()-1] == mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture())) + { + if(exePathComponents[exePathComponents.size()-4] == U_("bin")) + { + modeSourceProject = true; + } + } + } + if(modeSourceProject) + { + m_InstallPath = mpt::GetAbsolutePath(exePath + P_("..\\") + P_("..\\") + P_("..\\") + P_("..\\")); + m_InstallBinPath = mpt::GetAbsolutePath(exePath + P_("..\\")); + m_InstallBinArchPath = exePath; + m_InstallPkgPath = mpt::GetAbsolutePath(exePath + P_("..\\") + P_("..\\") + P_("..\\") + P_("..\\packageTemplate\\")); + } else if(modeMultiArch) + { + m_InstallPath = mpt::GetAbsolutePath(exePath + P_("..\\") + P_("..\\")); + m_InstallBinPath = mpt::GetAbsolutePath(exePath + P_("..\\")); + m_InstallBinArchPath = exePath; + m_InstallPkgPath = mpt::GetAbsolutePath(exePath + P_("..\\") + P_("..\\")); + } else + { + m_InstallPath = exePath; + m_InstallBinPath = exePath; + m_InstallBinArchPath = exePath; + m_InstallPkgPath = exePath; + } + + // Determine paths, portable mode, first run. Do not yet update any state. + mpt::PathString configPathPortable = (modeSourceProject ? exePath : m_InstallPath); // config path in portable mode + mpt::PathString configPathUser; // config path in default non-portable mode + { + // Try to find a nice directory where we should store our settings (default: %APPDATA%) + TCHAR dir[MAX_PATH] = { 0 }; + if((SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, dir) == S_OK) + || (SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, dir) == S_OK)) + { + // Store our app settings in %APPDATA% or "My Documents" + configPathUser = mpt::PathString::FromNative(dir) + P_("\\OpenMPT\\"); + } + } + + // Check if the user has configured portable mode. + bool configInstallPortable = false; + mpt::PathString portableFlagFilename = (configPathPortable + P_("OpenMPT.portable")); + bool configPortableFlag = portableFlagFilename.IsFile(); + configInstallPortable = configInstallPortable || configPortableFlag; + // before 1.29.00.13: + configInstallPortable = configInstallPortable || (GetPrivateProfileInt(_T("Paths"), _T("UseAppDataDirectory"), 1, (configPathPortable + P_("mptrack.ini")).AsNative().c_str()) == 0); + // convert to new style + if(configInstallPortable && !configPortableFlag) + { + mpt::SafeOutputFile f(portableFlagFilename); + } + + // Determine portable mode. + bool portableMode = overridePortable || configInstallPortable || configPathUser.empty(); + + // Update config dir + m_ConfigPath = portableMode ? configPathPortable : configPathUser; + + // Set up default file locations + m_szConfigFileName = m_ConfigPath + P_("mptrack.ini"); // config file + m_szPluginCacheFileName = m_ConfigPath + P_("plugin.cache"); // plugin cache + + // Force use of custom ini file rather than windowsDir\executableName.ini + if(m_pszProfileName) + { + free((void *)m_pszProfileName); + } + m_pszProfileName = _tcsdup(m_szConfigFileName.ToCString()); + + m_bInstallerMode = !modeSourceProject && !portableMode; + m_bPortableMode = portableMode; + m_bSourceTreeMode = modeSourceProject; + +} + + +void CTrackApp::CreatePaths() +{ + // Create missing diretories + if(!IsPortableMode()) + { + if(!m_ConfigPath.IsDirectory()) + { + CreateDirectory(m_ConfigPath.AsNative().c_str(), 0); + } + } + if(!(GetConfigPath() + P_("Components")).IsDirectory()) + { + CreateDirectory((GetConfigPath() + P_("Components")).AsNative().c_str(), 0); + } + if(!(GetConfigPath() + P_("Components\\") + mpt::PathString::FromUnicode(mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture()))).IsDirectory()) + { + CreateDirectory((GetConfigPath() + P_("Components\\") + mpt::PathString::FromUnicode(mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture()))).AsNative().c_str(), 0); + } + + // Handle updates from old versions. + + if(!IsPortableMode()) + { + + // Move the config files if they're still in the old place. + MoveConfigFile(P_("mptrack.ini")); + MoveConfigFile(P_("plugin.cache")); + + // Import old tunings + const mpt::PathString oldTunings = GetInstallPath() + P_("tunings\\"); + + if(oldTunings.IsDirectory()) + { + const mpt::PathString searchPattern = oldTunings + P_("*.*"); + WIN32_FIND_DATA FindFileData; + HANDLE hFind; + hFind = FindFirstFile(searchPattern.AsNative().c_str(), &FindFileData); + if(hFind != INVALID_HANDLE_VALUE) + { + do + { + MoveConfigFile(mpt::PathString::FromNative(FindFileData.cFileName), P_("tunings\\")); + } while(FindNextFile(hFind, &FindFileData) != 0); + } + FindClose(hFind); + RemoveDirectory(oldTunings.AsNative().c_str()); + } + + } + +} + + +#if !defined(MPT_BUILD_RETRO) + +bool CTrackApp::CheckSystemSupport() +{ + const mpt::ustring lf = U_("\n"); + const mpt::ustring url = Build::GetURL(Build::Url::Download); + if(!BuildVariants::ProcessorCanRunCurrentBuild()) + { + mpt::ustring text; + text += U_("Your CPU is too old to run this variant of OpenMPT.") + lf; + text += U_("OpenMPT will exit now.") + lf; + Reporting::Error(text, "OpenMPT"); + return false; + } + if(BuildVariants::IsKnownSystem() && !BuildVariants::SystemCanRunCurrentBuild()) + { + mpt::ustring text; + text += U_("Your system does not meet the minimum requirements for this variant of OpenMPT.") + lf; + if(mpt::OS::Windows::IsOriginal()) + { + text += U_("OpenMPT will exit now.") + lf; + } + Reporting::Error(text, "OpenMPT"); + if(mpt::OS::Windows::IsOriginal()) + { + return false; + } else + { + return true; // may work though + } + } + return true; +} + +#endif // !MPT_BUILD_RETRO + + +BOOL CTrackApp::InitInstanceEarly(CMPTCommandLineInfo &cmdInfo) +{ + // The first step of InitInstance, always executed without any crash handler. + + #ifndef UNICODE + if(MessageBox(NULL, + _T("STOP!!!") _T("\n") + _T("This is an ANSI (as opposed to a UNICODE) build of OpenMPT.") _T("\n") + _T("\n") + _T("ANSI builds are NOT SUPPORTED and WILL CAUSE CORRUPTION of the OpenMPT configuration and exhibit other unintended behaviour.") _T("\n") + _T("\n") + _T("Please use an official build of OpenMPT or compile 'OpenMPT.sln' instead of 'OpenMPT-ANSI.sln'.") _T("\n") + _T("\n") + _T("Continue starting OpenMPT anyway?") _T("\n"), + _T("OpenMPT"), MB_ICONSTOP | MB_YESNO| MB_DEFBUTTON2) + != IDYES) + { + ExitProcess(1); + } + #endif + + // Call the base class. + // This is required for MFC RestartManager integration. + if(!CWinApp::InitInstance()) + { + return FALSE; + } + + #if MPT_COMPILER_MSVC + _CrtSetDebugFillThreshold(0); // Disable buffer filling in secure enhanced CRT functions. + #endif + + // Avoid e.g. audio APIs trying to load wdmaud.drv from arbitrary working directory + ::SetCurrentDirectory(mpt::GetExecutablePath().AsNative().c_str()); + + // Initialize OLE MFC support + BOOL oleinit = AfxOleInit(); + ASSERT(oleinit != FALSE); // no MPT_ASSERT here! + + // Parse command line for standard shell commands, DDE, file open + ParseCommandLine(cmdInfo); + + // Set up paths to store configuration in + SetupPaths(cmdInfo.m_portable); + + if(cmdInfo.m_sharedInstance && IPCWindow::SendToIPC(cmdInfo.m_fileNames)) + { + ExitProcess(0); + } + + // Initialize DocManager (for DDE) + // requires mpt::PathString + ASSERT(nullptr == m_pDocManager); // no MPT_ASSERT here! + m_pDocManager = new CModDocManager(); + + if(IsDebuggerPresent() && cmdInfo.m_debugCrashHandler) + { + ExceptionHandler::useAnyCrashHandler = true; + ExceptionHandler::useImplicitFallbackSEH = false; + ExceptionHandler::useExplicitSEH = true; + ExceptionHandler::handleStdTerminate = true; + ExceptionHandler::handleMfcExceptions = true; + ExceptionHandler::debugExceptionHandler = true; + } else if(IsDebuggerPresent() || cmdInfo.m_noCrashHandler) + { + ExceptionHandler::useAnyCrashHandler = false; + ExceptionHandler::useImplicitFallbackSEH = false; + ExceptionHandler::useExplicitSEH = false; + ExceptionHandler::handleStdTerminate = false; + ExceptionHandler::handleMfcExceptions = false; + ExceptionHandler::debugExceptionHandler = false; + } else + { + ExceptionHandler::useAnyCrashHandler = true; + ExceptionHandler::useImplicitFallbackSEH = true; + ExceptionHandler::useExplicitSEH = true; + ExceptionHandler::handleStdTerminate = true; + ExceptionHandler::handleMfcExceptions = true; + ExceptionHandler::debugExceptionHandler = false; + } + + return TRUE; +} + + +BOOL CTrackApp::InitInstanceImpl(CMPTCommandLineInfo &cmdInfo) +{ + + m_GuiThreadId = GetCurrentThreadId(); + + mpt::log::Trace::SetThreadId(mpt::log::Trace::ThreadKindGUI, m_GuiThreadId); + + if(ExceptionHandler::useAnyCrashHandler) + { + ExceptionHandler::Register(); + } + + // Start loading + BeginWaitCursor(); + + MPT_LOG_GLOBAL(LogInformation, "", U_("OpenMPT Start")); + + // create the tracker-global random device + m_RD = std::make_unique<mpt::random_device>(); + // make the device available to non-tracker-only code + mpt::set_global_random_device(m_RD.get()); + // create and seed the traker-global best PRNG with the random device + m_PRNG = std::make_unique<mpt::thread_safe_prng<mpt::default_prng> >(mpt::make_prng<mpt::default_prng>(RandomDevice())); + // make the best PRNG available to non-tracker-only code + mpt::set_global_prng(m_PRNG.get()); + // additionally, seed the C rand() PRNG, just in case any third party library calls rand() + mpt::crand::reseed(RandomDevice()); + + m_Gdiplus = std::make_unique<GdiplusRAII>(); + + if(cmdInfo.m_noWine) + { + mpt::OS::Windows::PreventWineDetection(); + } + + #ifdef MPT_ENABLE_ARCH_INTRINSICS + if(!cmdInfo.m_noAssembly) + { + CPU::EnableAvailableFeatures(); + } + #endif // MPT_ENABLE_ARCH_INTRINSICS + + if(mpt::OS::Windows::IsWine()) + { + SetWineVersion(std::make_shared<mpt::OS::Wine::VersionContext>()); + } + + // Create paths to store configuration in + CreatePaths(); + + m_pSettingsIniFile = new IniFileSettingsBackend(m_szConfigFileName); + m_pSettings = new SettingsContainer(m_pSettingsIniFile); + + m_pDebugSettings = new DebugSettings(*m_pSettings); + + m_pTrackerSettings = new TrackerSettings(*m_pSettings); + + MPT_LOG_GLOBAL(LogInformation, "", U_("OpenMPT settings initialized.")); + + if(ExceptionHandler::useAnyCrashHandler) + { + ExceptionHandler::ConfigureSystemHandler(); + } + + if(TrackerSettings::Instance().MiscUseSingleInstance && IPCWindow::SendToIPC(cmdInfo.m_fileNames)) + { + ExitProcess(0); + } + + IPCWindow::Open(m_hInstance); + + m_pSongSettingsIniFile = new IniFileSettingsBackend(GetConfigPath() + P_("SongSettings.ini")); + m_pSongSettings = new SettingsContainer(m_pSongSettingsIniFile); + + m_pComponentManagerSettings = new ComponentManagerSettings(TrackerSettings::Instance(), GetConfigPath()); + + m_pPluginCache = new IniFileSettingsContainer(m_szPluginCacheFileName); + + // Load standard INI file options (without MRU) + // requires SetupPaths+CreatePaths called + LoadStdProfileSettings(0); + + // Set process priority class + #ifndef _DEBUG + SetPriorityClass(GetCurrentProcess(), TrackerSettings::Instance().MiscProcessPriorityClass); + #endif + + // Dynamic DPI-awareness. Some users might want to disable DPI-awareness because of their DPI-unaware VST plugins. + bool setDPI = false; + // For Windows 10, Creators Update (1703) and newer + { + mpt::Library user32(mpt::LibraryPath::System(P_("user32"))); + if (user32.IsValid()) + { + enum MPT_DPI_AWARENESS_CONTEXT + { + MPT_DPI_AWARENESS_CONTEXT_UNAWARE = -1, + MPT_DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = -2, + MPT_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = -3, + MPT_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4, + MPT_DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED = -5, // 1809 update and newer + }; + using PSETPROCESSDPIAWARENESSCONTEXT = BOOL(WINAPI *)(HANDLE); + PSETPROCESSDPIAWARENESSCONTEXT SetProcessDpiAwarenessContext = nullptr; + if(user32.Bind(SetProcessDpiAwarenessContext, "SetProcessDpiAwarenessContext")) + { + if (TrackerSettings::Instance().highResUI) + { + setDPI = (SetProcessDpiAwarenessContext(HANDLE(MPT_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) == TRUE); + } else + { + if (SetProcessDpiAwarenessContext(HANDLE(MPT_DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED)) == TRUE) + setDPI = true; + else + setDPI = (SetProcessDpiAwarenessContext(HANDLE(MPT_DPI_AWARENESS_CONTEXT_UNAWARE)) == TRUE); + } + } + } + } + // For Windows 8.1 and newer + if(!setDPI) + { + mpt::Library shcore(mpt::LibraryPath::System(P_("SHCore"))); + if(shcore.IsValid()) + { + using PSETPROCESSDPIAWARENESS = HRESULT (WINAPI *)(int); + PSETPROCESSDPIAWARENESS SetProcessDPIAwareness = nullptr; + if(shcore.Bind(SetProcessDPIAwareness, "SetProcessDpiAwareness")) + { + setDPI = (SetProcessDPIAwareness(TrackerSettings::Instance().highResUI ? 2 : 0) == S_OK); + } + } + } + // For Vista and newer + if(!setDPI && TrackerSettings::Instance().highResUI) + { + mpt::Library user32(mpt::LibraryPath::System(P_("user32"))); + if(user32.IsValid()) + { + using PSETPROCESSDPIAWARE = BOOL (WINAPI *)(); + PSETPROCESSDPIAWARE SetProcessDPIAware = nullptr; + if(user32.Bind(SetProcessDPIAware, "SetProcessDPIAware")) + { + SetProcessDPIAware(); + } + } + } + + // create main MDI Frame window + CMainFrame* pMainFrame = new CMainFrame(); + if(!pMainFrame->LoadFrame(IDR_MAINFRAME)) return FALSE; + m_pMainWnd = pMainFrame; + + // Show splash screen + if(cmdInfo.m_bShowSplash && TrackerSettings::Instance().m_ShowSplashScreen) + { + StartSplashScreen(); + } + + // create component manager + ComponentManager::Init(*m_pComponentManagerSettings); + + // load components + ComponentManager::Instance()->Startup(); + + // Wine Support + if(mpt::OS::Windows::IsWine()) + { + WineIntegration::Initialize(); + WineIntegration::Load(); + } + + // Register document templates + m_pModTemplate = new CModDocTemplate( + IDR_MODULETYPE, + RUNTIME_CLASS(CModDoc), + RUNTIME_CLASS(CChildFrame), // custom MDI child frame + RUNTIME_CLASS(CModControlView)); + AddDocTemplate(m_pModTemplate); + + // Load Midi Library + ImportMidiConfig(theApp.GetSettings(), {}, true); + + // Enable DDE Execute open + // requires m_pDocManager + EnableShellOpen(); + + // Enable drag/drop open + m_pMainWnd->DragAcceptFiles(); + + // Load sound APIs + // requires TrackerSettings + m_pAllSoundDeviceComponents = std::make_unique<AllSoundDeviceComponents>(); + auto GetSysInfo = [&]() + { + if(mpt::OS::Windows::IsWine()) + { + return SoundDevice::SysInfo(mpt::osinfo::get_class(), mpt::OS::Windows::Version::Current(), mpt::OS::Windows::IsWine(), GetWineVersion()->HostClass(), GetWineVersion()->Version()); + } + return SoundDevice::SysInfo(mpt::osinfo::get_class(), mpt::OS::Windows::Version::Current(), mpt::OS::Windows::IsWine(), mpt::osinfo::osclass::Unknown, mpt::osinfo::windows::wine::version()); + }; + SoundDevice::SysInfo sysInfo = GetSysInfo(); + SoundDevice::AppInfo appInfo; + appInfo.SetName(U_("OpenMPT")); + appInfo.SetHWND(*m_pMainWnd); + appInfo.BoostedThreadPriorityXP = TrackerSettings::Instance().SoundBoostedThreadPriority; + appInfo.BoostedThreadMMCSSClassVista = TrackerSettings::Instance().SoundBoostedThreadMMCSSClass; + appInfo.BoostedThreadRealtimePosix = TrackerSettings::Instance().SoundBoostedThreadRealtimePosix; + appInfo.BoostedThreadNicenessPosix = TrackerSettings::Instance().SoundBoostedThreadNicenessPosix; + appInfo.BoostedThreadRtprioPosix = TrackerSettings::Instance().SoundBoostedThreadRtprioPosix; + appInfo.MaskDriverCrashes = TrackerSettings::Instance().SoundMaskDriverCrashes; + appInfo.AllowDeferredProcessing = TrackerSettings::Instance().SoundAllowDeferredProcessing; + std::vector<std::shared_ptr<SoundDevice::IDevicesEnumerator>> deviceEnumerators = SoundDevice::Manager::GetEnabledEnumerators(*m_pAllSoundDeviceComponents); + deviceEnumerators.push_back(std::static_pointer_cast<SoundDevice::IDevicesEnumerator>(std::make_shared<SoundDevice::DevicesEnumerator<SoundDevice::SoundDeviceStub>>())); + m_pSoundDevicesManager = std::make_unique<SoundDevice::Manager>(m_GlobalLogger, sysInfo, appInfo, std::move(deviceEnumerators)); + m_pTrackerSettings->MigrateOldSoundDeviceSettings(*m_pSoundDevicesManager); + + // Set default note names + CSoundFile::SetDefaultNoteNames(); + + // Load DLS Banks + if (!cmdInfo.m_noDls) + m_scannedDlsBanks = LoadDefaultDLSBanks(); + + // Initialize Plugins + if (!cmdInfo.m_noPlugins) InitializeDXPlugins(); + + // Initialize CMainFrame + pMainFrame->Initialize(); + InitCommonControls(); + pMainFrame->m_InputHandler->UpdateMainMenu(); + + // Dispatch commands specified on the command line + if(cmdInfo.m_nShellCommand == CCommandLineInfo::FileNew) + { + // When not asked to open any existing file, + // we do not want to open an empty new one on startup. + cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing; + } + bool shellSuccess = false; + if(cmdInfo.m_fileNames.empty()) + { + shellSuccess = ProcessShellCommand(cmdInfo) != FALSE; + } else + { + cmdInfo.m_nShellCommand = CCommandLineInfo::FileOpen; + for(const auto &filename : cmdInfo.m_fileNames) + { + cmdInfo.m_strFileName = filename.ToCString(); + shellSuccess |= ProcessShellCommand(cmdInfo) != FALSE; + } + } + if(!shellSuccess) + { + EndWaitCursor(); + StopSplashScreen(); + return FALSE; + } + + pMainFrame->ShowWindow(m_nCmdShow); + pMainFrame->UpdateWindow(); + + EndWaitCursor(); + + + // Perform startup tasks. + +#if !defined(MPT_BUILD_RETRO) + // Check whether we are running the best build for the given system. + if(!cmdInfo.m_noSysCheck) + { + if(!CheckSystemSupport()) + { + StopSplashScreen(); + return FALSE; + } + } +#endif // !MPT_BUILD_RETRO + + if(TrackerSettings::Instance().FirstRun) + { + // On high-DPI devices, automatically upscale pattern font + FontSetting font = TrackerSettings::Instance().patternFont; + font.size = Clamp(Util::GetDPIy(m_pMainWnd->m_hWnd) / 96 - 1, 0, 9); + TrackerSettings::Instance().patternFont = font; + new WelcomeDlg(m_pMainWnd); + } else + { +#if !defined(MPT_BUILD_RETRO) + bool deprecatedSoundDevice = GetSoundDevicesManager()->FindDeviceInfo(TrackerSettings::Instance().GetSoundDeviceIdentifier()).IsDeprecated(); + bool showSettings = deprecatedSoundDevice && !TrackerSettings::Instance().m_SoundDeprecatedDeviceWarningShown && (Reporting::Confirm( + U_("You have currently selected a sound device which is deprecated. MME/WaveOut support will be removed in a future OpenMPT version.\n") + + U_("The recommended sound device type is WASAPI.\n") + + U_("Do you want to change your sound device settings now?"), + U_("OpenMPT - Deprecated sound device") + ) == cnfYes); + if(showSettings) + { + TrackerSettings::Instance().m_SoundDeprecatedDeviceWarningShown = true; + m_pMainWnd->PostMessage(WM_COMMAND, ID_VIEW_OPTIONS); + } +#endif // !MPT_BUILD_RETRO + } + +#ifdef ENABLE_TESTS + if(!cmdInfo.m_noTests) + Test::DoTests(); +#endif + + if(TrackerSettings::Instance().m_SoundSettingsOpenDeviceAtStartup) + { + pMainFrame->InitPreview(); + pMainFrame->PreparePreview(NOTE_NOTECUT, 0); + pMainFrame->PlayPreview(); + } + + if(!TrackerSettings::Instance().FirstRun) + { +#if defined(MPT_ENABLE_UPDATE) + if(CUpdateCheck::IsSuitableUpdateMoment()) + { + CUpdateCheck::DoAutoUpdateCheck(); + } +#endif // MPT_ENABLE_UPDATE + } + + return TRUE; +} + + +BOOL CTrackApp::InitInstance() +{ + CMPTCommandLineInfo cmdInfo; + if(!InitInstanceEarly(cmdInfo)) + { + return FALSE; + } + return InitInstanceLate(cmdInfo); +} + + +BOOL CTrackApp::InitInstanceLate(CMPTCommandLineInfo &cmdInfo) +{ + BOOL result = FALSE; + if(ExceptionHandler::useExplicitSEH) + { + // https://support.microsoft.com/en-us/kb/173652 + __try + { + result = InitInstanceImpl(cmdInfo); + } __except(ExceptionHandler::ExceptionFilter(GetExceptionInformation())) + { + std::abort(); + } + } else + { + result = InitInstanceImpl(cmdInfo); + } + return result; +} + + +int CTrackApp::Run() +{ + int result = 255; + if(ExceptionHandler::useExplicitSEH) + { + // https://support.microsoft.com/en-us/kb/173652 + __try + { + result = CWinApp::Run(); + } __except(ExceptionHandler::ExceptionFilter(GetExceptionInformation())) + { + std::abort(); + } + } else + { + result = CWinApp::Run(); + } + return result; +} + + +LRESULT CTrackApp::ProcessWndProcException(CException * e, const MSG * pMsg) +{ + if(ExceptionHandler::handleMfcExceptions) + { + LRESULT result = 0L; // as per documentation + if(pMsg) + { + if(pMsg->message == WM_COMMAND) + { + result = (LRESULT)TRUE; // as per documentation + } + } + if(dynamic_cast<CMemoryException*>(e)) + { + e->ReportError(); + //ExceptionHandler::UnhandledMFCException(e, pMsg); + } else + { + ExceptionHandler::UnhandledMFCException(e, pMsg); + } + return result; + } else + { + return CWinApp::ProcessWndProcException(e, pMsg); + } +} + + +int CTrackApp::ExitInstance() +{ + int result = 0; + if(ExceptionHandler::useExplicitSEH) + { + // https://support.microsoft.com/en-us/kb/173652 + __try + { + result = ExitInstanceImpl(); + } __except(ExceptionHandler::ExceptionFilter(GetExceptionInformation())) + { + std::abort(); + } + } else + { + result = ExitInstanceImpl(); + } + return result; +} + + +int CTrackApp::ExitInstanceImpl() +{ + IPCWindow::Close(); + + m_pSoundDevicesManager = nullptr; + m_pAllSoundDeviceComponents = nullptr; + ExportMidiConfig(theApp.GetSettings()); + AddScannedDLSBanks(); + SaveDefaultDLSBanks(); + gpDLSBanks.clear(); + + // Uninitialize Plugins + UninitializeDXPlugins(); + + ComponentManager::Release(); + + delete m_pPluginCache; + m_pPluginCache = nullptr; + delete m_pComponentManagerSettings; + m_pComponentManagerSettings = nullptr; + delete m_pTrackerSettings; + m_pTrackerSettings = nullptr; + delete m_pDebugSettings; + m_pDebugSettings = nullptr; + delete m_pSettings; + m_pSettings = nullptr; + delete m_pSettingsIniFile; + m_pSettingsIniFile = nullptr; + delete m_pSongSettings; + m_pSongSettings = nullptr; + delete m_pSongSettingsIniFile; + m_pSongSettingsIniFile = nullptr; + + if(mpt::OS::Windows::IsWine()) + { + SetWineVersion(nullptr); + } + + m_Gdiplus.reset(); + + mpt::set_global_prng(nullptr); + m_PRNG.reset(); + mpt::set_global_random_device(nullptr); + m_RD.reset(); + + if(ExceptionHandler::useAnyCrashHandler) + { + ExceptionHandler::UnconfigureSystemHandler(); + ExceptionHandler::Unregister(); + } + + return CWinApp::ExitInstance(); +} + + +//////////////////////////////////////////////////////////////////////////////// +// App Messages + + +CModDoc *CTrackApp::NewDocument(MODTYPE newType) +{ + // Build from template + if(newType == MOD_TYPE_NONE) + { + const mpt::PathString templateFile = TrackerSettings::Instance().defaultTemplateFile; + if(TrackerSettings::Instance().defaultNewFileAction == nfDefaultTemplate && !templateFile.empty()) + { + // Template file can be either a filename inside one of the preset and user TemplateModules folders, or a full path. + const mpt::PathString dirs[] = { GetConfigPath() + P_("TemplateModules\\"), GetInstallPath() + P_("TemplateModules\\"), mpt::PathString() }; + for(const auto &dir : dirs) + { + if((dir + templateFile).IsFile()) + { + if(CModDoc *modDoc = static_cast<CModDoc *>(m_pModTemplate->OpenTemplateFile(dir + templateFile))) + { + return modDoc; + } + } + } + } + + + // Default module type + newType = TrackerSettings::Instance().defaultModType; + + // Get active document to make the new module of the same type + CModDoc *pModDoc = CMainFrame::GetMainFrame()->GetActiveDoc(); + if(pModDoc != nullptr && TrackerSettings::Instance().defaultNewFileAction == nfSameAsCurrent) + { + newType = pModDoc->GetSoundFile().GetBestSaveFormat(); + } + } + + SetDefaultDocType(newType); + return static_cast<CModDoc *>(m_pModTemplate->OpenDocumentFile(_T(""))); +} + + +void CTrackApp::OpenModulesDialog(std::vector<mpt::PathString> &files, const mpt::PathString &overridePath) +{ + files.clear(); + + static constexpr std::string_view commonExts[] = {"mod", "s3m", "xm", "it", "mptm", "mo3", "oxm", "nst", "stk", "m15", "pt36", "mid", "rmi", "smf", "wav", "mdz", "s3z", "xmz", "itz", "mdr"}; + std::string exts, extsWithoutCommon; + for(const auto &ext : CSoundFile::GetSupportedExtensions(true)) + { + const auto filter = std::string("*.") + ext + std::string(";"); + exts += filter; + if(!mpt::contains(commonExts, ext)) + extsWithoutCommon += filter; + } + + static int nFilterIndex = 0; + FileDialog dlg = OpenFileDialog() + .AllowMultiSelect() + .ExtensionFilter("All Modules (*.mptm,*.mod,*.xm,*.s3m,*.it,...)|" + exts + ";mod.*" + "|" + "Compressed Modules (*.mdz,*.s3z,*.xmz,*.itz,*.mo3,*.oxm,...)|*.mdz;*.s3z;*.xmz;*.itz;*.mdr;*.zip;*.rar;*.lha;*.pma;*.lzs;*.gz;*.mo3;*.oxm" + "|" + "ProTracker Modules (*.mod,*.nst)|*.mod;mod.*;*.mdz;*.nst;*.m15;*.stk;*.pt36|" + "Scream Tracker Modules (*.s3m,*.stm)|*.s3m;*.stm;*.s3z;*.stx|" + "FastTracker Modules (*.xm)|*.xm;*.xmz|" + "Impulse Tracker Modules (*.it)|*.it;*.itz|" + "OpenMPT Modules (*.mptm)|*.mptm;*.mptmz|" + "Other Modules (*.mtm,*.okt,*.mdl,*.669,*.far,...)|" + extsWithoutCommon + "|" + "Wave Files (*.wav)|*.wav|" + "MIDI Files (*.mid,*.rmi)|*.mid;*.rmi;*.smf|" + "All Files (*.*)|*.*||") + .WorkingDirectory(overridePath.empty() ? TrackerSettings::Instance().PathSongs.GetWorkingDir() : overridePath) + .FilterIndex(&nFilterIndex); + if(!dlg.Show()) return; + + if(overridePath.empty()) + TrackerSettings::Instance().PathSongs.SetWorkingDir(dlg.GetWorkingDirectory()); + + files = dlg.GetFilenames(); +} + + +void CTrackApp::OnFileOpen() +{ + FileDialog::PathList files; + OpenModulesDialog(files); + for(const auto &file : files) + { + OpenDocumentFile(file.ToCString()); + } +} + + +// App command to run the dialog +void CTrackApp::OnAppAbout() +{ + if (CAboutDlg::instance) return; + CAboutDlg::instance = new CAboutDlg(); + CAboutDlg::instance->Create(IDD_ABOUTBOX, m_pMainWnd); +} + + +///////////////////////////////////////////////////////////////////////////// +// Splash Screen + +class CSplashScreen: public CDialog +{ +protected: + std::unique_ptr<Gdiplus::Image> m_Image; + +public: + ~CSplashScreen(); + BOOL OnInitDialog() override; + void OnOK() override; + void OnCancel() override { OnOK(); } + void OnPaint(); + BOOL OnEraseBkgnd(CDC *) { return TRUE; } + + DECLARE_MESSAGE_MAP() +}; + +BEGIN_MESSAGE_MAP(CSplashScreen, CDialog) + ON_WM_PAINT() + ON_WM_ERASEBKGND() +END_MESSAGE_MAP() + +static CSplashScreen *gpSplashScreen = NULL; + +static DWORD64 gSplashScreenStartTime = 0; + + +CSplashScreen::~CSplashScreen() +{ + gpSplashScreen = nullptr; +} + + +void CSplashScreen::OnPaint() +{ + CPaintDC dc(this); + Gdiplus::Graphics gfx(dc); + + CRect rect; + GetClientRect(&rect); + gfx.SetInterpolationMode(Gdiplus::InterpolationModeHighQuality); + gfx.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality); + gfx.DrawImage(m_Image.get(), 0, 0, rect.right, rect.bottom); + + CDialog::OnPaint(); +} + + +BOOL CSplashScreen::OnInitDialog() +{ + CDialog::OnInitDialog(); + + try + { + m_Image = GDIP::LoadPixelImage(GetResource(MAKEINTRESOURCE(IDB_SPLASHNOFOLDFIN), _T("PNG"))); + } catch(const bad_image &) + { + return FALSE; + } + + CRect rect; + GetWindowRect(&rect); + const int width = Util::ScalePixels(m_Image->GetWidth(), m_hWnd) / 2; + const int height = Util::ScalePixels(m_Image->GetHeight(), m_hWnd) / 2; + SetWindowPos(nullptr, + rect.left - ((width - rect.Width()) / 2), + rect.top - ((height - rect.Height()) / 2), + width, + height, + SWP_NOZORDER | SWP_NOCOPYBITS); + + return TRUE; +} + + +void CSplashScreen::OnOK() +{ + StopSplashScreen(); +} + + +static void StartSplashScreen() +{ + if(!gpSplashScreen) + { + gpSplashScreen = new CSplashScreen(); + gpSplashScreen->Create(IDD_SPLASHSCREEN, theApp.m_pMainWnd); + gpSplashScreen->ShowWindow(SW_SHOW); + gpSplashScreen->UpdateWindow(); + gpSplashScreen->BeginWaitCursor(); + gSplashScreenStartTime = Util::GetTickCount64(); + } +} + + +static void StopSplashScreen() +{ + if(gpSplashScreen) + { + gpSplashScreen->EndWaitCursor(); + gpSplashScreen->DestroyWindow(); + delete gpSplashScreen; + gpSplashScreen = nullptr; + } +} + + +static void TimeoutSplashScreen() +{ + if(gpSplashScreen) + { + if(Util::GetTickCount64() - gSplashScreenStartTime > 100) + { + StopSplashScreen(); + } + } +} + + +///////////////////////////////////////////////////////////////////////////// +// Idle-time processing + +BOOL CTrackApp::OnIdle(LONG lCount) +{ + BOOL b = CWinApp::OnIdle(lCount); + + TimeoutSplashScreen(); + + if(CMainFrame::GetMainFrame()) + { + CMainFrame::GetMainFrame()->IdleHandlerSounddevice(); + + if(m_scannedDlsBanksAvailable) + { + if(AddScannedDLSBanks()) + CMainFrame::GetMainFrame()->RefreshDlsBanks(); + } + } + + // Call plugins idle routine for open editor + if (m_pPluginManager) + { + DWORD curTime = timeGetTime(); + //rewbs.vstCompliance: call @ 50Hz + if (curTime - m_dwLastPluginIdleCall > 20 || curTime < m_dwLastPluginIdleCall) + { + m_pPluginManager->OnIdle(); + m_dwLastPluginIdleCall = curTime; + } + } + + return b; +} + + +///////////////////////////////////////////////////////////////////////////// +// DIB + + +RGBQUAD rgb2quad(COLORREF c) +{ + RGBQUAD r; + r.rgbBlue = GetBValue(c); + r.rgbGreen = GetGValue(c); + r.rgbRed = GetRValue(c); + r.rgbReserved = 0; + return r; +} + + +void DibBlt(HDC hdc, int x, int y, int sizex, int sizey, int srcx, int srcy, MODPLUGDIB *lpdib) +{ + if (!lpdib) return; + SetDIBitsToDevice( hdc, + x, + y, + sizex, + sizey, + srcx, + lpdib->bmiHeader.biHeight - srcy - sizey, + 0, + lpdib->bmiHeader.biHeight, + lpdib->lpDibBits, + (LPBITMAPINFO)lpdib, + DIB_RGB_COLORS); +} + + +MODPLUGDIB *LoadDib(LPCTSTR lpszName) +{ + mpt::const_byte_span data = GetResource(lpszName, RT_BITMAP); + if(!data.data()) + { + return nullptr; + } + LPBITMAPINFO p = (LPBITMAPINFO)data.data(); + MODPLUGDIB *pmd = new MODPLUGDIB; + pmd->bmiHeader = p->bmiHeader; + for (int i=0; i<16; i++) pmd->bmiColors[i] = p->bmiColors[i]; + LPBYTE lpDibBits = (LPBYTE)p; + lpDibBits += p->bmiHeader.biSize + 16 * sizeof(RGBQUAD); + pmd->lpDibBits = lpDibBits; + return pmd; +} + +int DrawTextT(HDC hdc, const wchar_t *lpchText, int cchText, LPRECT lprc, UINT format) +{ + return ::DrawTextW(hdc, lpchText, cchText, lprc, format); +} + +int DrawTextT(HDC hdc, const char *lpchText, int cchText, LPRECT lprc, UINT format) +{ + return ::DrawTextA(hdc, lpchText, cchText, lprc, format); +} + +template<typename Tchar> +static void DrawButtonRectImpl(HDC hdc, CRect rect, const Tchar *lpszText, bool disabled, bool pushed, DWORD textFlags, uint32 topMargin) +{ + int width = Util::ScalePixels(1, WindowFromDC(hdc)); + if(width != 1) + { + // Draw "real" buttons in Hi-DPI mode + DrawFrameControl(hdc, rect, DFC_BUTTON, pushed ? (DFCS_PUSHED | DFCS_BUTTONPUSH) : DFCS_BUTTONPUSH); + } else + { + const auto colorHighlight = GetSysColor(COLOR_BTNHIGHLIGHT), colorShadow = GetSysColor(COLOR_BTNSHADOW); + auto oldpen = SelectPen(hdc, GetStockObject(DC_PEN)); + ::SetDCPenColor(hdc, pushed ? colorShadow : colorHighlight); + ::FillRect(hdc, rect, GetSysColorBrush(COLOR_BTNFACE)); + ::MoveToEx(hdc, rect.left, rect.bottom - 1, nullptr); + ::LineTo(hdc, rect.left, rect.top); + ::LineTo(hdc, rect.right - 1, rect.top); + ::SetDCPenColor(hdc, pushed ? colorHighlight : colorShadow); + ::LineTo(hdc, rect.right - 1, rect.bottom - 1); + ::LineTo(hdc, rect.left, rect.bottom - 1); + SelectPen(hdc, oldpen); + } + + if(lpszText && lpszText[0]) + { + rect.DeflateRect(width, width); + if(pushed) + { + rect.top += width; + rect.left += width; + } + ::SetTextColor(hdc, GetSysColor(disabled ? COLOR_GRAYTEXT : COLOR_BTNTEXT)); + ::SetBkMode(hdc, TRANSPARENT); + rect.top += topMargin; + auto oldfont = SelectFont(hdc, CMainFrame::GetGUIFont()); + DrawTextT(hdc, lpszText, -1, &rect, textFlags | DT_SINGLELINE | DT_NOPREFIX); + SelectFont(hdc, oldfont); + } +} + + +void DrawButtonRect(HDC hdc, const RECT *lpRect, LPCSTR lpszText, BOOL bDisabled, BOOL bPushed, DWORD dwFlags, uint32 topMargin) +{ + DrawButtonRectImpl(hdc, *lpRect, lpszText, bDisabled, bPushed, dwFlags, topMargin); +} + + +void DrawButtonRect(HDC hdc, const RECT *lpRect, LPCWSTR lpszText, BOOL bDisabled, BOOL bPushed, DWORD dwFlags, uint32 topMargin) +{ + DrawButtonRectImpl(hdc, *lpRect, lpszText, bDisabled, bPushed, dwFlags, topMargin); +} + + + +////////////////////////////////////////////////////////////////////////////////// +// Misc functions + + +void ErrorBox(UINT nStringID, CWnd *parent) +{ + CString str; + BOOL resourceLoaded = str.LoadString(nStringID); + if(!resourceLoaded) + { + str.Format(_T("Resource string %u not found."), nStringID); + } + MPT_ASSERT(resourceLoaded); + Reporting::CustomNotification(str, _T("Error!"), MB_OK | MB_ICONERROR, parent); +} + + +CString GetWindowTextString(const CWnd &wnd) +{ + CString result; + wnd.GetWindowText(result); + return result; +} + + +mpt::ustring GetWindowTextUnicode(const CWnd &wnd) +{ + return mpt::ToUnicode(GetWindowTextString(wnd)); +} + + +//////////////////////////////////////////////////////////////////////////////// +// CFastBitmap 8-bit output / 4-bit input +// useful for lots of small blits with color mapping +// combined in one big blit + +void CFastBitmap::Init(MODPLUGDIB *lpTextDib) +{ + m_nBlendOffset = 0; + m_pTextDib = lpTextDib; + MemsetZero(m_Dib.bmiHeader); + m_nTextColor = 0; + m_nBkColor = 1; + m_Dib.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + m_Dib.bmiHeader.biWidth = 0; // Set later + m_Dib.bmiHeader.biHeight = 0; // Ditto + m_Dib.bmiHeader.biPlanes = 1; + m_Dib.bmiHeader.biBitCount = 8; + m_Dib.bmiHeader.biCompression = BI_RGB; + m_Dib.bmiHeader.biSizeImage = 0; + m_Dib.bmiHeader.biXPelsPerMeter = 96; + m_Dib.bmiHeader.biYPelsPerMeter = 96; + m_Dib.bmiHeader.biClrUsed = 0; + m_Dib.bmiHeader.biClrImportant = 256; // MAX_MODPALETTECOLORS; + m_n4BitPalette[0] = (BYTE)m_nTextColor; + m_n4BitPalette[4] = MODCOLOR_SEPSHADOW; + m_n4BitPalette[12] = MODCOLOR_SEPFACE; + m_n4BitPalette[14] = MODCOLOR_SEPHILITE; + m_n4BitPalette[15] = (BYTE)m_nBkColor; +} + + +void CFastBitmap::Blit(HDC hdc, int x, int y, int cx, int cy) +{ + SetDIBitsToDevice( hdc, + x, + y, + cx, + cy, + 0, + m_Dib.bmiHeader.biHeight - cy, + 0, + m_Dib.bmiHeader.biHeight, + &m_Dib.DibBits[0], + (LPBITMAPINFO)&m_Dib, + DIB_RGB_COLORS); +} + + +void CFastBitmap::SetColor(UINT nIndex, COLORREF cr) +{ + if (nIndex < 256) + { + m_Dib.bmiColors[nIndex].rgbRed = GetRValue(cr); + m_Dib.bmiColors[nIndex].rgbGreen = GetGValue(cr); + m_Dib.bmiColors[nIndex].rgbBlue = GetBValue(cr); + } +} + + +void CFastBitmap::SetAllColors(UINT nBaseIndex, UINT nColors, COLORREF *pcr) +{ + for (UINT i=0; i<nColors; i++) + { + SetColor(nBaseIndex+i, pcr[i]); + } +} + + +void CFastBitmap::SetBlendColor(COLORREF cr) +{ + UINT r = GetRValue(cr); + UINT g = GetGValue(cr); + UINT b = GetBValue(cr); + for (UINT i=0; i<BLEND_OFFSET; i++) + { + UINT m = (m_Dib.bmiColors[i].rgbRed >> 2) + + (m_Dib.bmiColors[i].rgbGreen >> 1) + + (m_Dib.bmiColors[i].rgbBlue >> 2); + m_Dib.bmiColors[i|BLEND_OFFSET].rgbRed = static_cast<BYTE>((m + r)>>1); + m_Dib.bmiColors[i|BLEND_OFFSET].rgbGreen = static_cast<BYTE>((m + g)>>1); + m_Dib.bmiColors[i|BLEND_OFFSET].rgbBlue = static_cast<BYTE>((m + b)>>1); + } +} + + +// Monochrome 4-bit bitmap (0=text, !0 = back) +void CFastBitmap::TextBlt(int x, int y, int cx, int cy, int srcx, int srcy, MODPLUGDIB *lpdib) +{ + const uint8 *psrc; + BYTE *pdest; + UINT x1, x2; + int srcwidth, srcinc; + + m_n4BitPalette[0] = (BYTE)m_nTextColor; + m_n4BitPalette[15] = (BYTE)m_nBkColor; + if (x < 0) + { + cx += x; + x = 0; + } + if (y < 0) + { + cy += y; + y = 0; + } + if ((x >= m_Dib.bmiHeader.biWidth) || (y >= m_Dib.bmiHeader.biHeight)) return; + if (x+cx >= m_Dib.bmiHeader.biWidth) cx = m_Dib.bmiHeader.biWidth - x; + if (y+cy >= m_Dib.bmiHeader.biHeight) cy = m_Dib.bmiHeader.biHeight - y; + if (!lpdib) lpdib = m_pTextDib; + if ((cx <= 0) || (cy <= 0) || (!lpdib)) return; + srcwidth = (lpdib->bmiHeader.biWidth+1) >> 1; + srcinc = srcwidth; + if (((int)lpdib->bmiHeader.biHeight) > 0) + { + srcy = lpdib->bmiHeader.biHeight - 1 - srcy; + srcinc = -srcinc; + } + x1 = srcx & 1; + x2 = x1 + cx; + pdest = &m_Dib.DibBits[((m_Dib.bmiHeader.biHeight - 1 - y) << m_nXShiftFactor) + x]; + psrc = lpdib->lpDibBits + (srcx >> 1) + (srcy * srcwidth); + for (int iy=0; iy<cy; iy++) + { + uint8 *p = pdest; + UINT ix = x1; + if (ix&1) + { + UINT b = psrc[ix >> 1]; + *p++ = m_n4BitPalette[b & 0x0F]+m_nBlendOffset; + ix++; + } + while (ix+1 < x2) + { + UINT b = psrc[ix >> 1]; + p[0] = m_n4BitPalette[b >> 4]+m_nBlendOffset; + p[1] = m_n4BitPalette[b & 0x0F]+m_nBlendOffset; + ix+=2; + p+=2; + } + if (x2&1) + { + UINT b = psrc[ix >> 1]; + *p++ = m_n4BitPalette[b >> 4]+m_nBlendOffset; + } + pdest -= m_Dib.bmiHeader.biWidth; + psrc += srcinc; + } +} + + +void CFastBitmap::SetSize(int x, int y) +{ + if(x > 4) + { + // Compute the required shift factor for obtaining a power-of-two bitmap width + m_nXShiftFactor = 1; + x--; + while(x >>= 1) + { + m_nXShiftFactor++; + } + } else + { + // Bitmaps rows are aligned to 4 bytes, so let this bitmap be exactly 4 pixels wide. + m_nXShiftFactor = 2; + } + + x = (1 << m_nXShiftFactor); + if(m_Dib.DibBits.size() != static_cast<size_t>(y << m_nXShiftFactor)) m_Dib.DibBits.resize(y << m_nXShiftFactor); + m_Dib.bmiHeader.biWidth = x; + m_Dib.bmiHeader.biHeight = y; +} + + +/////////////////////////////////////////////////////////////////////////////////// +// +// DirectX Plugins +// + +void CTrackApp::InitializeDXPlugins() +{ + m_pPluginManager = new CVstPluginManager; + const size_t numPlugins = GetSettings().Read<int32>(U_("VST Plugins"), U_("NumPlugins"), 0); + + bool maskCrashes = TrackerSettings::Instance().BrokenPluginsWorkaroundVSTMaskAllCrashes; + + std::vector<VSTPluginLib *> nonFoundPlugs; + const mpt::PathString failedPlugin = GetSettings().Read<mpt::PathString>(U_("VST Plugins"), U_("FailedPlugin"), P_("")); + + CDialog pluginScanDlg; + CWnd *textWnd = nullptr; + DWORD64 scanStart = Util::GetTickCount64(); + + // Read tags for built-in plugins + for(auto plug : *m_pPluginManager) + { + mpt::ustring key = MPT_UFORMAT("Plugin{}{}.Tags")(mpt::ufmt::HEX0<8>(plug->pluginId1), mpt::ufmt::HEX0<8>(plug->pluginId2)); + plug->tags = GetSettings().Read<mpt::ustring>(U_("VST Plugins"), key, mpt::ustring()); + } + + // Restructured plugin cache + if(TrackerSettings::Instance().PreviousSettingsVersion < MPT_V("1.27.00.15")) + { + DeleteFile(m_szPluginCacheFileName.AsNative().c_str()); + GetPluginCache().ForgetAll(); + } + + m_pPluginManager->reserve(numPlugins); + auto plugIDFormat = MPT_UFORMAT("Plugin{}"); + auto scanFormat = MPT_CFORMAT("Scanning Plugin {} / {}...\n{}"); + auto tagFormat = MPT_UFORMAT("Plugin{}.Tags"); + for(size_t plug = 0; plug < numPlugins; plug++) + { + mpt::PathString plugPath = GetSettings().Read<mpt::PathString>(U_("VST Plugins"), plugIDFormat(plug), mpt::PathString()); + if(!plugPath.empty()) + { + plugPath = PathInstallRelativeToAbsolute(plugPath); + + if(!pluginScanDlg.m_hWnd && Util::GetTickCount64() >= scanStart + 2000) + { + // If this is taking too long, show the user what they're waiting for. + pluginScanDlg.Create(IDD_SCANPLUGINS, gpSplashScreen); + pluginScanDlg.ShowWindow(SW_SHOW); + pluginScanDlg.CenterWindow(gpSplashScreen); + textWnd = pluginScanDlg.GetDlgItem(IDC_SCANTEXT); + } else if(pluginScanDlg.m_hWnd && Util::GetTickCount64() >= scanStart + 30) + { + textWnd->SetWindowText(scanFormat(plug + 1, numPlugins + 1, plugPath)); + MSG msg; + while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + scanStart = Util::GetTickCount64(); + } + + if(plugPath == failedPlugin) + { + GetSettings().Remove(U_("VST Plugins"), U_("FailedPlugin")); + const CString text = _T("The following plugin has previously crashed OpenMPT during initialisation:\n\n") + failedPlugin.ToCString() + _T("\n\nDo you still want to load it?"); + if(Reporting::Confirm(text, false, true, &pluginScanDlg) == cnfNo) + { + continue; + } + } + + mpt::ustring plugTags = GetSettings().Read<mpt::ustring>(U_("VST Plugins"), tagFormat(plug), mpt::ustring()); + + bool plugFound = true; + VSTPluginLib *lib = m_pPluginManager->AddPlugin(plugPath, maskCrashes, plugTags, true, &plugFound); + if(!plugFound && lib != nullptr) + { + nonFoundPlugs.push_back(lib); + } + if(lib != nullptr && lib->libraryName == P_("MIDI Input Output") && lib->pluginId1 == PLUGMAGIC('V','s','t','P') && lib->pluginId2 == PLUGMAGIC('M','M','I','D')) + { + // This appears to be an old version of our MIDI I/O plugin, which is now built right into the main executable. + m_pPluginManager->RemovePlugin(lib); + } + } + } + GetPluginCache().Flush(); + if(pluginScanDlg.m_hWnd) + { + pluginScanDlg.DestroyWindow(); + } + if(!nonFoundPlugs.empty()) + { + PlugNotFoundDialog(nonFoundPlugs, nullptr).DoModal(); + } +} + + +void CTrackApp::UninitializeDXPlugins() +{ + if(!m_pPluginManager) return; + +#ifndef NO_PLUGINS + + size_t plugIndex = 0; + for(auto plug : *m_pPluginManager) + { + if(!plug->isBuiltIn) + { + mpt::PathString plugPath = plug->dllPath; + if(theApp.IsPortableMode()) + { + plugPath = PathAbsoluteToInstallRelative(plugPath); + } + theApp.GetSettings().Write<mpt::PathString>(U_("VST Plugins"), MPT_UFORMAT("Plugin{}")(plugIndex), plugPath); + + theApp.GetSettings().Write(U_("VST Plugins"), MPT_UFORMAT("Plugin{}.Tags")(plugIndex), plug->tags); + + plugIndex++; + } else + { + mpt::ustring key = MPT_UFORMAT("Plugin{}{}.Tags")(mpt::ufmt::HEX0<8>(plug->pluginId1), mpt::ufmt::HEX0<8>(plug->pluginId2)); + theApp.GetSettings().Write(U_("VST Plugins"), key, plug->tags); + } + } + theApp.GetSettings().Write(U_("VST Plugins"), U_("NumPlugins"), static_cast<uint32>(plugIndex)); +#endif // NO_PLUGINS + + delete m_pPluginManager; + m_pPluginManager = nullptr; +} + + +/////////////////////////////////////////////////////////////////////////////////// +// Internet-related functions + +bool CTrackApp::OpenURL(const char *url) +{ + if(!url) return false; + return OpenURL(mpt::PathString::FromUTF8(url)); +} + +bool CTrackApp::OpenURL(const std::string &url) +{ + return OpenURL(mpt::PathString::FromUTF8(url)); +} + +bool CTrackApp::OpenURL(const CString &url) +{ + return OpenURL(mpt::ToUnicode(url)); +} + +bool CTrackApp::OpenURL(const mpt::ustring &url) +{ + return OpenURL(mpt::PathString::FromUnicode(url)); +} + +bool CTrackApp::OpenURL(const mpt::PathString &lpszURL) +{ + if(!lpszURL.empty() && theApp.m_pMainWnd) + { + if(reinterpret_cast<INT_PTR>(ShellExecute( + theApp.m_pMainWnd->m_hWnd, + _T("open"), + lpszURL.AsNative().c_str(), + NULL, + NULL, + SW_SHOW)) >= 32) + { + return true; + } + } + return false; +} + + +CString CTrackApp::GetResamplingModeName(ResamplingMode mode, int length, bool addTaps) +{ + CString result; + switch(mode) + { + case SRCMODE_NEAREST: + result = (length > 1) ? _T("No Interpolation") : _T("None") ; + break; + case SRCMODE_LINEAR: + result = _T("Linear"); + break; + case SRCMODE_CUBIC: + result = _T("Cubic"); + break; + case SRCMODE_SINC8: + result = _T("Sinc"); + break; + case SRCMODE_SINC8LP: + result = _T("Sinc"); + break; + default: + MPT_ASSERT_NOTREACHED(); + break; + } + if(Resampling::HasAA(mode)) + { + result += (length > 1) ? _T(" + Low-Pass") : _T(" + LP"); + } + if(addTaps) + { + result += MPT_CFORMAT(" ({} tap{})")(Resampling::Length(mode), (Resampling::Length(mode) != 1) ? CString(_T("s")) : CString(_T(""))); + } + return result; +} + + +mpt::ustring CTrackApp::GetFriendlyMIDIPortName(const mpt::ustring &deviceName, bool isInputPort, bool addDeviceName) +{ + auto friendlyName = GetSettings().Read<mpt::ustring>(isInputPort ? U_("MIDI Input Ports") : U_("MIDI Output Ports"), deviceName, deviceName); + if(friendlyName.empty()) + return deviceName; + else if(addDeviceName && friendlyName != deviceName) + return friendlyName + UL_(" (") + deviceName + UL_(")"); + else + return friendlyName; +} + + +CString CTrackApp::GetFriendlyMIDIPortName(const CString &deviceName, bool isInputPort, bool addDeviceName) +{ + return mpt::ToCString(GetFriendlyMIDIPortName(mpt::ToUnicode(deviceName), isInputPort, addDeviceName)); +} + + +OPENMPT_NAMESPACE_END |