TempoSwingDialog.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. /*
  2. * TempoSwingDialog.cpp
  3. * --------------------
  4. * Purpose: Implementation of the tempo swing configuration dialog.
  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 "TempoSwingDialog.h"
  11. #include "Mainfrm.h"
  12. OPENMPT_NAMESPACE_BEGIN
  13. void CTempoSwingDlg::RowCtls::SetValue(TempoSwing::value_type v)
  14. {
  15. int32 val = Util::muldivr(static_cast<int32>(v) - TempoSwing::Unity, CTempoSwingDlg::SliderUnity, TempoSwing::Unity);
  16. valueSlider.SetPos(val);
  17. }
  18. TempoSwing::value_type CTempoSwingDlg::RowCtls::GetValue() const
  19. {
  20. return Util::muldivr(valueSlider.GetPos(), TempoSwing::Unity, SliderUnity) + TempoSwing::Unity;
  21. }
  22. BEGIN_MESSAGE_MAP(CTempoSwingDlg, CDialog)
  23. //{{AFX_MSG_MAP(CTempoSwingDlg)
  24. ON_WM_VSCROLL()
  25. ON_COMMAND(IDC_BUTTON1, &CTempoSwingDlg::OnReset)
  26. ON_COMMAND(IDC_BUTTON2, &CTempoSwingDlg::OnUseGlobal)
  27. ON_COMMAND(IDC_CHECK1, &CTempoSwingDlg::OnToggleGroup)
  28. ON_EN_CHANGE(IDC_EDIT1, &CTempoSwingDlg::OnGroupChanged)
  29. //}}AFX_MSG_MAP
  30. END_MESSAGE_MAP()
  31. int CTempoSwingDlg::m_groupSize = 1;
  32. CTempoSwingDlg::CTempoSwingDlg(CWnd *parent, const TempoSwing &currentTempoSwing, CSoundFile &sndFile, PATTERNINDEX pattern)
  33. : CDialog(IDD_TEMPO_SWING, parent)
  34. , m_container(*this)
  35. , m_scrollPos(0)
  36. , m_tempoSwing(currentTempoSwing)
  37. , m_origTempoSwing(pattern == PATTERNINDEX_INVALID ? sndFile.m_tempoSwing : sndFile.Patterns[pattern].GetTempoSwing())
  38. , m_sndFile(sndFile)
  39. , m_pattern(pattern)
  40. {
  41. m_groupSize = std::min(m_groupSize, static_cast<int>(m_tempoSwing.size()));
  42. }
  43. void CTempoSwingDlg::DoDataExchange(CDataExchange* pDX)
  44. {
  45. CDialog::DoDataExchange(pDX);
  46. DDX_Control(pDX, IDC_CHECK1, m_checkGroup);
  47. DDX_Control(pDX, IDC_SCROLLBAR1, m_scrollBar);
  48. DDX_Control(pDX, IDC_CONTAINER, m_container);
  49. }
  50. BOOL CTempoSwingDlg::OnInitDialog()
  51. {
  52. struct Measurements
  53. {
  54. enum
  55. {
  56. edRowLabelWidth = 64, // Label "Row 999:"
  57. edSliderWidth = 220, // Setting slider
  58. edSliderHeight = 20, // Setting slider
  59. edValueLabelWidth = 64, // Label "100%"
  60. edPaddingX = 8, // Spacing between elements
  61. edPaddingY = 4, // Spacing between elements
  62. edPaddingTop = 64, // Spacing from top of dialog
  63. edRowHeight = edSliderHeight + edPaddingY, // Height of one set of controls
  64. edFooterHeight = 32, // Buttons
  65. edScrollbarWidth = 16, // Width of optional scrollbar
  66. };
  67. const int rowLabelWidth;
  68. const int sliderWidth;
  69. const int sliderHeight;
  70. const int valueLabelWidth;
  71. const int paddingX;
  72. const int paddingY;
  73. const int paddingTop;
  74. const int rowHeight;
  75. const int footerHeight;
  76. const int scrollbarWidth;
  77. Measurements(HWND hWnd)
  78. : rowLabelWidth(Util::ScalePixels(edRowLabelWidth, hWnd))
  79. , sliderWidth(Util::ScalePixels(edSliderWidth, hWnd))
  80. , sliderHeight(Util::ScalePixels(edSliderHeight, hWnd))
  81. , valueLabelWidth(Util::ScalePixels(edValueLabelWidth, hWnd))
  82. , paddingX(Util::ScalePixels(edPaddingX, hWnd))
  83. , paddingY(Util::ScalePixels(edPaddingY, hWnd))
  84. , paddingTop(Util::ScalePixels(edPaddingTop, hWnd))
  85. , rowHeight(Util::ScalePixels(edRowHeight, hWnd))
  86. , footerHeight(Util::ScalePixels(edFooterHeight, hWnd))
  87. , scrollbarWidth(Util::ScalePixels(edScrollbarWidth, hWnd))
  88. { }
  89. };
  90. CDialog::OnInitDialog();
  91. Measurements m(m_hWnd);
  92. CRect windowRect, rect;
  93. GetWindowRect(windowRect);
  94. GetClientRect(rect);
  95. windowRect.bottom = windowRect.top + windowRect.Height() - rect.Height();
  96. CRect mainWindowRect;
  97. CMainFrame::GetMainFrame()->GetClientRect(mainWindowRect);
  98. const int realHeight = static_cast<int>(m_tempoSwing.size()) * m.rowHeight;
  99. const int displayHeight = std::min(realHeight, static_cast<int>(mainWindowRect.bottom - windowRect.Height() - m.paddingTop - m.footerHeight));
  100. CRect containerRect;
  101. m_container.GetClientRect(containerRect);
  102. containerRect.bottom = displayHeight;
  103. m_container.SetWindowPos(nullptr, 0, m.paddingTop, rect.right - m.scrollbarWidth, containerRect.bottom, SWP_NOZORDER);
  104. m_container.ModifyStyleEx(0, WS_EX_CONTROLPARENT, 0);
  105. // Need scrollbar?
  106. if(realHeight > displayHeight)
  107. {
  108. SCROLLINFO info;
  109. info.cbSize = sizeof(info);
  110. info.fMask = SIF_ALL;
  111. info.nMin = 0;
  112. info.nMax = realHeight;
  113. info.nPage = displayHeight;
  114. info.nTrackPos = info.nPos = 0;
  115. m_scrollBar.SetScrollInfo(&info, FALSE);
  116. CRect scrollRect;
  117. m_scrollBar.GetClientRect(scrollRect);
  118. m_scrollBar.SetWindowPos(nullptr, containerRect.right, m.paddingTop, scrollRect.Width(), displayHeight, SWP_NOZORDER);
  119. } else
  120. {
  121. m_scrollBar.ShowWindow(SW_HIDE);
  122. }
  123. rect.DeflateRect(m.paddingX, 0/* m.paddingTop*/, m.paddingX + m.scrollbarWidth, 0);
  124. GetDlgItem(IDC_BUTTON2)->ShowWindow((m_pattern != PATTERNINDEX_INVALID) ? SW_SHOW : SW_HIDE);
  125. m_controls.resize(m_tempoSwing.size());
  126. for(size_t i = 0; i < m_controls.size(); i++)
  127. {
  128. m_controls[i] = std::make_unique<RowCtls>();
  129. auto &r = m_controls[i];
  130. // Row label
  131. r->rowLabel.Create(MPT_CFORMAT("Row {}:")(i + 1), WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE, CRect(rect.left, rect.top, rect.right, rect.top + m.rowHeight), &m_container);
  132. r->rowLabel.SetFont(GetFont());
  133. // Value label
  134. r->valueLabel.Create(_T("100%"), WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE, CRect(rect.right - m.valueLabelWidth, rect.top, rect.right, rect.top + m.sliderHeight), &m_container);
  135. r->valueLabel.SetFont(GetFont());
  136. // Value slider
  137. r->valueSlider.Create(WS_CHILD | WS_VISIBLE | WS_TABSTOP | TBS_TOOLTIPS | TBS_AUTOTICKS, CRect(rect.left + m.rowLabelWidth, rect.top, rect.right - m.valueLabelWidth, rect.top + m.sliderHeight), &m_container, 0xFFFF);
  138. r->valueSlider.SetFont(GetFont());
  139. r->valueSlider.SetRange(-SliderResolution / 2, SliderResolution / 2);
  140. r->valueSlider.SetTicFreq(SliderResolution / 8);
  141. r->valueSlider.SetPageSize(SliderResolution / 8);
  142. r->valueSlider.SetPos(1); // Work around https://bugs.winehq.org/show_bug.cgi?id=41909
  143. r->SetValue(m_tempoSwing[i]);
  144. rect.MoveToY(rect.top + m.rowHeight);
  145. }
  146. ((CSpinButtonCtrl *)GetDlgItem(IDC_SPIN1))->SetRange32(1, static_cast<int>(m_tempoSwing.size()));
  147. SetDlgItemInt(IDC_EDIT1, m_groupSize);
  148. OnToggleGroup();
  149. m_container.OnHScroll(0, 0, reinterpret_cast<CScrollBar *>(&(m_controls[0]->valueSlider)));
  150. rect.MoveToY(m.paddingTop + containerRect.bottom + m.paddingY);
  151. {
  152. // Buttons at dialog bottom
  153. CRect buttonRect;
  154. for(auto i : { IDOK, IDCANCEL, IDC_BUTTON2 })
  155. {
  156. auto wnd = GetDlgItem(i);
  157. wnd->GetWindowRect(buttonRect);
  158. wnd->SetWindowPos(nullptr, buttonRect.left - windowRect.left - GetSystemMetrics(SM_CXEDGE), rect.top, 0, 0, SWP_NOSIZE | SWP_NOOWNERZORDER);
  159. }
  160. }
  161. windowRect.bottom += displayHeight + m.paddingTop + m.footerHeight;
  162. SetWindowPos(nullptr, 0, 0, windowRect.Width(), windowRect.Height(), SWP_NOMOVE | SWP_NOOWNERZORDER);
  163. EnableToolTips();
  164. return TRUE;
  165. }
  166. void CTempoSwingDlg::OnOK()
  167. {
  168. CDialog::OnOK();
  169. // If this is the default setup, just clear the vector.
  170. if(m_pattern == PATTERNINDEX_INVALID)
  171. {
  172. if(static_cast<size_t>(std::count(m_tempoSwing.begin(), m_tempoSwing.end(), static_cast<TempoSwing::value_type>(TempoSwing::Unity))) == m_tempoSwing.size())
  173. {
  174. m_tempoSwing.clear();
  175. }
  176. } else
  177. {
  178. if(m_tempoSwing == m_sndFile.m_tempoSwing)
  179. {
  180. m_tempoSwing.clear();
  181. }
  182. }
  183. OnClose();
  184. }
  185. void CTempoSwingDlg::OnCancel()
  186. {
  187. CDialog::OnCancel();
  188. OnClose();
  189. }
  190. void CTempoSwingDlg::OnClose()
  191. {
  192. // Restore original swing properties after preview
  193. if(m_pattern == PATTERNINDEX_INVALID)
  194. {
  195. m_sndFile.m_tempoSwing = m_origTempoSwing;
  196. } else
  197. {
  198. m_sndFile.Patterns[m_pattern].SetTempoSwing(m_origTempoSwing);
  199. }
  200. }
  201. void CTempoSwingDlg::OnReset()
  202. {
  203. for(size_t i = 0; i < m_controls.size(); i++)
  204. {
  205. m_controls[i]->valueSlider.SetPos(0);
  206. }
  207. m_container.OnHScroll(0, 0, reinterpret_cast<CScrollBar *>(&(m_controls[0]->valueSlider)));
  208. }
  209. void CTempoSwingDlg::OnUseGlobal()
  210. {
  211. if(m_sndFile.m_tempoSwing.empty())
  212. {
  213. OnReset();
  214. return;
  215. }
  216. for(size_t i = 0; i < m_tempoSwing.size(); i++)
  217. {
  218. m_controls[i]->SetValue(m_sndFile.m_tempoSwing[i % m_sndFile.m_tempoSwing.size()]);
  219. }
  220. m_container.OnHScroll(0, 0, reinterpret_cast<CScrollBar *>(&(m_controls[0]->valueSlider)));
  221. }
  222. void CTempoSwingDlg::OnToggleGroup()
  223. {
  224. const BOOL checked = m_checkGroup.GetCheck() != BST_UNCHECKED;
  225. GetDlgItem(IDC_EDIT1)->EnableWindow(checked);
  226. GetDlgItem(IDC_SPIN1)->EnableWindow(checked);
  227. }
  228. void CTempoSwingDlg::OnGroupChanged()
  229. {
  230. int val = GetDlgItemInt(IDC_EDIT1);
  231. if(val > 0) m_groupSize = std::min(val, static_cast<int>(m_tempoSwing.size()));
  232. }
  233. void CTempoSwingDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
  234. {
  235. if(pScrollBar == &m_scrollBar)
  236. {
  237. // Get the minimum and maximum scrollbar positions.
  238. int minpos;
  239. int maxpos;
  240. pScrollBar->GetScrollRange(&minpos, &maxpos);
  241. SCROLLINFO sbInfo;
  242. pScrollBar->GetScrollInfo(&sbInfo);
  243. // Get the current position of scroll box.
  244. int curpos = pScrollBar->GetScrollPos();
  245. // Determine the new position of scroll box.
  246. switch(nSBCode)
  247. {
  248. case SB_LEFT: // Scroll to far left.
  249. curpos = minpos;
  250. break;
  251. case SB_RIGHT: // Scroll to far right.
  252. curpos = maxpos;
  253. break;
  254. case SB_ENDSCROLL: // End scroll.
  255. m_container.Invalidate();
  256. break;
  257. case SB_LINELEFT: // Scroll left.
  258. if(curpos > minpos)
  259. curpos--;
  260. break;
  261. case SB_LINERIGHT: // Scroll right.
  262. if(curpos < maxpos)
  263. curpos++;
  264. break;
  265. case SB_PAGELEFT: // Scroll one page left.
  266. if(curpos > minpos)
  267. {
  268. curpos = std::max(minpos, curpos - static_cast<int>(sbInfo.nPage));
  269. }
  270. break;
  271. case SB_PAGERIGHT: // Scroll one page right.
  272. if(curpos < maxpos)
  273. {
  274. curpos = std::min(maxpos, curpos + static_cast<int>(sbInfo.nPage));
  275. }
  276. break;
  277. case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
  278. curpos = nPos; // of the scroll box at the end of the drag operation.
  279. break;
  280. case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
  281. curpos = nPos; // position that the scroll box has been dragged to.
  282. break;
  283. }
  284. // Set the new position of the thumb (scroll box).
  285. pScrollBar->SetScrollPos(curpos);
  286. m_container.ScrollWindowEx(0, m_scrollPos - curpos, nullptr, nullptr, nullptr, nullptr, SW_SCROLLCHILDREN | SW_INVALIDATE | SW_ERASE);
  287. m_scrollPos = curpos;
  288. }
  289. CDialog::OnVScroll(nSBCode, nPos, pScrollBar);
  290. }
  291. // Scrollable container for the sliders
  292. BEGIN_MESSAGE_MAP(CTempoSwingDlg::SliderContainer, CDialog)
  293. //{{AFX_MSG_MAP(CTempoSwingDlg::SliderContainer)
  294. ON_WM_HSCROLL()
  295. ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &CTempoSwingDlg::SliderContainer::OnToolTipNotify)
  296. //}}AFX_MSG_MAP
  297. END_MESSAGE_MAP()
  298. void CTempoSwingDlg::SliderContainer::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
  299. {
  300. if(m_parent.m_checkGroup.GetCheck() != BST_UNCHECKED)
  301. {
  302. // Edit groups
  303. size_t editedGroup = 0;
  304. int editedValue = reinterpret_cast<CSliderCtrl *>(pScrollBar)->GetPos();
  305. for(size_t i = 0; i < m_parent.m_controls.size(); i++)
  306. {
  307. if(m_parent.m_controls[i]->valueSlider.m_hWnd == pScrollBar->m_hWnd)
  308. {
  309. editedGroup = (i / m_parent.m_groupSize) % 2u;
  310. break;
  311. }
  312. }
  313. for(size_t i = 0; i < m_parent.m_controls.size(); i++)
  314. {
  315. if((i / m_parent.m_groupSize) % 2u == editedGroup)
  316. {
  317. m_parent.m_controls[i]->valueSlider.SetPos(editedValue);
  318. }
  319. }
  320. }
  321. for(size_t i = 0; i < m_parent.m_controls.size(); i++)
  322. {
  323. m_parent.m_tempoSwing[i] = m_parent.m_controls[i]->GetValue();
  324. }
  325. m_parent.m_tempoSwing.Normalize();
  326. // Apply preview
  327. if(m_parent.m_pattern == PATTERNINDEX_INVALID)
  328. {
  329. m_parent.m_sndFile.m_tempoSwing = m_parent.m_tempoSwing;
  330. } else
  331. {
  332. m_parent.m_sndFile.Patterns[m_parent.m_pattern].SetTempoSwing(m_parent.m_tempoSwing);
  333. }
  334. for(size_t i = 0; i < m_parent.m_tempoSwing.size(); i++)
  335. {
  336. TCHAR s[32];
  337. wsprintf(s, _T("%i%%"), Util::muldivr(m_parent.m_tempoSwing[i], 100, TempoSwing::Unity));
  338. m_parent.m_controls[i]->valueLabel.SetWindowText(s);
  339. }
  340. CStatic::OnHScroll(nSBCode, nPos, pScrollBar);
  341. }
  342. BOOL CTempoSwingDlg::SliderContainer::OnToolTipNotify(UINT, NMHDR *pNMHDR, LRESULT *)
  343. {
  344. TOOLTIPTEXT *pTTT = (TOOLTIPTEXT*)pNMHDR;
  345. for(size_t i = 0; i < m_parent.m_controls.size(); i++)
  346. {
  347. if((HWND)pNMHDR->idFrom == m_parent.m_controls[i]->valueSlider.m_hWnd)
  348. {
  349. int32 val = Util::muldivr(m_parent.m_tempoSwing[i], 100, TempoSwing::Unity) - 100;
  350. wsprintf(pTTT->szText, _T("%s%d"), val > 0 ? _T("+") : _T(""), val);
  351. return TRUE;
  352. }
  353. }
  354. return FALSE;
  355. }
  356. OPENMPT_NAMESPACE_END