/* * TempoSwingDialog.cpp * -------------------- * Purpose: Implementation of the tempo swing configuration dialog. * Notes : (currently none) * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "stdafx.h" #include "TempoSwingDialog.h" #include "Mainfrm.h" OPENMPT_NAMESPACE_BEGIN void CTempoSwingDlg::RowCtls::SetValue(TempoSwing::value_type v) { int32 val = Util::muldivr(static_cast(v) - TempoSwing::Unity, CTempoSwingDlg::SliderUnity, TempoSwing::Unity); valueSlider.SetPos(val); } TempoSwing::value_type CTempoSwingDlg::RowCtls::GetValue() const { return Util::muldivr(valueSlider.GetPos(), TempoSwing::Unity, SliderUnity) + TempoSwing::Unity; } BEGIN_MESSAGE_MAP(CTempoSwingDlg, CDialog) //{{AFX_MSG_MAP(CTempoSwingDlg) ON_WM_VSCROLL() ON_COMMAND(IDC_BUTTON1, &CTempoSwingDlg::OnReset) ON_COMMAND(IDC_BUTTON2, &CTempoSwingDlg::OnUseGlobal) ON_COMMAND(IDC_CHECK1, &CTempoSwingDlg::OnToggleGroup) ON_EN_CHANGE(IDC_EDIT1, &CTempoSwingDlg::OnGroupChanged) //}}AFX_MSG_MAP END_MESSAGE_MAP() int CTempoSwingDlg::m_groupSize = 1; CTempoSwingDlg::CTempoSwingDlg(CWnd *parent, const TempoSwing ¤tTempoSwing, CSoundFile &sndFile, PATTERNINDEX pattern) : CDialog(IDD_TEMPO_SWING, parent) , m_container(*this) , m_scrollPos(0) , m_tempoSwing(currentTempoSwing) , m_origTempoSwing(pattern == PATTERNINDEX_INVALID ? sndFile.m_tempoSwing : sndFile.Patterns[pattern].GetTempoSwing()) , m_sndFile(sndFile) , m_pattern(pattern) { m_groupSize = std::min(m_groupSize, static_cast(m_tempoSwing.size())); } void CTempoSwingDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Control(pDX, IDC_CHECK1, m_checkGroup); DDX_Control(pDX, IDC_SCROLLBAR1, m_scrollBar); DDX_Control(pDX, IDC_CONTAINER, m_container); } BOOL CTempoSwingDlg::OnInitDialog() { struct Measurements { enum { edRowLabelWidth = 64, // Label "Row 999:" edSliderWidth = 220, // Setting slider edSliderHeight = 20, // Setting slider edValueLabelWidth = 64, // Label "100%" edPaddingX = 8, // Spacing between elements edPaddingY = 4, // Spacing between elements edPaddingTop = 64, // Spacing from top of dialog edRowHeight = edSliderHeight + edPaddingY, // Height of one set of controls edFooterHeight = 32, // Buttons edScrollbarWidth = 16, // Width of optional scrollbar }; const int rowLabelWidth; const int sliderWidth; const int sliderHeight; const int valueLabelWidth; const int paddingX; const int paddingY; const int paddingTop; const int rowHeight; const int footerHeight; const int scrollbarWidth; Measurements(HWND hWnd) : rowLabelWidth(Util::ScalePixels(edRowLabelWidth, hWnd)) , sliderWidth(Util::ScalePixels(edSliderWidth, hWnd)) , sliderHeight(Util::ScalePixels(edSliderHeight, hWnd)) , valueLabelWidth(Util::ScalePixels(edValueLabelWidth, hWnd)) , paddingX(Util::ScalePixels(edPaddingX, hWnd)) , paddingY(Util::ScalePixels(edPaddingY, hWnd)) , paddingTop(Util::ScalePixels(edPaddingTop, hWnd)) , rowHeight(Util::ScalePixels(edRowHeight, hWnd)) , footerHeight(Util::ScalePixels(edFooterHeight, hWnd)) , scrollbarWidth(Util::ScalePixels(edScrollbarWidth, hWnd)) { } }; CDialog::OnInitDialog(); Measurements m(m_hWnd); CRect windowRect, rect; GetWindowRect(windowRect); GetClientRect(rect); windowRect.bottom = windowRect.top + windowRect.Height() - rect.Height(); CRect mainWindowRect; CMainFrame::GetMainFrame()->GetClientRect(mainWindowRect); const int realHeight = static_cast(m_tempoSwing.size()) * m.rowHeight; const int displayHeight = std::min(realHeight, static_cast(mainWindowRect.bottom - windowRect.Height() - m.paddingTop - m.footerHeight)); CRect containerRect; m_container.GetClientRect(containerRect); containerRect.bottom = displayHeight; m_container.SetWindowPos(nullptr, 0, m.paddingTop, rect.right - m.scrollbarWidth, containerRect.bottom, SWP_NOZORDER); m_container.ModifyStyleEx(0, WS_EX_CONTROLPARENT, 0); // Need scrollbar? if(realHeight > displayHeight) { SCROLLINFO info; info.cbSize = sizeof(info); info.fMask = SIF_ALL; info.nMin = 0; info.nMax = realHeight; info.nPage = displayHeight; info.nTrackPos = info.nPos = 0; m_scrollBar.SetScrollInfo(&info, FALSE); CRect scrollRect; m_scrollBar.GetClientRect(scrollRect); m_scrollBar.SetWindowPos(nullptr, containerRect.right, m.paddingTop, scrollRect.Width(), displayHeight, SWP_NOZORDER); } else { m_scrollBar.ShowWindow(SW_HIDE); } rect.DeflateRect(m.paddingX, 0/* m.paddingTop*/, m.paddingX + m.scrollbarWidth, 0); GetDlgItem(IDC_BUTTON2)->ShowWindow((m_pattern != PATTERNINDEX_INVALID) ? SW_SHOW : SW_HIDE); m_controls.resize(m_tempoSwing.size()); for(size_t i = 0; i < m_controls.size(); i++) { m_controls[i] = std::make_unique(); auto &r = m_controls[i]; // Row label 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); r->rowLabel.SetFont(GetFont()); // Value label 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); r->valueLabel.SetFont(GetFont()); // Value slider 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); r->valueSlider.SetFont(GetFont()); r->valueSlider.SetRange(-SliderResolution / 2, SliderResolution / 2); r->valueSlider.SetTicFreq(SliderResolution / 8); r->valueSlider.SetPageSize(SliderResolution / 8); r->valueSlider.SetPos(1); // Work around https://bugs.winehq.org/show_bug.cgi?id=41909 r->SetValue(m_tempoSwing[i]); rect.MoveToY(rect.top + m.rowHeight); } ((CSpinButtonCtrl *)GetDlgItem(IDC_SPIN1))->SetRange32(1, static_cast(m_tempoSwing.size())); SetDlgItemInt(IDC_EDIT1, m_groupSize); OnToggleGroup(); m_container.OnHScroll(0, 0, reinterpret_cast(&(m_controls[0]->valueSlider))); rect.MoveToY(m.paddingTop + containerRect.bottom + m.paddingY); { // Buttons at dialog bottom CRect buttonRect; for(auto i : { IDOK, IDCANCEL, IDC_BUTTON2 }) { auto wnd = GetDlgItem(i); wnd->GetWindowRect(buttonRect); wnd->SetWindowPos(nullptr, buttonRect.left - windowRect.left - GetSystemMetrics(SM_CXEDGE), rect.top, 0, 0, SWP_NOSIZE | SWP_NOOWNERZORDER); } } windowRect.bottom += displayHeight + m.paddingTop + m.footerHeight; SetWindowPos(nullptr, 0, 0, windowRect.Width(), windowRect.Height(), SWP_NOMOVE | SWP_NOOWNERZORDER); EnableToolTips(); return TRUE; } void CTempoSwingDlg::OnOK() { CDialog::OnOK(); // If this is the default setup, just clear the vector. if(m_pattern == PATTERNINDEX_INVALID) { if(static_cast(std::count(m_tempoSwing.begin(), m_tempoSwing.end(), static_cast(TempoSwing::Unity))) == m_tempoSwing.size()) { m_tempoSwing.clear(); } } else { if(m_tempoSwing == m_sndFile.m_tempoSwing) { m_tempoSwing.clear(); } } OnClose(); } void CTempoSwingDlg::OnCancel() { CDialog::OnCancel(); OnClose(); } void CTempoSwingDlg::OnClose() { // Restore original swing properties after preview if(m_pattern == PATTERNINDEX_INVALID) { m_sndFile.m_tempoSwing = m_origTempoSwing; } else { m_sndFile.Patterns[m_pattern].SetTempoSwing(m_origTempoSwing); } } void CTempoSwingDlg::OnReset() { for(size_t i = 0; i < m_controls.size(); i++) { m_controls[i]->valueSlider.SetPos(0); } m_container.OnHScroll(0, 0, reinterpret_cast(&(m_controls[0]->valueSlider))); } void CTempoSwingDlg::OnUseGlobal() { if(m_sndFile.m_tempoSwing.empty()) { OnReset(); return; } for(size_t i = 0; i < m_tempoSwing.size(); i++) { m_controls[i]->SetValue(m_sndFile.m_tempoSwing[i % m_sndFile.m_tempoSwing.size()]); } m_container.OnHScroll(0, 0, reinterpret_cast(&(m_controls[0]->valueSlider))); } void CTempoSwingDlg::OnToggleGroup() { const BOOL checked = m_checkGroup.GetCheck() != BST_UNCHECKED; GetDlgItem(IDC_EDIT1)->EnableWindow(checked); GetDlgItem(IDC_SPIN1)->EnableWindow(checked); } void CTempoSwingDlg::OnGroupChanged() { int val = GetDlgItemInt(IDC_EDIT1); if(val > 0) m_groupSize = std::min(val, static_cast(m_tempoSwing.size())); } void CTempoSwingDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar) { if(pScrollBar == &m_scrollBar) { // Get the minimum and maximum scrollbar positions. int minpos; int maxpos; pScrollBar->GetScrollRange(&minpos, &maxpos); SCROLLINFO sbInfo; pScrollBar->GetScrollInfo(&sbInfo); // Get the current position of scroll box. int curpos = pScrollBar->GetScrollPos(); // Determine the new position of scroll box. switch(nSBCode) { case SB_LEFT: // Scroll to far left. curpos = minpos; break; case SB_RIGHT: // Scroll to far right. curpos = maxpos; break; case SB_ENDSCROLL: // End scroll. m_container.Invalidate(); break; case SB_LINELEFT: // Scroll left. if(curpos > minpos) curpos--; break; case SB_LINERIGHT: // Scroll right. if(curpos < maxpos) curpos++; break; case SB_PAGELEFT: // Scroll one page left. if(curpos > minpos) { curpos = std::max(minpos, curpos - static_cast(sbInfo.nPage)); } break; case SB_PAGERIGHT: // Scroll one page right. if(curpos < maxpos) { curpos = std::min(maxpos, curpos + static_cast(sbInfo.nPage)); } break; case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position curpos = nPos; // of the scroll box at the end of the drag operation. break; case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the curpos = nPos; // position that the scroll box has been dragged to. break; } // Set the new position of the thumb (scroll box). pScrollBar->SetScrollPos(curpos); m_container.ScrollWindowEx(0, m_scrollPos - curpos, nullptr, nullptr, nullptr, nullptr, SW_SCROLLCHILDREN | SW_INVALIDATE | SW_ERASE); m_scrollPos = curpos; } CDialog::OnVScroll(nSBCode, nPos, pScrollBar); } // Scrollable container for the sliders BEGIN_MESSAGE_MAP(CTempoSwingDlg::SliderContainer, CDialog) //{{AFX_MSG_MAP(CTempoSwingDlg::SliderContainer) ON_WM_HSCROLL() ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &CTempoSwingDlg::SliderContainer::OnToolTipNotify) //}}AFX_MSG_MAP END_MESSAGE_MAP() void CTempoSwingDlg::SliderContainer::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar) { if(m_parent.m_checkGroup.GetCheck() != BST_UNCHECKED) { // Edit groups size_t editedGroup = 0; int editedValue = reinterpret_cast(pScrollBar)->GetPos(); for(size_t i = 0; i < m_parent.m_controls.size(); i++) { if(m_parent.m_controls[i]->valueSlider.m_hWnd == pScrollBar->m_hWnd) { editedGroup = (i / m_parent.m_groupSize) % 2u; break; } } for(size_t i = 0; i < m_parent.m_controls.size(); i++) { if((i / m_parent.m_groupSize) % 2u == editedGroup) { m_parent.m_controls[i]->valueSlider.SetPos(editedValue); } } } for(size_t i = 0; i < m_parent.m_controls.size(); i++) { m_parent.m_tempoSwing[i] = m_parent.m_controls[i]->GetValue(); } m_parent.m_tempoSwing.Normalize(); // Apply preview if(m_parent.m_pattern == PATTERNINDEX_INVALID) { m_parent.m_sndFile.m_tempoSwing = m_parent.m_tempoSwing; } else { m_parent.m_sndFile.Patterns[m_parent.m_pattern].SetTempoSwing(m_parent.m_tempoSwing); } for(size_t i = 0; i < m_parent.m_tempoSwing.size(); i++) { TCHAR s[32]; wsprintf(s, _T("%i%%"), Util::muldivr(m_parent.m_tempoSwing[i], 100, TempoSwing::Unity)); m_parent.m_controls[i]->valueLabel.SetWindowText(s); } CStatic::OnHScroll(nSBCode, nPos, pScrollBar); } BOOL CTempoSwingDlg::SliderContainer::OnToolTipNotify(UINT, NMHDR *pNMHDR, LRESULT *) { TOOLTIPTEXT *pTTT = (TOOLTIPTEXT*)pNMHDR; for(size_t i = 0; i < m_parent.m_controls.size(); i++) { if((HWND)pNMHDR->idFrom == m_parent.m_controls[i]->valueSlider.m_hWnd) { int32 val = Util::muldivr(m_parent.m_tempoSwing[i], 100, TempoSwing::Unity) - 100; wsprintf(pTTT->szText, _T("%s%d"), val > 0 ? _T("+") : _T(""), val); return TRUE; } } return FALSE; } OPENMPT_NAMESPACE_END