Ctrl_seq.cpp 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675
  1. /*
  2. * Ctrl_seq.cpp
  3. * ------------
  4. * Purpose: Order list for the pattern editor upper panel.
  5. * Notes : (currently none)
  6. * Authors: Olivier Lapicque
  7. * OpenMPT Devs
  8. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  9. */
  10. #include "stdafx.h"
  11. #include "Mainfrm.h"
  12. #include "InputHandler.h"
  13. #include "Moddoc.h"
  14. #include "../soundlib/mod_specifications.h"
  15. #include "Globals.h"
  16. #include "Ctrl_pat.h"
  17. #include "PatternClipboard.h"
  18. #include "../common/mptStringBuffer.h"
  19. OPENMPT_NAMESPACE_BEGIN
  20. enum SequenceAction : SEQUENCEINDEX
  21. {
  22. kAddSequence = MAX_SEQUENCES,
  23. kDuplicateSequence,
  24. kDeleteSequence,
  25. kSplitSequence,
  26. kMaxSequenceActions
  27. };
  28. // Little helper function to avoid copypasta
  29. static bool IsSelectionKeyPressed() { return CMainFrame::GetInputHandler()->SelectionPressed(); }
  30. static bool IsCtrlKeyPressed() { return CMainFrame::GetInputHandler()->CtrlPressed(); }
  31. //////////////////////////////////////////////////////////////
  32. // CPatEdit
  33. BOOL CPatEdit::PreTranslateMessage(MSG *pMsg)
  34. {
  35. if(((pMsg->message == WM_KEYDOWN) || (pMsg->message == WM_KEYUP)) && (pMsg->wParam == VK_TAB))
  36. {
  37. if((pMsg->message == WM_KEYUP) && (m_pParent))
  38. {
  39. m_pParent->SwitchToView();
  40. }
  41. return TRUE;
  42. }
  43. return CEdit::PreTranslateMessage(pMsg);
  44. }
  45. //////////////////////////////////////////////////////////////
  46. // COrderList
  47. BEGIN_MESSAGE_MAP(COrderList, CWnd)
  48. //{{AFX_MSG_MAP(COrderList)
  49. ON_WM_PAINT()
  50. ON_WM_ERASEBKGND()
  51. ON_WM_MOUSEMOVE()
  52. ON_WM_LBUTTONDOWN()
  53. ON_WM_LBUTTONDBLCLK()
  54. ON_WM_LBUTTONUP()
  55. ON_WM_RBUTTONDOWN()
  56. ON_WM_MBUTTONDOWN()
  57. ON_WM_SETFOCUS()
  58. ON_WM_KILLFOCUS()
  59. ON_WM_HSCROLL()
  60. ON_WM_SIZE()
  61. ON_COMMAND(ID_ORDERLIST_INSERT, &COrderList::OnInsertOrder)
  62. ON_COMMAND(ID_ORDERLIST_INSERT_SEPARATOR, &COrderList::OnInsertSeparatorPattern)
  63. ON_COMMAND(ID_ORDERLIST_DELETE, &COrderList::OnDeleteOrder)
  64. ON_COMMAND(ID_ORDERLIST_RENDER, &COrderList::OnRenderOrder)
  65. ON_COMMAND(ID_ORDERLIST_EDIT_COPY, &COrderList::OnEditCopy)
  66. ON_COMMAND(ID_ORDERLIST_EDIT_CUT, &COrderList::OnEditCut)
  67. ON_COMMAND(ID_ORDERLIST_EDIT_COPY_ORDERS, &COrderList::OnEditCopyOrders)
  68. ON_COMMAND(ID_PATTERN_PROPERTIES, &COrderList::OnPatternProperties)
  69. ON_COMMAND(ID_PLAYER_PLAY, &COrderList::OnPlayerPlay)
  70. ON_COMMAND(ID_PLAYER_PAUSE, &COrderList::OnPlayerPause)
  71. ON_COMMAND(ID_PLAYER_PLAYFROMSTART, &COrderList::OnPlayerPlayFromStart)
  72. ON_COMMAND(IDC_PATTERN_PLAYFROMSTART, &COrderList::OnPatternPlayFromStart)
  73. ON_COMMAND(ID_ORDERLIST_NEW, &COrderList::OnCreateNewPattern)
  74. ON_COMMAND(ID_ORDERLIST_COPY, &COrderList::OnDuplicatePattern)
  75. ON_COMMAND(ID_ORDERLIST_MERGE, &COrderList::OnMergePatterns)
  76. ON_COMMAND(ID_PATTERNCOPY, &COrderList::OnPatternCopy)
  77. ON_COMMAND(ID_PATTERNPASTE, &COrderList::OnPatternPaste)
  78. ON_COMMAND(ID_SETRESTARTPOS, &COrderList::OnSetRestartPos)
  79. ON_COMMAND(ID_ORDERLIST_LOCKPLAYBACK, &COrderList::OnLockPlayback)
  80. ON_COMMAND(ID_ORDERLIST_UNLOCKPLAYBACK, &COrderList::OnUnlockPlayback)
  81. ON_COMMAND_RANGE(ID_SEQUENCE_ITEM, ID_SEQUENCE_ITEM + kMaxSequenceActions - 1, &COrderList::OnSelectSequence)
  82. ON_MESSAGE(WM_MOD_DRAGONDROPPING, &COrderList::OnDragonDropping)
  83. ON_MESSAGE(WM_HELPHITTEST, &COrderList::OnHelpHitTest)
  84. ON_MESSAGE(WM_MOD_KEYCOMMAND, &COrderList::OnCustomKeyMsg)
  85. ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &COrderList::OnToolTipText)
  86. //}}AFX_MSG_MAP
  87. END_MESSAGE_MAP()
  88. COrderList::COrderList(CCtrlPatterns &parent, CModDoc &document)
  89. : m_nOrderlistMargins(TrackerSettings::Instance().orderlistMargins)
  90. , m_modDoc(document)
  91. , m_pParent(parent)
  92. {
  93. EnableActiveAccessibility();
  94. }
  95. bool COrderList::EnsureEditable(ORDERINDEX ord)
  96. {
  97. auto &sndFile = m_modDoc.GetSoundFile();
  98. if(ord >= Order().size())
  99. {
  100. if(ord < sndFile.GetModSpecifications().ordersMax)
  101. {
  102. try
  103. {
  104. Order().resize(ord + 1);
  105. } catch(mpt::out_of_memory e)
  106. {
  107. mpt::delete_out_of_memory(e);
  108. return false;
  109. }
  110. } else
  111. {
  112. return false;
  113. }
  114. }
  115. return true;
  116. }
  117. ModSequence &COrderList::Order() { return m_modDoc.GetSoundFile().Order(); }
  118. const ModSequence &COrderList::Order() const { return m_modDoc.GetSoundFile().Order(); }
  119. void COrderList::SetScrollPos(int pos)
  120. {
  121. // Work around 16-bit limitations of WM_HSCROLL
  122. SCROLLINFO si;
  123. MemsetZero(si);
  124. si.cbSize = sizeof(si);
  125. si.fMask = SIF_TRACKPOS;
  126. GetScrollInfo(SB_HORZ, &si);
  127. si.nPos = pos;
  128. SetScrollInfo(SB_HORZ, &si);
  129. }
  130. int COrderList::GetScrollPos(bool getTrackPos)
  131. {
  132. // Work around 16-bit limitations of WM_HSCROLL
  133. SCROLLINFO si;
  134. MemsetZero(si);
  135. si.cbSize = sizeof(si);
  136. si.fMask = SIF_TRACKPOS;
  137. GetScrollInfo(SB_HORZ, &si);
  138. return getTrackPos ? si.nTrackPos : si.nPos;
  139. }
  140. bool COrderList::IsOrderInMargins(int order, int startOrder)
  141. {
  142. const ORDERINDEX nMargins = GetMargins();
  143. return ((startOrder != 0 && order - startOrder < nMargins) ||
  144. order - startOrder >= GetLength() - nMargins);
  145. }
  146. void COrderList::EnsureVisible(ORDERINDEX order)
  147. {
  148. // nothing needs to be done
  149. if(!IsOrderInMargins(order, m_nXScroll) || order == ORDERINDEX_INVALID)
  150. return;
  151. if(order < m_nXScroll)
  152. {
  153. if(order < GetMargins())
  154. m_nXScroll = 0;
  155. else
  156. m_nXScroll = order - GetMargins();
  157. } else
  158. {
  159. m_nXScroll = order + 2 * GetMargins() - 1;
  160. if(m_nXScroll < GetLength())
  161. m_nXScroll = 0;
  162. else
  163. m_nXScroll -= GetLength();
  164. }
  165. }
  166. bool COrderList::IsPlaying() const
  167. {
  168. return (CMainFrame::GetMainFrame()->GetModPlaying() == &m_modDoc);
  169. }
  170. ORDERINDEX COrderList::GetOrderFromPoint(const CPoint &pt) const
  171. {
  172. if(m_cxFont)
  173. return mpt::saturate_cast<ORDERINDEX>(m_nXScroll + pt.x / m_cxFont);
  174. return 0;
  175. }
  176. CRect COrderList::GetRectFromOrder(ORDERINDEX ord) const
  177. {
  178. return CRect{CPoint{(ord - m_nXScroll) * m_cxFont, 0}, CSize{m_cxFont, m_cyFont}};
  179. }
  180. BOOL COrderList::Init(const CRect &rect, HFONT hFont)
  181. {
  182. CreateEx(WS_EX_STATICEDGE, NULL, _T(""), WS_CHILD | WS_VISIBLE, rect, &m_pParent, IDC_ORDERLIST);
  183. m_hFont = hFont;
  184. SendMessage(WM_SETFONT, (WPARAM)m_hFont);
  185. SetScrollPos(0);
  186. EnableScrollBarCtrl(SB_HORZ, TRUE);
  187. SetCurSel(0);
  188. EnableToolTips();
  189. return TRUE;
  190. }
  191. void COrderList::UpdateScrollInfo()
  192. {
  193. CRect rcClient;
  194. GetClientRect(&rcClient);
  195. if((m_cxFont > 0) && (rcClient.right > 0))
  196. {
  197. CRect rect;
  198. SCROLLINFO info;
  199. UINT nPage;
  200. int nMax = Order().GetLengthTailTrimmed();
  201. GetScrollInfo(SB_HORZ, &info, SIF_PAGE | SIF_RANGE);
  202. info.fMask = SIF_PAGE | SIF_RANGE;
  203. info.nMin = 0;
  204. nPage = rcClient.right / m_cxFont;
  205. if(nMax <= (int)nPage)
  206. nMax = nPage + 1;
  207. if((nMax != info.nMax) || (nPage != info.nPage))
  208. {
  209. info.nPage = nPage;
  210. info.nMax = nMax;
  211. SetScrollInfo(SB_HORZ, &info, TRUE);
  212. }
  213. }
  214. }
  215. int COrderList::GetFontWidth()
  216. {
  217. if((m_cxFont <= 0) && (m_hWnd) && (m_hFont))
  218. {
  219. CClientDC dc(this);
  220. HGDIOBJ oldfont = dc.SelectObject(m_hFont);
  221. CSize sz = dc.GetTextExtent(_T("000+"), 4);
  222. if(oldfont)
  223. dc.SelectObject(oldfont);
  224. return sz.cx;
  225. }
  226. return m_cxFont;
  227. }
  228. void COrderList::InvalidateSelection()
  229. {
  230. ORDERINDEX ordLo = m_nScrollPos, count = 1;
  231. static ORDERINDEX m_nScrollPos2Old = m_nScrollPos2nd;
  232. if(m_nScrollPos2Old != ORDERINDEX_INVALID)
  233. {
  234. // there were multiple orders selected - remove them all
  235. ORDERINDEX ordHi = m_nScrollPos;
  236. if(m_nScrollPos2Old < m_nScrollPos)
  237. ordLo = m_nScrollPos2Old;
  238. else
  239. ordHi = m_nScrollPos2Old;
  240. count = ordHi - ordLo + 1;
  241. }
  242. m_nScrollPos2Old = m_nScrollPos2nd;
  243. CRect rcClient, rect;
  244. GetClientRect(&rcClient);
  245. rect.left = rcClient.left + (ordLo - m_nXScroll) * m_cxFont;
  246. rect.top = rcClient.top;
  247. rect.right = rect.left + m_cxFont * count;
  248. rect.bottom = rcClient.bottom;
  249. rect &= rcClient;
  250. if(rect.right > rect.left)
  251. InvalidateRect(rect, FALSE);
  252. if(m_playPos != ORDERINDEX_INVALID)
  253. {
  254. rect.left = rcClient.left + (m_playPos - m_nXScroll) * m_cxFont;
  255. rect.top = rcClient.top;
  256. rect.right = rect.left + m_cxFont;
  257. rect &= rcClient;
  258. if(rect.right > rect.left)
  259. InvalidateRect(rect, FALSE);
  260. m_playPos = ORDERINDEX_INVALID;
  261. }
  262. }
  263. ORDERINDEX COrderList::GetLength()
  264. {
  265. CRect rcClient;
  266. GetClientRect(&rcClient);
  267. if(m_cxFont > 0)
  268. return mpt::saturate_cast<ORDERINDEX>(rcClient.right / m_cxFont);
  269. else
  270. {
  271. const int fontWidth = GetFontWidth();
  272. return (fontWidth > 0) ? mpt::saturate_cast<ORDERINDEX>(rcClient.right / fontWidth) : 0;
  273. }
  274. }
  275. OrdSelection COrderList::GetCurSel(bool ignoreSelection) const
  276. {
  277. // returns the currently selected order(s)
  278. OrdSelection result;
  279. result.firstOrd = result.lastOrd = m_nScrollPos;
  280. // ignoreSelection: true if only first selection marker is important.
  281. if(!ignoreSelection && m_nScrollPos2nd != ORDERINDEX_INVALID)
  282. {
  283. if(m_nScrollPos2nd < m_nScrollPos) // ord2 < ord1
  284. result.firstOrd = m_nScrollPos2nd;
  285. else
  286. result.lastOrd = m_nScrollPos2nd;
  287. }
  288. ORDERINDEX lastIndex = std::max(Order().GetLengthTailTrimmed(), m_modDoc.GetSoundFile().GetModSpecifications().ordersMax) - 1u;
  289. LimitMax(result.firstOrd, lastIndex);
  290. LimitMax(result.lastOrd, lastIndex);
  291. return result;
  292. }
  293. void COrderList::SetSelection(ORDERINDEX firstOrd, ORDERINDEX lastOrd)
  294. {
  295. SetCurSel(firstOrd, true, false, true);
  296. SetCurSel(lastOrd != ORDERINDEX_INVALID ? lastOrd : firstOrd, false, true, true);
  297. }
  298. bool COrderList::SetCurSel(ORDERINDEX sel, bool setPlayPos, bool shiftClick, bool ignoreCurSel)
  299. {
  300. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  301. CSoundFile &sndFile = m_modDoc.GetSoundFile();
  302. ORDERINDEX &ord = shiftClick ? m_nScrollPos2nd : m_nScrollPos;
  303. const ORDERINDEX lastIndex = std::max(Order().GetLength(), sndFile.GetModSpecifications().ordersMax) - 1u;
  304. if((sel < 0) || (sel > lastIndex) || (!m_pParent) || (!pMainFrm))
  305. return false;
  306. if(!ignoreCurSel && sel == ord && (sel == sndFile.m_PlayState.m_nCurrentOrder))
  307. return true;
  308. const ORDERINDEX shownLength = GetLength();
  309. InvalidateSelection();
  310. ord = sel;
  311. if(!EnsureEditable(ord))
  312. return false;
  313. if(!m_bScrolling)
  314. {
  315. const ORDERINDEX margins = GetMargins(GetMarginsMax(shownLength));
  316. if(ord < m_nXScroll + margins)
  317. {
  318. // Must move first shown sequence item to left in order to show the new active order.
  319. m_nXScroll = (ord > margins) ? (ord - margins) : 0;
  320. SetScrollPos(m_nXScroll);
  321. Invalidate(FALSE);
  322. } else
  323. {
  324. ORDERINDEX maxsel = shownLength;
  325. if(maxsel)
  326. maxsel--;
  327. if(ord - m_nXScroll >= maxsel - margins)
  328. {
  329. // Must move first shown sequence item to right in order to show the new active order.
  330. m_nXScroll = ord - (maxsel - margins);
  331. SetScrollPos(m_nXScroll);
  332. Invalidate(FALSE);
  333. }
  334. }
  335. }
  336. InvalidateSelection();
  337. PATTERNINDEX n = Order()[m_nScrollPos];
  338. if(setPlayPos && !shiftClick && sndFile.Patterns.IsValidPat(n))
  339. {
  340. const bool isPlaying = IsPlaying();
  341. bool changedPos = false;
  342. if(isPlaying && sndFile.m_SongFlags[SONG_PATTERNLOOP])
  343. {
  344. pMainFrm->ResetNotificationBuffer();
  345. // Update channel parameters and play time
  346. CriticalSection cs;
  347. m_modDoc.SetElapsedTime(m_nScrollPos, 0, !sndFile.m_SongFlags[SONG_PAUSED | SONG_STEP]);
  348. changedPos = true;
  349. } else if(m_pParent.GetFollowSong())
  350. {
  351. FlagSet<SongFlags> pausedFlags = sndFile.m_SongFlags & (SONG_PAUSED | SONG_STEP | SONG_PATTERNLOOP);
  352. // Update channel parameters and play time
  353. CriticalSection cs;
  354. sndFile.SetCurrentOrder(m_nScrollPos);
  355. m_modDoc.SetElapsedTime(m_nScrollPos, 0, !sndFile.m_SongFlags[SONG_PAUSED | SONG_STEP]);
  356. sndFile.m_SongFlags.set(pausedFlags);
  357. if(isPlaying)
  358. pMainFrm->ResetNotificationBuffer();
  359. changedPos = true;
  360. }
  361. if(changedPos && Order().IsPositionLocked(m_nScrollPos))
  362. {
  363. // Users wants to go somewhere else, so let them do that.
  364. OnUnlockPlayback();
  365. }
  366. m_pParent.SetCurrentPattern(n);
  367. } else if(setPlayPos && !shiftClick && n != Order().GetIgnoreIndex() && n != Order().GetInvalidPatIndex())
  368. {
  369. m_pParent.SetCurrentPattern(n);
  370. }
  371. UpdateInfoText();
  372. if(m_nScrollPos == m_nScrollPos2nd)
  373. m_nScrollPos2nd = ORDERINDEX_INVALID;
  374. return true;
  375. }
  376. PATTERNINDEX COrderList::GetCurrentPattern() const
  377. {
  378. const ModSequence &order = Order();
  379. if(m_nScrollPos < order.size())
  380. {
  381. return order[m_nScrollPos];
  382. }
  383. return 0;
  384. }
  385. BOOL COrderList::PreTranslateMessage(MSG *pMsg)
  386. {
  387. //handle Patterns View context keys that we want to take effect in the orderlist.
  388. if((pMsg->message == WM_SYSKEYUP) || (pMsg->message == WM_KEYUP) ||
  389. (pMsg->message == WM_SYSKEYDOWN) || (pMsg->message == WM_KEYDOWN))
  390. {
  391. CInputHandler *ih = CMainFrame::GetInputHandler();
  392. //Translate message manually
  393. UINT nChar = (UINT)pMsg->wParam;
  394. UINT nRepCnt = LOWORD(pMsg->lParam);
  395. UINT nFlags = HIWORD(pMsg->lParam);
  396. KeyEventType kT = ih->GetKeyEventType(nFlags);
  397. if(ih->KeyEvent(kCtxCtrlOrderlist, nChar, nRepCnt, nFlags, kT) != kcNull)
  398. return true; // Mapped to a command, no need to pass message on.
  399. //HACK: masquerade as kCtxViewPatternsNote context until we implement appropriate
  400. // command propagation to kCtxCtrlOrderlist context.
  401. if(ih->KeyEvent(kCtxViewPatternsNote, nChar, nRepCnt, nFlags, kT) != kcNull)
  402. return true; // Mapped to a command, no need to pass message on.
  403. // Handle Application (menu) key
  404. if(pMsg->message == WM_KEYDOWN && nChar == VK_APPS)
  405. {
  406. const auto selection = GetCurSel();
  407. auto pt = (GetRectFromOrder(selection.firstOrd) | GetRectFromOrder(selection.lastOrd)).CenterPoint();
  408. CRect clientRect;
  409. GetClientRect(clientRect);
  410. if(!clientRect.PtInRect(pt))
  411. pt = clientRect.CenterPoint();
  412. OnRButtonDown(0, pt);
  413. }
  414. }
  415. return CWnd::PreTranslateMessage(pMsg);
  416. }
  417. LRESULT COrderList::OnCustomKeyMsg(WPARAM wParam, LPARAM lParam)
  418. {
  419. bool isPlaying = IsPlaying();
  420. switch(wParam)
  421. {
  422. case kcEditCopy:
  423. OnEditCopy(); return wParam;
  424. case kcEditCut:
  425. OnEditCut(); return wParam;
  426. case kcEditPaste:
  427. OnPatternPaste(); return wParam;
  428. case kcOrderlistEditCopyOrders:
  429. OnEditCopyOrders(); return wParam;
  430. // Orderlist navigation
  431. case kcOrderlistNavigateLeftSelect:
  432. case kcOrderlistNavigateLeft:
  433. SetCurSelTo2ndSel(wParam == kcOrderlistNavigateLeftSelect); SetCurSel(m_nScrollPos - 1, wParam == kcOrderlistNavigateLeft || !isPlaying); return wParam;
  434. case kcOrderlistNavigateRightSelect:
  435. case kcOrderlistNavigateRight:
  436. SetCurSelTo2ndSel(wParam == kcOrderlistNavigateRightSelect); SetCurSel(m_nScrollPos + 1, wParam == kcOrderlistNavigateRight || !isPlaying); return wParam;
  437. case kcOrderlistNavigateFirstSelect:
  438. case kcOrderlistNavigateFirst:
  439. SetCurSelTo2ndSel(wParam == kcOrderlistNavigateFirstSelect); SetCurSel(0, wParam == kcOrderlistNavigateFirst || !isPlaying); return wParam;
  440. case kcEditSelectAll:
  441. SetCurSel(0, !isPlaying);
  442. [[fallthrough]];
  443. case kcOrderlistNavigateLastSelect:
  444. case kcOrderlistNavigateLast:
  445. {
  446. SetCurSelTo2ndSel(wParam == kcOrderlistNavigateLastSelect || wParam == kcEditSelectAll);
  447. ORDERINDEX nLast = Order().GetLengthTailTrimmed();
  448. if(nLast > 0) nLast--;
  449. SetCurSel(nLast, wParam == kcOrderlistNavigateLast || !isPlaying);
  450. }
  451. return wParam;
  452. // Orderlist edit
  453. case kcOrderlistEditDelete:
  454. OnDeleteOrder(); return wParam;
  455. case kcOrderlistEditInsert:
  456. OnInsertOrder(); return wParam;
  457. case kcOrderlistEditInsertSeparator:
  458. OnInsertSeparatorPattern(); return wParam;
  459. case kcOrderlistSwitchToPatternView:
  460. OnSwitchToView(); return wParam;
  461. case kcOrderlistEditPattern:
  462. OnLButtonDblClk(0, CPoint(0, 0)); OnSwitchToView(); return wParam;
  463. // Enter pattern number
  464. case kcOrderlistPat0:
  465. case kcOrderlistPat1:
  466. case kcOrderlistPat2:
  467. case kcOrderlistPat3:
  468. case kcOrderlistPat4:
  469. case kcOrderlistPat5:
  470. case kcOrderlistPat6:
  471. case kcOrderlistPat7:
  472. case kcOrderlistPat8:
  473. case kcOrderlistPat9:
  474. EnterPatternNum(static_cast<UINT>(wParam) - kcOrderlistPat0); return wParam;
  475. case kcOrderlistPatMinus:
  476. EnterPatternNum(10); return wParam;
  477. case kcOrderlistPatPlus:
  478. EnterPatternNum(11); return wParam;
  479. case kcOrderlistPatIgnore:
  480. EnterPatternNum(12); return wParam;
  481. case kcOrderlistPatInvalid:
  482. EnterPatternNum(13); return wParam;
  483. // kCtxViewPatternsNote messages
  484. case kcSwitchToOrderList:
  485. OnSwitchToView();
  486. return wParam;
  487. case kcChangeLoopStatus:
  488. m_pParent.OnModCtrlMsg(CTRLMSG_PAT_LOOP, -1); return wParam;
  489. case kcToggleFollowSong:
  490. m_pParent.OnModCtrlMsg(CTRLMSG_PAT_FOLLOWSONG, 1); return wParam;
  491. case kcChannelUnmuteAll:
  492. case kcUnmuteAllChnOnPatTransition:
  493. return m_pParent.SendMessage(WM_MOD_KEYCOMMAND, wParam, lParam);
  494. case kcOrderlistLockPlayback:
  495. OnLockPlayback(); return wParam;
  496. case kcOrderlistUnlockPlayback:
  497. OnUnlockPlayback(); return wParam;
  498. case kcDuplicatePattern:
  499. OnDuplicatePattern(); return wParam;
  500. case kcMergePatterns:
  501. OnMergePatterns(); return wParam;
  502. case kcNewPattern:
  503. OnCreateNewPattern(); return wParam;
  504. }
  505. return kcNull;
  506. }
  507. // Helper function to enter pattern index into the orderlist.
  508. // Call with param 0...9 (enter digit), 10 (decrease) or 11 (increase).
  509. void COrderList::EnterPatternNum(int enterNum)
  510. {
  511. CSoundFile &sndFile = m_modDoc.GetSoundFile();
  512. if(!EnsureEditable(m_nScrollPos))
  513. return;
  514. PATTERNINDEX curIndex = Order()[m_nScrollPos];
  515. const PATTERNINDEX maxIndex = std::max(PATTERNINDEX(1), sndFile.Patterns.GetNumPatterns()) - 1;
  516. const PATTERNINDEX firstInvalid = sndFile.GetModSpecifications().hasIgnoreIndex ? sndFile.Order.GetIgnoreIndex() : sndFile.Order.GetInvalidPatIndex();
  517. if(enterNum >= 0 && enterNum <= 9) // enter 0...9
  518. {
  519. if(curIndex >= sndFile.Patterns.Size())
  520. curIndex = 0;
  521. curIndex = curIndex * 10 + static_cast<PATTERNINDEX>(enterNum);
  522. static_assert(MAX_PATTERNS < 10000);
  523. if((curIndex >= 1000) && (curIndex > maxIndex)) curIndex %= 1000;
  524. if((curIndex >= 100) && (curIndex > maxIndex)) curIndex %= 100;
  525. if((curIndex >= 10) && (curIndex > maxIndex)) curIndex %= 10;
  526. } else if(enterNum == 10) // decrease pattern index
  527. {
  528. if(curIndex == 0)
  529. {
  530. curIndex = sndFile.Order.GetInvalidPatIndex();
  531. } else if(curIndex > maxIndex && curIndex <= firstInvalid)
  532. {
  533. curIndex = maxIndex;
  534. } else
  535. {
  536. do
  537. {
  538. curIndex--;
  539. } while(curIndex > 0 && curIndex < firstInvalid && !sndFile.Patterns.IsValidPat(curIndex));
  540. }
  541. } else if(enterNum == 11) // increase pattern index
  542. {
  543. if(curIndex >= sndFile.Order.GetInvalidPatIndex())
  544. {
  545. curIndex = 0;
  546. } else if(curIndex >= maxIndex && curIndex < firstInvalid)
  547. {
  548. curIndex = firstInvalid;
  549. } else
  550. {
  551. do
  552. {
  553. curIndex++;
  554. } while(curIndex <= maxIndex && !sndFile.Patterns.IsValidPat(curIndex));
  555. }
  556. } else if(enterNum == 12) // ignore index (+++)
  557. {
  558. if(sndFile.GetModSpecifications().hasIgnoreIndex)
  559. {
  560. curIndex = sndFile.Order.GetIgnoreIndex();
  561. }
  562. } else if(enterNum == 13) // invalid index (---)
  563. {
  564. curIndex = sndFile.Order.GetInvalidPatIndex();
  565. }
  566. // apply
  567. if(curIndex != Order()[m_nScrollPos])
  568. {
  569. Order()[m_nScrollPos] = curIndex;
  570. m_modDoc.SetModified();
  571. m_modDoc.UpdateAllViews(nullptr, SequenceHint().Data(), this);
  572. InvalidateSelection();
  573. m_pParent.SetCurrentPattern(curIndex);
  574. }
  575. }
  576. void COrderList::OnEditCut()
  577. {
  578. OnEditCopy();
  579. OnDeleteOrder();
  580. }
  581. void COrderList::OnCopy(bool onlyOrders)
  582. {
  583. const OrdSelection ordsel = GetCurSel();
  584. BeginWaitCursor();
  585. PatternClipboard::Copy(m_modDoc.GetSoundFile(), ordsel.firstOrd, ordsel.lastOrd, onlyOrders);
  586. PatternClipboardDialog::UpdateList();
  587. EndWaitCursor();
  588. }
  589. void COrderList::UpdateView(UpdateHint hint, CObject *pObj)
  590. {
  591. if(pObj != this && hint.ToType<SequenceHint>().GetType()[HINT_MODTYPE | HINT_MODSEQUENCE])
  592. {
  593. Invalidate(FALSE);
  594. UpdateInfoText();
  595. }
  596. if(hint.GetType()[HINT_MPTOPTIONS])
  597. {
  598. m_nOrderlistMargins = TrackerSettings::Instance().orderlistMargins;
  599. }
  600. }
  601. void COrderList::OnSwitchToView()
  602. {
  603. m_pParent.PostViewMessage(VIEWMSG_SETFOCUS);
  604. }
  605. void COrderList::UpdateInfoText()
  606. {
  607. if(::GetFocus() != m_hWnd)
  608. return;
  609. CSoundFile &sndFile = m_modDoc.GetSoundFile();
  610. const auto &order = Order();
  611. const ORDERINDEX length = order.GetLengthTailTrimmed();
  612. CString s;
  613. if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_HEXDISPLAY)
  614. s.Format(_T("Position %02Xh of %02Xh"), m_nScrollPos, length);
  615. else
  616. s.Format(_T("Position %u of %u (%02Xh of %02Xh)"), m_nScrollPos, length, m_nScrollPos, length);
  617. if(order.IsValidPat(m_nScrollPos))
  618. {
  619. if(const auto patName = sndFile.Patterns[order[m_nScrollPos]].GetName(); !patName.empty())
  620. s += _T(": ") + mpt::ToCString(sndFile.GetCharsetInternal(), patName);
  621. }
  622. CMainFrame::GetMainFrame()->SetInfoText(s);
  623. CMainFrame::GetMainFrame()->NotifyAccessibilityUpdate(*this);
  624. }
  625. // Accessible description for screen readers
  626. HRESULT COrderList::get_accName(VARIANT, BSTR *pszName)
  627. {
  628. CSoundFile &sndFile = m_modDoc.GetSoundFile();
  629. const auto &order = Order();
  630. CString s;
  631. const bool singleSel = m_nScrollPos2nd == ORDERINDEX_INVALID || m_nScrollPos2nd == m_nScrollPos;
  632. const auto firstOrd = singleSel ? m_nScrollPos : std::min(m_nScrollPos, m_nScrollPos2nd), lastOrd = singleSel ? m_nScrollPos : std::max(m_nScrollPos, m_nScrollPos2nd);
  633. if(singleSel)
  634. s = MPT_CFORMAT("Order {}, ")(m_nScrollPos);
  635. else
  636. s = MPT_CFORMAT("Order selection {} to {}: ")(firstOrd, lastOrd);
  637. bool first = true;
  638. for(ORDERINDEX o = firstOrd; o <= lastOrd; o++)
  639. {
  640. if(!first)
  641. s += _T(", ");
  642. first = false;
  643. PATTERNINDEX pat = order[o];
  644. if(pat == ModSequence::GetIgnoreIndex())
  645. s += _T(" Skip");
  646. else if(pat == ModSequence::GetInvalidPatIndex())
  647. s += _T(" Stop");
  648. else
  649. s += MPT_CFORMAT("Pattern {}")(pat);
  650. if(sndFile.Patterns.IsValidPat(pat))
  651. {
  652. if(const auto patName = sndFile.Patterns[pat].GetName(); !patName.empty())
  653. s += _T(" (") + mpt::ToCString(sndFile.GetCharsetInternal(), patName) + _T(")");
  654. }
  655. }
  656. *pszName = s.AllocSysString();
  657. return S_OK;
  658. }
  659. /////////////////////////////////////////////////////////////////
  660. // COrderList messages
  661. void COrderList::OnPaint()
  662. {
  663. TCHAR s[64];
  664. CPaintDC dc(this);
  665. HGDIOBJ oldfont = dc.SelectObject(m_hFont);
  666. HGDIOBJ oldpen = dc.SelectStockObject(DC_PEN);
  667. const auto separatorColor = GetSysColor(COLOR_WINDOW) ^ 0x808080;
  668. const auto colorText = GetSysColor(COLOR_WINDOWTEXT), colorInvalid = GetSysColor(COLOR_GRAYTEXT), colorTextSel = GetSysColor(COLOR_HIGHLIGHTTEXT);
  669. const auto windowBrush = GetSysColorBrush(COLOR_WINDOW), highlightBrush = GetSysColorBrush(COLOR_HIGHLIGHT), faceBrush = GetSysColorBrush(COLOR_BTNFACE);
  670. SetDCPenColor(dc, separatorColor);
  671. // First time?
  672. if(m_cxFont <= 0 || m_cyFont <= 0)
  673. {
  674. CSize sz = dc.GetTextExtent(_T("000+"), 4);
  675. m_cxFont = sz.cx;
  676. m_cyFont = sz.cy;
  677. }
  678. if(m_cxFont > 0 && m_cyFont > 0)
  679. {
  680. CRect rcClient;
  681. GetClientRect(&rcClient);
  682. CRect rect = rcClient;
  683. UpdateScrollInfo();
  684. dc.SetBkMode(TRANSPARENT);
  685. const OrdSelection selection = GetCurSel();
  686. const int lineWidth1 = Util::ScalePixels(1, m_hWnd);
  687. const int lineWidth2 = Util::ScalePixels(2, m_hWnd);
  688. const bool isFocussed = (::GetFocus() == m_hWnd);
  689. const auto &order = Order();
  690. CSoundFile &sndFile = m_modDoc.GetSoundFile();
  691. ORDERINDEX maxEntries = sndFile.GetModSpecifications().ordersMax;
  692. if(order.size() > maxEntries)
  693. {
  694. // Only computed if potentially needed.
  695. maxEntries = std::max(maxEntries, order.GetLengthTailTrimmed());
  696. }
  697. // Scrolling the shown orders(the showns rectangles)?
  698. for(size_t pos = m_nXScroll; rect.left < rcClient.right; pos++, rect.left += m_cxFont)
  699. {
  700. const ORDERINDEX ord = mpt::saturate_cast<ORDERINDEX>(pos);
  701. dc.SetTextColor(colorText);
  702. const bool inSelection = (ord >= selection.firstOrd && ord <= selection.lastOrd);
  703. const bool highLight = (isFocussed && inSelection);
  704. if((rect.right = rect.left + m_cxFont) > rcClient.right)
  705. rect.right = rcClient.right;
  706. rect.right--;
  707. HBRUSH background;
  708. if(highLight)
  709. background = highlightBrush; // Currently selected order item
  710. else if(order.IsPositionLocked(ord))
  711. background = faceBrush; // "Playback lock" indicator - grey out all order items which aren't played.
  712. else
  713. background = windowBrush; // Normal, unselected item.
  714. ::FillRect(dc, &rect, background);
  715. // Drawing the shown pattern-indicator or drag position.
  716. if(ord == (m_bDragging ? m_nDropPos : m_nScrollPos))
  717. {
  718. rect.InflateRect(-1, -1);
  719. dc.DrawFocusRect(&rect);
  720. rect.InflateRect(1, 1);
  721. }
  722. MoveToEx(dc, rect.right, rect.top, NULL);
  723. LineTo(dc, rect.right, rect.bottom);
  724. // Drawing the 'ctrl-transition' indicator
  725. if(ord == sndFile.m_PlayState.m_nSeqOverride && sndFile.m_PlayState.m_nSeqOverride != ORDERINDEX_INVALID)
  726. {
  727. dc.FillSolidRect(CRect{rect.left + 4, rect.bottom - 4 - lineWidth1, rect.right - 4, rect.bottom - 4}, separatorColor);
  728. }
  729. // Drawing 'playing'-indicator.
  730. if(ord == sndFile.GetCurrentOrder() && CMainFrame::GetMainFrame()->IsPlaying())
  731. {
  732. dc.FillSolidRect(CRect{rect.left + 4, rect.top + 2, rect.right - 4, rect.top + 2 + lineWidth1}, separatorColor);
  733. m_playPos = ord;
  734. }
  735. // Drawing drop indicator
  736. if(m_bDragging && ord == m_nDropPos && !inSelection)
  737. {
  738. const bool dropLeft = (m_nDropPos < selection.firstOrd) || TrackerSettings::Instance().orderListOldDropBehaviour;
  739. 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);
  740. }
  741. s[0] = _T('\0');
  742. const PATTERNINDEX pat = (ord < order.size()) ? order[ord] : PATTERNINDEX_INVALID;
  743. if(ord < maxEntries && (rect.left + m_cxFont - 4) <= rcClient.right)
  744. {
  745. if(pat == order.GetInvalidPatIndex())
  746. _tcscpy(s, _T("---"));
  747. else if(pat == order.GetIgnoreIndex())
  748. _tcscpy(s, _T("+++"));
  749. else
  750. wsprintf(s, _T("%u"), pat);
  751. }
  752. COLORREF textCol;
  753. if(highLight)
  754. textCol = colorTextSel; // Highlighted pattern
  755. else if(sndFile.Patterns.IsValidPat(pat))
  756. textCol = colorText; // Normal pattern
  757. else
  758. textCol = colorInvalid; // Non-existent pattern
  759. dc.SetTextColor(textCol);
  760. dc.DrawText(s, -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
  761. }
  762. }
  763. if(oldpen)
  764. dc.SelectObject(oldpen);
  765. if(oldfont)
  766. dc.SelectObject(oldfont);
  767. }
  768. void COrderList::OnSetFocus(CWnd *pWnd)
  769. {
  770. CWnd::OnSetFocus(pWnd);
  771. InvalidateSelection();
  772. UpdateInfoText();
  773. CMainFrame::GetMainFrame()->m_pOrderlistHasFocus = this;
  774. }
  775. void COrderList::OnKillFocus(CWnd *pWnd)
  776. {
  777. CWnd::OnKillFocus(pWnd);
  778. InvalidateSelection();
  779. CMainFrame::GetMainFrame()->m_pOrderlistHasFocus = nullptr;
  780. }
  781. void COrderList::OnLButtonDown(UINT nFlags, CPoint pt)
  782. {
  783. CRect rect;
  784. GetClientRect(&rect);
  785. if(pt.y < rect.bottom)
  786. {
  787. SetFocus();
  788. if(IsCtrlKeyPressed())
  789. {
  790. // Queue pattern
  791. QueuePattern(pt);
  792. } else
  793. {
  794. // mark pattern (+skip to)
  795. const int oldXScroll = m_nXScroll;
  796. ORDERINDEX ord = GetOrderFromPoint(pt);
  797. OrdSelection selection = GetCurSel();
  798. // check if cursor is in selection - if it is, only react on MouseUp as the user might want to drag those orders
  799. if(m_nScrollPos2nd == ORDERINDEX_INVALID || ord < selection.firstOrd || ord > selection.lastOrd)
  800. {
  801. m_nScrollPos2nd = ORDERINDEX_INVALID;
  802. SetCurSel(ord, true, IsSelectionKeyPressed());
  803. }
  804. m_bDragging = !IsOrderInMargins(m_nScrollPos, oldXScroll) || !IsOrderInMargins(m_nScrollPos2nd, oldXScroll);
  805. m_nMouseDownPos = ord;
  806. if(m_bDragging)
  807. {
  808. m_nDragOrder = m_nDropPos = GetCurSel(true).firstOrd;
  809. SetCapture();
  810. }
  811. }
  812. } else
  813. {
  814. CWnd::OnLButtonDown(nFlags, pt);
  815. }
  816. }
  817. void COrderList::OnLButtonUp(UINT nFlags, CPoint pt)
  818. {
  819. CRect rect;
  820. GetClientRect(&rect);
  821. // Copy or move orders?
  822. const bool copyOrders = IsSelectionKeyPressed();
  823. if(m_bDragging)
  824. {
  825. m_bDragging = false;
  826. ReleaseCapture();
  827. if(rect.PtInRect(pt))
  828. {
  829. ORDERINDEX n = GetOrderFromPoint(pt);
  830. const OrdSelection selection = GetCurSel();
  831. if(n != ORDERINDEX_INVALID && n == m_nDropPos && (n < selection.firstOrd || n > selection.lastOrd))
  832. {
  833. const bool multiSelection = (selection.firstOrd != selection.lastOrd);
  834. const bool moveBack = m_nDropPos < m_nDragOrder;
  835. ORDERINDEX moveCount = (selection.lastOrd - selection.firstOrd), movePos = selection.firstOrd;
  836. if(!moveBack && !TrackerSettings::Instance().orderListOldDropBehaviour)
  837. m_nDropPos++;
  838. bool modified = false;
  839. for(int i = 0; i <= moveCount; i++)
  840. {
  841. if(!m_modDoc.MoveOrder(movePos, m_nDropPos, true, copyOrders))
  842. break;
  843. modified = true;
  844. if(moveBack != copyOrders && multiSelection)
  845. {
  846. movePos++;
  847. m_nDropPos++;
  848. }
  849. if(moveBack && copyOrders && multiSelection)
  850. {
  851. movePos += 2;
  852. m_nDropPos++;
  853. }
  854. }
  855. if(multiSelection)
  856. {
  857. // adjust selection
  858. m_nScrollPos2nd = m_nDropPos - 1;
  859. m_nDropPos -= moveCount + (moveBack ? 0 : 1);
  860. SetCurSel((moveBack && !copyOrders) ? m_nDropPos - 1 : m_nDropPos);
  861. } else
  862. {
  863. SetCurSel((m_nDragOrder < m_nDropPos && !copyOrders) ? m_nDropPos - 1 : m_nDropPos);
  864. }
  865. // Did we actually change anything?
  866. if(modified)
  867. m_modDoc.SetModified();
  868. } else
  869. {
  870. if(pt.y < rect.bottom && n == m_nMouseDownPos && !copyOrders)
  871. {
  872. // Remove selection if we didn't drag anything but multiselect was active
  873. m_nScrollPos2nd = ORDERINDEX_INVALID;
  874. SetFocus();
  875. SetCurSel(n);
  876. }
  877. }
  878. }
  879. Invalidate(FALSE);
  880. } else
  881. {
  882. CWnd::OnLButtonUp(nFlags, pt);
  883. }
  884. }
  885. void COrderList::OnMouseMove(UINT nFlags, CPoint pt)
  886. {
  887. if((m_bDragging) && (m_cxFont))
  888. {
  889. CRect rect;
  890. GetClientRect(&rect);
  891. ORDERINDEX n = ORDERINDEX_INVALID;
  892. if(rect.PtInRect(pt))
  893. {
  894. CSoundFile &sndFile = m_modDoc.GetSoundFile();
  895. n = GetOrderFromPoint(pt);
  896. if(n >= Order().size() && n >= sndFile.GetModSpecifications().ordersMax)
  897. n = ORDERINDEX_INVALID;
  898. }
  899. if(n != m_nDropPos)
  900. {
  901. if(n != ORDERINDEX_INVALID)
  902. {
  903. m_nMouseDownPos = ORDERINDEX_INVALID;
  904. m_nDropPos = n;
  905. Invalidate(FALSE);
  906. SetCursor(CMainFrame::curDragging);
  907. } else
  908. {
  909. m_nDropPos = ORDERINDEX_INVALID;
  910. SetCursor(CMainFrame::curNoDrop);
  911. }
  912. }
  913. } else
  914. {
  915. CWnd::OnMouseMove(nFlags, pt);
  916. }
  917. }
  918. void COrderList::OnSelectSequence(UINT nid)
  919. {
  920. SelectSequence(static_cast<SEQUENCEINDEX>(nid - ID_SEQUENCE_ITEM));
  921. }
  922. void COrderList::OnRButtonDown(UINT nFlags, CPoint pt)
  923. {
  924. CRect rect;
  925. GetClientRect(&rect);
  926. if(m_bDragging)
  927. {
  928. m_nDropPos = ORDERINDEX_INVALID;
  929. OnLButtonUp(nFlags, pt);
  930. }
  931. if(pt.y >= rect.bottom)
  932. return;
  933. bool multiSelection = (m_nScrollPos2nd != ORDERINDEX_INVALID);
  934. if(!multiSelection)
  935. SetCurSel(GetOrderFromPoint(pt), false, false, false);
  936. SetFocus();
  937. HMENU hMenu = ::CreatePopupMenu();
  938. if(!hMenu)
  939. return;
  940. CSoundFile &sndFile = m_modDoc.GetSoundFile();
  941. // Check if at least one pattern in the current selection exists
  942. bool patExists = false;
  943. OrdSelection selection = GetCurSel();
  944. LimitMax(selection.lastOrd, Order().GetLastIndex());
  945. for(ORDERINDEX ord = selection.firstOrd; ord <= selection.lastOrd && !patExists; ord++)
  946. {
  947. patExists = Order().IsValidPat(ord);
  948. }
  949. const DWORD greyed = patExists ? 0 : MF_GRAYED;
  950. CInputHandler *ih = CMainFrame::GetInputHandler();
  951. if(multiSelection)
  952. {
  953. // Several patterns are selected.
  954. AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_INSERT, ih->GetKeyTextFromCommand(kcOrderlistEditInsert, _T("&Insert Patterns")));
  955. AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_DELETE, ih->GetKeyTextFromCommand(kcOrderlistEditDelete, _T("&Remove Patterns")));
  956. AppendMenu(hMenu, MF_SEPARATOR, NULL, _T(""));
  957. AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_EDIT_COPY, ih->GetKeyTextFromCommand(kcEditCopy, _T("&Copy Patterns")));
  958. AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_EDIT_COPY_ORDERS, ih->GetKeyTextFromCommand(kcOrderlistEditCopyOrders, _T("&Copy Orders")));
  959. AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_EDIT_CUT, ih->GetKeyTextFromCommand(kcEditCut, _T("&C&ut Patterns")));
  960. AppendMenu(hMenu, MF_STRING | greyed, ID_PATTERNPASTE, ih->GetKeyTextFromCommand(kcEditPaste, _T("P&aste Patterns")));
  961. AppendMenu(hMenu, MF_SEPARATOR, NULL, _T(""));
  962. AppendMenu(hMenu, MF_STRING | greyed, ID_ORDERLIST_COPY, ih->GetKeyTextFromCommand(kcDuplicatePattern, _T("&Duplicate Patterns")));
  963. AppendMenu(hMenu, MF_STRING | greyed, ID_ORDERLIST_MERGE, ih->GetKeyTextFromCommand(kcMergePatterns, _T("&Merge Patterns")));
  964. } else
  965. {
  966. // Only one pattern is selected
  967. AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_INSERT, ih->GetKeyTextFromCommand(kcOrderlistEditInsert, _T("&Insert Pattern")));
  968. if(sndFile.GetModSpecifications().hasIgnoreIndex)
  969. {
  970. AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_INSERT_SEPARATOR, ih->GetKeyTextFromCommand(kcOrderlistEditInsertSeparator, _T("&Insert Separator")));
  971. }
  972. AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_DELETE, ih->GetKeyTextFromCommand(kcOrderlistEditDelete, _T("&Remove Pattern")));
  973. AppendMenu(hMenu, MF_SEPARATOR, NULL, _T(""));
  974. AppendMenu(hMenu, MF_STRING, ID_ORDERLIST_NEW, ih->GetKeyTextFromCommand(kcNewPattern, _T("Create &New Pattern")));
  975. AppendMenu(hMenu, MF_STRING | greyed, ID_ORDERLIST_COPY, ih->GetKeyTextFromCommand(kcDuplicatePattern, _T("&Duplicate Pattern")));
  976. AppendMenu(hMenu, MF_STRING | greyed, ID_PATTERNCOPY, _T("&Copy Pattern"));
  977. AppendMenu(hMenu, MF_STRING, ID_PATTERNPASTE, ih->GetKeyTextFromCommand(kcEditPaste, _T("P&aste Pattern")));
  978. const bool hasPatternProperties = sndFile.GetModSpecifications().patternRowsMin != sndFile.GetModSpecifications().patternRowsMax;
  979. if(hasPatternProperties || sndFile.GetModSpecifications().hasRestartPos)
  980. {
  981. AppendMenu(hMenu, MF_SEPARATOR, NULL, _T(""));
  982. if(hasPatternProperties)
  983. AppendMenu(hMenu, MF_STRING | greyed, ID_PATTERN_PROPERTIES, _T("&Pattern Properties..."));
  984. if(sndFile.GetModSpecifications().hasRestartPos)
  985. AppendMenu(hMenu, MF_STRING | greyed | ((Order().GetRestartPos() == m_nScrollPos) ? MF_CHECKED : 0), ID_SETRESTARTPOS, _T("R&estart Position"));
  986. }
  987. if(sndFile.GetModSpecifications().sequencesMax > 1)
  988. {
  989. AppendMenu(hMenu, MF_SEPARATOR, NULL, _T(""));
  990. HMENU menuSequence = ::CreatePopupMenu();
  991. AppendMenu(hMenu, MF_POPUP, (UINT_PTR)menuSequence, _T("&Sequences"));
  992. const SEQUENCEINDEX numSequences = sndFile.Order.GetNumSequences();
  993. for(SEQUENCEINDEX i = 0; i < numSequences; i++)
  994. {
  995. CString str;
  996. if(sndFile.Order(i).GetName().empty())
  997. str = MPT_CFORMAT("Sequence {}")(i + 1);
  998. else
  999. str = MPT_CFORMAT("{}: {}")(i + 1, mpt::ToCString(sndFile.Order(i).GetName()));
  1000. const UINT flags = (sndFile.Order.GetCurrentSequenceIndex() == i) ? MF_STRING | MF_CHECKED : MF_STRING;
  1001. AppendMenu(menuSequence, flags, ID_SEQUENCE_ITEM + i, str);
  1002. }
  1003. if(sndFile.Order.GetNumSequences() < sndFile.GetModSpecifications().sequencesMax)
  1004. {
  1005. AppendMenu(menuSequence, MF_STRING, ID_SEQUENCE_ITEM + kDuplicateSequence, _T("&Duplicate current sequence"));
  1006. AppendMenu(menuSequence, MF_STRING, ID_SEQUENCE_ITEM + kAddSequence, _T("&Create empty sequence"));
  1007. }
  1008. if(sndFile.Order.GetNumSequences() > 1)
  1009. AppendMenu(menuSequence, MF_STRING, ID_SEQUENCE_ITEM + kDeleteSequence, _T("D&elete current sequence"));
  1010. else
  1011. AppendMenu(menuSequence, MF_STRING, ID_SEQUENCE_ITEM + kSplitSequence, _T("&Split sub songs into sequences"));
  1012. }
  1013. }
  1014. AppendMenu(hMenu, MF_SEPARATOR, NULL, _T(""));
  1015. 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")));
  1016. AppendMenu(hMenu, (sndFile.m_lockOrderStart == ORDERINDEX_INVALID ? (MF_STRING | MF_GRAYED) : MF_STRING), ID_ORDERLIST_UNLOCKPLAYBACK, ih->GetKeyTextFromCommand(kcOrderlistUnlockPlayback, _T("&Unlock Playback")));
  1017. AppendMenu(hMenu, MF_SEPARATOR, NULL, _T(""));
  1018. AppendMenu(hMenu, MF_STRING | greyed, ID_ORDERLIST_RENDER, _T("Render to &Wave"));
  1019. ClientToScreen(&pt);
  1020. ::TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, m_hWnd, NULL);
  1021. ::DestroyMenu(hMenu);
  1022. }
  1023. void COrderList::OnLButtonDblClk(UINT, CPoint)
  1024. {
  1025. auto &sndFile = m_modDoc.GetSoundFile();
  1026. m_nScrollPos2nd = ORDERINDEX_INVALID;
  1027. SetFocus();
  1028. if(!EnsureEditable(m_nScrollPos))
  1029. return;
  1030. PATTERNINDEX pat = Order()[m_nScrollPos];
  1031. if(sndFile.Patterns.IsValidPat(pat))
  1032. m_pParent.SetCurrentPattern(pat);
  1033. else if(pat != sndFile.Order.GetIgnoreIndex())
  1034. OnCreateNewPattern();
  1035. }
  1036. void COrderList::OnMButtonDown(UINT nFlags, CPoint pt)
  1037. {
  1038. MPT_UNREFERENCED_PARAMETER(nFlags);
  1039. QueuePattern(pt);
  1040. }
  1041. void COrderList::OnHScroll(UINT nSBCode, UINT /*nPos*/, CScrollBar *)
  1042. {
  1043. UINT nNewPos = m_nXScroll;
  1044. UINT smin, smax;
  1045. GetScrollRange(SB_HORZ, (LPINT)&smin, (LPINT)&smax);
  1046. m_bScrolling = true;
  1047. switch(nSBCode)
  1048. {
  1049. case SB_LINELEFT: if (nNewPos) nNewPos--; break;
  1050. case SB_LINERIGHT: if (nNewPos < smax) nNewPos++; break;
  1051. case SB_PAGELEFT: if (nNewPos > 4) nNewPos -= 4; else nNewPos = 0; break;
  1052. case SB_PAGERIGHT: if (nNewPos + 4 < smax) nNewPos += 4; else nNewPos = smax; break;
  1053. case SB_THUMBPOSITION:
  1054. case SB_THUMBTRACK: nNewPos = GetScrollPos(true); break;
  1055. case SB_LEFT: nNewPos = 0; break;
  1056. case SB_RIGHT: nNewPos = smax; break;
  1057. case SB_ENDSCROLL: m_bScrolling = false; break;
  1058. }
  1059. if (nNewPos > smax) nNewPos = smax;
  1060. if (nNewPos != m_nXScroll)
  1061. {
  1062. m_nXScroll = static_cast<ORDERINDEX>(nNewPos);
  1063. SetScrollPos(m_nXScroll);
  1064. Invalidate(FALSE);
  1065. }
  1066. }
  1067. void COrderList::OnSize(UINT nType, int cx, int cy)
  1068. {
  1069. int nPos;
  1070. int smin, smax;
  1071. CWnd::OnSize(nType, cx, cy);
  1072. UpdateScrollInfo();
  1073. GetScrollRange(SB_HORZ, &smin, &smax);
  1074. nPos = GetScrollPos();
  1075. if(nPos > smax)
  1076. nPos = smax;
  1077. if(m_nXScroll != nPos)
  1078. {
  1079. m_nXScroll = mpt::saturate_cast<ORDERINDEX>(nPos);
  1080. SetScrollPos(m_nXScroll);
  1081. Invalidate(FALSE);
  1082. }
  1083. }
  1084. void COrderList::OnInsertOrder()
  1085. {
  1086. // insert the same order(s) after the currently selected order(s)
  1087. ModSequence &order = Order();
  1088. const OrdSelection selection = GetCurSel();
  1089. const ORDERINDEX insertCount = order.insert(selection.lastOrd + 1, selection.lastOrd - selection.firstOrd + 1);
  1090. if(!insertCount)
  1091. return;
  1092. std::copy(order.begin() + selection.firstOrd, order.begin() + selection.firstOrd + insertCount, order.begin() + selection.lastOrd + 1);
  1093. InsertUpdatePlaystate(selection.firstOrd, selection.lastOrd);
  1094. m_nScrollPos = std::min(ORDERINDEX(selection.lastOrd + 1), order.GetLastIndex());
  1095. if(insertCount > 1)
  1096. m_nScrollPos2nd = std::min(ORDERINDEX(m_nScrollPos + insertCount - 1), order.GetLastIndex());
  1097. else
  1098. m_nScrollPos2nd = ORDERINDEX_INVALID;
  1099. InvalidateSelection();
  1100. EnsureVisible(m_nScrollPos2nd);
  1101. // first inserted order has higher priority than the last one
  1102. EnsureVisible(m_nScrollPos);
  1103. Invalidate(FALSE);
  1104. m_modDoc.SetModified();
  1105. m_modDoc.UpdateAllViews(nullptr, SequenceHint().Data(), this);
  1106. }
  1107. void COrderList::OnInsertSeparatorPattern()
  1108. {
  1109. // Insert a separator pattern after the current pattern, don't move order list cursor
  1110. ModSequence &order = Order();
  1111. const OrdSelection selection = GetCurSel(true);
  1112. ORDERINDEX insertPos = selection.firstOrd;
  1113. if(!EnsureEditable(insertPos))
  1114. return;
  1115. if(order[insertPos] != order.GetInvalidPatIndex())
  1116. {
  1117. // If we're not inserting at a stop (---) index, we move on by one position.
  1118. insertPos++;
  1119. order.insert(insertPos, 1, order.GetIgnoreIndex());
  1120. } else
  1121. {
  1122. order[insertPos] = order.GetIgnoreIndex();
  1123. }
  1124. InsertUpdatePlaystate(insertPos, insertPos);
  1125. Invalidate(FALSE);
  1126. m_modDoc.SetModified();
  1127. m_modDoc.UpdateAllViews(nullptr, SequenceHint().Data(), this);
  1128. }
  1129. void COrderList::OnRenderOrder()
  1130. {
  1131. OrdSelection selection = GetCurSel();
  1132. m_modDoc.OnFileWaveConvert(selection.firstOrd, selection.lastOrd);
  1133. }
  1134. void COrderList::OnDeleteOrder()
  1135. {
  1136. OrdSelection selection = GetCurSel();
  1137. // remove selection
  1138. m_nScrollPos2nd = ORDERINDEX_INVALID;
  1139. Order().Remove(selection.firstOrd, selection.lastOrd);
  1140. m_modDoc.SetModified();
  1141. Invalidate(FALSE);
  1142. m_modDoc.UpdateAllViews(nullptr, SequenceHint().Data(), this);
  1143. DeleteUpdatePlaystate(selection.firstOrd, selection.lastOrd);
  1144. SetCurSel(selection.firstOrd, true, false, true);
  1145. }
  1146. void COrderList::OnPatternProperties()
  1147. {
  1148. ModSequence &order = Order();
  1149. const auto ord = GetCurSel(true).firstOrd;
  1150. if(order.IsValidPat(ord))
  1151. m_pParent.PostViewMessage(VIEWMSG_PATTERNPROPERTIES, order[ord]);
  1152. }
  1153. void COrderList::OnPlayerPlay()
  1154. {
  1155. m_pParent.PostMessage(WM_COMMAND, ID_PLAYER_PLAY);
  1156. }
  1157. void COrderList::OnPlayerPause()
  1158. {
  1159. m_pParent.PostMessage(WM_COMMAND, ID_PLAYER_PAUSE);
  1160. }
  1161. void COrderList::OnPlayerPlayFromStart()
  1162. {
  1163. m_pParent.PostMessage(WM_COMMAND, ID_PLAYER_PLAYFROMSTART);
  1164. }
  1165. void COrderList::OnPatternPlayFromStart()
  1166. {
  1167. m_pParent.PostMessage(WM_COMMAND, IDC_PATTERN_PLAYFROMSTART);
  1168. }
  1169. void COrderList::OnCreateNewPattern()
  1170. {
  1171. m_pParent.PostMessage(WM_COMMAND, ID_ORDERLIST_NEW);
  1172. }
  1173. void COrderList::OnDuplicatePattern()
  1174. {
  1175. m_pParent.PostMessage(WM_COMMAND, ID_ORDERLIST_COPY);
  1176. }
  1177. void COrderList::OnMergePatterns()
  1178. {
  1179. m_pParent.PostMessage(WM_COMMAND, ID_ORDERLIST_MERGE);
  1180. }
  1181. void COrderList::OnPatternCopy()
  1182. {
  1183. m_pParent.PostMessage(WM_COMMAND, ID_PATTERNCOPY);
  1184. }
  1185. void COrderList::OnPatternPaste()
  1186. {
  1187. m_pParent.PostMessage(WM_COMMAND, ID_PATTERNPASTE);
  1188. }
  1189. void COrderList::OnSetRestartPos()
  1190. {
  1191. CSoundFile &sndFile = m_modDoc.GetSoundFile();
  1192. bool modified = false;
  1193. if(m_nScrollPos == Order().GetRestartPos())
  1194. {
  1195. // Unset position
  1196. modified = (m_nScrollPos != 0);
  1197. Order().SetRestartPos(0);
  1198. } else if(sndFile.GetModSpecifications().hasRestartPos)
  1199. {
  1200. // Set new position
  1201. modified = true;
  1202. Order().SetRestartPos(m_nScrollPos);
  1203. }
  1204. if(modified)
  1205. {
  1206. m_modDoc.SetModified();
  1207. m_modDoc.UpdateAllViews(nullptr, SequenceHint().RestartPos(), this);
  1208. }
  1209. }
  1210. LRESULT COrderList::OnHelpHitTest(WPARAM, LPARAM)
  1211. {
  1212. return HID_BASE_COMMAND + IDC_ORDERLIST;
  1213. }
  1214. LRESULT COrderList::OnDragonDropping(WPARAM doDrop, LPARAM lParam)
  1215. {
  1216. const DRAGONDROP *pDropInfo = (const DRAGONDROP *)lParam;
  1217. CPoint pt;
  1218. if((!pDropInfo) || (&m_modDoc.GetSoundFile() != pDropInfo->sndFile) || (!m_cxFont))
  1219. return FALSE;
  1220. BOOL canDrop = FALSE;
  1221. switch(pDropInfo->dropType)
  1222. {
  1223. case DRAGONDROP_ORDER:
  1224. if(pDropInfo->dropItem >= Order().size())
  1225. break;
  1226. case DRAGONDROP_PATTERN:
  1227. canDrop = TRUE;
  1228. break;
  1229. }
  1230. if(!canDrop || !doDrop)
  1231. return canDrop;
  1232. GetCursorPos(&pt);
  1233. ScreenToClient(&pt);
  1234. if(pt.x < 0)
  1235. pt.x = 0;
  1236. ORDERINDEX posDest = mpt::saturate_cast<ORDERINDEX>(m_nXScroll + (pt.x / m_cxFont));
  1237. if(posDest >= Order().size())
  1238. return FALSE;
  1239. switch(pDropInfo->dropType)
  1240. {
  1241. case DRAGONDROP_PATTERN:
  1242. Order()[posDest] = static_cast<PATTERNINDEX>(pDropInfo->dropItem);
  1243. break;
  1244. case DRAGONDROP_ORDER:
  1245. Order()[posDest] = Order()[pDropInfo->dropItem];
  1246. break;
  1247. }
  1248. if(canDrop)
  1249. {
  1250. Invalidate(FALSE);
  1251. m_modDoc.SetModified();
  1252. m_modDoc.UpdateAllViews(nullptr, SequenceHint().Data(), this);
  1253. SetCurSel(posDest, true);
  1254. }
  1255. return canDrop;
  1256. }
  1257. ORDERINDEX COrderList::SetMargins(int i)
  1258. {
  1259. m_nOrderlistMargins = i;
  1260. return GetMargins();
  1261. }
  1262. void COrderList::SelectSequence(const SEQUENCEINDEX seq)
  1263. {
  1264. CriticalSection cs;
  1265. CMainFrame::GetMainFrame()->ResetNotificationBuffer();
  1266. CSoundFile &sndFile = m_modDoc.GetSoundFile();
  1267. const bool editSequence = seq >= sndFile.Order.GetNumSequences();
  1268. if(seq == kSplitSequence)
  1269. {
  1270. if(!sndFile.Order.CanSplitSubsongs())
  1271. {
  1272. Reporting::Information(U_("No sub songs have been found in this sequence."));
  1273. return;
  1274. }
  1275. 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)
  1276. return;
  1277. if(!sndFile.Order.SplitSubsongsToMultipleSequences())
  1278. return;
  1279. } else if(seq == kDeleteSequence)
  1280. {
  1281. SEQUENCEINDEX curSeq = sndFile.Order.GetCurrentSequenceIndex();
  1282. mpt::ustring str = MPT_UFORMAT("Remove sequence {}: {}?")(curSeq + 1, mpt::ToUnicode(Order().GetName()));
  1283. if(Reporting::Confirm(str) == cnfYes)
  1284. sndFile.Order.RemoveSequence(curSeq);
  1285. else
  1286. return;
  1287. } else if(seq == kAddSequence || seq == kDuplicateSequence)
  1288. {
  1289. const bool duplicate = (seq == kDuplicateSequence);
  1290. const SEQUENCEINDEX newIndex = sndFile.Order.GetCurrentSequenceIndex() + 1u;
  1291. std::vector<SEQUENCEINDEX> newOrder(sndFile.Order.GetNumSequences());
  1292. std::iota(newOrder.begin(), newOrder.end(), SEQUENCEINDEX(0));
  1293. newOrder.insert(newOrder.begin() + newIndex, duplicate ? sndFile.Order.GetCurrentSequenceIndex() : SEQUENCEINDEX_INVALID);
  1294. if(m_modDoc.ReArrangeSequences(newOrder))
  1295. {
  1296. sndFile.Order.SetSequence(newIndex);
  1297. if(const auto name = sndFile.Order().GetName(); duplicate && !name.empty())
  1298. sndFile.Order().SetName(name + U_(" (Copy)"));
  1299. m_modDoc.UpdateAllViews(nullptr, SequenceHint(SEQUENCEINDEX_INVALID).Names().Data());
  1300. }
  1301. } else if(seq == sndFile.Order.GetCurrentSequenceIndex())
  1302. return;
  1303. else if(seq < sndFile.Order.GetNumSequences())
  1304. sndFile.Order.SetSequence(seq);
  1305. ORDERINDEX posCandidate = Order().GetLengthTailTrimmed() - 1;
  1306. SetCurSel(std::min(m_nScrollPos, posCandidate), true, false, true);
  1307. m_pParent.SetCurrentPattern(Order()[m_nScrollPos]);
  1308. UpdateScrollInfo();
  1309. // This won't make sense anymore in the new sequence.
  1310. OnUnlockPlayback();
  1311. cs.Leave();
  1312. if(editSequence)
  1313. m_modDoc.SetModified();
  1314. m_modDoc.UpdateAllViews(nullptr, SequenceHint().Data(), nullptr);
  1315. }
  1316. void COrderList::QueuePattern(CPoint pt)
  1317. {
  1318. CRect rect;
  1319. GetClientRect(&rect);
  1320. if(!rect.PtInRect(pt))
  1321. return;
  1322. CSoundFile &sndFile = m_modDoc.GetSoundFile();
  1323. const PATTERNINDEX ignoreIndex = sndFile.Order.GetIgnoreIndex();
  1324. const PATTERNINDEX stopIndex = sndFile.Order.GetInvalidPatIndex();
  1325. const ORDERINDEX length = Order().GetLength();
  1326. ORDERINDEX order = GetOrderFromPoint(pt);
  1327. // If this is not a playable order item, find the next valid item.
  1328. while(order < length && (Order()[order] == ignoreIndex || Order()[order] == stopIndex))
  1329. {
  1330. order++;
  1331. }
  1332. if(order < length)
  1333. {
  1334. if(sndFile.m_PlayState.m_nSeqOverride == order)
  1335. {
  1336. // This item is already queued: Dequeue it.
  1337. sndFile.m_PlayState.m_nSeqOverride = ORDERINDEX_INVALID;
  1338. } else
  1339. {
  1340. if(Order().IsPositionLocked(order))
  1341. {
  1342. // Users wants to go somewhere else, so let them do that.
  1343. OnUnlockPlayback();
  1344. }
  1345. sndFile.m_PlayState.m_nSeqOverride = order;
  1346. }
  1347. Invalidate(FALSE);
  1348. }
  1349. }
  1350. void COrderList::OnLockPlayback()
  1351. {
  1352. CSoundFile &sndFile = m_modDoc.GetSoundFile();
  1353. OrdSelection selection = GetCurSel();
  1354. if(selection.firstOrd == sndFile.m_lockOrderStart && selection.lastOrd == sndFile.m_lockOrderEnd)
  1355. {
  1356. OnUnlockPlayback();
  1357. } else
  1358. {
  1359. sndFile.m_lockOrderStart = selection.firstOrd;
  1360. sndFile.m_lockOrderEnd = selection.lastOrd;
  1361. Invalidate(FALSE);
  1362. }
  1363. }
  1364. void COrderList::OnUnlockPlayback()
  1365. {
  1366. CSoundFile &sndFile = m_modDoc.GetSoundFile();
  1367. sndFile.m_lockOrderStart = sndFile.m_lockOrderEnd = ORDERINDEX_INVALID;
  1368. Invalidate(FALSE);
  1369. }
  1370. void COrderList::InsertUpdatePlaystate(ORDERINDEX first, ORDERINDEX last)
  1371. {
  1372. auto &sndFile = m_modDoc.GetSoundFile();
  1373. Util::InsertItem(first, last, sndFile.m_PlayState.m_nNextOrder);
  1374. if(sndFile.m_PlayState.m_nSeqOverride != ORDERINDEX_INVALID)
  1375. Util::InsertItem(first, last, sndFile.m_PlayState.m_nSeqOverride);
  1376. // Adjust order lock position
  1377. if(sndFile.m_lockOrderStart != ORDERINDEX_INVALID)
  1378. Util::InsertRange(first, last, sndFile.m_lockOrderStart, sndFile.m_lockOrderEnd);
  1379. }
  1380. void COrderList::DeleteUpdatePlaystate(ORDERINDEX first, ORDERINDEX last)
  1381. {
  1382. auto &sndFile = m_modDoc.GetSoundFile();
  1383. Util::DeleteItem(first, last, sndFile.m_PlayState.m_nNextOrder);
  1384. if(sndFile.m_PlayState.m_nSeqOverride != ORDERINDEX_INVALID)
  1385. Util::DeleteItem(first, last, sndFile.m_PlayState.m_nSeqOverride);
  1386. // Adjust order lock position
  1387. if(sndFile.m_lockOrderStart != ORDERINDEX_INVALID)
  1388. Util::DeleteRange(first, last, sndFile.m_lockOrderStart, sndFile.m_lockOrderEnd);
  1389. }
  1390. INT_PTR COrderList::OnToolHitTest(CPoint point, TOOLINFO *pTI) const
  1391. {
  1392. CRect rect;
  1393. GetClientRect(&rect);
  1394. pTI->hwnd = m_hWnd;
  1395. pTI->uId = GetOrderFromPoint(point);
  1396. pTI->rect = rect;
  1397. pTI->lpszText = LPSTR_TEXTCALLBACK;
  1398. return pTI->uId;
  1399. }
  1400. BOOL COrderList::OnToolTipText(UINT, NMHDR *pNMHDR, LRESULT *)
  1401. {
  1402. TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pNMHDR;
  1403. if(!(pTTT->uFlags & TTF_IDISHWND))
  1404. {
  1405. CString text;
  1406. const CSoundFile &sndFile = m_modDoc.GetSoundFile();
  1407. const ModSequence &order = Order();
  1408. const ORDERINDEX ord = mpt::saturate_cast<ORDERINDEX>(pNMHDR->idFrom), ordLen = order.GetLengthTailTrimmed();
  1409. text.Format(_T("Position %u of %u [%02Xh of %02Xh]"), ord, ordLen, ord, ordLen);
  1410. if(order.IsValidPat(ord))
  1411. {
  1412. PATTERNINDEX pat = order[ord];
  1413. const std::string name = sndFile.Patterns[pat].GetName();
  1414. if(!name.empty())
  1415. {
  1416. ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, int32_max); // Allow multiline tooltip
  1417. text += _T("\r\n") + mpt::ToCString(sndFile.GetCharsetInternal(), name);
  1418. }
  1419. }
  1420. mpt::String::WriteCStringBuf(pTTT->szText) = text;
  1421. return TRUE;
  1422. }
  1423. return FALSE;
  1424. }
  1425. OPENMPT_NAMESPACE_END