dlg_misc.cpp 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567
  1. /*
  2. * dlg_misc.cpp
  3. * ------------
  4. * Purpose: Implementation of various OpenMPT dialogs.
  5. * Notes : (currently none)
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #include "stdafx.h"
  10. #include "Mptrack.h"
  11. #include "Moddoc.h"
  12. #include "Mainfrm.h"
  13. #include "dlg_misc.h"
  14. #include "Dlsbank.h"
  15. #include "Childfrm.h"
  16. #include "../soundlib/plugins/PlugInterface.h"
  17. #include "ChannelManagerDlg.h"
  18. #include "TempoSwingDialog.h"
  19. #include "../soundlib/mod_specifications.h"
  20. #include "../common/version.h"
  21. #include "../common/mptStringBuffer.h"
  22. OPENMPT_NAMESPACE_BEGIN
  23. ///////////////////////////////////////////////////////////////////////
  24. // CModTypeDlg
  25. BEGIN_MESSAGE_MAP(CModTypeDlg, CDialog)
  26. //{{AFX_MSG_MAP(CModTypeDlg)
  27. ON_CBN_SELCHANGE(IDC_COMBO1, &CModTypeDlg::UpdateDialog)
  28. ON_CBN_SELCHANGE(IDC_COMBO_TEMPOMODE, &CModTypeDlg::OnTempoModeChanged)
  29. ON_COMMAND(IDC_CHECK_PT1X, &CModTypeDlg::OnPTModeChanged)
  30. ON_COMMAND(IDC_BUTTON1, &CModTypeDlg::OnTempoSwing)
  31. ON_COMMAND(IDC_BUTTON2, &CModTypeDlg::OnLegacyPlaybackSettings)
  32. ON_COMMAND(IDC_BUTTON3, &CModTypeDlg::OnDefaultBehaviour)
  33. ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &CModTypeDlg::OnToolTipNotify)
  34. //}}AFX_MSG_MAP
  35. END_MESSAGE_MAP()
  36. void CModTypeDlg::DoDataExchange(CDataExchange* pDX)
  37. {
  38. CDialog::DoDataExchange(pDX);
  39. //{{AFX_DATA_MAP(CModTypeDlg)
  40. DDX_Control(pDX, IDC_COMBO1, m_TypeBox);
  41. DDX_Control(pDX, IDC_COMBO2, m_ChannelsBox);
  42. DDX_Control(pDX, IDC_COMBO_TEMPOMODE, m_TempoModeBox);
  43. DDX_Control(pDX, IDC_COMBO_MIXLEVELS, m_PlugMixBox);
  44. DDX_Control(pDX, IDC_CHECK1, m_CheckBox1);
  45. DDX_Control(pDX, IDC_CHECK2, m_CheckBox2);
  46. DDX_Control(pDX, IDC_CHECK3, m_CheckBox3);
  47. DDX_Control(pDX, IDC_CHECK4, m_CheckBox4);
  48. DDX_Control(pDX, IDC_CHECK5, m_CheckBox5);
  49. DDX_Control(pDX, IDC_CHECK_PT1X, m_CheckBoxPT1x);
  50. DDX_Control(pDX, IDC_CHECK_AMIGALIMITS, m_CheckBoxAmigaLimits);
  51. //}}AFX_DATA_MAP
  52. }
  53. BOOL CModTypeDlg::OnInitDialog()
  54. {
  55. CDialog::OnInitDialog();
  56. m_nType = sndFile.GetType();
  57. m_nChannels = sndFile.GetNumChannels();
  58. m_tempoSwing = sndFile.m_tempoSwing;
  59. m_playBehaviour = sndFile.m_playBehaviour;
  60. initialized = false;
  61. // Mod types
  62. m_TypeBox.SetItemData(m_TypeBox.AddString(_T("ProTracker MOD")), MOD_TYPE_MOD);
  63. m_TypeBox.SetItemData(m_TypeBox.AddString(_T("Scream Tracker S3M")), MOD_TYPE_S3M);
  64. m_TypeBox.SetItemData(m_TypeBox.AddString(_T("FastTracker XM")), MOD_TYPE_XM);
  65. m_TypeBox.SetItemData(m_TypeBox.AddString(_T("Impulse Tracker IT")), MOD_TYPE_IT);
  66. m_TypeBox.SetItemData(m_TypeBox.AddString(_T("OpenMPT MPTM")), MOD_TYPE_MPT);
  67. switch(m_nType)
  68. {
  69. case MOD_TYPE_S3M: m_TypeBox.SetCurSel(1); break;
  70. case MOD_TYPE_XM: m_TypeBox.SetCurSel(2); break;
  71. case MOD_TYPE_IT: m_TypeBox.SetCurSel(3); break;
  72. case MOD_TYPE_MPT: m_TypeBox.SetCurSel(4); break;
  73. default: m_TypeBox.SetCurSel(0); break;
  74. }
  75. // Time signature information
  76. SetDlgItemInt(IDC_ROWSPERBEAT, sndFile.m_nDefaultRowsPerBeat);
  77. SetDlgItemInt(IDC_ROWSPERMEASURE, sndFile.m_nDefaultRowsPerMeasure);
  78. // Version information
  79. if(sndFile.m_dwCreatedWithVersion) SetDlgItemText(IDC_EDIT_CREATEDWITH, _T("OpenMPT ") + FormatVersionNumber(sndFile.m_dwCreatedWithVersion));
  80. SetDlgItemText(IDC_EDIT_SAVEDWITH, mpt::ToCString(sndFile.m_modFormat.madeWithTracker.empty() ? sndFile.m_modFormat.formatName : sndFile.m_modFormat.madeWithTracker));
  81. const int iconSize = Util::ScalePixels(32, m_hWnd);
  82. m_warnIcon = (HICON)::LoadImage(NULL, IDI_EXCLAMATION, IMAGE_ICON, iconSize, iconSize, LR_SHARED);
  83. UpdateDialog();
  84. initialized = true;
  85. EnableToolTips(TRUE);
  86. return TRUE;
  87. }
  88. CString CModTypeDlg::FormatVersionNumber(Version version)
  89. {
  90. return mpt::ToCString(version.ToUString() + (version.IsTestVersion() ? U_(" (test build)") : U_("")));
  91. }
  92. void CModTypeDlg::UpdateChannelCBox()
  93. {
  94. const MODTYPE type = static_cast<MODTYPE>(m_TypeBox.GetItemData(m_TypeBox.GetCurSel()));
  95. CHANNELINDEX currChanSel = static_cast<CHANNELINDEX>(m_ChannelsBox.GetItemData(m_ChannelsBox.GetCurSel()));
  96. const CHANNELINDEX minChans = CSoundFile::GetModSpecifications(type).channelsMin;
  97. const CHANNELINDEX maxChans = CSoundFile::GetModSpecifications(type).channelsMax;
  98. if(m_ChannelsBox.GetCount() < 1
  99. || m_ChannelsBox.GetItemData(0) != minChans
  100. || m_ChannelsBox.GetItemData(m_ChannelsBox.GetCount() - 1) != maxChans)
  101. {
  102. // Update channel list if number of supported channels has changed.
  103. if(m_ChannelsBox.GetCount() < 1) currChanSel = m_nChannels;
  104. m_ChannelsBox.ResetContent();
  105. CString s;
  106. for(CHANNELINDEX i = minChans; i <= maxChans; i++)
  107. {
  108. s.Format(_T("%u Channel%s"), i, (i != 1) ? _T("s") : _T(""));
  109. m_ChannelsBox.SetItemData(m_ChannelsBox.AddString(s), i);
  110. }
  111. Limit(currChanSel, minChans, maxChans);
  112. m_ChannelsBox.SetCurSel(currChanSel - minChans);
  113. }
  114. }
  115. void CModTypeDlg::UpdateDialog()
  116. {
  117. m_nType = static_cast<MODTYPE>(m_TypeBox.GetItemData(m_TypeBox.GetCurSel()));
  118. UpdateChannelCBox();
  119. m_CheckBox1.SetCheck(sndFile.m_SongFlags[SONG_LINEARSLIDES] ? BST_CHECKED : BST_UNCHECKED);
  120. m_CheckBox2.SetCheck(sndFile.m_SongFlags[SONG_FASTVOLSLIDES] ? BST_CHECKED : BST_UNCHECKED);
  121. m_CheckBox3.SetCheck(sndFile.m_SongFlags[SONG_ITOLDEFFECTS] ? BST_CHECKED : BST_UNCHECKED);
  122. m_CheckBox4.SetCheck(sndFile.m_SongFlags[SONG_ITCOMPATGXX] ? BST_CHECKED : BST_UNCHECKED);
  123. m_CheckBox5.SetCheck(sndFile.m_SongFlags[SONG_EXFILTERRANGE] ? BST_CHECKED : BST_UNCHECKED);
  124. m_CheckBoxPT1x.SetCheck(sndFile.m_SongFlags[SONG_PT_MODE] ? BST_CHECKED : BST_UNCHECKED);
  125. m_CheckBoxAmigaLimits.SetCheck(sndFile.m_SongFlags[SONG_AMIGALIMITS] ? BST_CHECKED : BST_UNCHECKED);
  126. const FlagSet<SongFlags> allowedFlags(sndFile.GetModSpecifications(m_nType).songFlags);
  127. m_CheckBox1.EnableWindow(allowedFlags[SONG_LINEARSLIDES]);
  128. m_CheckBox2.EnableWindow(allowedFlags[SONG_FASTVOLSLIDES]);
  129. m_CheckBox3.EnableWindow(allowedFlags[SONG_ITOLDEFFECTS]);
  130. m_CheckBox4.EnableWindow(allowedFlags[SONG_ITCOMPATGXX]);
  131. m_CheckBox5.EnableWindow(allowedFlags[SONG_EXFILTERRANGE]);
  132. m_CheckBoxPT1x.EnableWindow(allowedFlags[SONG_PT_MODE]);
  133. m_CheckBoxAmigaLimits.EnableWindow(allowedFlags[SONG_AMIGALIMITS]);
  134. // These two checkboxes are mutually exclusive and share the same screen space
  135. m_CheckBoxPT1x.ShowWindow(m_nType == MOD_TYPE_MOD ? SW_SHOW : SW_HIDE);
  136. m_CheckBox5.ShowWindow(m_nType != MOD_TYPE_MOD ? SW_SHOW : SW_HIDE);
  137. if(allowedFlags[SONG_PT_MODE]) OnPTModeChanged();
  138. // Tempo modes
  139. const TempoMode oldTempoMode = initialized ? static_cast<TempoMode>(m_TempoModeBox.GetItemData(m_TempoModeBox.GetCurSel())) : sndFile.m_nTempoMode;
  140. m_TempoModeBox.ResetContent();
  141. m_TempoModeBox.SetItemData(m_TempoModeBox.AddString(_T("Classic")), static_cast<DWORD_PTR>(TempoMode::Classic));
  142. if(m_nType == MOD_TYPE_MPT || (sndFile.GetType() != MOD_TYPE_MPT && sndFile.m_nTempoMode == TempoMode::Alternative))
  143. m_TempoModeBox.SetItemData(m_TempoModeBox.AddString(_T("Alternative")), static_cast<DWORD_PTR>(TempoMode::Alternative));
  144. if(m_nType == MOD_TYPE_MPT || (sndFile.GetType() != MOD_TYPE_MPT && sndFile.m_nTempoMode == TempoMode::Modern))
  145. m_TempoModeBox.SetItemData(m_TempoModeBox.AddString(_T("Modern (accurate)")), static_cast<DWORD_PTR>(TempoMode::Modern));
  146. m_TempoModeBox.SetCurSel(0);
  147. for(int i = m_TempoModeBox.GetCount(); i > 0; i--)
  148. {
  149. if(static_cast<TempoMode>(m_TempoModeBox.GetItemData(i)) == oldTempoMode)
  150. {
  151. m_TempoModeBox.SetCurSel(i);
  152. break;
  153. }
  154. }
  155. OnTempoModeChanged();
  156. // Mix levels
  157. const MixLevels oldMixLevels = initialized ? static_cast<MixLevels>(m_PlugMixBox.GetItemData(m_PlugMixBox.GetCurSel())) : sndFile.GetMixLevels();
  158. m_PlugMixBox.ResetContent();
  159. if(m_nType == MOD_TYPE_MPT || sndFile.GetMixLevels() == MixLevels::v1_17RC3) // In XM/IT, this is only shown for backwards compatibility with existing tunes
  160. m_PlugMixBox.SetItemData(m_PlugMixBox.AddString(_T("OpenMPT 1.17RC3")), static_cast<DWORD_PTR>(MixLevels::v1_17RC3));
  161. if(sndFile.GetMixLevels() == MixLevels::v1_17RC2) // Only shown for backwards compatibility with existing tunes
  162. m_PlugMixBox.SetItemData(m_PlugMixBox.AddString(_T("OpenMPT 1.17RC2")), static_cast<DWORD_PTR>(MixLevels::v1_17RC2));
  163. if(sndFile.GetMixLevels() == MixLevels::v1_17RC1) // Ditto
  164. m_PlugMixBox.SetItemData(m_PlugMixBox.AddString(_T("OpenMPT 1.17RC1")), static_cast<DWORD_PTR>(MixLevels::v1_17RC1));
  165. if(sndFile.GetMixLevels() == MixLevels::Original) // Ditto
  166. m_PlugMixBox.SetItemData(m_PlugMixBox.AddString(_T("Original (MPT 1.16)")), static_cast<DWORD_PTR>(MixLevels::Original));
  167. int compatMixMode = m_PlugMixBox.AddString(_T("Compatible"));
  168. m_PlugMixBox.SetItemData(compatMixMode, static_cast<DWORD_PTR>(MixLevels::Compatible));
  169. if(m_nType == MOD_TYPE_XM)
  170. m_PlugMixBox.SetItemData(m_PlugMixBox.AddString(_T("Compatible (FT2 Pan Law)")), static_cast<DWORD_PTR>(MixLevels::CompatibleFT2));
  171. // Default to compatible mix mode
  172. m_PlugMixBox.SetCurSel(compatMixMode);
  173. int mixCount = m_PlugMixBox.GetCount();
  174. for(int i = 0; i < mixCount; i++)
  175. {
  176. if(static_cast<MixLevels>(m_PlugMixBox.GetItemData(i)) == oldMixLevels)
  177. {
  178. m_PlugMixBox.SetCurSel(i);
  179. break;
  180. }
  181. }
  182. const bool XMorITorMPT = (m_nType & (MOD_TYPE_XM | MOD_TYPE_IT | MOD_TYPE_MPT));
  183. const bool isMPTM = (m_nType == MOD_TYPE_MPT);
  184. // Mixmode Box
  185. GetDlgItem(IDC_TEXT_MIXMODE)->EnableWindow(XMorITorMPT);
  186. m_PlugMixBox.EnableWindow(XMorITorMPT);
  187. // Tempo mode box
  188. m_TempoModeBox.EnableWindow(XMorITorMPT);
  189. GetDlgItem(IDC_ROWSPERBEAT)->EnableWindow(XMorITorMPT);
  190. GetDlgItem(IDC_ROWSPERMEASURE)->EnableWindow(XMorITorMPT);
  191. GetDlgItem(IDC_TEXT_ROWSPERBEAT)->EnableWindow(XMorITorMPT);
  192. GetDlgItem(IDC_TEXT_ROWSPERMEASURE)->EnableWindow(XMorITorMPT);
  193. GetDlgItem(IDC_TEXT_TEMPOMODE)->EnableWindow(XMorITorMPT);
  194. GetDlgItem(IDC_FRAME_TEMPOMODE)->EnableWindow(XMorITorMPT);
  195. // Compatibility settings
  196. const PlayBehaviourSet defaultBehaviour = CSoundFile::GetDefaultPlaybackBehaviour(m_nType);
  197. const PlayBehaviourSet supportedBehaviour = CSoundFile::GetSupportedPlaybackBehaviour(m_nType);
  198. bool enableSetDefaults = false, showWarning = false;
  199. if(m_nType & (MOD_TYPE_MPT | MOD_TYPE_IT | MOD_TYPE_XM))
  200. {
  201. for(size_t i = 0; i < m_playBehaviour.size(); i++)
  202. {
  203. // Some flags are not really important for "default" behaviour.
  204. if(defaultBehaviour[i] != m_playBehaviour[i]
  205. && i != MSF_COMPATIBLE_PLAY
  206. && i != kFT2VolumeRamping)
  207. {
  208. enableSetDefaults = true;
  209. if(!isMPTM)
  210. {
  211. showWarning = true;
  212. break;
  213. }
  214. }
  215. if(isMPTM && m_playBehaviour[i] && !supportedBehaviour[i])
  216. {
  217. enableSetDefaults = true;
  218. showWarning = true;
  219. break;
  220. }
  221. }
  222. }
  223. static_cast<CStatic *>(GetDlgItem(IDC_STATIC1))->SetIcon(showWarning ? m_warnIcon : nullptr);
  224. GetDlgItem(IDC_STATIC2)->SetWindowText(showWarning
  225. ? _T("Playback settings have been set to legacy compatibility mode. Click \"Set Defaults\" to use the recommended settings instead.")
  226. : _T("Compatibility settings are currently optimal. It is advised to not edit them."));
  227. GetDlgItem(IDC_BUTTON3)->EnableWindow(enableSetDefaults ? TRUE : FALSE);
  228. }
  229. void CModTypeDlg::OnPTModeChanged()
  230. {
  231. // PT1/2 mode enforces Amiga limits
  232. const bool ptMode = IsDlgButtonChecked(IDC_CHECK_PT1X) != BST_UNCHECKED;
  233. m_CheckBoxAmigaLimits.EnableWindow(!ptMode);
  234. if(ptMode) m_CheckBoxAmigaLimits.SetCheck(BST_CHECKED);
  235. }
  236. void CModTypeDlg::OnTempoModeChanged()
  237. {
  238. GetDlgItem(IDC_BUTTON1)->EnableWindow(static_cast<TempoMode>(m_TempoModeBox.GetItemData(m_TempoModeBox.GetCurSel())) == TempoMode::Modern);
  239. }
  240. void CModTypeDlg::OnTempoSwing()
  241. {
  242. const ROWINDEX oldRPB = sndFile.m_nDefaultRowsPerBeat;
  243. const ROWINDEX oldRPM = sndFile.m_nDefaultRowsPerMeasure;
  244. const TempoMode oldMode = sndFile.m_nTempoMode;
  245. // Temporarily apply new tempo signature for preview
  246. const ROWINDEX newRPB = std::clamp(static_cast<ROWINDEX>(GetDlgItemInt(IDC_ROWSPERBEAT)), ROWINDEX(1), MAX_ROWS_PER_BEAT);
  247. const ROWINDEX newRPM = std::clamp(static_cast<ROWINDEX>(GetDlgItemInt(IDC_ROWSPERMEASURE)), newRPB, MAX_ROWS_PER_BEAT);
  248. sndFile.m_nDefaultRowsPerBeat = newRPB;
  249. sndFile.m_nDefaultRowsPerMeasure = newRPM;
  250. sndFile.m_nTempoMode = TempoMode::Modern;
  251. m_tempoSwing.resize(newRPB, TempoSwing::Unity);
  252. CTempoSwingDlg dlg(this, m_tempoSwing, sndFile);
  253. if(dlg.DoModal() == IDOK)
  254. {
  255. m_tempoSwing = dlg.m_tempoSwing;
  256. }
  257. sndFile.m_nDefaultRowsPerBeat = oldRPB;
  258. sndFile.m_nDefaultRowsPerMeasure = oldRPM;
  259. sndFile.m_nTempoMode = oldMode;
  260. }
  261. void CModTypeDlg::OnLegacyPlaybackSettings()
  262. {
  263. CLegacyPlaybackSettingsDlg dlg(this, m_playBehaviour, m_nType);
  264. if(dlg.DoModal() == IDOK)
  265. {
  266. m_playBehaviour = dlg.GetPlayBehaviour();
  267. }
  268. UpdateDialog();
  269. }
  270. void CModTypeDlg::OnDefaultBehaviour()
  271. {
  272. m_playBehaviour = CSoundFile::GetDefaultPlaybackBehaviour(m_nType);
  273. UpdateDialog();
  274. }
  275. bool CModTypeDlg::VerifyData()
  276. {
  277. const int newRPB = GetDlgItemInt(IDC_ROWSPERBEAT);
  278. const int newRPM = GetDlgItemInt(IDC_ROWSPERMEASURE);
  279. if(newRPB > newRPM)
  280. {
  281. Reporting::Warning("Error: Rows per measure must be greater than or equal to rows per beat.");
  282. GetDlgItem(IDC_ROWSPERMEASURE)->SetFocus();
  283. return false;
  284. }
  285. if(newRPB == 0 && static_cast<TempoMode>(m_TempoModeBox.GetItemData(m_TempoModeBox.GetCurSel())) == TempoMode::Modern)
  286. {
  287. Reporting::Warning("Error: Rows per beat must be greater than 0 in modern tempo mode.");
  288. GetDlgItem(IDC_ROWSPERBEAT)->SetFocus();
  289. return false;
  290. }
  291. int sel = static_cast<int>(m_ChannelsBox.GetItemData(m_ChannelsBox.GetCurSel()));
  292. MODTYPE type = static_cast<MODTYPE>(m_TypeBox.GetItemData(m_TypeBox.GetCurSel()));
  293. CHANNELINDEX maxChans = CSoundFile::GetModSpecifications(type).channelsMax;
  294. if(sel > maxChans)
  295. {
  296. CString error;
  297. error.Format(_T("Error: Maximum number of channels for this module type is %u."), maxChans);
  298. Reporting::Warning(error);
  299. return false;
  300. }
  301. if(maxChans < sndFile.GetNumChannels())
  302. {
  303. if(Reporting::Confirm("New module type supports less channels than currently used, and reducing channel number is required. Continue?") != cnfYes)
  304. return false;
  305. }
  306. return true;
  307. }
  308. void CModTypeDlg::OnOK()
  309. {
  310. if (!VerifyData())
  311. return;
  312. int sel = m_TypeBox.GetCurSel();
  313. if (sel >= 0)
  314. {
  315. m_nType = static_cast<MODTYPE>(m_TypeBox.GetItemData(sel));
  316. }
  317. const auto &newModSpecs = sndFile.GetModSpecifications(m_nType);
  318. sndFile.m_SongFlags.set(SONG_LINEARSLIDES, m_CheckBox1.GetCheck() != BST_UNCHECKED);
  319. sndFile.m_SongFlags.set(SONG_FASTVOLSLIDES, m_CheckBox2.GetCheck() != BST_UNCHECKED);
  320. sndFile.m_SongFlags.set(SONG_ITOLDEFFECTS, m_CheckBox3.GetCheck() != BST_UNCHECKED);
  321. sndFile.m_SongFlags.set(SONG_ITCOMPATGXX, m_CheckBox4.GetCheck() != BST_UNCHECKED);
  322. sndFile.m_SongFlags.set(SONG_EXFILTERRANGE, m_CheckBox5.GetCheck() != BST_UNCHECKED);
  323. sndFile.m_SongFlags.set(SONG_PT_MODE, m_CheckBoxPT1x.GetCheck() != BST_UNCHECKED);
  324. sndFile.m_SongFlags.set(SONG_AMIGALIMITS, m_CheckBoxAmigaLimits.GetCheck() != BST_UNCHECKED);
  325. sel = m_ChannelsBox.GetCurSel();
  326. if (sel >= 0)
  327. {
  328. m_nChannels = static_cast<CHANNELINDEX>(m_ChannelsBox.GetItemData(sel));
  329. }
  330. sndFile.m_nDefaultRowsPerBeat = std::min(static_cast<ROWINDEX>(GetDlgItemInt(IDC_ROWSPERBEAT)), MAX_ROWS_PER_BEAT);
  331. sndFile.m_nDefaultRowsPerMeasure = std::min(static_cast<ROWINDEX>(GetDlgItemInt(IDC_ROWSPERMEASURE)), MAX_ROWS_PER_BEAT);
  332. sel = m_TempoModeBox.GetCurSel();
  333. if(sel >= 0)
  334. {
  335. const auto oldMode = sndFile.m_nTempoMode;
  336. sndFile.m_nTempoMode = static_cast<TempoMode>(m_TempoModeBox.GetItemData(sel));
  337. if(oldMode == TempoMode::Modern && sndFile.m_nTempoMode != TempoMode::Modern)
  338. {
  339. double newTempo = sndFile.m_nDefaultTempo.ToDouble() * (sndFile.m_nDefaultSpeed * sndFile.m_nDefaultRowsPerBeat) / ((sndFile.m_nTempoMode == TempoMode::Classic) ? 24 : 60);
  340. if(!newModSpecs.hasFractionalTempo)
  341. newTempo = std::round(newTempo);
  342. sndFile.m_nDefaultTempo = Clamp(TEMPO(newTempo), newModSpecs.GetTempoMin(), newModSpecs.GetTempoMax());
  343. }
  344. }
  345. if(sndFile.m_nTempoMode == TempoMode::Modern)
  346. {
  347. sndFile.m_tempoSwing = m_tempoSwing;
  348. if(!sndFile.m_tempoSwing.empty())
  349. sndFile.m_tempoSwing.resize(sndFile.m_nDefaultRowsPerBeat);
  350. } else
  351. {
  352. sndFile.m_tempoSwing.clear();
  353. }
  354. sel = m_PlugMixBox.GetCurSel();
  355. if(sel >= 0)
  356. {
  357. sndFile.SetMixLevels(static_cast<MixLevels>(m_PlugMixBox.GetItemData(sel)));
  358. }
  359. PlayBehaviourSet allowedFlags = CSoundFile::GetSupportedPlaybackBehaviour(m_nType);
  360. for(size_t i = 0; i < kMaxPlayBehaviours; i++)
  361. {
  362. // Only set those flags which are supported by the new format or were already enabled previously
  363. sndFile.m_playBehaviour.set(i, m_playBehaviour[i] && (allowedFlags[i] || (sndFile.m_playBehaviour[i] && sndFile.GetType() == m_nType)));
  364. }
  365. DestroyIcon(m_warnIcon);
  366. CDialog::OnOK();
  367. }
  368. BOOL CModTypeDlg::OnToolTipNotify(UINT, NMHDR *pNMHDR, LRESULT *)
  369. {
  370. TOOLTIPTEXT *pTTT = (TOOLTIPTEXT*)pNMHDR;
  371. UINT_PTR nID = pNMHDR->idFrom;
  372. if(pTTT->uFlags & TTF_IDISHWND)
  373. {
  374. // idFrom is actually the HWND of the tool
  375. nID = ::GetDlgCtrlID((HWND)nID);
  376. }
  377. mpt::tstring text;
  378. switch(nID)
  379. {
  380. case IDC_CHECK1:
  381. text = _T("Note slides always slide the same amount, not depending on the sample frequency.");
  382. break;
  383. case IDC_CHECK2:
  384. text = _T("Old Scream Tracker 3 volume slide behaviour (not recommended).");
  385. break;
  386. case IDC_CHECK3:
  387. text = _T("Play some effects like in early versions of Impulse Tracker (not recommended).");
  388. break;
  389. case IDC_CHECK4:
  390. text = _T("Gxx and Exx/Fxx won't share effect memory. Gxx resets instrument envelopes.");
  391. break;
  392. case IDC_CHECK5:
  393. text = _T("The resonant filter's frequency range is increased from about 5kHz to 10kHz.");
  394. break;
  395. case IDC_CHECK_PT1X:
  396. text = _T("Enforce Amiga frequency limits, ProTracker offset bug emulation.");
  397. break;
  398. case IDC_COMBO_MIXLEVELS:
  399. text = _T("Mixing method of sample and instrument plugin levels.");
  400. break;
  401. case IDC_BUTTON1:
  402. if(!GetDlgItem(IDC_BUTTON1)->IsWindowEnabled())
  403. {
  404. text = _T("Tempo swing is only available in modern tempo mode.");
  405. } else
  406. {
  407. text = _T("Swing setting: ");
  408. if(m_tempoSwing.empty())
  409. {
  410. text += _T("Default");
  411. } else
  412. {
  413. for(size_t i = 0; i < m_tempoSwing.size(); i++)
  414. {
  415. if(i > 0)
  416. text += _T(" / ");
  417. text += MPT_TFORMAT("{}%")(Util::muldivr(m_tempoSwing[i], 100, TempoSwing::Unity));
  418. }
  419. }
  420. }
  421. }
  422. mpt::String::WriteWinBuf(pTTT->szText) = text;
  423. return TRUE;
  424. }
  425. //////////////////////////////////////////////////////////////////////////////
  426. // Legacy Playback Settings dialog
  427. BEGIN_MESSAGE_MAP(CLegacyPlaybackSettingsDlg, ResizableDialog)
  428. ON_COMMAND(IDC_BUTTON1, &CLegacyPlaybackSettingsDlg::OnSelectDefaults)
  429. ON_EN_UPDATE(IDC_EDIT1, &CLegacyPlaybackSettingsDlg::OnFilterStringChanged)
  430. ON_CLBN_CHKCHANGE(IDC_LIST1, &CLegacyPlaybackSettingsDlg::UpdateSelectDefaults)
  431. END_MESSAGE_MAP()
  432. void CLegacyPlaybackSettingsDlg::DoDataExchange(CDataExchange* pDX)
  433. {
  434. ResizableDialog::DoDataExchange(pDX);
  435. DDX_Control(pDX, IDC_LIST1, m_CheckList);
  436. }
  437. BOOL CLegacyPlaybackSettingsDlg::OnInitDialog()
  438. {
  439. ResizableDialog::OnInitDialog();
  440. OnFilterStringChanged();
  441. UpdateSelectDefaults();
  442. return TRUE;
  443. }
  444. void CLegacyPlaybackSettingsDlg::OnSelectDefaults()
  445. {
  446. const int count = m_CheckList.GetCount();
  447. m_playBehaviour = CSoundFile::GetDefaultPlaybackBehaviour(m_modType);
  448. for(int i = 0; i < count; i++)
  449. {
  450. m_CheckList.SetCheck(i, m_playBehaviour[m_CheckList.GetItemData(i)] ? BST_CHECKED : BST_UNCHECKED);
  451. }
  452. }
  453. void CLegacyPlaybackSettingsDlg::UpdateSelectDefaults()
  454. {
  455. const int count = m_CheckList.GetCount();
  456. for(int i = 0; i < count; i++)
  457. {
  458. m_playBehaviour.set(m_CheckList.GetItemData(i), m_CheckList.GetCheck(i) != BST_UNCHECKED);
  459. }
  460. const auto defaults = CSoundFile::GetDefaultPlaybackBehaviour(m_modType);
  461. GetDlgItem(IDC_BUTTON1)->EnableWindow(m_playBehaviour != defaults ? TRUE : FALSE);
  462. }
  463. void CLegacyPlaybackSettingsDlg::OnFilterStringChanged()
  464. {
  465. CString s;
  466. GetDlgItemText(IDC_EDIT1, s);
  467. const bool filterActive = !s.IsEmpty();
  468. s.MakeLower();
  469. m_CheckList.SetRedraw(FALSE);
  470. m_CheckList.ResetContent();
  471. const auto allowedFlags = CSoundFile::GetSupportedPlaybackBehaviour(m_modType);
  472. for(size_t i = 0; i < kMaxPlayBehaviours; i++)
  473. {
  474. const TCHAR *desc = _T("");
  475. switch(i)
  476. {
  477. case MSF_COMPATIBLE_PLAY: continue;
  478. case kMPTOldSwingBehaviour: desc = _T("OpenMPT 1.17 compatible random variation behaviour for instruments"); break;
  479. case kMIDICCBugEmulation: desc = _T("Plugin volume MIDI CC bug emulation"); break;
  480. case kOldMIDIPitchBends: desc = _T("Old Pitch Wheel behaviour for instrument plugins"); break;
  481. case kFT2VolumeRamping: desc = _T("Use smooth Fasttracker 2 volume ramping"); break;
  482. case kMODVBlankTiming: desc = _T("VBlank timing: F20 and above sets speed instead of tempo"); break;
  483. case kSlidesAtSpeed1: desc = _T("Execute regular portamento slides at speed 1"); break;
  484. case kPeriodsAreHertz: desc = _T("Compute note frequency in Hertz rather than periods"); break;
  485. case kTempoClamp: desc = _T("Clamp tempo to 32-255 range"); break;
  486. case kPerChannelGlobalVolSlide: desc = _T("Global volume slide memory is per-channel"); break;
  487. case kPanOverride: desc = _T("Panning commands override surround and random pan variation"); break;
  488. case kITInstrWithoutNote: desc = _T("Retrigger instrument envelopes on instrument change"); break;
  489. case kITVolColFinePortamento: desc = _T("Volume column portamento never does fine portamento"); break;
  490. case kITArpeggio: desc = _T("IT arpeggio algorithm"); break;
  491. case kITOutOfRangeDelay: desc = _T("Out-of-range delay commands queue new instrument"); break;
  492. case kITPortaMemoryShare: desc = _T("Gxx shares memory with Exx and Fxx"); break;
  493. case kITPatternLoopTargetReset: desc = _T("After finishing a pattern loop, set the pattern loop target to the next row"); break;
  494. case kITFT2PatternLoop: desc = _T("Nested pattern loop behaviour"); break;
  495. case kITPingPongNoReset: desc = _T("Do not reset ping pong direction with instrument numbers"); break;
  496. case kITEnvelopeReset: desc = _T("IT envelope reset behaviour"); break;
  497. case kITClearOldNoteAfterCut: desc = _T("Forget the previous note after cutting it"); break;
  498. case kITVibratoTremoloPanbrello: desc = _T("More IT-like Vibrato, Tremolo and Panbrello handling"); break;
  499. case kITTremor: desc = _T("Ixx behaves like in IT"); break;
  500. case kITRetrigger: desc = _T("Qxx behaves like in IT"); break;
  501. case kITMultiSampleBehaviour: desc = _T("Properly update C-5 frequency when changing note in multisampled instrument"); break;
  502. case kITPortaTargetReached: desc = _T("Clear portamento target after it has been reached"); break;
  503. case kITPatternLoopBreak: desc = _T("Do not reset loop count on pattern break"); break;
  504. case kITOffset: desc = _T("Offset after sample end is treated like in IT"); break;
  505. case kITSwingBehaviour: desc = _T("Volume and panning random variation work more like in IT"); break;
  506. case kITNNAReset: desc = _T("NNA is reset on every note change, not every instrument change"); break;
  507. case kITSCxStopsSample: desc = _T("SCx really stops the sample and does not just mute it"); break;
  508. case kITEnvelopePositionHandling: desc = _T("IT-style envelope position advance + enable/disable behaviour"); break;
  509. case kITPortamentoInstrument: desc = _T("More compatible instrument change + portamento"); break;
  510. case kITPingPongMode: desc = _T("Do not repeat last sample point in ping pong loop, like IT's software mixer"); break;
  511. case kITRealNoteMapping: desc = _T("Use triggered note rather than translated note for PPS and DNA note check"); break;
  512. case kITHighOffsetNoRetrig: desc = _T("SAx does not apply an offset effect to a note next to it"); break;
  513. case kITFilterBehaviour: desc = _T("User IT's filter coefficients (unless extended filter range is used) and behaviour"); break;
  514. case kITNoSurroundPan: desc = _T("Panning modulation is disabled on surround channels"); break;
  515. case kITShortSampleRetrig: desc = _T("Do not retrigger already stopped channels"); break;
  516. case kITPortaNoNote: desc = _T("Do not apply any portamento if no previous note is playing"); break;
  517. case kITFT2DontResetNoteOffOnPorta:
  518. if(m_modType == MOD_TYPE_XM)
  519. desc = _T("Reset note-off on portamento if there is an instrument number");
  520. else
  521. desc = _T("Reset note-off on portamento if there is an instrument number in Compatible Gxx mode");
  522. break;
  523. case kITVolColMemory: desc = _T("Volume column effects share their memory with the effect column"); break;
  524. case kITPortamentoSwapResetsPos: desc = _T("Portamento with sample swap plays the new sample from the beginning"); break;
  525. case kITEmptyNoteMapSlot: desc = _T("Ignore instrument note map entries with no note completely"); break;
  526. case kITFirstTickHandling: desc = _T("IT-style first tick handling"); break;
  527. case kITSampleAndHoldPanbrello: desc = _T("IT-style sample&hold panbrello waveform"); break;
  528. case kITClearPortaTarget: desc = _T("New notes reset portamento target in IT"); break;
  529. case kITPanbrelloHold: desc = _T("Do not reset panbrello effect until next note or panning effect"); break;
  530. case kITPanningReset: desc = _T("Sample and instrument panning is only applied on note change, not instrument change"); break;
  531. case kITPatternLoopWithJumpsOld: desc = _T("Bxx on the same row as SBx terminates the loop in IT"); break;
  532. case kITInstrWithNoteOff: desc = _T("Instrument number with note-off recalls default volume"); break;
  533. case kFT2Arpeggio: desc = _T("FT2 arpeggio algorithm"); break;
  534. case kFT2Retrigger: desc = _T("Rxx behaves like in FT2"); break;
  535. case kFT2VolColVibrato: desc = _T("Vibrato speed in volume column does not actually execute the vibrato effect"); break;
  536. case kFT2PortaNoNote: desc = _T("Do not play portamento-ed note if no previous note is playing"); break;
  537. case kFT2KeyOff: desc = _T("FT2-style Kxx handling"); break;
  538. case kFT2PanSlide: desc = _T("Volume-column pan slides are finer"); break;
  539. case kFT2ST3OffsetOutOfRange: desc = _T("Offset past sample end stops the note"); break;
  540. case kFT2RestrictXCommand: desc = _T("Do not allow ModPlug extensions to X command"); break;
  541. case kFT2RetrigWithNoteDelay: desc = _T("Retrigger envelopes if there is a note delay with no note"); break;
  542. case kFT2SetPanEnvPos: desc = _T("Lxx only sets the pan envelope position if the volume envelope's sustain flag is set"); break;
  543. case kFT2PortaIgnoreInstr: desc = _T("Portamento with instrument number applies volume settings of new sample, but not the new sample itself"); break;
  544. case kFT2VolColMemory: desc = _T("No volume column memory"); break;
  545. case kFT2LoopE60Restart: desc = _T("Next pattern starts on the same row as the last E60 command"); break;
  546. case kFT2ProcessSilentChannels: desc = _T("Keep processing faded channels for later portamento pickup"); break;
  547. case kFT2ReloadSampleSettings: desc = _T("Reload sample settings even if a note-off is placed next to an instrument number"); break;
  548. case kFT2PortaDelay: desc = _T("Portamento with note delay next to it is ignored"); break;
  549. case kFT2Transpose: desc = _T("Ignore out-of-range transposed notes"); break;
  550. case kFT2PatternLoopWithJumps: desc = _T("Bxx or Dxx on the same row as E6x terminates the loop"); break;
  551. case kFT2PortaTargetNoReset: desc = _T("Portamento target is not reset with new notes"); break;
  552. case kFT2EnvelopeEscape: desc = _T("Sustain point at end of envelope loop stops the loop after release"); break;
  553. case kFT2Tremor: desc = _T("Txx behaves like in FT2"); break;
  554. case kFT2OutOfRangeDelay: desc = _T("Do not trigger notes with out-of-range note delay"); break;
  555. case kFT2Periods: desc = _T("Use FT2's broken period handling"); break;
  556. case kFT2PanWithDelayedNoteOff: desc = _T("Panning command with delayed note-off is ignored"); break;
  557. case kFT2VolColDelay: desc = _T("FT2-style volume column handling if there is a note delay"); break;
  558. case kFT2FinetunePrecision: desc = _T("Round sample finetune to multiples of 8"); break;
  559. case kFT2NoteOffFlags: desc = _T("Fade instrument on note-off when there is no volume envelope; instrument numbers reset note-off status"); break;
  560. case kITMultiSampleInstrumentNumber: desc = _T("Lone instrument number after portamento within multi-sampled instrument sets the target sample's settings"); break;
  561. case kRowDelayWithNoteDelay: desc = _T("Note delays next to a row delay are repeated on every row repetition"); break;
  562. case kFT2MODTremoloRampWaveform: desc = _T("Emulate FT2/ProTracker tremolo ramp down / triangle waveform"); break;
  563. case kFT2PortaUpDownMemory: desc = _T("Portamento Up and Down have separate effect memory"); break;
  564. case kST3NoMutedChannels: desc = _T("Do not process any effects on muted S3M channels"); break;
  565. case kST3EffectMemory: desc = _T("Most effects share the same memory"); break;
  566. case kST3PortaSampleChange: desc = _T("Portamento with instrument number applies volume settings of new sample, but not the new sample itself (GUS)"); break;
  567. case kST3VibratoMemory: desc = _T("Do not remember vibrato type in effect memory"); break;
  568. case kST3LimitPeriod: desc = _T("ModPlug Tracker frequency limits"); break;
  569. case KST3PortaAfterArpeggio: desc = _T("Portamento immediately following an arpeggio effect continues at the last arpeggiated note"); break;
  570. case kMODOneShotLoops: desc = _T("ProTracker one-shot loops"); break;
  571. case kMODIgnorePanning: desc = _T("Ignore panning commands"); break;
  572. case kMODSampleSwap: desc = _T("Enable on-the-fly sample swapping"); break;
  573. case kMODOutOfRangeNoteDelay: desc = _T("Out-of-range note delay is played on next row"); break;
  574. case kMODTempoOnSecondTick: desc = _T("Tempo changes are handled on second tick instead of first"); break;
  575. case kFT2PanSustainRelease: desc = _T("If the sustain point of the panning envelope is reached before key-off, it is never released"); break;
  576. case kLegacyReleaseNode: desc = _T("Old volume envelope release node scaling behaviour"); break;
  577. case kOPLBeatingOscillators: desc = _T("Beating OPL oscillators"); break;
  578. case kST3OffsetWithoutInstrument: desc = _T("Notes without instrument use the previous note's sample offset"); break;
  579. case kReleaseNodePastSustainBug: desc = _T("Broken release node after sustain end behaviour"); break;
  580. case kFT2NoteDelayWithoutInstr: desc = _T("Delayed instrument-less notes should not recall volume and panning"); break;
  581. case kOPLFlexibleNoteOff: desc = _T("Full control over OPL notes after note-off"); break;
  582. case kITInstrWithNoteOffOldEffects: desc = _T("Instrument number with note-off retriggers envelopes with Old Effects enabled"); break;
  583. case kMIDIVolumeOnNoteOffBug: desc = _T("Reset VST volume on note-off"); break;
  584. case kITDoNotOverrideChannelPan: desc = _T("Instruments / samples with forced panning do not override channel panning for following instruments / samples"); break;
  585. case kITPatternLoopWithJumps: desc = _T("Bxx right of SBx terminates the loop in IT"); break;
  586. case kITDCTBehaviour: desc = _T("Duplicate Sample Check requires same instrument, Duplicate Note Check uses pattern notes for comparison"); break;
  587. case kOPLwithNNA: desc = _T("New Note Action / Duplicate Note Action set to Note Off and Note Fade affect OPL notes like samples"); break;
  588. case kST3RetrigAfterNoteCut: desc = _T("Notes cannot be retriggered after they have been cut"); break;
  589. case kST3SampleSwap: desc = _T("Enable on-the-fly sample swapping (SoundBlaster driver)"); break;
  590. case kOPLRealRetrig: desc = _T("Retrigger (Qxy) affects OPL notes"); break;
  591. case kOPLNoResetAtEnvelopeEnd: desc = _T("Do not reset OPL channel status at end of envelopes"); break;
  592. case kOPLNoteStopWith0Hz: desc = _T("OPL key-off sets note frequency to 0 Hz"); break;
  593. case kOPLNoteOffOnNoteChange: desc = _T("Send OPL key-off when triggering notes"); break;
  594. case kFT2PortaResetDirection: desc = _T("Tone Portamento direction resets after reaching portamento target from below"); break;
  595. case kApplyUpperPeriodLimit: desc = _T("Apply lower frequency limit"); break;
  596. case kApplyOffsetWithoutNote: desc = _T("Offset commands work without a note next to them"); break;
  597. case kITPitchPanSeparation: desc = _T("Pitch / Pan Separation can be overridden by panning commands"); break;
  598. case kImprecisePingPongLoops: desc = _T("Use old imprecise ping-pong loop end calculation"); break;
  599. default: MPT_ASSERT_NOTREACHED();
  600. }
  601. if(filterActive && CString{desc}.MakeLower().Find(s) < 0)
  602. continue;
  603. if(m_playBehaviour[i] || allowedFlags[i])
  604. {
  605. int item = m_CheckList.AddString(desc);
  606. m_CheckList.SetItemData(item, i);
  607. int check = m_playBehaviour[i] ? BST_CHECKED : BST_UNCHECKED;
  608. if(!allowedFlags[i])
  609. check = BST_INDETERMINATE; // Is checked but not supported by format -> grey out
  610. m_CheckList.SetCheck(item, check);
  611. }
  612. }
  613. m_CheckList.SetRedraw(TRUE);
  614. }
  615. ///////////////////////////////////////////////////////////
  616. // CRemoveChannelsDlg
  617. void CRemoveChannelsDlg::DoDataExchange(CDataExchange* pDX)
  618. {
  619. CDialog::DoDataExchange(pDX);
  620. DDX_Control(pDX, IDC_REMCHANSLIST, m_RemChansList);
  621. }
  622. BEGIN_MESSAGE_MAP(CRemoveChannelsDlg, CDialog)
  623. ON_LBN_SELCHANGE(IDC_REMCHANSLIST, &CRemoveChannelsDlg::OnChannelChanged)
  624. END_MESSAGE_MAP()
  625. BOOL CRemoveChannelsDlg::OnInitDialog()
  626. {
  627. CString s;
  628. CDialog::OnInitDialog();
  629. const CHANNELINDEX numChannels = sndFile.GetNumChannels();
  630. for(CHANNELINDEX n = 0; n < numChannels; n++)
  631. {
  632. s = MPT_CFORMAT("Channel {}")(n + 1);
  633. if(sndFile.ChnSettings[n].szName[0] >= 0x20)
  634. {
  635. s += _T(": ");
  636. s += mpt::ToCString(sndFile.GetCharsetInternal(), sndFile.ChnSettings[n].szName);
  637. }
  638. m_RemChansList.SetItemData(m_RemChansList.AddString(s), n);
  639. if (!m_bKeepMask[n]) m_RemChansList.SetSel(n);
  640. }
  641. if (m_nRemove > 0)
  642. s = MPT_CFORMAT("Select {} channel{} to remove:")(m_nRemove, (m_nRemove != 1) ? CString(_T("s")) : CString(_T("")));
  643. else
  644. s = MPT_CFORMAT("Select channels to remove (the minimum number of remaining channels is {})")(sndFile.GetModSpecifications().channelsMin);
  645. SetDlgItemText(IDC_QUESTION1, s);
  646. if(GetDlgItem(IDCANCEL)) GetDlgItem(IDCANCEL)->ShowWindow(m_ShowCancel);
  647. OnChannelChanged();
  648. return TRUE;
  649. }
  650. void CRemoveChannelsDlg::OnOK()
  651. {
  652. int selCount = m_RemChansList.GetSelCount();
  653. std::vector<int> selected(selCount);
  654. m_RemChansList.GetSelItems(selCount, selected.data());
  655. m_bKeepMask.assign(sndFile.GetNumChannels(), true);
  656. for (const auto sel : selected)
  657. {
  658. m_bKeepMask[sel] = false;
  659. }
  660. if ((static_cast<CHANNELINDEX>(selCount) == m_nRemove && selCount > 0)
  661. || (m_nRemove == 0 && (sndFile.GetNumChannels() >= selCount + sndFile.GetModSpecifications().channelsMin)))
  662. CDialog::OnOK();
  663. else
  664. CDialog::OnCancel();
  665. }
  666. void CRemoveChannelsDlg::OnChannelChanged()
  667. {
  668. const UINT selCount = m_RemChansList.GetSelCount();
  669. GetDlgItem(IDOK)->EnableWindow(((selCount == m_nRemove && selCount > 0) || (m_nRemove == 0 && (sndFile.GetNumChannels() >= selCount + sndFile.GetModSpecifications().channelsMin) && selCount > 0)) ? TRUE : FALSE);
  670. }
  671. InfoDialog::InfoDialog(CWnd *parent)
  672. : ResizableDialog(IDD_INFO_BOX, parent)
  673. { }
  674. BOOL InfoDialog::OnInitDialog()
  675. {
  676. ResizableDialog::OnInitDialog();
  677. SetWindowText(m_caption.c_str());
  678. SetDlgItemText(IDC_EDIT1, m_content.c_str());
  679. return TRUE;
  680. }
  681. void InfoDialog::SetContent(mpt::winstring content)
  682. {
  683. m_content = std::move(content);
  684. }
  685. void InfoDialog::SetCaption(mpt::winstring caption)
  686. {
  687. m_caption = std::move(caption);
  688. }
  689. ////////////////////////////////////////////////////////////////////////////////
  690. // Sound Bank Information
  691. CSoundBankProperties::CSoundBankProperties(const CDLSBank &bank, CWnd *parent)
  692. : InfoDialog(parent)
  693. {
  694. const SOUNDBANKINFO &bi = bank.GetBankInfo();
  695. std::string info;
  696. info.reserve(128 + bi.szBankName.size() + bi.szDescription.size() + bi.szCopyRight.size() + bi.szEngineer.size() + bi.szSoftware.size() + bi.szComments.size());
  697. info = "Type:\t" + std::string((bank.GetBankType() & SOUNDBANK_TYPE_SF2) ? "Sound Font (SF2)" : "Downloadable Sound (DLS)");
  698. if (bi.szBankName.size())
  699. info += "\r\nName:\t" + bi.szBankName;
  700. if (bi.szDescription.size())
  701. info += "\r\n\t" + bi.szDescription;
  702. if (bi.szCopyRight.size())
  703. info += "\r\nCopyright:\t" + bi.szCopyRight;
  704. if (bi.szEngineer.size())
  705. info += "\r\nAuthor:\t" + bi.szEngineer;
  706. if (bi.szSoftware.size())
  707. info += "\r\nSoftware:\t" + bi.szSoftware;
  708. if (bi.szComments.size())
  709. info += "\r\n\r\nComments:\r\n" + bi.szComments;
  710. SetCaption((bank.GetFileName().AsNative() + _T(" - Sound Bank Information")));
  711. SetContent(mpt::ToWin(mpt::Charset::Locale, info));
  712. }
  713. ////////////////////////////////////////////////////////////////////////////////////////////
  714. // Keyboard Control
  715. static constexpr uint8 whitetab[7] = {0,2,4,5,7,9,11};
  716. static constexpr uint8 blacktab[7] = {0xff,1,3,0xff,6,8,10};
  717. BEGIN_MESSAGE_MAP(CKeyboardControl, CWnd)
  718. ON_WM_DESTROY()
  719. ON_WM_PAINT()
  720. ON_WM_MOUSEMOVE()
  721. ON_WM_LBUTTONDOWN()
  722. ON_WM_LBUTTONUP()
  723. END_MESSAGE_MAP()
  724. void CKeyboardControl::Init(CWnd *parent, int octaves, bool cursorNotify)
  725. {
  726. m_parent = parent;
  727. m_nOctaves = std::max(1, octaves);
  728. m_cursorNotify = cursorNotify;
  729. MemsetZero(KeyFlags);
  730. MemsetZero(m_sampleNum);
  731. // Point size to pixels
  732. int fontSize = -MulDiv(60, Util::GetDPIy(m_hWnd), 720);
  733. m_font.CreateFont(fontSize, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_RASTER_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, FIXED_PITCH | FF_DONTCARE, _T("MS Shell Dlg"));
  734. }
  735. void CKeyboardControl::OnDestroy()
  736. {
  737. m_font.DeleteObject();
  738. }
  739. void CKeyboardControl::DrawKey(CPaintDC &dc, const CRect rect, int key, bool black) const
  740. {
  741. const bool selected = (key == m_nSelection);
  742. COLORREF color = black ? RGB(20, 20, 20) : RGB(255, 255, 255);
  743. if(m_mouseDown && selected)
  744. color = black ? RGB(104, 104, 104) : RGB(212, 212, 212);
  745. else if(selected)
  746. color = black ? RGB(130, 130, 130) : RGB(228, 228, 228);
  747. dc.SetDCBrushColor(color);
  748. dc.Rectangle(&rect);
  749. if(static_cast<size_t>(key) < std::size(KeyFlags) && KeyFlags[key] != KEYFLAG_NORMAL)
  750. {
  751. const int margin = black ? 0 : 2;
  752. CRect ellipseRect(rect.left + margin, rect.bottom - rect.Width() + margin, rect.right - margin, rect.bottom - margin);
  753. dc.SetDCBrushColor((KeyFlags[key] & KEYFLAG_BRIGHTDOT) ? RGB(255, 192, 192) : RGB(255, 0, 0));
  754. dc.Ellipse(ellipseRect);
  755. if(m_sampleNum[key] != 0)
  756. {
  757. dc.SetTextColor((KeyFlags[key] & KEYFLAG_BRIGHTDOT) ? RGB(0, 0, 0) : RGB(255, 255, 255));
  758. TCHAR s[16];
  759. wsprintf(s, _T("%u"), m_sampleNum[key]);
  760. dc.DrawText(s, -1, ellipseRect, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
  761. }
  762. if(KeyFlags[key] == (KEYFLAG_REDDOT | KEYFLAG_BRIGHTDOT))
  763. {
  764. // Both flags set: Draw second dot
  765. ellipseRect.MoveToY(ellipseRect.top - ellipseRect.Height() - 2);
  766. dc.SetDCBrushColor(RGB(255, 0, 0));
  767. dc.Ellipse(ellipseRect);
  768. }
  769. }
  770. }
  771. void CKeyboardControl::OnPaint()
  772. {
  773. CRect rcClient, rect;
  774. CPaintDC dc(this);
  775. dc.SetBkMode(TRANSPARENT);
  776. GetClientRect(&rcClient);
  777. rect = rcClient;
  778. auto oldBrush = dc.SelectObject(GetStockObject(DC_BRUSH));
  779. auto oldPen = dc.SelectObject(GetStockObject(DC_PEN));
  780. auto oldFont = dc.SelectObject(&m_font);
  781. // Rectangle outline
  782. dc.SetDCPenColor(RGB(50, 50, 50));
  783. // White notes
  784. for(int note = 0; note < m_nOctaves * 7; note++)
  785. {
  786. rect.right = ((note + 1) * rcClient.Width()) / (m_nOctaves * 7);
  787. int val = (note / 7) * 12 + whitetab[note % 7];
  788. DrawKey(dc, rect, val, false);
  789. rect.left = rect.right - 1;
  790. }
  791. // Black notes
  792. rect = rcClient;
  793. rect.bottom -= rcClient.Height() / 3;
  794. for(int note = 0; note < m_nOctaves * 7; note++)
  795. {
  796. switch(note % 7)
  797. {
  798. case 1:
  799. case 2:
  800. case 4:
  801. case 5:
  802. case 6:
  803. {
  804. rect.left = (note * rcClient.Width()) / (m_nOctaves * 7);
  805. rect.right = rect.left;
  806. int delta = rcClient.Width() / (m_nOctaves * 7 * 3);
  807. rect.left -= delta;
  808. rect.right += delta;
  809. int val = (note / 7) * 12 + blacktab[note % 7];
  810. DrawKey(dc, rect, val, true);
  811. break;
  812. }
  813. }
  814. }
  815. dc.SelectObject(oldBrush);
  816. dc.SelectObject(oldPen);
  817. dc.SelectObject(oldFont);
  818. }
  819. void CKeyboardControl::OnMouseMove(UINT flags, CPoint point)
  820. {
  821. CRect rcClient, rect;
  822. GetClientRect(&rcClient);
  823. rect = rcClient;
  824. int xmin = rcClient.right;
  825. int xmax = rcClient.left;
  826. int sel = -1;
  827. // White notes
  828. for(int note = 0; note < m_nOctaves * 7; note++)
  829. {
  830. int val = (note / 7) * 12 + whitetab[note % 7];
  831. rect.right = ((note + 1) * rcClient.Width()) / (m_nOctaves * 7);
  832. if (val == m_nSelection)
  833. {
  834. if (rect.left < xmin) xmin = rect.left;
  835. if (rect.right > xmax) xmax = rect.right;
  836. }
  837. if (rect.PtInRect(point))
  838. {
  839. sel = val;
  840. if (rect.left < xmin) xmin = rect.left;
  841. if (rect.right > xmax) xmax = rect.right;
  842. }
  843. rect.left = rect.right - 1;
  844. }
  845. // Black notes
  846. rect = rcClient;
  847. rect.bottom -= rcClient.Height() / 3;
  848. for(int note = 0; note < m_nOctaves * 7; note++)
  849. {
  850. switch(note % 7)
  851. {
  852. case 1:
  853. case 2:
  854. case 4:
  855. case 5:
  856. case 6:
  857. {
  858. int val = (note / 7) * 12 + blacktab[note % 7];
  859. rect.left = (note * rcClient.Width()) / (m_nOctaves * 7);
  860. rect.right = rect.left;
  861. int delta = rcClient.Width() / (m_nOctaves * 7 * 3);
  862. rect.left -= delta;
  863. rect.right += delta;
  864. if(val == m_nSelection)
  865. {
  866. if(rect.left < xmin)
  867. xmin = rect.left;
  868. if(rect.right > xmax)
  869. xmax = rect.right;
  870. }
  871. if(rect.PtInRect(point))
  872. {
  873. sel = val;
  874. if(rect.left < xmin)
  875. xmin = rect.left;
  876. if(rect.right > xmax)
  877. xmax = rect.right;
  878. }
  879. break;
  880. }
  881. }
  882. }
  883. // Check for selection change
  884. if(sel != m_nSelection)
  885. {
  886. m_nSelection = sel;
  887. rcClient.left = xmin;
  888. rcClient.right = xmax;
  889. InvalidateRect(&rcClient, FALSE);
  890. if(m_cursorNotify && m_parent)
  891. {
  892. m_parent->PostMessage(WM_MOD_KBDNOTIFY, KBDNOTIFY_MOUSEMOVE, m_nSelection);
  893. if(flags & MK_LBUTTON)
  894. m_parent->SendMessage(WM_MOD_KBDNOTIFY, KBDNOTIFY_LBUTTONDOWN, m_nSelection);
  895. }
  896. }
  897. if(sel >= 0)
  898. {
  899. if(!m_mouseCapture)
  900. {
  901. m_mouseCapture = true;
  902. SetCapture();
  903. }
  904. } else
  905. {
  906. if(m_mouseCapture)
  907. {
  908. m_mouseCapture = false;
  909. ReleaseCapture();
  910. }
  911. }
  912. }
  913. void CKeyboardControl::OnLButtonDown(UINT, CPoint)
  914. {
  915. m_mouseDown = true;
  916. InvalidateRect(nullptr, FALSE);
  917. if(m_parent)
  918. m_parent->SendMessage(WM_MOD_KBDNOTIFY, KBDNOTIFY_LBUTTONDOWN, m_nSelection);
  919. }
  920. void CKeyboardControl::OnLButtonUp(UINT, CPoint)
  921. {
  922. m_mouseDown = false;
  923. InvalidateRect(nullptr, FALSE);
  924. if(m_parent)
  925. m_parent->SendMessage(WM_MOD_KBDNOTIFY, KBDNOTIFY_LBUTTONUP, m_nSelection);
  926. }
  927. ////////////////////////////////////////////////////////////////////////////////
  928. //
  929. // Sample Map
  930. //
  931. BEGIN_MESSAGE_MAP(CSampleMapDlg, CDialog)
  932. ON_MESSAGE(WM_MOD_KBDNOTIFY, &CSampleMapDlg::OnKeyboardNotify)
  933. ON_WM_HSCROLL()
  934. ON_COMMAND(IDC_CHECK1, &CSampleMapDlg::OnUpdateSamples)
  935. ON_CBN_SELCHANGE(IDC_COMBO1, &CSampleMapDlg::OnUpdateKeyboard)
  936. END_MESSAGE_MAP()
  937. void CSampleMapDlg::DoDataExchange(CDataExchange* pDX)
  938. {
  939. CDialog::DoDataExchange(pDX);
  940. //{{AFX_DATA_MAP(CSampleMapDlg)
  941. DDX_Control(pDX, IDC_KEYBOARD1, m_Keyboard);
  942. DDX_Control(pDX, IDC_COMBO1, m_CbnSample);
  943. DDX_Control(pDX, IDC_SLIDER1, m_SbOctave);
  944. //}}AFX_DATA_MAP
  945. }
  946. BOOL CSampleMapDlg::OnInitDialog()
  947. {
  948. CDialog::OnInitDialog();
  949. ModInstrument *pIns = sndFile.Instruments[m_nInstrument];
  950. if(pIns)
  951. {
  952. for(UINT i = 0; i < NOTE_MAX; i++)
  953. {
  954. KeyboardMap[i] = pIns->Keyboard[i];
  955. }
  956. }
  957. m_Keyboard.Init(this, 3, TRUE);
  958. m_SbOctave.SetRange(0, 7);
  959. m_SbOctave.SetPos(4);
  960. OnUpdateSamples();
  961. OnUpdateOctave();
  962. return TRUE;
  963. }
  964. void CSampleMapDlg::OnHScroll(UINT nCode, UINT nPos, CScrollBar *pBar)
  965. {
  966. CDialog::OnHScroll(nCode, nPos, pBar);
  967. OnUpdateKeyboard();
  968. OnUpdateOctave();
  969. }
  970. void CSampleMapDlg::OnUpdateSamples()
  971. {
  972. UINT oldPos = 0;
  973. UINT newPos = 0;
  974. if(m_nInstrument >= MAX_INSTRUMENTS)
  975. return;
  976. if(m_CbnSample.GetCount() > 0)
  977. oldPos = static_cast<UINT>(m_CbnSample.GetItemData(m_CbnSample.GetCurSel()));
  978. m_CbnSample.SetRedraw(FALSE);
  979. m_CbnSample.ResetContent();
  980. const bool showAll = (IsDlgButtonChecked(IDC_CHECK1) != FALSE) || (*std::max_element(std::begin(KeyboardMap), std::end(KeyboardMap)) == 0);
  981. UINT insertPos = m_CbnSample.AddString(_T("0: No sample"));
  982. m_CbnSample.SetItemData(insertPos, 0);
  983. for(SAMPLEINDEX i = 1; i <= sndFile.GetNumSamples(); i++)
  984. {
  985. bool isUsed = showAll || mpt::contains(KeyboardMap, i);
  986. if(isUsed)
  987. {
  988. CString sampleName;
  989. sampleName.Format(_T("%d: %s"), i, mpt::ToCString(sndFile.GetCharsetInternal(), sndFile.GetSampleName(i)).GetString());
  990. insertPos = m_CbnSample.AddString(sampleName);
  991. m_CbnSample.SetItemData(insertPos, i);
  992. if(i == oldPos)
  993. newPos = insertPos;
  994. }
  995. }
  996. m_CbnSample.SetRedraw(TRUE);
  997. m_CbnSample.SetCurSel(newPos);
  998. OnUpdateKeyboard();
  999. }
  1000. void CSampleMapDlg::OnUpdateOctave()
  1001. {
  1002. TCHAR s[64];
  1003. const UINT baseOctave = m_SbOctave.GetPos() & 7;
  1004. wsprintf(s, _T("Octaves %u-%u"), baseOctave, baseOctave + 2);
  1005. SetDlgItemText(IDC_TEXT1, s);
  1006. }
  1007. void CSampleMapDlg::OnUpdateKeyboard()
  1008. {
  1009. SAMPLEINDEX nSample = static_cast<SAMPLEINDEX>(m_CbnSample.GetItemData(m_CbnSample.GetCurSel()));
  1010. const UINT baseOctave = m_SbOctave.GetPos() & 7;
  1011. bool redraw = false;
  1012. for(UINT iNote = 0; iNote < 3 * 12; iNote++)
  1013. {
  1014. uint8 oldFlags = m_Keyboard.GetFlags(iNote);
  1015. SAMPLEINDEX oldSmp = m_Keyboard.GetSample(iNote);
  1016. UINT ndx = baseOctave * 12 + iNote;
  1017. uint8 newFlags = CKeyboardControl::KEYFLAG_NORMAL;
  1018. if(KeyboardMap[ndx] == nSample)
  1019. newFlags = CKeyboardControl::KEYFLAG_REDDOT;
  1020. else if(KeyboardMap[ndx] != 0)
  1021. newFlags = CKeyboardControl::KEYFLAG_BRIGHTDOT;
  1022. if(newFlags != oldFlags || oldSmp != KeyboardMap[ndx])
  1023. {
  1024. m_Keyboard.SetFlags(iNote, newFlags);
  1025. m_Keyboard.SetSample(iNote, KeyboardMap[ndx]);
  1026. redraw = true;
  1027. }
  1028. }
  1029. if(redraw)
  1030. m_Keyboard.InvalidateRect(NULL, FALSE);
  1031. }
  1032. LRESULT CSampleMapDlg::OnKeyboardNotify(WPARAM wParam, LPARAM lParam)
  1033. {
  1034. TCHAR s[32] = _T("--");
  1035. if((lParam >= 0) && (lParam < 3 * 12))
  1036. {
  1037. const SAMPLEINDEX sample = static_cast<SAMPLEINDEX>(m_CbnSample.GetItemData(m_CbnSample.GetCurSel()));
  1038. const uint32 baseOctave = m_SbOctave.GetPos() & 7;
  1039. const CString temp = mpt::ToCString(sndFile.GetNoteName(static_cast<ModCommand::NOTE>(lParam + 1 + 12 * baseOctave), m_nInstrument));
  1040. if(temp.GetLength() >= mpt::saturate_cast<int>(std::size(s)))
  1041. wsprintf(s, _T("%s"), _T("..."));
  1042. else
  1043. wsprintf(s, _T("%s"), temp.GetString());
  1044. ModInstrument *pIns = sndFile.Instruments[m_nInstrument];
  1045. if((wParam == KBDNOTIFY_LBUTTONDOWN) && (sample < MAX_SAMPLES) && (pIns))
  1046. {
  1047. const uint32 note = static_cast<uint32>(baseOctave * 12 + lParam);
  1048. if(mouseAction == mouseUnknown)
  1049. {
  1050. // Mouse down -> decide if we are going to set or remove notes
  1051. mouseAction = mouseSet;
  1052. if(KeyboardMap[note] == sample)
  1053. {
  1054. mouseAction = (KeyboardMap[note] == pIns->Keyboard[note]) ? mouseZero : mouseUnset;
  1055. }
  1056. }
  1057. switch(mouseAction)
  1058. {
  1059. case mouseSet:
  1060. KeyboardMap[note] = sample;
  1061. break;
  1062. case mouseUnset:
  1063. KeyboardMap[note] = pIns->Keyboard[note];
  1064. break;
  1065. case mouseZero:
  1066. if(KeyboardMap[note] == sample)
  1067. {
  1068. KeyboardMap[note] = 0;
  1069. }
  1070. break;
  1071. }
  1072. OnUpdateKeyboard();
  1073. }
  1074. }
  1075. if(wParam == KBDNOTIFY_LBUTTONUP)
  1076. {
  1077. mouseAction = mouseUnknown;
  1078. }
  1079. SetDlgItemText(IDC_TEXT2, s);
  1080. return 0;
  1081. }
  1082. void CSampleMapDlg::OnOK()
  1083. {
  1084. ModInstrument *pIns = sndFile.Instruments[m_nInstrument];
  1085. if(pIns)
  1086. {
  1087. bool modified = false;
  1088. for(UINT i = 0; i < NOTE_MAX; i++)
  1089. {
  1090. if(KeyboardMap[i] != pIns->Keyboard[i])
  1091. {
  1092. pIns->Keyboard[i] = KeyboardMap[i];
  1093. modified = true;
  1094. }
  1095. }
  1096. if(modified)
  1097. {
  1098. CDialog::OnOK();
  1099. return;
  1100. }
  1101. }
  1102. CDialog::OnCancel();
  1103. }
  1104. ////////////////////////////////////////////////////////////////////////////////////////////
  1105. // Edit history dialog
  1106. BEGIN_MESSAGE_MAP(CEditHistoryDlg, ResizableDialog)
  1107. ON_COMMAND(IDC_BTN_CLEAR, &CEditHistoryDlg::OnClearHistory)
  1108. END_MESSAGE_MAP()
  1109. BOOL CEditHistoryDlg::OnInitDialog()
  1110. {
  1111. ResizableDialog::OnInitDialog();
  1112. CString s;
  1113. uint64 totalTime = 0;
  1114. const auto &editHistory = m_modDoc.GetSoundFile().GetFileHistory();
  1115. const bool isEmpty = editHistory.empty();
  1116. for(const auto &entry : editHistory)
  1117. {
  1118. totalTime += entry.openTime;
  1119. // Date
  1120. CString sDate;
  1121. if(entry.HasValidDate())
  1122. {
  1123. TCHAR szDate[32];
  1124. _tcsftime(szDate, std::size(szDate), _T("%d %b %Y, %H:%M:%S"), &entry.loadDate);
  1125. sDate = szDate;
  1126. } else
  1127. {
  1128. sDate = _T("<unknown date>");
  1129. }
  1130. // Time + stuff
  1131. uint32 duration = mpt::saturate_round<uint32>(entry.openTime / HISTORY_TIMER_PRECISION);
  1132. s += MPT_CFORMAT("Loaded {}, open for {}h {}m {}s\r\n")(
  1133. sDate, mpt::cfmt::dec(duration / 3600), mpt::cfmt::dec0<2>((duration / 60) % 60), mpt::cfmt::dec0<2>(duration % 60));
  1134. }
  1135. if(isEmpty)
  1136. {
  1137. s = _T("No information available about the previous edit history of this module.");
  1138. }
  1139. SetDlgItemText(IDC_EDIT_HISTORY, s);
  1140. // Total edit time
  1141. s.Empty();
  1142. if(totalTime)
  1143. {
  1144. totalTime = mpt::saturate_round<uint64>(totalTime / HISTORY_TIMER_PRECISION);
  1145. s.Format(_T("Total edit time: %lluh %02llum %02llus (%zu session%s)"), totalTime / 3600, (totalTime / 60) % 60, totalTime % 60, editHistory.size(), (editHistory.size() != 1) ? _T("s") : _T(""));
  1146. SetDlgItemText(IDC_TOTAL_EDIT_TIME, s);
  1147. // Window title
  1148. s.Format(_T("Edit History for %s"), m_modDoc.GetTitle().GetString());
  1149. SetWindowText(s);
  1150. }
  1151. // Enable or disable Clear button
  1152. GetDlgItem(IDC_BTN_CLEAR)->EnableWindow(isEmpty ? FALSE : TRUE);
  1153. return TRUE;
  1154. }
  1155. void CEditHistoryDlg::OnClearHistory()
  1156. {
  1157. if(!m_modDoc.GetSoundFile().GetFileHistory().empty())
  1158. {
  1159. m_modDoc.GetSoundFile().GetFileHistory().clear();
  1160. m_modDoc.SetModified();
  1161. OnInitDialog();
  1162. }
  1163. }
  1164. /////////////////////////////////////////////////////////////////////////
  1165. // Generic input dialog
  1166. void CInputDlg::DoDataExchange(CDataExchange* pDX)
  1167. {
  1168. CDialog::DoDataExchange(pDX);
  1169. if(m_minValueInt == m_maxValueInt && m_minValueDbl == m_maxValueDbl)
  1170. {
  1171. // Only need this for freeform text
  1172. DDX_Control(pDX, IDC_EDIT1, m_edit);
  1173. }
  1174. DDX_Control(pDX, IDC_SPIN1, m_spin);
  1175. }
  1176. BOOL CInputDlg::OnInitDialog()
  1177. {
  1178. CDialog::OnInitDialog();
  1179. SetDlgItemText(IDC_PROMPT, m_description);
  1180. // Get all current control sizes and positions
  1181. CRect windowRect, labelRect, inputRect, okRect, cancelRect;
  1182. GetWindowRect(windowRect);
  1183. GetDlgItem(IDC_PROMPT)->GetWindowRect(labelRect);
  1184. GetDlgItem(IDC_EDIT1)->GetWindowRect(inputRect);
  1185. GetDlgItem(IDOK)->GetWindowRect(okRect);
  1186. GetDlgItem(IDCANCEL)->GetWindowRect(cancelRect);
  1187. ScreenToClient(labelRect);
  1188. ScreenToClient(inputRect);
  1189. ScreenToClient(okRect);
  1190. ScreenToClient(cancelRect);
  1191. // Find out how big our label shall be
  1192. HDC dc = ::GetDC(m_hWnd);
  1193. CRect textRect(0,0,0,0);
  1194. DrawText(dc, m_description, m_description.GetLength(), textRect, DT_CALCRECT);
  1195. LPtoDP(dc, &textRect.BottomRight(), 1);
  1196. ::ReleaseDC(m_hWnd, dc);
  1197. if(textRect.right < 320) textRect.right = 320;
  1198. const int windowWidth = windowRect.Width() - labelRect.Width() + textRect.right;
  1199. const int windowHeight = windowRect.Height() - labelRect.Height() + textRect.bottom;
  1200. // Resize and move all controls
  1201. GetDlgItem(IDC_PROMPT)->SetWindowPos(nullptr, 0, 0, textRect.right, textRect.bottom, SWP_NOMOVE | SWP_NOZORDER);
  1202. GetDlgItem(IDC_EDIT1)->SetWindowPos(nullptr, inputRect.left, labelRect.top + textRect.bottom + (inputRect.top - labelRect.bottom), textRect.right, inputRect.Height(), SWP_NOZORDER);
  1203. GetDlgItem(IDOK)->SetWindowPos(nullptr, windowWidth - (windowRect.Width() - okRect.left), windowHeight - (windowRect.Height() - okRect.top), 0, 0, SWP_NOSIZE | SWP_NOZORDER);
  1204. GetDlgItem(IDCANCEL)->SetWindowPos(nullptr, windowWidth - (windowRect.Width() - cancelRect.left), windowHeight - (windowRect.Height() - cancelRect.top), 0, 0, SWP_NOSIZE | SWP_NOZORDER);
  1205. SetWindowPos(nullptr, 0, 0, windowWidth, windowHeight, SWP_NOMOVE | SWP_NOZORDER);
  1206. if(m_minValueInt != m_maxValueInt)
  1207. {
  1208. // Numeric (int)
  1209. m_spin.SetRange32(m_minValueInt, m_maxValueInt);
  1210. m_edit.SubclassDlgItem(IDC_EDIT1, this);
  1211. m_edit.ModifyStyle(0, ES_NUMBER);
  1212. m_edit.AllowNegative(m_minValueInt < 0);
  1213. m_edit.AllowFractions(false);
  1214. SetDlgItemInt(IDC_EDIT1, resultAsInt);
  1215. m_spin.SetBuddy(&m_edit);
  1216. } else if(m_minValueDbl != m_maxValueDbl)
  1217. {
  1218. // Numeric (double)
  1219. m_spin.SetRange32(static_cast<int32>(m_minValueDbl), static_cast<int32>(m_maxValueDbl));
  1220. m_edit.SubclassDlgItem(IDC_EDIT1, this);
  1221. m_edit.ModifyStyle(0, ES_NUMBER);
  1222. m_edit.AllowNegative(m_minValueDbl < 0);
  1223. m_edit.AllowFractions(true);
  1224. m_edit.SetDecimalValue(resultAsDouble);
  1225. m_spin.SetBuddy(&m_edit);
  1226. } else
  1227. {
  1228. // Text
  1229. m_spin.ShowWindow(SW_HIDE);
  1230. if(m_maxLength > 0)
  1231. Edit_LimitText(m_edit, m_maxLength);
  1232. SetDlgItemText(IDC_EDIT1, resultAsString);
  1233. }
  1234. return TRUE;
  1235. }
  1236. void CInputDlg::OnOK()
  1237. {
  1238. CDialog::OnOK();
  1239. GetDlgItemText(IDC_EDIT1, resultAsString);
  1240. resultAsInt = static_cast<int32>(GetDlgItemInt(IDC_EDIT1));
  1241. Limit(resultAsInt, m_minValueInt, m_maxValueInt);
  1242. m_edit.GetDecimalValue(resultAsDouble);
  1243. Limit(resultAsDouble, m_minValueDbl, m_maxValueDbl);
  1244. }
  1245. ///////////////////////////////////////////////////////////////////////////////////////
  1246. // Messagebox with 'don't show again'-option.
  1247. class CMsgBoxHidable : public CDialog
  1248. {
  1249. public:
  1250. CMsgBoxHidable(const TCHAR *strMsg, bool checkStatus = true, CWnd* pParent = NULL);
  1251. enum { IDD = IDD_MSGBOX_HIDABLE };
  1252. const TCHAR *m_StrMsg;
  1253. int m_nCheckStatus;
  1254. protected:
  1255. void DoDataExchange(CDataExchange* pDX) override; // DDX/DDV support
  1256. BOOL OnInitDialog() override;
  1257. };
  1258. struct MsgBoxHidableMessage
  1259. {
  1260. const TCHAR *message;
  1261. uint32 mask;
  1262. bool defaultDontShowAgainStatus; // true for don't show again, false for show again.
  1263. };
  1264. static constexpr MsgBoxHidableMessage HidableMessages[] =
  1265. {
  1266. { _T("Note: First two bytes of oneshot samples are silenced for ProTracker compatibility."), 1, true },
  1267. { _T("Hint: To create IT-files without MPT-specific extensions included, try compatibility export from File-menu."), 1 << 1, true },
  1268. { _T("Press OK to apply signed/unsigned conversion\n (note: this often significantly increases volume level)"), 1 << 2, false },
  1269. { _T("Hint: To create XM-files without MPT-specific extensions included, try compatibility export from File-menu."), 1 << 3, true },
  1270. { _T("Warning: The exported file will not contain any of MPT's file format hacks."), 1 << 4, true },
  1271. };
  1272. static_assert(mpt::array_size<decltype(HidableMessages)>::size == enMsgBoxHidableMessage_count);
  1273. // Messagebox with 'don't show this again'-checkbox. Uses parameter 'enMsg'
  1274. // to get the needed information from message array, and updates the variable that
  1275. // controls the show/don't show-flags.
  1276. void MsgBoxHidable(enMsgBoxHidableMessage enMsg)
  1277. {
  1278. // Check whether the message should be shown.
  1279. if((TrackerSettings::Instance().gnMsgBoxVisiblityFlags & HidableMessages[enMsg].mask) == 0)
  1280. return;
  1281. // Show dialog.
  1282. CMsgBoxHidable dlg(HidableMessages[enMsg].message, HidableMessages[enMsg].defaultDontShowAgainStatus);
  1283. dlg.DoModal();
  1284. // Update visibility flags.
  1285. const uint32 mask = HidableMessages[enMsg].mask;
  1286. if(dlg.m_nCheckStatus == BST_CHECKED)
  1287. TrackerSettings::Instance().gnMsgBoxVisiblityFlags &= ~mask;
  1288. else
  1289. TrackerSettings::Instance().gnMsgBoxVisiblityFlags |= mask;
  1290. }
  1291. CMsgBoxHidable::CMsgBoxHidable(const TCHAR *strMsg, bool checkStatus, CWnd* pParent)
  1292. : CDialog(CMsgBoxHidable::IDD, pParent)
  1293. , m_StrMsg(strMsg)
  1294. , m_nCheckStatus((checkStatus) ? BST_CHECKED : BST_UNCHECKED)
  1295. {}
  1296. BOOL CMsgBoxHidable::OnInitDialog()
  1297. {
  1298. CDialog::OnInitDialog();
  1299. SetDlgItemText(IDC_MESSAGETEXT, m_StrMsg);
  1300. SetWindowText(AfxGetAppName());
  1301. return TRUE;
  1302. }
  1303. void CMsgBoxHidable::DoDataExchange(CDataExchange* pDX)
  1304. {
  1305. CDialog::DoDataExchange(pDX);
  1306. DDX_Check(pDX, IDC_DONTSHOWAGAIN, m_nCheckStatus);
  1307. }
  1308. /////////////////////////////////////////////////////////////////////////////////////
  1309. /////////////////////////////////////////////////////////////////////////////////////
  1310. void AppendNotesToControl(CComboBox& combobox, ModCommand::NOTE noteStart, ModCommand::NOTE noteEnd)
  1311. {
  1312. const ModCommand::NOTE upperLimit = std::min(ModCommand::NOTE(NOTE_MAX), noteEnd);
  1313. for(ModCommand::NOTE note = noteStart; note <= upperLimit; note++)
  1314. combobox.SetItemData(combobox.AddString(mpt::ToCString(CSoundFile::GetNoteName(note, CSoundFile::GetDefaultNoteNames()))), note);
  1315. }
  1316. void AppendNotesToControlEx(CComboBox& combobox, const CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ModCommand::NOTE noteStart, ModCommand::NOTE noteEnd)
  1317. {
  1318. bool addSpecial = noteStart == noteEnd;
  1319. if(noteStart == noteEnd)
  1320. {
  1321. noteStart = sndFile.GetModSpecifications().noteMin;
  1322. noteEnd = sndFile.GetModSpecifications().noteMax;
  1323. }
  1324. for(ModCommand::NOTE note = noteStart; note <= noteEnd; note++)
  1325. {
  1326. combobox.SetItemData(combobox.AddString(mpt::ToCString(sndFile.GetNoteName(note, nInstr))), note);
  1327. }
  1328. if(addSpecial)
  1329. {
  1330. for(ModCommand::NOTE note = NOTE_MIN_SPECIAL - 1; note++ < NOTE_MAX_SPECIAL;)
  1331. {
  1332. if(sndFile.GetModSpecifications().HasNote(note))
  1333. combobox.SetItemData(combobox.AddString(szSpecialNoteNamesMPT[note - NOTE_MIN_SPECIAL]), note);
  1334. }
  1335. }
  1336. }
  1337. OPENMPT_NAMESPACE_END