/* * Mainbar.cpp * ----------- * Purpose: Implementation of OpenMPT's window toolbar. * Notes : (currently none) * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "stdafx.h" #include "Mptrack.h" #include "Mainfrm.h" #include "InputHandler.h" #include "View_tre.h" #include "ImageLists.h" #include "Moddoc.h" #include "../soundlib/mod_specifications.h" #include "../common/mptStringBuffer.h" OPENMPT_NAMESPACE_BEGIN ///////////////////////////////////////////////////////////////////// // CToolBarEx: custom toolbar base class void CToolBarEx::SetHorizontal() { m_bVertical = false; SetBarStyle(GetBarStyle() | CBRS_ALIGN_TOP); } void CToolBarEx::SetVertical() { m_bVertical = true; } CSize CToolBarEx::CalcDynamicLayout(int nLength, DWORD dwMode) { CSize sizeResult; // if we're committing set the buttons appropriately if(dwMode & LM_COMMIT) { if(dwMode & LM_VERTDOCK) { if(!m_bVertical) SetVertical(); } else { if(m_bVertical) SetHorizontal(); } sizeResult = CToolBar::CalcDynamicLayout(nLength, dwMode); } else { const bool wasVertical = m_bVertical; const bool doSwitch = (dwMode & LM_HORZ) ? wasVertical : !wasVertical; if(doSwitch) { if(wasVertical) SetHorizontal(); else SetVertical(); } sizeResult = CToolBar::CalcDynamicLayout(nLength, dwMode); if(doSwitch) { if(wasVertical) SetHorizontal(); else SetVertical(); } } return sizeResult; } BOOL CToolBarEx::EnableControl(CWnd &wnd, UINT nIndex, UINT nHeight) { if(wnd.m_hWnd != NULL) { CRect rect; GetItemRect(nIndex, rect); if(nHeight) { int n = (rect.bottom + rect.top - nHeight) / 2; if(n > rect.top) rect.top = n; } wnd.SetWindowPos(NULL, rect.left, rect.top, 0, 0, SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOCOPYBITS); wnd.ShowWindow(SW_SHOW); } return TRUE; } void CToolBarEx::ChangeCtrlStyle(LONG lStyle, BOOL bSetStyle) { if(m_hWnd) { LONG lStyleOld = GetWindowLong(m_hWnd, GWL_STYLE); if(bSetStyle) lStyleOld |= lStyle; else lStyleOld &= ~lStyle; SetWindowLong(m_hWnd, GWL_STYLE, lStyleOld); SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE); Invalidate(); } } void CToolBarEx::EnableFlatButtons(BOOL bFlat) { m_bFlatButtons = bFlat ? true : false; ChangeCtrlStyle(TBSTYLE_FLAT, bFlat); } ///////////////////////////////////////////////////////////////////// // CMainToolBar #define SCALEWIDTH(x) (Util::ScalePixels(x, m_hWnd)) #define SCALEHEIGHT(x) (Util::ScalePixels(x, m_hWnd)) // Play Command #define PLAYCMD_INDEX 10 #define TOOLBAR_IMAGE_PAUSE 8 #define TOOLBAR_IMAGE_PLAY 13 // Base octave #define EDITOCTAVE_INDEX 13 #define EDITOCTAVE_WIDTH SCALEWIDTH(55) #define EDITOCTAVE_HEIGHT SCALEHEIGHT(20) #define SPINOCTAVE_INDEX (EDITOCTAVE_INDEX+1) #define SPINOCTAVE_WIDTH SCALEWIDTH(16) #define SPINOCTAVE_HEIGHT (EDITOCTAVE_HEIGHT) // Static "Tempo:" #define TEMPOTEXT_INDEX 16 #define TEMPOTEXT_WIDTH SCALEWIDTH(45) #define TEMPOTEXT_HEIGHT SCALEHEIGHT(20) // Edit Tempo #define EDITTEMPO_INDEX (TEMPOTEXT_INDEX+1) #define EDITTEMPO_WIDTH SCALEWIDTH(48) #define EDITTEMPO_HEIGHT SCALEHEIGHT(20) // Spin Tempo #define SPINTEMPO_INDEX (EDITTEMPO_INDEX+1) #define SPINTEMPO_WIDTH SCALEWIDTH(16) #define SPINTEMPO_HEIGHT (EDITTEMPO_HEIGHT) // Static "Speed:" #define SPEEDTEXT_INDEX 20 #define SPEEDTEXT_WIDTH SCALEWIDTH(57) #define SPEEDTEXT_HEIGHT (TEMPOTEXT_HEIGHT) // Edit Speed #define EDITSPEED_INDEX (SPEEDTEXT_INDEX+1) #define EDITSPEED_WIDTH SCALEWIDTH(28) #define EDITSPEED_HEIGHT (EDITTEMPO_HEIGHT) // Spin Speed #define SPINSPEED_INDEX (EDITSPEED_INDEX+1) #define SPINSPEED_WIDTH SCALEWIDTH(16) #define SPINSPEED_HEIGHT (EDITSPEED_HEIGHT) // Static "Rows/Beat:" #define RPBTEXT_INDEX 24 #define RPBTEXT_WIDTH SCALEWIDTH(63) #define RPBTEXT_HEIGHT (TEMPOTEXT_HEIGHT) // Edit Speed #define EDITRPB_INDEX (RPBTEXT_INDEX+1) #define EDITRPB_WIDTH SCALEWIDTH(28) #define EDITRPB_HEIGHT (EDITTEMPO_HEIGHT) // Spin Speed #define SPINRPB_INDEX (EDITRPB_INDEX+1) #define SPINRPB_WIDTH SCALEWIDTH(16) #define SPINRPB_HEIGHT (EDITRPB_HEIGHT) // VU Meters #define VUMETER_INDEX (SPINRPB_INDEX+6) #define VUMETER_WIDTH SCALEWIDTH(255) #define VUMETER_HEIGHT SCALEHEIGHT(19) static UINT MainButtons[] = { // same order as in the bitmap 'mainbar.bmp' ID_FILE_NEW, ID_FILE_OPEN, ID_FILE_SAVE, ID_SEPARATOR, ID_EDIT_CUT, ID_EDIT_COPY, ID_EDIT_PASTE, ID_SEPARATOR, ID_MIDI_RECORD, ID_PLAYER_STOP, ID_PLAYER_PAUSE, ID_PLAYER_PLAYFROMSTART, ID_SEPARATOR, ID_SEPARATOR, ID_SEPARATOR, ID_SEPARATOR, ID_SEPARATOR, ID_SEPARATOR, ID_SEPARATOR, ID_SEPARATOR, ID_SEPARATOR, ID_SEPARATOR, ID_SEPARATOR, ID_SEPARATOR, ID_SEPARATOR, ID_SEPARATOR, ID_SEPARATOR, ID_SEPARATOR, ID_VIEW_OPTIONS, ID_PANIC, ID_UPDATE_AVAILABLE, ID_SEPARATOR, ID_SEPARATOR, // VU Meter }; enum { MAX_MIDI_DEVICES = 256 }; BEGIN_MESSAGE_MAP(CMainToolBar, CToolBarEx) ON_WM_VSCROLL() ON_NOTIFY_EX_RANGE(TTN_NEEDTEXT, 0, 0xFFFF, &CMainToolBar::OnToolTipText) ON_NOTIFY_REFLECT(TBN_DROPDOWN, &CMainToolBar::OnTbnDropDownToolBar) ON_COMMAND_RANGE(ID_SELECT_MIDI_DEVICE, ID_SELECT_MIDI_DEVICE + MAX_MIDI_DEVICES, &CMainToolBar::OnSelectMIDIDevice) END_MESSAGE_MAP() template static bool CreateTextWnd(TWnd &wnd, const TCHAR *text, DWORD style, CWnd *parent, UINT id) { auto dc = parent->GetDC(); auto oldFont = dc->SelectObject(CMainFrame::GetGUIFont()); const auto size = dc->GetTextExtent(text); dc->SelectObject(oldFont); parent->ReleaseDC(dc); CRect rect{0, 0, size.cx + Util::ScalePixels(10, *parent), std::max(static_cast(size.cy) + Util::ScalePixels(4, *parent), Util::ScalePixels(20, *parent))}; return wnd.Create(text, style, rect, parent, id) != FALSE; } BOOL CMainToolBar::Create(CWnd *parent) { CRect rect; DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_SIZE_DYNAMIC | CBRS_TOOLTIPS | CBRS_FLYBY; if(!CToolBar::Create(parent, dwStyle)) return FALSE; CDC *dc = GetDC(); const auto hFont = reinterpret_cast(CMainFrame::GetGUIFont()); const double scaling = Util::GetDPIx(m_hWnd) / 96.0; const int imgSize = mpt::saturate_round(16 * scaling), btnSizeX = mpt::saturate_round(23 * scaling), btnSizeY = mpt::saturate_round(22 * scaling); m_ImageList.Create(IDB_MAINBAR, 16, 16, IMGLIST_NUMIMAGES, 1, dc, scaling, false); m_ImageListDisabled.Create(IDB_MAINBAR, 16, 16, IMGLIST_NUMIMAGES, 1, dc, scaling, true); ReleaseDC(dc); GetToolBarCtrl().SetBitmapSize(CSize(imgSize, imgSize)); GetToolBarCtrl().SetButtonSize(CSize(btnSizeX, btnSizeY)); GetToolBarCtrl().SetImageList(&m_ImageList); GetToolBarCtrl().SetDisabledImageList(&m_ImageListDisabled); SendMessage(WM_SETFONT, hFont, TRUE); if(!SetButtons(MainButtons, mpt::saturate_cast(std::size(MainButtons)))) return FALSE; CRect temp; GetItemRect(0, temp); SetSizes(CSize(temp.Width(), temp.Height()), CSize(imgSize, imgSize)); // Dropdown menus for New and MIDI buttons LPARAM dwExStyle = GetToolBarCtrl().SendMessage(TB_GETEXTENDEDSTYLE) | TBSTYLE_EX_DRAWDDARROWS; GetToolBarCtrl().SendMessage(TB_SETEXTENDEDSTYLE, 0, dwExStyle); SetButtonStyle(CommandToIndex(ID_FILE_NEW), GetButtonStyle(CommandToIndex(ID_FILE_NEW)) | TBSTYLE_DROPDOWN); SetButtonStyle(CommandToIndex(ID_MIDI_RECORD), GetButtonStyle(CommandToIndex(ID_MIDI_RECORD)) | TBSTYLE_DROPDOWN); nCurrentSpeed = 6; nCurrentTempo.Set(125); nCurrentRowsPerBeat = 4; nCurrentOctave = -1; // Octave Edit Box if(!CreateTextWnd(m_EditOctave, _T("Octave 9"), WS_CHILD | WS_BORDER | SS_LEFT | SS_CENTERIMAGE, this, IDC_EDIT_BASEOCTAVE)) return FALSE; rect.SetRect(0, 0, SPINOCTAVE_WIDTH, SPINOCTAVE_HEIGHT); m_SpinOctave.Create(WS_CHILD | UDS_ALIGNRIGHT, rect, this, IDC_SPIN_BASEOCTAVE); // Tempo Text if(!CreateTextWnd(m_StaticTempo, _T("Tempo:"), WS_CHILD | SS_CENTER | SS_CENTERIMAGE, this, IDC_TEXT_CURRENTTEMPO)) return FALSE; // Tempo EditBox if(!CreateTextWnd(m_EditTempo, _T("999.999"), WS_CHILD | WS_BORDER | SS_LEFT | SS_CENTERIMAGE , this, IDC_EDIT_CURRENTTEMPO)) return FALSE; // Tempo Spin rect.SetRect(0, 0, SPINTEMPO_WIDTH, SPINTEMPO_HEIGHT); m_SpinTempo.Create(WS_CHILD | UDS_ALIGNRIGHT, rect, this, IDC_SPIN_CURRENTTEMPO); // Speed Text if(!CreateTextWnd(m_StaticSpeed, _T("Ticks/Row:"), WS_CHILD | SS_CENTER | SS_CENTERIMAGE, this, IDC_TEXT_CURRENTSPEED)) return FALSE; // Speed EditBox if(!CreateTextWnd(m_EditSpeed, _T("999"), WS_CHILD | WS_BORDER | SS_LEFT | SS_CENTERIMAGE , this, IDC_EDIT_CURRENTSPEED)) return FALSE; // Speed Spin rect.SetRect(0, 0, SPINSPEED_WIDTH, SPINSPEED_HEIGHT); m_SpinSpeed.Create(WS_CHILD | UDS_ALIGNRIGHT, rect, this, IDC_SPIN_CURRENTSPEED); // Rows per Beat Text if(!CreateTextWnd(m_StaticRowsPerBeat, _T("Rows/Beat:"), WS_CHILD | SS_CENTER | SS_CENTERIMAGE, this, IDC_TEXT_RPB)) return FALSE; // Rows per Beat EditBox if(!CreateTextWnd(m_EditRowsPerBeat, _T("9999"), WS_CHILD | WS_BORDER | SS_LEFT | SS_CENTERIMAGE , this, IDC_EDIT_RPB)) return FALSE; // Rows per Beat Spin rect.SetRect(0, 0, SPINRPB_WIDTH, SPINRPB_HEIGHT); m_SpinRowsPerBeat.Create(WS_CHILD | UDS_ALIGNRIGHT, rect, this, IDC_SPIN_RPB); // VU Meter rect.SetRect(0, 0, VUMETER_WIDTH, VUMETER_HEIGHT); //m_VuMeter.CreateEx(WS_EX_STATICEDGE, "STATIC", "", WS_CHILD | WS_BORDER | SS_NOTIFY, rect, this, IDC_VUMETER); m_VuMeter.Create(_T(""), WS_CHILD | WS_BORDER | SS_NOTIFY, rect, this, IDC_VUMETER); // Adjust control styles m_EditOctave.SendMessage(WM_SETFONT, hFont); m_EditOctave.ModifyStyleEx(0, WS_EX_STATICEDGE, SWP_NOACTIVATE); m_StaticTempo.SendMessage(WM_SETFONT, hFont); m_EditTempo.SendMessage(WM_SETFONT, hFont); m_EditTempo.ModifyStyleEx(0, WS_EX_STATICEDGE, SWP_NOACTIVATE); m_StaticSpeed.SendMessage(WM_SETFONT, hFont); m_EditSpeed.SendMessage(WM_SETFONT, hFont); m_EditSpeed.ModifyStyleEx(0, WS_EX_STATICEDGE, SWP_NOACTIVATE); m_StaticRowsPerBeat.SendMessage(WM_SETFONT, hFont); m_EditRowsPerBeat.SendMessage(WM_SETFONT, hFont); m_EditRowsPerBeat.ModifyStyleEx(0, WS_EX_STATICEDGE, SWP_NOACTIVATE); m_SpinOctave.SetRange(MIN_BASEOCTAVE, MAX_BASEOCTAVE); m_SpinOctave.SetPos(4); m_SpinTempo.SetRange(-1, 1); m_SpinTempo.SetPos(0); m_SpinSpeed.SetRange(-1, 1); m_SpinSpeed.SetPos(0); m_SpinRowsPerBeat.SetRange(-1, 1); m_SpinRowsPerBeat.SetPos(0); // Display everything SetWindowText(_T("Main")); SetBaseOctave(4); SetCurrentSong(nullptr); EnableDocking(CBRS_ALIGN_ANY); GetToolBarCtrl().SetState(ID_UPDATE_AVAILABLE, TBSTATE_HIDDEN); return TRUE; } void CMainToolBar::Init(CMainFrame *pMainFrm) { EnableFlatButtons(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_FLATBUTTONS); SetHorizontal(); pMainFrm->DockControlBar(this); } static int GetWndWidth(const CWnd &wnd) { CRect rect; wnd.GetClientRect(rect); return rect.right; } void CMainToolBar::SetHorizontal() { CToolBarEx::SetHorizontal(); m_VuMeter.SetOrientation(true); SetButtonInfo(EDITOCTAVE_INDEX, IDC_EDIT_BASEOCTAVE, TBBS_SEPARATOR, GetWndWidth(m_EditOctave)); SetButtonInfo(SPINOCTAVE_INDEX, IDC_SPIN_BASEOCTAVE, TBBS_SEPARATOR, SPINOCTAVE_WIDTH); SetButtonInfo(TEMPOTEXT_INDEX, IDC_TEXT_CURRENTTEMPO, TBBS_SEPARATOR, GetWndWidth(m_StaticTempo)); SetButtonInfo(EDITTEMPO_INDEX, IDC_EDIT_CURRENTTEMPO, TBBS_SEPARATOR, GetWndWidth(m_EditTempo)); SetButtonInfo(SPINTEMPO_INDEX, IDC_SPIN_CURRENTTEMPO, TBBS_SEPARATOR, SPINTEMPO_WIDTH); SetButtonInfo(SPEEDTEXT_INDEX, IDC_TEXT_CURRENTSPEED, TBBS_SEPARATOR, GetWndWidth(m_StaticSpeed)); SetButtonInfo(EDITSPEED_INDEX, IDC_EDIT_CURRENTSPEED, TBBS_SEPARATOR, GetWndWidth(m_EditSpeed)); SetButtonInfo(SPINSPEED_INDEX, IDC_SPIN_CURRENTSPEED, TBBS_SEPARATOR, SPINSPEED_WIDTH); SetButtonInfo(RPBTEXT_INDEX, IDC_TEXT_RPB, TBBS_SEPARATOR, GetWndWidth(m_StaticRowsPerBeat)); SetButtonInfo(EDITRPB_INDEX, IDC_EDIT_RPB, TBBS_SEPARATOR, GetWndWidth(m_EditRowsPerBeat)); SetButtonInfo(SPINRPB_INDEX, IDC_SPIN_RPB, TBBS_SEPARATOR, SPINRPB_WIDTH); SetButtonInfo(VUMETER_INDEX, IDC_VUMETER, TBBS_SEPARATOR, VUMETER_WIDTH); //SetButtonInfo(SPINSPEED_INDEX+1, IDC_TEXT_BPM, TBBS_SEPARATOR, SPEEDTEXT_WIDTH); // Octave Box EnableControl(m_EditOctave, EDITOCTAVE_INDEX); EnableControl(m_SpinOctave, SPINOCTAVE_INDEX); // Tempo EnableControl(m_StaticTempo, TEMPOTEXT_INDEX, TEMPOTEXT_HEIGHT); EnableControl(m_EditTempo, EDITTEMPO_INDEX, EDITTEMPO_HEIGHT); EnableControl(m_SpinTempo, SPINTEMPO_INDEX, SPINTEMPO_HEIGHT); // Speed EnableControl(m_StaticSpeed, SPEEDTEXT_INDEX, SPEEDTEXT_HEIGHT); EnableControl(m_EditSpeed, EDITSPEED_INDEX, EDITSPEED_HEIGHT); EnableControl(m_SpinSpeed, SPINSPEED_INDEX, SPINSPEED_HEIGHT); // Rows per Beat EnableControl(m_StaticRowsPerBeat, RPBTEXT_INDEX, RPBTEXT_HEIGHT); EnableControl(m_EditRowsPerBeat, EDITRPB_INDEX, EDITRPB_HEIGHT); EnableControl(m_SpinRowsPerBeat, SPINRPB_INDEX, SPINRPB_HEIGHT); EnableControl(m_VuMeter, VUMETER_INDEX, VUMETER_HEIGHT); } void CMainToolBar::SetVertical() { CToolBarEx::SetVertical(); m_VuMeter.SetOrientation(false); // Change Buttons SetButtonInfo(EDITOCTAVE_INDEX, ID_SEPARATOR, TBBS_SEPARATOR, 1); SetButtonInfo(SPINOCTAVE_INDEX, ID_SEPARATOR, TBBS_SEPARATOR, 1); SetButtonInfo(TEMPOTEXT_INDEX, ID_SEPARATOR, TBBS_SEPARATOR, 1); SetButtonInfo(EDITTEMPO_INDEX, ID_SEPARATOR, TBBS_SEPARATOR, 1); SetButtonInfo(SPINTEMPO_INDEX, ID_SEPARATOR, TBBS_SEPARATOR, 1); SetButtonInfo(SPEEDTEXT_INDEX, ID_SEPARATOR, TBBS_SEPARATOR, 1); SetButtonInfo(EDITSPEED_INDEX, ID_SEPARATOR, TBBS_SEPARATOR, 1); SetButtonInfo(SPINSPEED_INDEX, ID_SEPARATOR, TBBS_SEPARATOR, 1); SetButtonInfo(RPBTEXT_INDEX, ID_SEPARATOR, TBBS_SEPARATOR, 1); SetButtonInfo(EDITRPB_INDEX, ID_SEPARATOR, TBBS_SEPARATOR, 1); SetButtonInfo(SPINRPB_INDEX, ID_SEPARATOR, TBBS_SEPARATOR, 1); SetButtonInfo(VUMETER_INDEX, IDC_VUMETER, TBBS_SEPARATOR, VUMETER_HEIGHT); // Hide Controls if(m_EditOctave.m_hWnd) m_EditOctave.ShowWindow(SW_HIDE); if(m_SpinOctave.m_hWnd) m_SpinOctave.ShowWindow(SW_HIDE); if(m_StaticTempo.m_hWnd) m_StaticTempo.ShowWindow(SW_HIDE); if(m_EditTempo.m_hWnd) m_EditTempo.ShowWindow(SW_HIDE); if(m_SpinTempo.m_hWnd) m_SpinTempo.ShowWindow(SW_HIDE); if(m_StaticSpeed.m_hWnd) m_StaticSpeed.ShowWindow(SW_HIDE); if(m_EditSpeed.m_hWnd) m_EditSpeed.ShowWindow(SW_HIDE); if(m_SpinSpeed.m_hWnd) m_SpinSpeed.ShowWindow(SW_HIDE); if(m_StaticRowsPerBeat.m_hWnd) m_StaticRowsPerBeat.ShowWindow(SW_HIDE); if(m_EditRowsPerBeat.m_hWnd) m_EditRowsPerBeat.ShowWindow(SW_HIDE); if(m_SpinRowsPerBeat.m_hWnd) m_SpinRowsPerBeat.ShowWindow(SW_HIDE); EnableControl(m_VuMeter, VUMETER_INDEX, VUMETER_HEIGHT); //if(m_StaticBPM.m_hWnd) m_StaticBPM.ShowWindow(SW_HIDE); } UINT CMainToolBar::GetBaseOctave() const { if(nCurrentOctave >= MIN_BASEOCTAVE) return (UINT)nCurrentOctave; return 4; } BOOL CMainToolBar::SetBaseOctave(UINT nOctave) { TCHAR s[64]; if((nOctave < MIN_BASEOCTAVE) || (nOctave > MAX_BASEOCTAVE)) return FALSE; if(nOctave != (UINT)nCurrentOctave) { nCurrentOctave = nOctave; wsprintf(s, _T(" Octave %d"), nOctave); m_EditOctave.SetWindowText(s); m_SpinOctave.SetPos(nOctave); } return TRUE; } bool CMainToolBar::ShowUpdateInfo(const CString &newVersion, const CString &infoURL, bool showHighLight) { GetToolBarCtrl().SetState(ID_UPDATE_AVAILABLE, TBSTATE_ENABLED); if(m_bVertical) SetVertical(); else SetHorizontal(); CRect rect; GetToolBarCtrl().GetRect(ID_UPDATE_AVAILABLE, &rect); CPoint pt = rect.CenterPoint(); ClientToScreen(&pt); CMainFrame::GetMainFrame()->GetWindowRect(rect); LimitMax(pt.x, rect.right); if(showHighLight) { return m_tooltip.ShowUpdate(*this, newVersion, infoURL, rect, pt, ID_UPDATE_AVAILABLE); } else { return true; } } void CMainToolBar::RemoveUpdateInfo() { if(m_tooltip) m_tooltip.Pop(); GetToolBarCtrl().SetState(ID_UPDATE_AVAILABLE, TBSTATE_HIDDEN); } BOOL CMainToolBar::SetCurrentSong(CSoundFile *pSndFile) { static CSoundFile *sndFile = nullptr; if(pSndFile != sndFile) { sndFile = pSndFile; } // Update Info if(pSndFile) { TCHAR s[32]; // Update play/pause button if(nCurrentTempo == TEMPO(0, 0)) SetButtonInfo(PLAYCMD_INDEX, ID_PLAYER_PAUSE, TBBS_BUTTON, TOOLBAR_IMAGE_PAUSE); // Update Speed int nSpeed = pSndFile->m_PlayState.m_nMusicSpeed; if(nSpeed != nCurrentSpeed) { CModDoc *modDoc = pSndFile->GetpModDoc(); if(modDoc != nullptr) { // Update envelope views if speed has changed modDoc->UpdateAllViews(InstrumentHint().Envelope()); } if(nCurrentSpeed < 0) m_SpinSpeed.EnableWindow(TRUE); nCurrentSpeed = nSpeed; wsprintf(s, _T("%u"), static_cast(nCurrentSpeed)); m_EditSpeed.SetWindowText(s); } TEMPO nTempo = pSndFile->m_PlayState.m_nMusicTempo; if(nTempo != nCurrentTempo) { if(nCurrentTempo <= TEMPO(0, 0)) m_SpinTempo.EnableWindow(TRUE); nCurrentTempo = nTempo; if(nCurrentTempo.GetFract() == 0) _stprintf(s, _T("%u"), nCurrentTempo.GetInt()); else _stprintf(s, _T("%.4f"), nCurrentTempo.ToDouble()); m_EditTempo.SetWindowText(s); } int nRowsPerBeat = pSndFile->m_PlayState.m_nCurrentRowsPerBeat; if(nRowsPerBeat != nCurrentRowsPerBeat) { if(nCurrentRowsPerBeat < 0) m_SpinRowsPerBeat.EnableWindow(TRUE); nCurrentRowsPerBeat = nRowsPerBeat; wsprintf(s, _T("%u"), static_cast(nCurrentRowsPerBeat)); m_EditRowsPerBeat.SetWindowText(s); } } else { if(nCurrentTempo > TEMPO(0, 0)) { nCurrentTempo.Set(0); m_EditTempo.SetWindowText(_T("---")); m_SpinTempo.EnableWindow(FALSE); SetButtonInfo(PLAYCMD_INDEX, ID_PLAYER_PLAY, TBBS_BUTTON, TOOLBAR_IMAGE_PLAY); } if(nCurrentSpeed != -1) { nCurrentSpeed = -1; m_EditSpeed.SetWindowText(_T("---")); m_SpinSpeed.EnableWindow(FALSE); } if(nCurrentRowsPerBeat != -1) { nCurrentRowsPerBeat = -1; m_EditRowsPerBeat.SetWindowText(_T("---")); m_SpinRowsPerBeat.EnableWindow(FALSE); } } return TRUE; } void CMainToolBar::OnVScroll(UINT nCode, UINT nPos, CScrollBar *pScrollBar) { CMainFrame *pMainFrm; CToolBarEx::OnVScroll(nCode, nPos, pScrollBar); short int oct = (short int)m_SpinOctave.GetPos(); if((oct >= MIN_BASEOCTAVE) && ((int)oct != nCurrentOctave)) { SetBaseOctave(oct); } if((nCurrentSpeed < 0) || (nCurrentTempo <= TEMPO(0, 0))) return; if((pMainFrm = CMainFrame::GetMainFrame()) != nullptr) { CSoundFile *pSndFile = pMainFrm->GetSoundFilePlaying(); if(pSndFile) { const auto &specs = pSndFile->GetModSpecifications(); int n; if((n = mpt::signum(m_SpinTempo.GetPos32())) != 0) { TEMPO newTempo; if(specs.hasFractionalTempo) { n *= TEMPO::fractFact; if(CMainFrame::GetMainFrame()->GetInputHandler()->CtrlPressed()) n /= 100; else n /= 10; newTempo.SetRaw(n); } else { newTempo = TEMPO(n, 0); } newTempo += nCurrentTempo; pSndFile->SetTempo(Clamp(newTempo, specs.GetTempoMin(), specs.GetTempoMax()), true); m_SpinTempo.SetPos(0); } if((n = mpt::signum(m_SpinSpeed.GetPos32())) != 0) { pSndFile->m_PlayState.m_nMusicSpeed = Clamp(uint32(nCurrentSpeed + n), specs.speedMin, specs.speedMax); m_SpinSpeed.SetPos(0); } if((n = m_SpinRowsPerBeat.GetPos32()) != 0) { if(n < 0) { if(nCurrentRowsPerBeat > 1) SetRowsPerBeat(nCurrentRowsPerBeat - 1); } else if(static_cast(nCurrentRowsPerBeat) < pSndFile->m_PlayState.m_nCurrentRowsPerMeasure) { SetRowsPerBeat(nCurrentRowsPerBeat + 1); } m_SpinRowsPerBeat.SetPos(0); // Update pattern editor pMainFrm->PostMessage(WM_MOD_INVALIDATEPATTERNS, HINT_MPTOPTIONS); } SetCurrentSong(pSndFile); } } } void CMainToolBar::OnTbnDropDownToolBar(NMHDR *pNMHDR, LRESULT *pResult) { NMTOOLBAR *pToolBar = reinterpret_cast(pNMHDR); ClientToScreen(&(pToolBar->rcButton)); switch(pToolBar->iItem) { case ID_FILE_NEW: CMainFrame::GetMainFrame()->GetFileMenu()->GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pToolBar->rcButton.left, pToolBar->rcButton.bottom, this); break; case ID_MIDI_RECORD: // Show a list of MIDI devices { HMENU hMenu = ::CreatePopupMenu(); MIDIINCAPS mic; UINT numDevs = midiInGetNumDevs(); if(numDevs > MAX_MIDI_DEVICES) numDevs = MAX_MIDI_DEVICES; UINT current = TrackerSettings::Instance().GetCurrentMIDIDevice(); for(UINT i = 0; i < numDevs; i++) { mic.szPname[0] = 0; if(midiInGetDevCaps(i, &mic, sizeof(mic)) == MMSYSERR_NOERROR) { ::AppendMenu(hMenu, MF_STRING | (i == current ? MF_CHECKED : 0), ID_SELECT_MIDI_DEVICE + i, theApp.GetFriendlyMIDIPortName(mpt::String::ReadCStringBuf(mic.szPname), true)); } } if(!numDevs) { ::AppendMenu(hMenu, MF_STRING | MF_GRAYED, 0, _T("No MIDI input devices found")); } ::TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, pToolBar->rcButton.left, pToolBar->rcButton.bottom, 0, m_hWnd, NULL); ::DestroyMenu(hMenu); } break; } *pResult = 0; } void CMainToolBar::OnSelectMIDIDevice(UINT id) { CMainFrame::GetMainFrame()->midiCloseDevice(); TrackerSettings::Instance().SetMIDIDevice(id - ID_SELECT_MIDI_DEVICE); CMainFrame::GetMainFrame()->midiOpenDevice(); } void CMainToolBar::SetRowsPerBeat(ROWINDEX nNewRPB) { CMainFrame *pMainFrm = CMainFrame::GetMainFrame(); if(pMainFrm == nullptr) return; CModDoc *pModDoc = pMainFrm->GetModPlaying(); CSoundFile *pSndFile = pMainFrm->GetSoundFilePlaying(); if(pModDoc == nullptr || pSndFile == nullptr) return; pSndFile->m_PlayState.m_nCurrentRowsPerBeat = nNewRPB; PATTERNINDEX nPat = pSndFile->GetCurrentPattern(); if(pSndFile->Patterns[nPat].GetOverrideSignature()) { if(nNewRPB <= pSndFile->Patterns[nPat].GetRowsPerMeasure()) { pSndFile->Patterns[nPat].SetSignature(nNewRPB, pSndFile->Patterns[nPat].GetRowsPerMeasure()); TempoSwing swing = pSndFile->Patterns[nPat].GetTempoSwing(); if(!swing.empty()) { swing.resize(nNewRPB); pSndFile->Patterns[nPat].SetTempoSwing(swing); } pModDoc->SetModified(); } } else { if(nNewRPB <= pSndFile->m_nDefaultRowsPerMeasure) { pSndFile->m_nDefaultRowsPerBeat = nNewRPB; if(!pSndFile->m_tempoSwing.empty()) pSndFile->m_tempoSwing.resize(nNewRPB); pModDoc->SetModified(); } } } BOOL CMainToolBar::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult) { auto pTTT = reinterpret_cast(pNMHDR); UINT_PTR id = pNMHDR->idFrom; if(pTTT->uFlags & TTF_IDISHWND) { // idFrom is actually the HWND of the tool id = (UINT_PTR)::GetDlgCtrlID((HWND)id); } const TCHAR *s = nullptr; CommandID cmd = kcNull; switch(id) { case ID_FILE_NEW: s = _T("New"); cmd = kcFileNew; break; case ID_FILE_OPEN: s = _T("Open"); cmd = kcFileOpen; break; case ID_FILE_SAVE: s = _T("Save"); cmd = kcFileSave; break; case ID_EDIT_CUT: s = _T("Cut"); cmd = kcEditCut; break; case ID_EDIT_COPY: s = _T("Copy"); cmd = kcEditCopy; break; case ID_EDIT_PASTE: s = _T("Paste"); cmd = kcEditPaste; break; case ID_MIDI_RECORD: s = _T("MIDI Record"); cmd = kcMidiRecord; break; case ID_PLAYER_STOP: s = _T("Stop"); cmd = kcStopSong; break; case ID_PLAYER_PLAY: s = _T("Play"); cmd = kcPlayPauseSong; break; case ID_PLAYER_PAUSE: s = _T("Pause"); cmd = kcPlayPauseSong; break; case ID_PLAYER_PLAYFROMSTART: s = _T("Play From Start"); cmd = kcPlaySongFromStart; break; case ID_VIEW_OPTIONS: s = _T("Setup"); cmd = kcViewOptions; break; case ID_PANIC: s = _T("Stop all hanging plugin and sample voices"); cmd = kcPanic; break; case ID_UPDATE_AVAILABLE: s = _T("A new update is available."); break; } if(s == nullptr) return FALSE; mpt::tstring fmt = s; if(cmd != kcNull) { auto keyText = CMainFrame::GetInputHandler()->m_activeCommandSet->GetKeyTextFromCommand(cmd, 0); if(!keyText.IsEmpty()) fmt += MPT_TFORMAT(" ({})")(keyText); } mpt::String::WriteWinBuf(pTTT->szText) = fmt; *pResult = 0; // bring the tooltip window above other popup windows ::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_NOOWNERZORDER); return TRUE; // message was handled } /////////////////////////////////////////////////////////////////////////////////////////////////// // CModTreeBar BEGIN_MESSAGE_MAP(CModTreeBar, CDialogBar) //{{AFX_MSG_MAP(CModTreeBar) ON_WM_NCCALCSIZE() ON_WM_NCPAINT() ON_WM_NCHITTEST() ON_WM_SIZE() ON_WM_NCMOUSEMOVE() ON_WM_MOUSEMOVE() ON_WM_LBUTTONDOWN() ON_WM_NCLBUTTONDOWN() ON_WM_LBUTTONUP() ON_WM_NCLBUTTONUP() ON_MESSAGE(WM_INITDIALOG, &CModTreeBar::OnInitDialog) //}}AFX_MSG_MAP END_MESSAGE_MAP() CModTreeBar::CModTreeBar() { m_nTreeSplitRatio = TrackerSettings::Instance().glTreeSplitRatio; } LRESULT CModTreeBar::OnInitDialog(WPARAM wParam, LPARAM lParam) { LRESULT l = CDialogBar::HandleInitDialog(wParam, lParam); m_pModTreeData = new CModTree(nullptr); if(m_pModTreeData) m_pModTreeData->SubclassDlgItem(IDC_TREEDATA, this); m_pModTree = new CModTree(m_pModTreeData); if(m_pModTree) m_pModTree->SubclassDlgItem(IDC_TREEVIEW, this); m_dwStatus = 0; m_sizeDefault.cx = Util::ScalePixels(TrackerSettings::Instance().glTreeWindowWidth, m_hWnd) + 3; m_sizeDefault.cy = 32767; return l; } CModTreeBar::~CModTreeBar() { if(m_pModTree) { delete m_pModTree; m_pModTree = nullptr; } if(m_pModTreeData) { delete m_pModTreeData; m_pModTreeData = nullptr; } } void CModTreeBar::Init() { m_nTreeSplitRatio = TrackerSettings::Instance().glTreeSplitRatio; if(m_pModTree) { m_pModTreeData->Init(); m_pModTree->Init(); } } void CModTreeBar::RefreshDlsBanks() { if(m_pModTree) m_pModTree->RefreshDlsBanks(); } void CModTreeBar::RefreshMidiLibrary() { if(m_pModTree) m_pModTree->RefreshMidiLibrary(); } void CModTreeBar::OnOptionsChanged() { if(m_pModTree) m_pModTree->OnOptionsChanged(); } void CModTreeBar::RecalcLayout() { CRect rect; if((m_pModTree) && (m_pModTreeData)) { int cytree, cydata, cyavail; GetClientRect(&rect); cyavail = rect.Height() - 3; if(cyavail < 0) cyavail = 0; cytree = (cyavail * m_nTreeSplitRatio) >> 8; cydata = cyavail - cytree; m_pModTree->SetWindowPos(NULL, 0,0, rect.Width(), cytree, SWP_NOZORDER|SWP_NOACTIVATE); m_pModTreeData->SetWindowPos(NULL, 0,cytree+3, rect.Width(), cydata, SWP_NOZORDER|SWP_NOACTIVATE); } } CSize CModTreeBar::CalcFixedLayout(BOOL, BOOL) { int width = Util::ScalePixels(TrackerSettings::Instance().glTreeWindowWidth, m_hWnd); CSize sz; m_sizeDefault.cx = width; m_sizeDefault.cy = 32767; sz.cx = width + 3; if(sz.cx < 4) sz.cx = 4; sz.cy = 32767; return sz; } void CModTreeBar::DoMouseMove(CPoint pt) { CRect rect; if((m_dwStatus & (MTB_CAPTURE|MTB_DRAGGING)) && (::GetCapture() != m_hWnd)) { CancelTracking(); } if(m_dwStatus & MTB_DRAGGING) { if(m_dwStatus & MTB_VERTICAL) { if(m_pModTree) { m_pModTree->GetWindowRect(&rect); pt.y += rect.Height(); } GetClientRect(&rect); pt.y -= ptDragging.y; if(pt.y < 0) pt.y = 0; if(pt.y > rect.Height()) pt.y = rect.Height(); if((!(m_dwStatus & MTB_TRACKER)) || (pt.y != (int)m_nTrackPos)) { if(m_dwStatus & MTB_TRACKER) OnInvertTracker(m_nTrackPos); m_nTrackPos = pt.y; OnInvertTracker(m_nTrackPos); m_dwStatus |= MTB_TRACKER; } } else { pt.x -= ptDragging.x - m_cxOriginal + 3; if(pt.x < 0) pt.x = 0; if((!(m_dwStatus & MTB_TRACKER)) || (pt.x != (int)m_nTrackPos)) { if(m_dwStatus & MTB_TRACKER) OnInvertTracker(m_nTrackPos); m_nTrackPos = pt.x; OnInvertTracker(m_nTrackPos); m_dwStatus |= MTB_TRACKER; } } } else { UINT nCursor = 0; GetClientRect(&rect); rect.left = rect.right - 2; rect.right = rect.left + 5; if(rect.PtInRect(pt)) { nCursor = AFX_IDC_HSPLITBAR; } else if(m_pModTree) { m_pModTree->GetWindowRect(&rect); rect.right = rect.Width(); rect.left = 0; rect.top = rect.Height()-1; rect.bottom = rect.top + 5; if(rect.PtInRect(pt)) { nCursor = AFX_IDC_VSPLITBAR; } } if(nCursor) { UINT nDir = (nCursor == AFX_IDC_VSPLITBAR) ? MTB_VERTICAL : 0; BOOL bLoad = FALSE; if(!(m_dwStatus & MTB_CAPTURE)) { m_dwStatus |= MTB_CAPTURE; SetCapture(); bLoad = TRUE; } else { if(nDir != (m_dwStatus & MTB_VERTICAL)) bLoad = TRUE; } m_dwStatus &= ~MTB_VERTICAL; m_dwStatus |= nDir; if(bLoad) SetCursor(theApp.LoadCursor(nCursor)); } else { if(m_dwStatus & MTB_CAPTURE) { m_dwStatus &= ~MTB_CAPTURE; ReleaseCapture(); SetCursor(LoadCursor(NULL, IDC_ARROW)); } } } } void CModTreeBar::DoLButtonDown(CPoint pt) { if((m_dwStatus & MTB_CAPTURE) && (!(m_dwStatus & MTB_DRAGGING))) { CRect rect; GetWindowRect(&rect); m_cxOriginal = rect.Width(); m_cyOriginal = rect.Height(); ptDragging = pt; m_dwStatus |= MTB_DRAGGING; DoMouseMove(pt); } } void CModTreeBar::DoLButtonUp() { if(m_dwStatus & MTB_DRAGGING) { CRect rect; m_dwStatus &= ~MTB_DRAGGING; if(m_dwStatus & MTB_TRACKER) { OnInvertTracker(m_nTrackPos); m_dwStatus &= ~MTB_TRACKER; } if(m_dwStatus & MTB_VERTICAL) { GetClientRect(&rect); int cyavail = rect.Height() - 3; if(cyavail < 4) cyavail = 4; int ratio = (m_nTrackPos << 8) / cyavail; if(ratio < 0) ratio = 0; if(ratio > 256) ratio = 256; m_nTreeSplitRatio = ratio; TrackerSettings::Instance().glTreeSplitRatio = ratio; RecalcLayout(); } else { GetWindowRect(&rect); m_nTrackPos += 3; if(m_nTrackPos < 4) m_nTrackPos = 4; CMainFrame *pMainFrm = CMainFrame::GetMainFrame(); if((m_nTrackPos != (UINT)rect.Width()) && (pMainFrm)) { TrackerSettings::Instance().glTreeWindowWidth = Util::ScalePixelsInv(m_nTrackPos - 3, m_hWnd); m_sizeDefault.cx = m_nTrackPos; m_sizeDefault.cy = 32767; pMainFrm->RecalcLayout(); } } } } void CModTreeBar::CancelTracking() { if(m_dwStatus & MTB_TRACKER) { OnInvertTracker(m_nTrackPos); m_dwStatus &= ~MTB_TRACKER; } m_dwStatus &= ~MTB_DRAGGING; if(m_dwStatus & MTB_CAPTURE) { m_dwStatus &= ~MTB_CAPTURE; ReleaseCapture(); } } void CModTreeBar::OnInvertTracker(UINT x) { CMainFrame *pMainFrm = CMainFrame::GetMainFrame(); if(pMainFrm) { CRect rect; GetClientRect(&rect); if(m_dwStatus & MTB_VERTICAL) { rect.top = x; rect.bottom = rect.top + 4; } else { rect.left = x; rect.right = rect.left + 4; } ClientToScreen(&rect); pMainFrm->ScreenToClient(&rect); // pat-blt without clip children on CDC* pDC = pMainFrm->GetDC(); // invert the brush pattern (looks just like frame window sizing) CBrush* pBrush = CDC::GetHalftoneBrush(); HBRUSH hOldBrush = NULL; if(pBrush != NULL) hOldBrush = (HBRUSH)SelectObject(pDC->m_hDC, pBrush->m_hObject); pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATINVERT); if(hOldBrush != NULL) SelectObject(pDC->m_hDC, hOldBrush); ReleaseDC(pDC); } } void CModTreeBar::OnDocumentCreated(CModDoc *pModDoc) { if(m_pModTree && pModDoc) m_pModTree->AddDocument(*pModDoc); } void CModTreeBar::OnDocumentClosed(CModDoc *pModDoc) { if(m_pModTree && pModDoc) m_pModTree->RemoveDocument(*pModDoc); } void CModTreeBar::OnUpdate(CModDoc *pModDoc, UpdateHint hint, CObject *pHint) { if(m_pModTree) m_pModTree->OnUpdate(pModDoc, hint, pHint); } void CModTreeBar::UpdatePlayPos(CModDoc *pModDoc, Notification *pNotify) { if(m_pModTree && pModDoc) m_pModTree->UpdatePlayPos(*pModDoc, pNotify); } //////////////////////////////////////////////////////////////////////////////////////////////////// // CModTreeBar message handlers void CModTreeBar::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp) { CDialogBar::OnNcCalcSize(bCalcValidRects, lpncsp); if(lpncsp) { lpncsp->rgrc[0].right -= 3; if(lpncsp->rgrc[0].right < lpncsp->rgrc[0].left) lpncsp->rgrc[0].right = lpncsp->rgrc[0].left; } } LRESULT CModTreeBar::OnNcHitTest(CPoint point) { CRect rect; GetWindowRect(&rect); rect.DeflateRect(1,1); rect.right -= 3; if(!rect.PtInRect(point)) return HTBORDER; return CDialogBar::OnNcHitTest(point); } void CModTreeBar::OnNcPaint() { RECT rect; CDialogBar::OnNcPaint(); GetWindowRect(&rect); // Assumes there is no other non-client items rect.right -= rect.left; rect.bottom -= rect.top; rect.top = 0; rect.left = rect.right - 3; if((rect.left < rect.right) && (rect.top < rect.bottom)) { CDC *pDC = GetWindowDC(); HDC hdc = pDC->m_hDC; FillRect(hdc, &rect, GetSysColorBrush(COLOR_BTNFACE)); ReleaseDC(pDC); } } void CModTreeBar::OnSize(UINT nType, int cx, int cy) { CDialogBar::OnSize(nType, cx, cy); RecalcLayout(); } void CModTreeBar::OnNcMouseMove(UINT, CPoint point) { CRect rect; CPoint pt = point; GetWindowRect(&rect); pt.x -= rect.left; pt.y -= rect.top; DoMouseMove(pt); } void CModTreeBar::OnMouseMove(UINT, CPoint point) { DoMouseMove(point); } void CModTreeBar::OnNcLButtonDown(UINT, CPoint point) { CRect rect; CPoint pt = point; GetWindowRect(&rect); pt.x -= rect.left; pt.y -= rect.top; DoLButtonDown(pt); } void CModTreeBar::OnLButtonDown(UINT, CPoint point) { DoLButtonDown(point); } void CModTreeBar::OnNcLButtonUp(UINT, CPoint) { DoLButtonUp(); } void CModTreeBar::OnLButtonUp(UINT, CPoint) { DoLButtonUp(); } HWND CModTreeBar::GetModTreeHWND() { return m_pModTree->m_hWnd; } LRESULT CModTreeBar::SendMessageToModTree(UINT cmdID, WPARAM wParam, LPARAM lParam) { if(::GetFocus() == m_pModTree->m_hWnd) return m_pModTree->SendMessage(cmdID, wParam, lParam); if(::GetFocus() == m_pModTreeData->m_hWnd) return m_pModTreeData->SendMessage(cmdID, wParam, lParam); return 0; } bool CModTreeBar::SetTreeSoundfile(FileReader &file) { return m_pModTree->SetSoundFile(file); } //////////////////////////////////////////////////////////////////////////////// // // Stereo VU Meter for toolbar // BEGIN_MESSAGE_MAP(CStereoVU, CStatic) ON_WM_PAINT() ON_WM_LBUTTONDOWN() END_MESSAGE_MAP() void CStereoVU::OnPaint() { CRect rect; CPaintDC dc(this); DrawVuMeters(dc, true); } void CStereoVU::SetVuMeter(uint8 validChannels, const uint32 channels[4], bool force) { bool changed = false; if(validChannels == 0) { // reset validChannels = numChannels; } else if(validChannels != numChannels) { changed = true; force = true; numChannels = validChannels; allowRightToLeft = (numChannels > 2); } for(uint8 c = 0; c < validChannels; ++c) { if(vuMeter[c] != channels[c]) { changed = true; } } if(changed) { DWORD curTime = timeGetTime(); if(curTime - lastVuUpdateTime >= TrackerSettings::Instance().VuMeterUpdateInterval || force) { for(uint8 c = 0; c < validChannels; ++c) { vuMeter[c] = channels[c]; } CClientDC dc(this); DrawVuMeters(dc, force); lastVuUpdateTime = curTime; } } } // Draw stereo VU void CStereoVU::DrawVuMeters(CDC &dc, bool redraw) { CRect rect; GetClientRect(&rect); if(redraw) { dc.FillSolidRect(rect.left, rect.top, rect.Width(), rect.Height(), RGB(0,0,0)); } for(uint8 channel = 0; channel < numChannels; ++channel) { CRect chanrect = rect; if(horizontal) { if(allowRightToLeft) { const int col = channel % 2; const int row = channel / 2; float width = (rect.Width() - 2.0f) / 2.0f; float height = rect.Height() / float(numChannels/2); chanrect.top = mpt::saturate_round(rect.top + height * row); chanrect.bottom = mpt::saturate_round(chanrect.top + height) - 1; chanrect.left = mpt::saturate_round(rect.left + width * col) + ((col == 1) ? 2 : 0); chanrect.right = mpt::saturate_round(chanrect.left + width) - 1; } else { float height = rect.Height() / float(numChannels); chanrect.top = mpt::saturate_round(rect.top + height * channel); chanrect.bottom = mpt::saturate_round(chanrect.top + height) - 1; } } else { float width = rect.Width() / float(numChannels); chanrect.left = mpt::saturate_round(rect.left + width * channel); chanrect.right = mpt::saturate_round(chanrect.left + width) - 1; } DrawVuMeter(dc, chanrect, channel, redraw); } } // Draw a single VU Meter void CStereoVU::DrawVuMeter(CDC &dc, const CRect &rect, int index, bool redraw) { uint32 vu = vuMeter[index]; if(CMainFrame::GetMainFrame()->GetSoundFilePlaying() == nullptr) { vu = 0; } const bool clip = (vu & Notification::ClipVU) != 0; vu = (vu & (~Notification::ClipVU)) >> 8; if(horizontal) { const bool rtl = allowRightToLeft && ((index % 2) == 0); const int cx = std::max(1, rect.Width()); int v = (vu * cx) >> 8; for(int x = 0; x <= cx; x += 2) { int pen = Clamp((x * NUM_VUMETER_PENS) / cx, 0, NUM_VUMETER_PENS - 1); const bool last = (x == (cx & ~0x1)); // Darken everything above volume, unless it's the clip indicator if(v <= x && (!last || !clip)) pen += NUM_VUMETER_PENS; bool draw = redraw || (v < lastV[index] && v<=x && x<=lastV[index]) || (lastV[index] < v && lastV[index]<=x && x<=v); draw = draw || (last && clip != lastClip[index]); if(draw) dc.FillSolidRect( ((!rtl) ? (rect.left + x) : (rect.right - x)), rect.top, 1, rect.Height(), CMainFrame::gcolrefVuMeter[pen]); if(last) lastClip[index] = clip; } lastV[index] = v; } else { const int cy = std::max(1, rect.Height()); int v = (vu * cy) >> 8; for(int ry = rect.bottom - 1; ry > rect.top; ry -= 2) { const int y0 = rect.bottom - ry; int pen = Clamp((y0 * NUM_VUMETER_PENS) / cy, 0, NUM_VUMETER_PENS - 1); const bool last = (ry == rect.top + 1); // Darken everything above volume, unless it's the clip indicator if(v <= y0 && (!last || !clip)) pen += NUM_VUMETER_PENS; bool draw = redraw || (v < lastV[index] && v<=ry && ry<=lastV[index]) || (lastV[index] < v && lastV[index]<=ry && ry<=v); draw = draw || (last && clip != lastClip[index]); if(draw) dc.FillSolidRect(rect.left, ry, rect.Width(), 1, CMainFrame::gcolrefVuMeter[pen]); if(last) lastClip[index] = clip; } lastV[index] = v; } } void CStereoVU::OnLButtonDown(UINT, CPoint) { // Reset clip indicator. CMainFrame::GetMainFrame()->m_VUMeterInput.ResetClipped(); CMainFrame::GetMainFrame()->m_VUMeterOutput.ResetClipped(); } OPENMPT_NAMESPACE_END