mirror of
https://github.com/WinampDesktop/winamp.git
synced 2024-11-28 09:31:02 +01:00
1676 lines
48 KiB
C++
1676 lines
48 KiB
C++
/*
|
|
* Ctrl_seq.cpp
|
|
* ------------
|
|
* Purpose: Order list for the pattern editor upper panel.
|
|
* Notes : (currently none)
|
|
* Authors: Olivier Lapicque
|
|
* OpenMPT Devs
|
|
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
|
|
*/
|
|
|
|
|
|
#include "stdafx.h"
|
|
#include "Mainfrm.h"
|
|
#include "InputHandler.h"
|
|
#include "Moddoc.h"
|
|
#include "../soundlib/mod_specifications.h"
|
|
#include "Globals.h"
|
|
#include "Ctrl_pat.h"
|
|
#include "PatternClipboard.h"
|
|
#include "../common/mptStringBuffer.h"
|
|
|
|
|
|
OPENMPT_NAMESPACE_BEGIN
|
|
|
|
enum SequenceAction : SEQUENCEINDEX
|
|
{
|
|
kAddSequence = MAX_SEQUENCES,
|
|
kDuplicateSequence,
|
|
kDeleteSequence,
|
|
kSplitSequence,
|
|
|
|
kMaxSequenceActions
|
|
};
|
|
|
|
// Little helper function to avoid copypasta
|
|
static bool IsSelectionKeyPressed() { return CMainFrame::GetInputHandler()->SelectionPressed(); }
|
|
static bool IsCtrlKeyPressed() { return CMainFrame::GetInputHandler()->CtrlPressed(); }
|
|
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
// CPatEdit
|
|
|
|
BOOL CPatEdit::PreTranslateMessage(MSG *pMsg)
|
|
{
|
|
if(((pMsg->message == WM_KEYDOWN) || (pMsg->message == WM_KEYUP)) && (pMsg->wParam == VK_TAB))
|
|
{
|
|
if((pMsg->message == WM_KEYUP) && (m_pParent))
|
|
{
|
|
m_pParent->SwitchToView();
|
|
}
|
|
return TRUE;
|
|
}
|
|
return CEdit::PreTranslateMessage(pMsg);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
// COrderList
|
|
|
|
BEGIN_MESSAGE_MAP(COrderList, CWnd)
|
|
//{{AFX_MSG_MAP(COrderList)
|
|
ON_WM_PAINT()
|
|
ON_WM_ERASEBKGND()
|
|
ON_WM_MOUSEMOVE()
|
|
ON_WM_LBUTTONDOWN()
|
|
ON_WM_LBUTTONDBLCLK()
|
|
ON_WM_LBUTTONUP()
|
|
ON_WM_RBUTTONDOWN()
|
|
ON_WM_MBUTTONDOWN()
|
|
ON_WM_SETFOCUS()
|
|
ON_WM_KILLFOCUS()
|
|
ON_WM_HSCROLL()
|
|
ON_WM_SIZE()
|
|
|
|
ON_COMMAND(ID_ORDERLIST_INSERT, &COrderList::OnInsertOrder)
|
|
ON_COMMAND(ID_ORDERLIST_INSERT_SEPARATOR, &COrderList::OnInsertSeparatorPattern)
|
|
ON_COMMAND(ID_ORDERLIST_DELETE, &COrderList::OnDeleteOrder)
|
|
ON_COMMAND(ID_ORDERLIST_RENDER, &COrderList::OnRenderOrder)
|
|
ON_COMMAND(ID_ORDERLIST_EDIT_COPY, &COrderList::OnEditCopy)
|
|
ON_COMMAND(ID_ORDERLIST_EDIT_CUT, &COrderList::OnEditCut)
|
|
ON_COMMAND(ID_ORDERLIST_EDIT_COPY_ORDERS, &COrderList::OnEditCopyOrders)
|
|
|
|
ON_COMMAND(ID_PATTERN_PROPERTIES, &COrderList::OnPatternProperties)
|
|
ON_COMMAND(ID_PLAYER_PLAY, &COrderList::OnPlayerPlay)
|
|
ON_COMMAND(ID_PLAYER_PAUSE, &COrderList::OnPlayerPause)
|
|
ON_COMMAND(ID_PLAYER_PLAYFROMSTART, &COrderList::OnPlayerPlayFromStart)
|
|
ON_COMMAND(IDC_PATTERN_PLAYFROMSTART, &COrderList::OnPatternPlayFromStart)
|
|
ON_COMMAND(ID_ORDERLIST_NEW, &COrderList::OnCreateNewPattern)
|
|
ON_COMMAND(ID_ORDERLIST_COPY, &COrderList::OnDuplicatePattern)
|
|
ON_COMMAND(ID_ORDERLIST_MERGE, &COrderList::OnMergePatterns)
|
|
ON_COMMAND(ID_PATTERNCOPY, &COrderList::OnPatternCopy)
|
|
ON_COMMAND(ID_PATTERNPASTE, &COrderList::OnPatternPaste)
|
|
ON_COMMAND(ID_SETRESTARTPOS, &COrderList::OnSetRestartPos)
|
|
ON_COMMAND(ID_ORDERLIST_LOCKPLAYBACK, &COrderList::OnLockPlayback)
|
|
ON_COMMAND(ID_ORDERLIST_UNLOCKPLAYBACK, &COrderList::OnUnlockPlayback)
|
|
ON_COMMAND_RANGE(ID_SEQUENCE_ITEM, ID_SEQUENCE_ITEM + kMaxSequenceActions - 1, &COrderList::OnSelectSequence)
|
|
ON_MESSAGE(WM_MOD_DRAGONDROPPING, &COrderList::OnDragonDropping)
|
|
ON_MESSAGE(WM_HELPHITTEST, &COrderList::OnHelpHitTest)
|
|
ON_MESSAGE(WM_MOD_KEYCOMMAND, &COrderList::OnCustomKeyMsg)
|
|
ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &COrderList::OnToolTipText)
|
|
//}}AFX_MSG_MAP
|
|
END_MESSAGE_MAP()
|
|
|
|
|
|
COrderList::COrderList(CCtrlPatterns &parent, CModDoc &document)
|
|
: m_nOrderlistMargins(TrackerSettings::Instance().orderlistMargins)
|
|
, m_modDoc(document)
|
|
, m_pParent(parent)
|
|
{
|
|
EnableActiveAccessibility();
|
|
}
|
|
|
|
|
|
bool COrderList::EnsureEditable(ORDERINDEX ord)
|
|
{
|
|
auto &sndFile = m_modDoc.GetSoundFile();
|
|
if(ord >= Order().size())
|
|
{
|
|
if(ord < sndFile.GetModSpecifications().ordersMax)
|
|
{
|
|
try
|
|
{
|
|
Order().resize(ord + 1);
|
|
} catch(mpt::out_of_memory e)
|
|
{
|
|
mpt::delete_out_of_memory(e);
|
|
return false;
|
|
}
|
|
} else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
ModSequence &COrderList::Order() { return m_modDoc.GetSoundFile().Order(); }
|
|
const ModSequence &COrderList::Order() const { return m_modDoc.GetSoundFile().Order(); }
|
|
|
|
|
|
void COrderList::SetScrollPos(int pos)
|
|
{
|
|
// Work around 16-bit limitations of WM_HSCROLL
|
|
SCROLLINFO si;
|
|
MemsetZero(si);
|
|
si.cbSize = sizeof(si);
|
|
si.fMask = SIF_TRACKPOS;
|
|
GetScrollInfo(SB_HORZ, &si);
|
|
si.nPos = pos;
|
|
SetScrollInfo(SB_HORZ, &si);
|
|
}
|
|
|
|
|
|
int COrderList::GetScrollPos(bool getTrackPos)
|
|
{
|
|
// Work around 16-bit limitations of WM_HSCROLL
|
|
SCROLLINFO si;
|
|
MemsetZero(si);
|
|
si.cbSize = sizeof(si);
|
|
si.fMask = SIF_TRACKPOS;
|
|
GetScrollInfo(SB_HORZ, &si);
|
|
return getTrackPos ? si.nTrackPos : si.nPos;
|
|
}
|
|
|
|
|
|
bool COrderList::IsOrderInMargins(int order, int startOrder)
|
|
{
|
|
const ORDERINDEX nMargins = GetMargins();
|
|
return ((startOrder != 0 && order - startOrder < nMargins) ||
|
|
order - startOrder >= GetLength() - nMargins);
|
|
}
|
|
|
|
|
|
void COrderList::EnsureVisible(ORDERINDEX order)
|
|
{
|
|
// nothing needs to be done
|
|
if(!IsOrderInMargins(order, m_nXScroll) || order == ORDERINDEX_INVALID)
|
|
return;
|
|
|
|
if(order < m_nXScroll)
|
|
{
|
|
if(order < GetMargins())
|
|
m_nXScroll = 0;
|
|
else
|
|
m_nXScroll = order - GetMargins();
|
|
} else
|
|
{
|
|
m_nXScroll = order + 2 * GetMargins() - 1;
|
|
if(m_nXScroll < GetLength())
|
|
m_nXScroll = 0;
|
|
else
|
|
m_nXScroll -= GetLength();
|
|
}
|
|
}
|
|
|
|
|
|
bool COrderList::IsPlaying() const
|
|
{
|
|
return (CMainFrame::GetMainFrame()->GetModPlaying() == &m_modDoc);
|
|
}
|
|
|
|
|
|
ORDERINDEX COrderList::GetOrderFromPoint(const CPoint &pt) const
|
|
{
|
|
if(m_cxFont)
|
|
return mpt::saturate_cast<ORDERINDEX>(m_nXScroll + pt.x / m_cxFont);
|
|
return 0;
|
|
}
|
|
|
|
|
|
CRect COrderList::GetRectFromOrder(ORDERINDEX ord) const
|
|
{
|
|
return CRect{CPoint{(ord - m_nXScroll) * m_cxFont, 0}, CSize{m_cxFont, m_cyFont}};
|
|
}
|
|
|
|
|
|
BOOL COrderList::Init(const CRect &rect, HFONT hFont)
|
|
{
|
|
CreateEx(WS_EX_STATICEDGE, NULL, _T(""), WS_CHILD | WS_VISIBLE, rect, &m_pParent, IDC_ORDERLIST);
|
|
m_hFont = hFont;
|
|
SendMessage(WM_SETFONT, (WPARAM)m_hFont);
|
|
SetScrollPos(0);
|
|
EnableScrollBarCtrl(SB_HORZ, TRUE);
|
|
SetCurSel(0);
|
|
EnableToolTips();
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
void COrderList::UpdateScrollInfo()
|
|
{
|
|
CRect rcClient;
|
|
|
|
GetClientRect(&rcClient);
|
|
if((m_cxFont > 0) && (rcClient.right > 0))
|
|
{
|
|
CRect rect;
|
|
SCROLLINFO info;
|
|
UINT nPage;
|
|
|
|
int nMax = Order().GetLengthTailTrimmed();
|
|
|
|
GetScrollInfo(SB_HORZ, &info, SIF_PAGE | SIF_RANGE);
|
|
info.fMask = SIF_PAGE | SIF_RANGE;
|
|
info.nMin = 0;
|
|
nPage = rcClient.right / m_cxFont;
|
|
if(nMax <= (int)nPage)
|
|
nMax = nPage + 1;
|
|
if((nMax != info.nMax) || (nPage != info.nPage))
|
|
{
|
|
info.nPage = nPage;
|
|
info.nMax = nMax;
|
|
SetScrollInfo(SB_HORZ, &info, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int COrderList::GetFontWidth()
|
|
{
|
|
if((m_cxFont <= 0) && (m_hWnd) && (m_hFont))
|
|
{
|
|
CClientDC dc(this);
|
|
HGDIOBJ oldfont = dc.SelectObject(m_hFont);
|
|
CSize sz = dc.GetTextExtent(_T("000+"), 4);
|
|
if(oldfont)
|
|
dc.SelectObject(oldfont);
|
|
return sz.cx;
|
|
}
|
|
return m_cxFont;
|
|
}
|
|
|
|
|
|
void COrderList::InvalidateSelection()
|
|
{
|
|
ORDERINDEX ordLo = m_nScrollPos, count = 1;
|
|
static ORDERINDEX m_nScrollPos2Old = m_nScrollPos2nd;
|
|
if(m_nScrollPos2Old != ORDERINDEX_INVALID)
|
|
{
|
|
// there were multiple orders selected - remove them all
|
|
ORDERINDEX ordHi = m_nScrollPos;
|
|
if(m_nScrollPos2Old < m_nScrollPos)
|
|
ordLo = m_nScrollPos2Old;
|
|
else
|
|
ordHi = m_nScrollPos2Old;
|
|
count = ordHi - ordLo + 1;
|
|
}
|
|
m_nScrollPos2Old = m_nScrollPos2nd;
|
|
CRect rcClient, rect;
|
|
GetClientRect(&rcClient);
|
|
rect.left = rcClient.left + (ordLo - m_nXScroll) * m_cxFont;
|
|
rect.top = rcClient.top;
|
|
rect.right = rect.left + m_cxFont * count;
|
|
rect.bottom = rcClient.bottom;
|
|
rect &= rcClient;
|
|
if(rect.right > rect.left)
|
|
InvalidateRect(rect, FALSE);
|
|
if(m_playPos != ORDERINDEX_INVALID)
|
|
{
|
|
rect.left = rcClient.left + (m_playPos - m_nXScroll) * m_cxFont;
|
|
rect.top = rcClient.top;
|
|
rect.right = rect.left + m_cxFont;
|
|
rect &= rcClient;
|
|
if(rect.right > rect.left)
|
|
InvalidateRect(rect, FALSE);
|
|
m_playPos = ORDERINDEX_INVALID;
|
|
}
|
|
}
|
|
|
|
|
|
ORDERINDEX COrderList::GetLength()
|
|
{
|
|
CRect rcClient;
|
|
GetClientRect(&rcClient);
|
|
if(m_cxFont > 0)
|
|
return mpt::saturate_cast<ORDERINDEX>(rcClient.right / m_cxFont);
|
|
else
|
|
{
|
|
const int fontWidth = GetFontWidth();
|
|
return (fontWidth > 0) ? mpt::saturate_cast<ORDERINDEX>(rcClient.right / fontWidth) : 0;
|
|
}
|
|
}
|
|
|
|
|
|
OrdSelection COrderList::GetCurSel(bool ignoreSelection) const
|
|
{
|
|
// returns the currently selected order(s)
|
|
OrdSelection result;
|
|
result.firstOrd = result.lastOrd = m_nScrollPos;
|
|
// ignoreSelection: true if only first selection marker is important.
|
|
if(!ignoreSelection && m_nScrollPos2nd != ORDERINDEX_INVALID)
|
|
{
|
|
if(m_nScrollPos2nd < m_nScrollPos) // ord2 < ord1
|
|
result.firstOrd = m_nScrollPos2nd;
|
|
else
|
|
result.lastOrd = m_nScrollPos2nd;
|
|
}
|
|
ORDERINDEX lastIndex = std::max(Order().GetLengthTailTrimmed(), m_modDoc.GetSoundFile().GetModSpecifications().ordersMax) - 1u;
|
|
LimitMax(result.firstOrd, lastIndex);
|
|
LimitMax(result.lastOrd, lastIndex);
|
|
return result;
|
|
}
|
|
|
|
|
|
void COrderList::SetSelection(ORDERINDEX firstOrd, ORDERINDEX lastOrd)
|
|
{
|
|
SetCurSel(firstOrd, true, false, true);
|
|
SetCurSel(lastOrd != ORDERINDEX_INVALID ? lastOrd : firstOrd, false, true, true);
|
|
}
|
|
|
|
|
|
bool COrderList::SetCurSel(ORDERINDEX sel, bool setPlayPos, bool shiftClick, bool ignoreCurSel)
|
|
{
|
|
CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
|
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
|
ORDERINDEX &ord = shiftClick ? m_nScrollPos2nd : m_nScrollPos;
|
|
const ORDERINDEX lastIndex = std::max(Order().GetLength(), sndFile.GetModSpecifications().ordersMax) - 1u;
|
|
|
|
if((sel < 0) || (sel > lastIndex) || (!m_pParent) || (!pMainFrm))
|
|
return false;
|
|
if(!ignoreCurSel && sel == ord && (sel == sndFile.m_PlayState.m_nCurrentOrder))
|
|
return true;
|
|
const ORDERINDEX shownLength = GetLength();
|
|
InvalidateSelection();
|
|
ord = sel;
|
|
if(!EnsureEditable(ord))
|
|
return false;
|
|
|
|
if(!m_bScrolling)
|
|
{
|
|
const ORDERINDEX margins = GetMargins(GetMarginsMax(shownLength));
|
|
if(ord < m_nXScroll + margins)
|
|
{
|
|
// Must move first shown sequence item to left in order to show the new active order.
|
|
m_nXScroll = (ord > margins) ? (ord - margins) : 0;
|
|
SetScrollPos(m_nXScroll);
|
|
Invalidate(FALSE);
|
|
} else
|
|
{
|
|
ORDERINDEX maxsel = shownLength;
|
|
if(maxsel)
|
|
maxsel--;
|
|
if(ord - m_nXScroll >= maxsel - margins)
|
|
{
|
|
// Must move first shown sequence item to right in order to show the new active order.
|
|
m_nXScroll = ord - (maxsel - margins);
|
|
SetScrollPos(m_nXScroll);
|
|
Invalidate(FALSE);
|
|
}
|
|
}
|
|
}
|
|
InvalidateSelection();
|
|
PATTERNINDEX n = Order()[m_nScrollPos];
|
|
if(setPlayPos && !shiftClick && sndFile.Patterns.IsValidPat(n))
|
|
{
|
|
const bool isPlaying = IsPlaying();
|
|
bool changedPos = false;
|
|
|
|
if(isPlaying && sndFile.m_SongFlags[SONG_PATTERNLOOP])
|
|
{
|
|
pMainFrm->ResetNotificationBuffer();
|
|
|
|
// Update channel parameters and play time
|
|
CriticalSection cs;
|
|
m_modDoc.SetElapsedTime(m_nScrollPos, 0, !sndFile.m_SongFlags[SONG_PAUSED | SONG_STEP]);
|
|
|
|
changedPos = true;
|
|
} else if(m_pParent.GetFollowSong())
|
|
{
|
|
FlagSet<SongFlags> pausedFlags = sndFile.m_SongFlags & (SONG_PAUSED | SONG_STEP | SONG_PATTERNLOOP);
|
|
// Update channel parameters and play time
|
|
CriticalSection cs;
|
|
sndFile.SetCurrentOrder(m_nScrollPos);
|
|
m_modDoc.SetElapsedTime(m_nScrollPos, 0, !sndFile.m_SongFlags[SONG_PAUSED | SONG_STEP]);
|
|
sndFile.m_SongFlags.set(pausedFlags);
|
|
|
|
if(isPlaying)
|
|
pMainFrm->ResetNotificationBuffer();
|
|
changedPos = true;
|
|
}
|
|
|
|
if(changedPos && Order().IsPositionLocked(m_nScrollPos))
|
|
{
|
|
// Users wants to go somewhere else, so let them do that.
|
|
OnUnlockPlayback();
|
|
}
|
|
|
|
m_pParent.SetCurrentPattern(n);
|
|
} else if(setPlayPos && !shiftClick && n != Order().GetIgnoreIndex() && n != Order().GetInvalidPatIndex())
|
|
{
|
|
m_pParent.SetCurrentPattern(n);
|
|
}
|
|
UpdateInfoText();
|
|
if(m_nScrollPos == m_nScrollPos2nd)
|
|
m_nScrollPos2nd = ORDERINDEX_INVALID;
|
|
return true;
|
|
}
|
|
|
|
|
|
PATTERNINDEX COrderList::GetCurrentPattern() const
|
|
{
|
|
const ModSequence &order = Order();
|
|
if(m_nScrollPos < order.size())
|
|
{
|
|
return order[m_nScrollPos];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
BOOL COrderList::PreTranslateMessage(MSG *pMsg)
|
|
{
|
|
//handle Patterns View context keys that we want to take effect in the orderlist.
|
|
if((pMsg->message == WM_SYSKEYUP) || (pMsg->message == WM_KEYUP) ||
|
|
(pMsg->message == WM_SYSKEYDOWN) || (pMsg->message == WM_KEYDOWN))
|
|
{
|
|
CInputHandler *ih = CMainFrame::GetInputHandler();
|
|
|
|
//Translate message manually
|
|
UINT nChar = (UINT)pMsg->wParam;
|
|
UINT nRepCnt = LOWORD(pMsg->lParam);
|
|
UINT nFlags = HIWORD(pMsg->lParam);
|
|
KeyEventType kT = ih->GetKeyEventType(nFlags);
|
|
|
|
if(ih->KeyEvent(kCtxCtrlOrderlist, nChar, nRepCnt, nFlags, kT) != kcNull)
|
|
return true; // Mapped to a command, no need to pass message on.
|
|
|
|
//HACK: masquerade as kCtxViewPatternsNote context until we implement appropriate
|
|
// command propagation to kCtxCtrlOrderlist context.
|
|
|
|
if(ih->KeyEvent(kCtxViewPatternsNote, nChar, nRepCnt, nFlags, kT) != kcNull)
|
|
return true; // Mapped to a command, no need to pass message on.
|
|
|
|
// Handle Application (menu) key
|
|
if(pMsg->message == WM_KEYDOWN && nChar == VK_APPS)
|
|
{
|
|
const auto selection = GetCurSel();
|
|
auto pt = (GetRectFromOrder(selection.firstOrd) | GetRectFromOrder(selection.lastOrd)).CenterPoint();
|
|
CRect clientRect;
|
|
GetClientRect(clientRect);
|
|
if(!clientRect.PtInRect(pt))
|
|
pt = clientRect.CenterPoint();
|
|
OnRButtonDown(0, pt);
|
|
}
|
|
}
|
|
|
|
return CWnd::PreTranslateMessage(pMsg);
|
|
}
|
|
|
|
|
|
LRESULT COrderList::OnCustomKeyMsg(WPARAM wParam, LPARAM lParam)
|
|
{
|
|
bool isPlaying = IsPlaying();
|
|
switch(wParam)
|
|
{
|
|
case kcEditCopy:
|
|
OnEditCopy(); return wParam;
|
|
case kcEditCut:
|
|
OnEditCut(); return wParam;
|
|
case kcEditPaste:
|
|
OnPatternPaste(); return wParam;
|
|
case kcOrderlistEditCopyOrders:
|
|
OnEditCopyOrders(); return wParam;
|
|
|
|
// Orderlist navigation
|
|
case kcOrderlistNavigateLeftSelect:
|
|
case kcOrderlistNavigateLeft:
|
|
SetCurSelTo2ndSel(wParam == kcOrderlistNavigateLeftSelect); SetCurSel(m_nScrollPos - 1, wParam == kcOrderlistNavigateLeft || !isPlaying); return wParam;
|
|
case kcOrderlistNavigateRightSelect:
|
|
case kcOrderlistNavigateRight:
|
|
SetCurSelTo2ndSel(wParam == kcOrderlistNavigateRightSelect); SetCurSel(m_nScrollPos + 1, wParam == kcOrderlistNavigateRight || !isPlaying); return wParam;
|
|
case kcOrderlistNavigateFirstSelect:
|
|
case kcOrderlistNavigateFirst:
|
|
SetCurSelTo2ndSel(wParam == kcOrderlistNavigateFirstSelect); SetCurSel(0, wParam == kcOrderlistNavigateFirst || !isPlaying); return wParam;
|
|
case kcEditSelectAll:
|
|
SetCurSel(0, !isPlaying);
|
|
[[fallthrough]];
|
|
case kcOrderlistNavigateLastSelect:
|
|
case kcOrderlistNavigateLast:
|
|
{
|
|
SetCurSelTo2ndSel(wParam == kcOrderlistNavigateLastSelect || wParam == kcEditSelectAll);
|
|
ORDERINDEX nLast = Order().GetLengthTailTrimmed();
|
|
if(nLast > 0) nLast--;
|
|
SetCurSel(nLast, wParam == kcOrderlistNavigateLast || !isPlaying);
|
|
}
|
|
return wParam;
|
|
|
|
// Orderlist edit
|
|
case kcOrderlistEditDelete:
|
|
OnDeleteOrder(); return wParam;
|
|
case kcOrderlistEditInsert:
|
|
OnInsertOrder(); return wParam;
|
|
case kcOrderlistEditInsertSeparator:
|
|
OnInsertSeparatorPattern(); return wParam;
|
|
case kcOrderlistSwitchToPatternView:
|
|
OnSwitchToView(); return wParam;
|
|
case kcOrderlistEditPattern:
|
|
OnLButtonDblClk(0, CPoint(0, 0)); OnSwitchToView(); return wParam;
|
|
|
|
// Enter pattern number
|
|
case kcOrderlistPat0:
|
|
case kcOrderlistPat1:
|
|
case kcOrderlistPat2:
|
|
case kcOrderlistPat3:
|
|
case kcOrderlistPat4:
|
|
case kcOrderlistPat5:
|
|
case kcOrderlistPat6:
|
|
case kcOrderlistPat7:
|
|
case kcOrderlistPat8:
|
|
case kcOrderlistPat9:
|
|
EnterPatternNum(static_cast<UINT>(wParam) - kcOrderlistPat0); return wParam;
|
|
case kcOrderlistPatMinus:
|
|
EnterPatternNum(10); return wParam;
|
|
case kcOrderlistPatPlus:
|
|
EnterPatternNum(11); return wParam;
|
|
case kcOrderlistPatIgnore:
|
|
EnterPatternNum(12); return wParam;
|
|
case kcOrderlistPatInvalid:
|
|
EnterPatternNum(13); return wParam;
|
|
|
|
// kCtxViewPatternsNote messages
|
|
case kcSwitchToOrderList:
|
|
OnSwitchToView();
|
|
return wParam;
|
|
case kcChangeLoopStatus:
|
|
m_pParent.OnModCtrlMsg(CTRLMSG_PAT_LOOP, -1); return wParam;
|
|
case kcToggleFollowSong:
|
|
m_pParent.OnModCtrlMsg(CTRLMSG_PAT_FOLLOWSONG, 1); return wParam;
|
|
|
|
case kcChannelUnmuteAll:
|
|
case kcUnmuteAllChnOnPatTransition:
|
|
return m_pParent.SendMessage(WM_MOD_KEYCOMMAND, wParam, lParam);
|
|
|
|
case kcOrderlistLockPlayback:
|
|
OnLockPlayback(); return wParam;
|
|
case kcOrderlistUnlockPlayback:
|
|
OnUnlockPlayback(); return wParam;
|
|
|
|
case kcDuplicatePattern:
|
|
OnDuplicatePattern(); return wParam;
|
|
case kcMergePatterns:
|
|
OnMergePatterns(); return wParam;
|
|
case kcNewPattern:
|
|
OnCreateNewPattern(); return wParam;
|
|
}
|
|
|
|
return kcNull;
|
|
}
|
|
|
|
|
|
// Helper function to enter pattern index into the orderlist.
|
|
// Call with param 0...9 (enter digit), 10 (decrease) or 11 (increase).
|
|
void COrderList::EnterPatternNum(int enterNum)
|
|
{
|
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
|
|
|
if(!EnsureEditable(m_nScrollPos))
|
|
return;
|
|
|
|
PATTERNINDEX curIndex = Order()[m_nScrollPos];
|
|
const PATTERNINDEX maxIndex = std::max(PATTERNINDEX(1), sndFile.Patterns.GetNumPatterns()) - 1;
|
|
const PATTERNINDEX firstInvalid = sndFile.GetModSpecifications().hasIgnoreIndex ? sndFile.Order.GetIgnoreIndex() : sndFile.Order.GetInvalidPatIndex();
|
|
|
|
if(enterNum >= 0 && enterNum <= 9) // enter 0...9
|
|
{
|
|
if(curIndex >= sndFile.Patterns.Size())
|
|
curIndex = 0;
|
|
|
|
curIndex = curIndex * 10 + static_cast<PATTERNINDEX>(enterNum);
|
|
static_assert(MAX_PATTERNS < 10000);
|
|
if((curIndex >= 1000) && (curIndex > maxIndex)) curIndex %= 1000;
|
|
if((curIndex >= 100) && (curIndex > maxIndex)) curIndex %= 100;
|
|
if((curIndex >= 10) && (curIndex > maxIndex)) curIndex %= 10;
|
|
} else if(enterNum == 10) // decrease pattern index
|
|
{
|
|
if(curIndex == 0)
|
|
{
|
|
curIndex = sndFile.Order.GetInvalidPatIndex();
|
|
} else if(curIndex > maxIndex && curIndex <= firstInvalid)
|
|
{
|
|
curIndex = maxIndex;
|
|
} else
|
|
{
|
|
do
|
|
{
|
|
curIndex--;
|
|
} while(curIndex > 0 && curIndex < firstInvalid && !sndFile.Patterns.IsValidPat(curIndex));
|
|
}
|
|
} else if(enterNum == 11) // increase pattern index
|
|
{
|
|
if(curIndex >= sndFile.Order.GetInvalidPatIndex())
|
|
{
|
|
curIndex = 0;
|
|
} else if(curIndex >= maxIndex && curIndex < firstInvalid)
|
|
{
|
|
curIndex = firstInvalid;
|
|
} else
|
|
{
|
|
do
|
|
{
|
|
curIndex++;
|
|
} while(curIndex <= maxIndex && !sndFile.Patterns.IsValidPat(curIndex));
|
|
}
|
|
} else if(enterNum == 12) // ignore index (+++)
|
|
{
|
|
if(sndFile.GetModSpecifications().hasIgnoreIndex)
|
|
{
|
|
curIndex = sndFile.Order.GetIgnoreIndex();
|
|
}
|
|
} else if(enterNum == 13) // invalid index (---)
|
|
{
|
|
curIndex = sndFile.Order.GetInvalidPatIndex();
|
|
}
|
|
// apply
|
|
if(curIndex != Order()[m_nScrollPos])
|
|
{
|
|
Order()[m_nScrollPos] = curIndex;
|
|
m_modDoc.SetModified();
|
|
m_modDoc.UpdateAllViews(nullptr, SequenceHint().Data(), this);
|
|
InvalidateSelection();
|
|
m_pParent.SetCurrentPattern(curIndex);
|
|
}
|
|
}
|
|
|
|
|
|
void COrderList::OnEditCut()
|
|
{
|
|
OnEditCopy();
|
|
OnDeleteOrder();
|
|
}
|
|
|
|
|
|
void COrderList::OnCopy(bool onlyOrders)
|
|
{
|
|
const OrdSelection ordsel = GetCurSel();
|
|
BeginWaitCursor();
|
|
PatternClipboard::Copy(m_modDoc.GetSoundFile(), ordsel.firstOrd, ordsel.lastOrd, onlyOrders);
|
|
PatternClipboardDialog::UpdateList();
|
|
EndWaitCursor();
|
|
}
|
|
|
|
|
|
void COrderList::UpdateView(UpdateHint hint, CObject *pObj)
|
|
{
|
|
if(pObj != this && hint.ToType<SequenceHint>().GetType()[HINT_MODTYPE | HINT_MODSEQUENCE])
|
|
{
|
|
Invalidate(FALSE);
|
|
UpdateInfoText();
|
|
}
|
|
if(hint.GetType()[HINT_MPTOPTIONS])
|
|
{
|
|
m_nOrderlistMargins = TrackerSettings::Instance().orderlistMargins;
|
|
}
|
|
}
|
|
|
|
|
|
void COrderList::OnSwitchToView()
|
|
{
|
|
m_pParent.PostViewMessage(VIEWMSG_SETFOCUS);
|
|
}
|
|
|
|
|
|
void COrderList::UpdateInfoText()
|
|
{
|
|
if(::GetFocus() != m_hWnd)
|
|
return;
|
|
|
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
|
const auto &order = Order();
|
|
|
|
const ORDERINDEX length = order.GetLengthTailTrimmed();
|
|
CString s;
|
|
if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_HEXDISPLAY)
|
|
s.Format(_T("Position %02Xh of %02Xh"), m_nScrollPos, length);
|
|
else
|
|
s.Format(_T("Position %u of %u (%02Xh of %02Xh)"), m_nScrollPos, length, m_nScrollPos, length);
|
|
|
|
if(order.IsValidPat(m_nScrollPos))
|
|
{
|
|
if(const auto patName = sndFile.Patterns[order[m_nScrollPos]].GetName(); !patName.empty())
|
|
s += _T(": ") + mpt::ToCString(sndFile.GetCharsetInternal(), patName);
|
|
}
|
|
CMainFrame::GetMainFrame()->SetInfoText(s);
|
|
|
|
CMainFrame::GetMainFrame()->NotifyAccessibilityUpdate(*this);
|
|
}
|
|
|
|
|
|
// Accessible description for screen readers
|
|
HRESULT COrderList::get_accName(VARIANT, BSTR *pszName)
|
|
{
|
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
|
const auto &order = Order();
|
|
|
|
CString s;
|
|
const bool singleSel = m_nScrollPos2nd == ORDERINDEX_INVALID || m_nScrollPos2nd == m_nScrollPos;
|
|
const auto firstOrd = singleSel ? m_nScrollPos : std::min(m_nScrollPos, m_nScrollPos2nd), lastOrd = singleSel ? m_nScrollPos : std::max(m_nScrollPos, m_nScrollPos2nd);
|
|
if(singleSel)
|
|
s = MPT_CFORMAT("Order {}, ")(m_nScrollPos);
|
|
else
|
|
s = MPT_CFORMAT("Order selection {} to {}: ")(firstOrd, lastOrd);
|
|
bool first = true;
|
|
for(ORDERINDEX o = firstOrd; o <= lastOrd; o++)
|
|
{
|
|
if(!first)
|
|
s += _T(", ");
|
|
first = false;
|
|
PATTERNINDEX pat = order[o];
|
|
if(pat == ModSequence::GetIgnoreIndex())
|
|
s += _T(" Skip");
|
|
else if(pat == ModSequence::GetInvalidPatIndex())
|
|
s += _T(" Stop");
|
|
else
|
|
s += MPT_CFORMAT("Pattern {}")(pat);
|
|
if(sndFile.Patterns.IsValidPat(pat))
|
|
{
|
|
if(const auto patName = sndFile.Patterns[pat].GetName(); !patName.empty())
|
|
s += _T(" (") + mpt::ToCString(sndFile.GetCharsetInternal(), patName) + _T(")");
|
|
}
|
|
}
|
|
|
|
*pszName = s.AllocSysString();
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// COrderList messages
|
|
|
|
void COrderList::OnPaint()
|
|
{
|
|
TCHAR s[64];
|
|
CPaintDC dc(this);
|
|
HGDIOBJ oldfont = dc.SelectObject(m_hFont);
|
|
HGDIOBJ oldpen = dc.SelectStockObject(DC_PEN);
|
|
const auto separatorColor = GetSysColor(COLOR_WINDOW) ^ 0x808080;
|
|
const auto colorText = GetSysColor(COLOR_WINDOWTEXT), colorInvalid = GetSysColor(COLOR_GRAYTEXT), colorTextSel = GetSysColor(COLOR_HIGHLIGHTTEXT);
|
|
const auto windowBrush = GetSysColorBrush(COLOR_WINDOW), highlightBrush = GetSysColorBrush(COLOR_HIGHLIGHT), faceBrush = GetSysColorBrush(COLOR_BTNFACE);
|
|
|
|
SetDCPenColor(dc, separatorColor);
|
|
|
|
// First time?
|
|
if(m_cxFont <= 0 || m_cyFont <= 0)
|
|
{
|
|
CSize sz = dc.GetTextExtent(_T("000+"), 4);
|
|
m_cxFont = sz.cx;
|
|
m_cyFont = sz.cy;
|
|
}
|
|
|
|
if(m_cxFont > 0 && m_cyFont > 0)
|
|
{
|
|
CRect rcClient;
|
|
GetClientRect(&rcClient);
|
|
CRect rect = rcClient;
|
|
|
|
UpdateScrollInfo();
|
|
dc.SetBkMode(TRANSPARENT);
|
|
const OrdSelection selection = GetCurSel();
|
|
|
|
const int lineWidth1 = Util::ScalePixels(1, m_hWnd);
|
|
const int lineWidth2 = Util::ScalePixels(2, m_hWnd);
|
|
const bool isFocussed = (::GetFocus() == m_hWnd);
|
|
|
|
const auto &order = Order();
|
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
|
ORDERINDEX maxEntries = sndFile.GetModSpecifications().ordersMax;
|
|
if(order.size() > maxEntries)
|
|
{
|
|
// Only computed if potentially needed.
|
|
maxEntries = std::max(maxEntries, order.GetLengthTailTrimmed());
|
|
}
|
|
|
|
// Scrolling the shown orders(the showns rectangles)?
|
|
for(size_t pos = m_nXScroll; rect.left < rcClient.right; pos++, rect.left += m_cxFont)
|
|
{
|
|
const ORDERINDEX ord = mpt::saturate_cast<ORDERINDEX>(pos);
|
|
dc.SetTextColor(colorText);
|
|
const bool inSelection = (ord >= selection.firstOrd && ord <= selection.lastOrd);
|
|
const bool highLight = (isFocussed && inSelection);
|
|
if((rect.right = rect.left + m_cxFont) > rcClient.right)
|
|
rect.right = rcClient.right;
|
|
rect.right--;
|
|
|
|
HBRUSH background;
|
|
if(highLight)
|
|
background = highlightBrush; // Currently selected order item
|
|
else if(order.IsPositionLocked(ord))
|
|
background = faceBrush; // "Playback lock" indicator - grey out all order items which aren't played.
|
|
else
|
|
background = windowBrush; // Normal, unselected item.
|
|
::FillRect(dc, &rect, background);
|
|
|
|
// Drawing the shown pattern-indicator or drag position.
|
|
if(ord == (m_bDragging ? m_nDropPos : m_nScrollPos))
|
|
{
|
|
rect.InflateRect(-1, -1);
|
|
dc.DrawFocusRect(&rect);
|
|
rect.InflateRect(1, 1);
|
|
}
|
|
MoveToEx(dc, rect.right, rect.top, NULL);
|
|
LineTo(dc, rect.right, rect.bottom);
|
|
|
|
// Drawing the 'ctrl-transition' indicator
|
|
if(ord == sndFile.m_PlayState.m_nSeqOverride && sndFile.m_PlayState.m_nSeqOverride != ORDERINDEX_INVALID)
|
|
{
|
|
dc.FillSolidRect(CRect{rect.left + 4, rect.bottom - 4 - lineWidth1, rect.right - 4, rect.bottom - 4}, separatorColor);
|
|
}
|
|
|
|
// Drawing 'playing'-indicator.
|
|
if(ord == sndFile.GetCurrentOrder() && CMainFrame::GetMainFrame()->IsPlaying())
|
|
{
|
|
dc.FillSolidRect(CRect{rect.left + 4, rect.top + 2, rect.right - 4, rect.top + 2 + lineWidth1}, separatorColor);
|
|
m_playPos = ord;
|
|
}
|
|
|
|
// Drawing drop indicator
|
|
if(m_bDragging && ord == m_nDropPos && !inSelection)
|
|
{
|
|
const bool dropLeft = (m_nDropPos < selection.firstOrd) || TrackerSettings::Instance().orderListOldDropBehaviour;
|
|
dc.FillSolidRect(CRect{dropLeft ? (rect.left + 2) : (rect.right - 2 - lineWidth2), rect.top + 2, dropLeft ? (rect.left + 2 + lineWidth2) : (rect.right - 2), rect.bottom - 2}, separatorColor);
|
|
}
|
|
|
|
s[0] = _T('\0');
|
|
const PATTERNINDEX pat = (ord < order.size()) ? order[ord] : PATTERNINDEX_INVALID;
|
|
if(ord < maxEntries && (rect.left + m_cxFont - 4) <= rcClient.right)
|
|
{
|
|
if(pat == order.GetInvalidPatIndex())
|
|
_tcscpy(s, _T("---"));
|
|
else if(pat == order.GetIgnoreIndex())
|
|
_tcscpy(s, _T("+++"));
|
|
else
|
|
wsprintf(s, _T("%u"), pat);
|
|
}
|
|
|
|
COLORREF textCol;
|
|
if(highLight)
|
|
textCol = colorTextSel; // Highlighted pattern
|
|
else if(sndFile.Patterns.IsValidPat(pat))
|
|
textCol = colorText; // Normal pattern
|
|
else
|
|
textCol = colorInvalid; // Non-existent pattern
|
|
dc.SetTextColor(textCol);
|
|
dc.DrawText(s, -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
|
|
}
|
|
}
|
|
if(oldpen)
|
|
dc.SelectObject(oldpen);
|
|
if(oldfont)
|
|
dc.SelectObject(oldfont);
|
|
}
|
|
|
|
|
|
void COrderList::OnSetFocus(CWnd *pWnd)
|
|
{
|
|
CWnd::OnSetFocus(pWnd);
|
|
InvalidateSelection();
|
|
UpdateInfoText();
|
|
CMainFrame::GetMainFrame()->m_pOrderlistHasFocus = this;
|
|
}
|
|
|
|
|
|
void COrderList::OnKillFocus(CWnd *pWnd)
|
|
{
|
|
CWnd::OnKillFocus(pWnd);
|
|
InvalidateSelection();
|
|
CMainFrame::GetMainFrame()->m_pOrderlistHasFocus = nullptr;
|
|
}
|
|
|
|
|
|
void COrderList::OnLButtonDown(UINT nFlags, CPoint pt)
|
|
{
|
|
CRect rect;
|
|
GetClientRect(&rect);
|
|
if(pt.y < rect.bottom)
|
|
{
|
|
SetFocus();
|
|
|
|
if(IsCtrlKeyPressed())
|
|
{
|
|
// Queue pattern
|
|
QueuePattern(pt);
|
|
} else
|
|
{
|
|
// mark pattern (+skip to)
|
|
const int oldXScroll = m_nXScroll;
|
|
|
|
ORDERINDEX ord = GetOrderFromPoint(pt);
|
|
OrdSelection selection = GetCurSel();
|
|
|
|
// check if cursor is in selection - if it is, only react on MouseUp as the user might want to drag those orders
|
|
if(m_nScrollPos2nd == ORDERINDEX_INVALID || ord < selection.firstOrd || ord > selection.lastOrd)
|
|
{
|
|
m_nScrollPos2nd = ORDERINDEX_INVALID;
|
|
SetCurSel(ord, true, IsSelectionKeyPressed());
|
|
}
|
|
m_bDragging = !IsOrderInMargins(m_nScrollPos, oldXScroll) || !IsOrderInMargins(m_nScrollPos2nd, oldXScroll);
|
|
|
|
m_nMouseDownPos = ord;
|
|
if(m_bDragging)
|
|
{
|
|
m_nDragOrder = m_nDropPos = GetCurSel(true).firstOrd;
|
|
SetCapture();
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
CWnd::OnLButtonDown(nFlags, pt);
|
|
}
|
|
}
|
|
|
|
|
|
void COrderList::OnLButtonUp(UINT nFlags, CPoint pt)
|
|
{
|
|
CRect rect;
|
|
GetClientRect(&rect);
|
|
|
|
// Copy or move orders?
|
|
const bool copyOrders = IsSelectionKeyPressed();
|
|
|
|
if(m_bDragging)
|
|
{
|
|
m_bDragging = false;
|
|
ReleaseCapture();
|
|
if(rect.PtInRect(pt))
|
|
{
|
|
ORDERINDEX n = GetOrderFromPoint(pt);
|
|
const OrdSelection selection = GetCurSel();
|
|
if(n != ORDERINDEX_INVALID && n == m_nDropPos && (n < selection.firstOrd || n > selection.lastOrd))
|
|
{
|
|
const bool multiSelection = (selection.firstOrd != selection.lastOrd);
|
|
const bool moveBack = m_nDropPos < m_nDragOrder;
|
|
ORDERINDEX moveCount = (selection.lastOrd - selection.firstOrd), movePos = selection.firstOrd;
|
|
|
|
if(!moveBack && !TrackerSettings::Instance().orderListOldDropBehaviour)
|
|
m_nDropPos++;
|
|
|
|
bool modified = false;
|
|
for(int i = 0; i <= moveCount; i++)
|
|
{
|
|
if(!m_modDoc.MoveOrder(movePos, m_nDropPos, true, copyOrders))
|
|
break;
|
|
modified = true;
|
|
if(moveBack != copyOrders && multiSelection)
|
|
{
|
|
movePos++;
|
|
m_nDropPos++;
|
|
}
|
|
if(moveBack && copyOrders && multiSelection)
|
|
{
|
|
movePos += 2;
|
|
m_nDropPos++;
|
|
}
|
|
}
|
|
|
|
if(multiSelection)
|
|
{
|
|
// adjust selection
|
|
m_nScrollPos2nd = m_nDropPos - 1;
|
|
m_nDropPos -= moveCount + (moveBack ? 0 : 1);
|
|
SetCurSel((moveBack && !copyOrders) ? m_nDropPos - 1 : m_nDropPos);
|
|
} else
|
|
{
|
|
SetCurSel((m_nDragOrder < m_nDropPos && !copyOrders) ? m_nDropPos - 1 : m_nDropPos);
|
|
}
|
|
// Did we actually change anything?
|
|
if(modified)
|
|
m_modDoc.SetModified();
|
|
} else
|
|
{
|
|
if(pt.y < rect.bottom && n == m_nMouseDownPos && !copyOrders)
|
|
{
|
|
// Remove selection if we didn't drag anything but multiselect was active
|
|
m_nScrollPos2nd = ORDERINDEX_INVALID;
|
|
SetFocus();
|
|
SetCurSel(n);
|
|
}
|
|
}
|
|
}
|
|
Invalidate(FALSE);
|
|
} else
|
|
{
|
|
CWnd::OnLButtonUp(nFlags, pt);
|
|
}
|
|
}
|
|
|
|
|
|
void COrderList::OnMouseMove(UINT nFlags, CPoint pt)
|
|
{
|
|
if((m_bDragging) && (m_cxFont))
|
|
{
|
|
CRect rect;
|
|
|
|
GetClientRect(&rect);
|
|
ORDERINDEX n = ORDERINDEX_INVALID;
|
|
if(rect.PtInRect(pt))
|
|
{
|
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
|
n = GetOrderFromPoint(pt);
|
|
if(n >= Order().size() && n >= sndFile.GetModSpecifications().ordersMax)
|
|
n = ORDERINDEX_INVALID;
|
|
}
|
|
if(n != m_nDropPos)
|
|
{
|
|
if(n != ORDERINDEX_INVALID)
|
|
{
|
|
m_nMouseDownPos = ORDERINDEX_INVALID;
|
|
m_nDropPos = n;
|
|
Invalidate(FALSE);
|
|
SetCursor(CMainFrame::curDragging);
|
|
} else
|
|
{
|
|
m_nDropPos = ORDERINDEX_INVALID;
|
|
SetCursor(CMainFrame::curNoDrop);
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
CWnd::OnMouseMove(nFlags, pt);
|
|
}
|
|
}
|
|
|
|
|
|
void COrderList::OnSelectSequence(UINT nid)
|
|
{
|
|
SelectSequence(static_cast<SEQUENCEINDEX>(nid - ID_SEQUENCE_ITEM));
|
|
}
|
|
|
|
|
|
void COrderList::OnRButtonDown(UINT nFlags, CPoint pt)
|
|
{
|
|
CRect rect;
|
|
GetClientRect(&rect);
|
|
if(m_bDragging)
|
|
{
|
|
m_nDropPos = ORDERINDEX_INVALID;
|
|
OnLButtonUp(nFlags, pt);
|
|
}
|
|
if(pt.y >= rect.bottom)
|
|
return;
|
|
|
|
bool multiSelection = (m_nScrollPos2nd != ORDERINDEX_INVALID);
|
|
|
|
if(!multiSelection)
|
|
SetCurSel(GetOrderFromPoint(pt), false, false, false);
|
|
SetFocus();
|
|
HMENU hMenu = ::CreatePopupMenu();
|
|
if(!hMenu)
|
|
return;
|
|
|
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
|
|
|
// Check if at least one pattern in the current selection exists
|
|
bool patExists = false;
|
|
OrdSelection selection = GetCurSel();
|
|
LimitMax(selection.lastOrd, Order().GetLastIndex());
|
|
for(ORDERINDEX ord = selection.firstOrd; ord <= selection.lastOrd && !patExists; ord++)
|
|
{
|
|
patExists = Order().IsValidPat(ord);
|
|
}
|
|
|
|
const DWORD greyed = patExists ? 0 : MF_GRAYED;
|
|
|
|
CInputHandler *ih = CMainFrame::GetInputHandler();
|
|
|
|
if(multiSelection)
|
|
{
|
|
// Several patterns are selected.
|
|
AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_INSERT, ih->GetKeyTextFromCommand(kcOrderlistEditInsert, _T("&Insert Patterns")));
|
|
AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_DELETE, ih->GetKeyTextFromCommand(kcOrderlistEditDelete, _T("&Remove Patterns")));
|
|
AppendMenu(hMenu, MF_SEPARATOR, NULL, _T(""));
|
|
AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_EDIT_COPY, ih->GetKeyTextFromCommand(kcEditCopy, _T("&Copy Patterns")));
|
|
AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_EDIT_COPY_ORDERS, ih->GetKeyTextFromCommand(kcOrderlistEditCopyOrders, _T("&Copy Orders")));
|
|
AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_EDIT_CUT, ih->GetKeyTextFromCommand(kcEditCut, _T("&C&ut Patterns")));
|
|
AppendMenu(hMenu, MF_STRING | greyed, ID_PATTERNPASTE, ih->GetKeyTextFromCommand(kcEditPaste, _T("P&aste Patterns")));
|
|
AppendMenu(hMenu, MF_SEPARATOR, NULL, _T(""));
|
|
AppendMenu(hMenu, MF_STRING | greyed, ID_ORDERLIST_COPY, ih->GetKeyTextFromCommand(kcDuplicatePattern, _T("&Duplicate Patterns")));
|
|
AppendMenu(hMenu, MF_STRING | greyed, ID_ORDERLIST_MERGE, ih->GetKeyTextFromCommand(kcMergePatterns, _T("&Merge Patterns")));
|
|
} else
|
|
{
|
|
// Only one pattern is selected
|
|
AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_INSERT, ih->GetKeyTextFromCommand(kcOrderlistEditInsert, _T("&Insert Pattern")));
|
|
if(sndFile.GetModSpecifications().hasIgnoreIndex)
|
|
{
|
|
AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_INSERT_SEPARATOR, ih->GetKeyTextFromCommand(kcOrderlistEditInsertSeparator, _T("&Insert Separator")));
|
|
}
|
|
AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_DELETE, ih->GetKeyTextFromCommand(kcOrderlistEditDelete, _T("&Remove Pattern")));
|
|
AppendMenu(hMenu, MF_SEPARATOR, NULL, _T(""));
|
|
AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_NEW, ih->GetKeyTextFromCommand(kcNewPattern, _T("Create &New Pattern")));
|
|
AppendMenu(hMenu, MF_STRING | greyed, ID_ORDERLIST_COPY, ih->GetKeyTextFromCommand(kcDuplicatePattern, _T("&Duplicate Pattern")));
|
|
AppendMenu(hMenu, MF_STRING | greyed, ID_PATTERNCOPY, _T("&Copy Pattern"));
|
|
AppendMenu(hMenu, MF_STRING, ID_PATTERNPASTE, ih->GetKeyTextFromCommand(kcEditPaste, _T("P&aste Pattern")));
|
|
const bool hasPatternProperties = sndFile.GetModSpecifications().patternRowsMin != sndFile.GetModSpecifications().patternRowsMax;
|
|
if(hasPatternProperties || sndFile.GetModSpecifications().hasRestartPos)
|
|
{
|
|
AppendMenu(hMenu, MF_SEPARATOR, NULL, _T(""));
|
|
if(hasPatternProperties)
|
|
AppendMenu(hMenu, MF_STRING | greyed, ID_PATTERN_PROPERTIES, _T("&Pattern Properties..."));
|
|
if(sndFile.GetModSpecifications().hasRestartPos)
|
|
AppendMenu(hMenu, MF_STRING | greyed | ((Order().GetRestartPos() == m_nScrollPos) ? MF_CHECKED : 0), ID_SETRESTARTPOS, _T("R&estart Position"));
|
|
}
|
|
if(sndFile.GetModSpecifications().sequencesMax > 1)
|
|
{
|
|
AppendMenu(hMenu, MF_SEPARATOR, NULL, _T(""));
|
|
|
|
HMENU menuSequence = ::CreatePopupMenu();
|
|
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)menuSequence, _T("&Sequences"));
|
|
|
|
const SEQUENCEINDEX numSequences = sndFile.Order.GetNumSequences();
|
|
for(SEQUENCEINDEX i = 0; i < numSequences; i++)
|
|
{
|
|
CString str;
|
|
if(sndFile.Order(i).GetName().empty())
|
|
str = MPT_CFORMAT("Sequence {}")(i + 1);
|
|
else
|
|
str = MPT_CFORMAT("{}: {}")(i + 1, mpt::ToCString(sndFile.Order(i).GetName()));
|
|
const UINT flags = (sndFile.Order.GetCurrentSequenceIndex() == i) ? MF_STRING | MF_CHECKED : MF_STRING;
|
|
AppendMenu(menuSequence, flags, ID_SEQUENCE_ITEM + i, str);
|
|
}
|
|
if(sndFile.Order.GetNumSequences() < sndFile.GetModSpecifications().sequencesMax)
|
|
{
|
|
AppendMenu(menuSequence, MF_STRING, ID_SEQUENCE_ITEM + kDuplicateSequence, _T("&Duplicate current sequence"));
|
|
AppendMenu(menuSequence, MF_STRING, ID_SEQUENCE_ITEM + kAddSequence, _T("&Create empty sequence"));
|
|
}
|
|
if(sndFile.Order.GetNumSequences() > 1)
|
|
AppendMenu(menuSequence, MF_STRING, ID_SEQUENCE_ITEM + kDeleteSequence, _T("D&elete current sequence"));
|
|
else
|
|
AppendMenu(menuSequence, MF_STRING, ID_SEQUENCE_ITEM + kSplitSequence, _T("&Split sub songs into sequences"));
|
|
}
|
|
}
|
|
AppendMenu(hMenu, MF_SEPARATOR, NULL, _T(""));
|
|
AppendMenu(hMenu, ((selection.firstOrd == sndFile.m_lockOrderStart && selection.lastOrd == sndFile.m_lockOrderEnd) ? (MF_STRING | MF_CHECKED) : MF_STRING), ID_ORDERLIST_LOCKPLAYBACK, ih->GetKeyTextFromCommand(kcOrderlistLockPlayback, _T("&Lock Playback to Selection")));
|
|
AppendMenu(hMenu, (sndFile.m_lockOrderStart == ORDERINDEX_INVALID ? (MF_STRING | MF_GRAYED) : MF_STRING), ID_ORDERLIST_UNLOCKPLAYBACK, ih->GetKeyTextFromCommand(kcOrderlistUnlockPlayback, _T("&Unlock Playback")));
|
|
|
|
AppendMenu(hMenu, MF_SEPARATOR, NULL, _T(""));
|
|
AppendMenu(hMenu, MF_STRING | greyed, ID_ORDERLIST_RENDER, _T("Render to &Wave"));
|
|
|
|
ClientToScreen(&pt);
|
|
::TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, m_hWnd, NULL);
|
|
::DestroyMenu(hMenu);
|
|
}
|
|
|
|
|
|
void COrderList::OnLButtonDblClk(UINT, CPoint)
|
|
{
|
|
auto &sndFile = m_modDoc.GetSoundFile();
|
|
m_nScrollPos2nd = ORDERINDEX_INVALID;
|
|
SetFocus();
|
|
if(!EnsureEditable(m_nScrollPos))
|
|
return;
|
|
PATTERNINDEX pat = Order()[m_nScrollPos];
|
|
if(sndFile.Patterns.IsValidPat(pat))
|
|
m_pParent.SetCurrentPattern(pat);
|
|
else if(pat != sndFile.Order.GetIgnoreIndex())
|
|
OnCreateNewPattern();
|
|
}
|
|
|
|
|
|
void COrderList::OnMButtonDown(UINT nFlags, CPoint pt)
|
|
{
|
|
MPT_UNREFERENCED_PARAMETER(nFlags);
|
|
QueuePattern(pt);
|
|
}
|
|
|
|
|
|
void COrderList::OnHScroll(UINT nSBCode, UINT /*nPos*/, CScrollBar *)
|
|
{
|
|
UINT nNewPos = m_nXScroll;
|
|
UINT smin, smax;
|
|
|
|
GetScrollRange(SB_HORZ, (LPINT)&smin, (LPINT)&smax);
|
|
m_bScrolling = true;
|
|
switch(nSBCode)
|
|
{
|
|
case SB_LINELEFT: if (nNewPos) nNewPos--; break;
|
|
case SB_LINERIGHT: if (nNewPos < smax) nNewPos++; break;
|
|
case SB_PAGELEFT: if (nNewPos > 4) nNewPos -= 4; else nNewPos = 0; break;
|
|
case SB_PAGERIGHT: if (nNewPos + 4 < smax) nNewPos += 4; else nNewPos = smax; break;
|
|
case SB_THUMBPOSITION:
|
|
case SB_THUMBTRACK: nNewPos = GetScrollPos(true); break;
|
|
case SB_LEFT: nNewPos = 0; break;
|
|
case SB_RIGHT: nNewPos = smax; break;
|
|
case SB_ENDSCROLL: m_bScrolling = false; break;
|
|
}
|
|
if (nNewPos > smax) nNewPos = smax;
|
|
if (nNewPos != m_nXScroll)
|
|
{
|
|
m_nXScroll = static_cast<ORDERINDEX>(nNewPos);
|
|
SetScrollPos(m_nXScroll);
|
|
Invalidate(FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
void COrderList::OnSize(UINT nType, int cx, int cy)
|
|
{
|
|
int nPos;
|
|
int smin, smax;
|
|
|
|
CWnd::OnSize(nType, cx, cy);
|
|
UpdateScrollInfo();
|
|
GetScrollRange(SB_HORZ, &smin, &smax);
|
|
nPos = GetScrollPos();
|
|
if(nPos > smax)
|
|
nPos = smax;
|
|
if(m_nXScroll != nPos)
|
|
{
|
|
m_nXScroll = mpt::saturate_cast<ORDERINDEX>(nPos);
|
|
SetScrollPos(m_nXScroll);
|
|
Invalidate(FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
void COrderList::OnInsertOrder()
|
|
{
|
|
// insert the same order(s) after the currently selected order(s)
|
|
ModSequence &order = Order();
|
|
|
|
const OrdSelection selection = GetCurSel();
|
|
const ORDERINDEX insertCount = order.insert(selection.lastOrd + 1, selection.lastOrd - selection.firstOrd + 1);
|
|
if(!insertCount)
|
|
return;
|
|
|
|
std::copy(order.begin() + selection.firstOrd, order.begin() + selection.firstOrd + insertCount, order.begin() + selection.lastOrd + 1);
|
|
|
|
InsertUpdatePlaystate(selection.firstOrd, selection.lastOrd);
|
|
|
|
m_nScrollPos = std::min(ORDERINDEX(selection.lastOrd + 1), order.GetLastIndex());
|
|
if(insertCount > 1)
|
|
m_nScrollPos2nd = std::min(ORDERINDEX(m_nScrollPos + insertCount - 1), order.GetLastIndex());
|
|
else
|
|
m_nScrollPos2nd = ORDERINDEX_INVALID;
|
|
|
|
InvalidateSelection();
|
|
EnsureVisible(m_nScrollPos2nd);
|
|
// first inserted order has higher priority than the last one
|
|
EnsureVisible(m_nScrollPos);
|
|
|
|
Invalidate(FALSE);
|
|
m_modDoc.SetModified();
|
|
m_modDoc.UpdateAllViews(nullptr, SequenceHint().Data(), this);
|
|
}
|
|
|
|
|
|
void COrderList::OnInsertSeparatorPattern()
|
|
{
|
|
// Insert a separator pattern after the current pattern, don't move order list cursor
|
|
ModSequence &order = Order();
|
|
|
|
const OrdSelection selection = GetCurSel(true);
|
|
ORDERINDEX insertPos = selection.firstOrd;
|
|
|
|
if(!EnsureEditable(insertPos))
|
|
return;
|
|
if(order[insertPos] != order.GetInvalidPatIndex())
|
|
{
|
|
// If we're not inserting at a stop (---) index, we move on by one position.
|
|
insertPos++;
|
|
order.insert(insertPos, 1, order.GetIgnoreIndex());
|
|
} else
|
|
{
|
|
order[insertPos] = order.GetIgnoreIndex();
|
|
}
|
|
|
|
InsertUpdatePlaystate(insertPos, insertPos);
|
|
|
|
Invalidate(FALSE);
|
|
m_modDoc.SetModified();
|
|
m_modDoc.UpdateAllViews(nullptr, SequenceHint().Data(), this);
|
|
}
|
|
|
|
|
|
void COrderList::OnRenderOrder()
|
|
{
|
|
OrdSelection selection = GetCurSel();
|
|
m_modDoc.OnFileWaveConvert(selection.firstOrd, selection.lastOrd);
|
|
}
|
|
|
|
|
|
void COrderList::OnDeleteOrder()
|
|
{
|
|
OrdSelection selection = GetCurSel();
|
|
// remove selection
|
|
m_nScrollPos2nd = ORDERINDEX_INVALID;
|
|
|
|
Order().Remove(selection.firstOrd, selection.lastOrd);
|
|
|
|
m_modDoc.SetModified();
|
|
Invalidate(FALSE);
|
|
m_modDoc.UpdateAllViews(nullptr, SequenceHint().Data(), this);
|
|
|
|
DeleteUpdatePlaystate(selection.firstOrd, selection.lastOrd);
|
|
|
|
SetCurSel(selection.firstOrd, true, false, true);
|
|
}
|
|
|
|
|
|
void COrderList::OnPatternProperties()
|
|
{
|
|
ModSequence &order = Order();
|
|
const auto ord = GetCurSel(true).firstOrd;
|
|
if(order.IsValidPat(ord))
|
|
m_pParent.PostViewMessage(VIEWMSG_PATTERNPROPERTIES, order[ord]);
|
|
}
|
|
|
|
|
|
void COrderList::OnPlayerPlay()
|
|
{
|
|
m_pParent.PostMessage(WM_COMMAND, ID_PLAYER_PLAY);
|
|
}
|
|
|
|
|
|
void COrderList::OnPlayerPause()
|
|
{
|
|
m_pParent.PostMessage(WM_COMMAND, ID_PLAYER_PAUSE);
|
|
}
|
|
|
|
|
|
void COrderList::OnPlayerPlayFromStart()
|
|
{
|
|
m_pParent.PostMessage(WM_COMMAND, ID_PLAYER_PLAYFROMSTART);
|
|
}
|
|
|
|
|
|
void COrderList::OnPatternPlayFromStart()
|
|
{
|
|
m_pParent.PostMessage(WM_COMMAND, IDC_PATTERN_PLAYFROMSTART);
|
|
}
|
|
|
|
|
|
void COrderList::OnCreateNewPattern()
|
|
{
|
|
m_pParent.PostMessage(WM_COMMAND, ID_ORDERLIST_NEW);
|
|
}
|
|
|
|
|
|
void COrderList::OnDuplicatePattern()
|
|
{
|
|
m_pParent.PostMessage(WM_COMMAND, ID_ORDERLIST_COPY);
|
|
}
|
|
|
|
|
|
void COrderList::OnMergePatterns()
|
|
{
|
|
m_pParent.PostMessage(WM_COMMAND, ID_ORDERLIST_MERGE);
|
|
}
|
|
|
|
|
|
void COrderList::OnPatternCopy()
|
|
{
|
|
m_pParent.PostMessage(WM_COMMAND, ID_PATTERNCOPY);
|
|
}
|
|
|
|
|
|
void COrderList::OnPatternPaste()
|
|
{
|
|
m_pParent.PostMessage(WM_COMMAND, ID_PATTERNPASTE);
|
|
}
|
|
|
|
|
|
void COrderList::OnSetRestartPos()
|
|
{
|
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
|
bool modified = false;
|
|
if(m_nScrollPos == Order().GetRestartPos())
|
|
{
|
|
// Unset position
|
|
modified = (m_nScrollPos != 0);
|
|
Order().SetRestartPos(0);
|
|
} else if(sndFile.GetModSpecifications().hasRestartPos)
|
|
{
|
|
// Set new position
|
|
modified = true;
|
|
Order().SetRestartPos(m_nScrollPos);
|
|
}
|
|
if(modified)
|
|
{
|
|
m_modDoc.SetModified();
|
|
m_modDoc.UpdateAllViews(nullptr, SequenceHint().RestartPos(), this);
|
|
}
|
|
}
|
|
|
|
|
|
LRESULT COrderList::OnHelpHitTest(WPARAM, LPARAM)
|
|
{
|
|
return HID_BASE_COMMAND + IDC_ORDERLIST;
|
|
}
|
|
|
|
|
|
LRESULT COrderList::OnDragonDropping(WPARAM doDrop, LPARAM lParam)
|
|
{
|
|
const DRAGONDROP *pDropInfo = (const DRAGONDROP *)lParam;
|
|
CPoint pt;
|
|
|
|
if((!pDropInfo) || (&m_modDoc.GetSoundFile() != pDropInfo->sndFile) || (!m_cxFont))
|
|
return FALSE;
|
|
BOOL canDrop = FALSE;
|
|
switch(pDropInfo->dropType)
|
|
{
|
|
case DRAGONDROP_ORDER:
|
|
if(pDropInfo->dropItem >= Order().size())
|
|
break;
|
|
case DRAGONDROP_PATTERN:
|
|
canDrop = TRUE;
|
|
break;
|
|
}
|
|
if(!canDrop || !doDrop)
|
|
return canDrop;
|
|
GetCursorPos(&pt);
|
|
ScreenToClient(&pt);
|
|
if(pt.x < 0)
|
|
pt.x = 0;
|
|
ORDERINDEX posDest = mpt::saturate_cast<ORDERINDEX>(m_nXScroll + (pt.x / m_cxFont));
|
|
if(posDest >= Order().size())
|
|
return FALSE;
|
|
switch(pDropInfo->dropType)
|
|
{
|
|
case DRAGONDROP_PATTERN:
|
|
Order()[posDest] = static_cast<PATTERNINDEX>(pDropInfo->dropItem);
|
|
break;
|
|
|
|
case DRAGONDROP_ORDER:
|
|
Order()[posDest] = Order()[pDropInfo->dropItem];
|
|
break;
|
|
}
|
|
if(canDrop)
|
|
{
|
|
Invalidate(FALSE);
|
|
m_modDoc.SetModified();
|
|
m_modDoc.UpdateAllViews(nullptr, SequenceHint().Data(), this);
|
|
SetCurSel(posDest, true);
|
|
}
|
|
return canDrop;
|
|
}
|
|
|
|
|
|
ORDERINDEX COrderList::SetMargins(int i)
|
|
{
|
|
m_nOrderlistMargins = i;
|
|
return GetMargins();
|
|
}
|
|
|
|
|
|
void COrderList::SelectSequence(const SEQUENCEINDEX seq)
|
|
{
|
|
CriticalSection cs;
|
|
|
|
CMainFrame::GetMainFrame()->ResetNotificationBuffer();
|
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
|
const bool editSequence = seq >= sndFile.Order.GetNumSequences();
|
|
if(seq == kSplitSequence)
|
|
{
|
|
if(!sndFile.Order.CanSplitSubsongs())
|
|
{
|
|
Reporting::Information(U_("No sub songs have been found in this sequence."));
|
|
return;
|
|
}
|
|
if(Reporting::Confirm(U_("The order list contains separator items.\nDo you want to split the sequence at the separators into multiple song sequences?")) != cnfYes)
|
|
return;
|
|
if(!sndFile.Order.SplitSubsongsToMultipleSequences())
|
|
return;
|
|
} else if(seq == kDeleteSequence)
|
|
{
|
|
SEQUENCEINDEX curSeq = sndFile.Order.GetCurrentSequenceIndex();
|
|
mpt::ustring str = MPT_UFORMAT("Remove sequence {}: {}?")(curSeq + 1, mpt::ToUnicode(Order().GetName()));
|
|
if(Reporting::Confirm(str) == cnfYes)
|
|
sndFile.Order.RemoveSequence(curSeq);
|
|
else
|
|
return;
|
|
} else if(seq == kAddSequence || seq == kDuplicateSequence)
|
|
{
|
|
const bool duplicate = (seq == kDuplicateSequence);
|
|
const SEQUENCEINDEX newIndex = sndFile.Order.GetCurrentSequenceIndex() + 1u;
|
|
std::vector<SEQUENCEINDEX> newOrder(sndFile.Order.GetNumSequences());
|
|
std::iota(newOrder.begin(), newOrder.end(), SEQUENCEINDEX(0));
|
|
newOrder.insert(newOrder.begin() + newIndex, duplicate ? sndFile.Order.GetCurrentSequenceIndex() : SEQUENCEINDEX_INVALID);
|
|
if(m_modDoc.ReArrangeSequences(newOrder))
|
|
{
|
|
sndFile.Order.SetSequence(newIndex);
|
|
if(const auto name = sndFile.Order().GetName(); duplicate && !name.empty())
|
|
sndFile.Order().SetName(name + U_(" (Copy)"));
|
|
m_modDoc.UpdateAllViews(nullptr, SequenceHint(SEQUENCEINDEX_INVALID).Names().Data());
|
|
}
|
|
} else if(seq == sndFile.Order.GetCurrentSequenceIndex())
|
|
return;
|
|
else if(seq < sndFile.Order.GetNumSequences())
|
|
sndFile.Order.SetSequence(seq);
|
|
ORDERINDEX posCandidate = Order().GetLengthTailTrimmed() - 1;
|
|
SetCurSel(std::min(m_nScrollPos, posCandidate), true, false, true);
|
|
m_pParent.SetCurrentPattern(Order()[m_nScrollPos]);
|
|
|
|
UpdateScrollInfo();
|
|
// This won't make sense anymore in the new sequence.
|
|
OnUnlockPlayback();
|
|
|
|
cs.Leave();
|
|
|
|
if(editSequence)
|
|
m_modDoc.SetModified();
|
|
m_modDoc.UpdateAllViews(nullptr, SequenceHint().Data(), nullptr);
|
|
}
|
|
|
|
|
|
void COrderList::QueuePattern(CPoint pt)
|
|
{
|
|
CRect rect;
|
|
GetClientRect(&rect);
|
|
|
|
if(!rect.PtInRect(pt))
|
|
return;
|
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
|
|
|
const PATTERNINDEX ignoreIndex = sndFile.Order.GetIgnoreIndex();
|
|
const PATTERNINDEX stopIndex = sndFile.Order.GetInvalidPatIndex();
|
|
const ORDERINDEX length = Order().GetLength();
|
|
ORDERINDEX order = GetOrderFromPoint(pt);
|
|
|
|
// If this is not a playable order item, find the next valid item.
|
|
while(order < length && (Order()[order] == ignoreIndex || Order()[order] == stopIndex))
|
|
{
|
|
order++;
|
|
}
|
|
|
|
if(order < length)
|
|
{
|
|
if(sndFile.m_PlayState.m_nSeqOverride == order)
|
|
{
|
|
// This item is already queued: Dequeue it.
|
|
sndFile.m_PlayState.m_nSeqOverride = ORDERINDEX_INVALID;
|
|
} else
|
|
{
|
|
if(Order().IsPositionLocked(order))
|
|
{
|
|
// Users wants to go somewhere else, so let them do that.
|
|
OnUnlockPlayback();
|
|
}
|
|
|
|
sndFile.m_PlayState.m_nSeqOverride = order;
|
|
}
|
|
Invalidate(FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
void COrderList::OnLockPlayback()
|
|
{
|
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
|
|
|
OrdSelection selection = GetCurSel();
|
|
if(selection.firstOrd == sndFile.m_lockOrderStart && selection.lastOrd == sndFile.m_lockOrderEnd)
|
|
{
|
|
OnUnlockPlayback();
|
|
} else
|
|
{
|
|
sndFile.m_lockOrderStart = selection.firstOrd;
|
|
sndFile.m_lockOrderEnd = selection.lastOrd;
|
|
Invalidate(FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
void COrderList::OnUnlockPlayback()
|
|
{
|
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
|
sndFile.m_lockOrderStart = sndFile.m_lockOrderEnd = ORDERINDEX_INVALID;
|
|
Invalidate(FALSE);
|
|
}
|
|
|
|
|
|
void COrderList::InsertUpdatePlaystate(ORDERINDEX first, ORDERINDEX last)
|
|
{
|
|
auto &sndFile = m_modDoc.GetSoundFile();
|
|
Util::InsertItem(first, last, sndFile.m_PlayState.m_nNextOrder);
|
|
if(sndFile.m_PlayState.m_nSeqOverride != ORDERINDEX_INVALID)
|
|
Util::InsertItem(first, last, sndFile.m_PlayState.m_nSeqOverride);
|
|
// Adjust order lock position
|
|
if(sndFile.m_lockOrderStart != ORDERINDEX_INVALID)
|
|
Util::InsertRange(first, last, sndFile.m_lockOrderStart, sndFile.m_lockOrderEnd);
|
|
}
|
|
|
|
|
|
void COrderList::DeleteUpdatePlaystate(ORDERINDEX first, ORDERINDEX last)
|
|
{
|
|
auto &sndFile = m_modDoc.GetSoundFile();
|
|
Util::DeleteItem(first, last, sndFile.m_PlayState.m_nNextOrder);
|
|
if(sndFile.m_PlayState.m_nSeqOverride != ORDERINDEX_INVALID)
|
|
Util::DeleteItem(first, last, sndFile.m_PlayState.m_nSeqOverride);
|
|
// Adjust order lock position
|
|
if(sndFile.m_lockOrderStart != ORDERINDEX_INVALID)
|
|
Util::DeleteRange(first, last, sndFile.m_lockOrderStart, sndFile.m_lockOrderEnd);
|
|
}
|
|
|
|
|
|
INT_PTR COrderList::OnToolHitTest(CPoint point, TOOLINFO *pTI) const
|
|
{
|
|
CRect rect;
|
|
GetClientRect(&rect);
|
|
|
|
pTI->hwnd = m_hWnd;
|
|
pTI->uId = GetOrderFromPoint(point);
|
|
pTI->rect = rect;
|
|
pTI->lpszText = LPSTR_TEXTCALLBACK;
|
|
return pTI->uId;
|
|
}
|
|
|
|
|
|
BOOL COrderList::OnToolTipText(UINT, NMHDR *pNMHDR, LRESULT *)
|
|
{
|
|
TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pNMHDR;
|
|
if(!(pTTT->uFlags & TTF_IDISHWND))
|
|
{
|
|
CString text;
|
|
const CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
|
const ModSequence &order = Order();
|
|
const ORDERINDEX ord = mpt::saturate_cast<ORDERINDEX>(pNMHDR->idFrom), ordLen = order.GetLengthTailTrimmed();
|
|
text.Format(_T("Position %u of %u [%02Xh of %02Xh]"), ord, ordLen, ord, ordLen);
|
|
if(order.IsValidPat(ord))
|
|
{
|
|
PATTERNINDEX pat = order[ord];
|
|
const std::string name = sndFile.Patterns[pat].GetName();
|
|
if(!name.empty())
|
|
{
|
|
::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, int32_max); // Allow multiline tooltip
|
|
text += _T("\r\n") + mpt::ToCString(sndFile.GetCharsetInternal(), name);
|
|
}
|
|
}
|
|
mpt::String::WriteCStringBuf(pTTT->szText) = text;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
OPENMPT_NAMESPACE_END
|