/*
 * View_pat.h
 * ----------
 * Purpose: Pattern tab, lower panel.
 * Notes  : (currently none)
 * Authors: Olivier Lapicque
 *          OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#pragma once

#include "openmpt/all/BuildSettings.hpp"

#include "Globals.h"
#include "PatternCursor.h"
#include "Moddoc.h"
#include "PatternEditorDialogs.h"
#include "PatternClipboard.h"

OPENMPT_NAMESPACE_BEGIN

class CModDoc;
class CEditCommand;
class CEffectVis;
class CInputHandler;

// Drag & Drop info
class DragItem
{
	uint32 v = 0;

	enum : uint32
	{
		ValueMask = 0x00FFFFFF,
		TypeMask = 0xFF000000,
	};

public:
	enum DragType : uint32
	{
		ChannelHeader = 0x01000000,
		PatternHeader = 0x02000000,
		PluginName = 0x04000000,
	};

	DragItem() = default;
	DragItem(DragType type, uint32 value)
	    : v(type | value) {}

	DragType Type() const { return static_cast<DragType>(v & TypeMask); }
	uint32 Value() const { return v & ValueMask; }
	INT_PTR ToIntPtr() const { return v; }
	bool IsValid() const { return v != 0; }

	bool operator==(const DragItem other) const { return v == other.v; }
	bool operator!=(const DragItem other) const { return v != other.v; }
};


// Edit Step aka Row Spacing
inline constexpr ROWINDEX MAX_SPACING = MAX_PATTERN_ROWS;


// Struct for controlling selection clearing. This is used to define which data fields should be cleared.
struct RowMask
{
	bool note : 1;
	bool instrument : 1;
	bool volume : 1;
	bool command : 1;
	bool parameter : 1;

	// Default row mask (all rows are selected)
	RowMask()
	{
		note = instrument = volume = command = parameter = true;
	};

	// Construct mask from list
	RowMask(bool n, bool i, bool v, bool c, bool p)
	{
		note = n;
		instrument = i;
		volume = v;
		command = c;
		parameter = p;
	}

	// Construct mask from column index
	RowMask(const PatternCursor &cursor)
	{
		const PatternCursor::Columns column = cursor.GetColumnType();

		note = (column == PatternCursor::noteColumn);
		instrument = (column == PatternCursor::instrColumn);
		volume = (column == PatternCursor::volumeColumn);
		command = (column == PatternCursor::effectColumn);
		parameter = (column == PatternCursor::paramColumn);
	}

	void Clear()
	{
		note = instrument = volume = command = parameter = false;
	}
};


struct PatternEditPos
{
	ROWINDEX row = ROWINDEX_INVALID;
	ORDERINDEX order = ORDERINDEX_INVALID;
	PATTERNINDEX pattern = PATTERNINDEX_INVALID;
	CHANNELINDEX channel = CHANNELINDEX_INVALID;
};


// Pattern editing class


class CViewPattern final : public CModScrollView
{
public:
	// Pattern status flags
	enum PatternStatus
	{
		psMouseDragSelect    = 0x01,     // Creating a selection using the mouse
		psKeyboardDragSelect = 0x02,     // Creating a selection using shortcuts
		psFocussed           = 0x04,     // Is the pattern editor focussed
		psFollowSong         = 0x08,     // Does the cursor follow playback
		psRecordingEnabled   = 0x10,     // Recording enabled
		psDragVScroll        = 0x40,     // Indicates that the vertical scrollbar is being dragged
		psShowVUMeters       = 0x80,     // Display channel VU meters
		psChordPlaying       = 0x100,    // Is a chord playing? (pretty much unused)
		psDragnDropEdit      = 0x200,    // Drag & Drop editing (?)
		psDragnDropping      = 0x400,    // Dragging a selection around
		psShiftSelect        = 0x800,    // User has made at least one selection using Shift-Click since the Shift key has been pressed.
		psCtrlDragSelect     = 0x1000,   // Creating a selection using Ctrl
		psShowPluginNames    = 0x2000,   // Show plugin names in channel headers
		psRowSelection       = 0x4000,   // Selecting a whole pattern row by clicking the row numbers
		psChannelSelection   = 0x8000,   // Double-clicked pattern to select a whole channel
		psDragging           = 0x10000,  // Drag&Drop: Dragging an item around
		psShiftDragging      = 0x20000,  // Drag&Drop: Dragging an item around and holding shift

		// All possible drag flags, to test if user is dragging a selection or a scrollbar
		psDragActive = psDragVScroll | psMouseDragSelect | psRowSelection | psChannelSelection,
	};

protected:
	CFastBitmap m_Dib;
	CDC m_offScreenDC;
	CBitmap m_offScreenBitmap;
	CEditCommand *m_pEditWnd = nullptr;
	CSize m_szHeader, m_szPluginHeader, m_szCell;
	CRect m_oldClient;
	UINT m_nMidRow, m_nSpacing, m_nAccelChar, m_nLastPlayedRow, m_nLastPlayedOrder;
	FlagSet<PatternStatus> m_Status;
	ROWINDEX m_nPlayRow, m_nNextPlayRow;
	uint32 m_nPlayTick, m_nTicksOnRow;
	PATTERNINDEX m_nPattern = 0, m_nPlayPat = 0;
	ORDERINDEX m_nOrder = 0;
	static int32 m_nTransposeAmount;

	int m_nXScroll = 0, m_nYScroll = 0;
	PatternCursor::Columns m_nDetailLevel = PatternCursor::lastColumn;  // Visible Columns

	// Cursor and selection positions
	PatternCursor m_Cursor;               // Current cursor position in pattern.
	PatternCursor m_StartSel, m_DragPos;  // Point where selection was started.
	PatternCursor m_MenuCursor;           // Position at which context menu was opened.
	PatternRect m_Selection;              // Upper-left / Lower-right corners of selection.

	// Drag&Drop
	DragItem m_nDragItem;  // Currently dragged item
	DragItem m_nDropItem;  // Currently hovered item during dragondrop
	RECT m_rcDragItem, m_rcDropItem;
	bool m_bInItemRect = false;

	// Drag-select record group
	std::vector<RecordGroup> m_initialDragRecordStatus;

	ModCommand::INSTR m_fallbackInstrument = 0;

	// Chord auto-detect interval
	DWORD m_autoChordStartTime = 0;
	ROWINDEX m_autoChordStartRow = ROWINDEX_INVALID;
	ORDERINDEX m_autoChordStartOrder = ORDERINDEX_INVALID;

	bool m_bContinueSearch : 1, m_bWholePatternFitsOnScreen : 1;

	ModCommand m_PCNoteEditMemory;  // PC Note edit memory
	static ModCommand m_cmdOld;     // Quick cursor copy/paste data

	QuickChannelProperties m_quickChannelProperties;

	// Chord preview
	CHANNELINDEX m_chordPatternChannels[MPTChord::notesPerChord];
	ModCommand::NOTE m_prevChordNote, m_prevChordBaseNote;

	// Note-off event buffer for MIDI sustain pedal
	std::array<std::vector<uint32>, 16> m_midiSustainBuffer;
	std::bitset<16> m_midiSustainActive;

	std::array<uint16, MAX_BASECHANNELS> ChnVUMeters;
	std::array<uint16, MAX_BASECHANNELS> OldVUMeters;

	std::bitset<128> m_baPlayingNote;
	CModDoc::NoteToChannelMap m_noteChannel;  // Note -> Preview channel assignment
	std::array<ModCommand::NOTE, 10> m_octaveKeyMemory;
	std::array<ModCommand::NOTE, MAX_BASECHANNELS> m_previousNote;
	std::array<uint8, NOTE_MAX + NOTE_MIN> m_activeNoteChannel;
	std::array<uint8, NOTE_MAX + NOTE_MIN> m_splitActiveNoteChannel;
	static constexpr uint8 NOTE_CHANNEL_MAP_INVALID = 0xFF;
	static_assert(MAX_BASECHANNELS <= std::numeric_limits<decltype(m_activeNoteChannel)::value_type>::max());
	static_assert(MAX_BASECHANNELS <= NOTE_CHANNEL_MAP_INVALID);

public:
	std::unique_ptr<CEffectVis> m_pEffectVis;

	CViewPattern();
	~CViewPattern();
	DECLARE_SERIAL(CViewPattern)

public:
	const CSoundFile *GetSoundFile() const;
	CSoundFile *GetSoundFile();

	const ModSequence &Order() const;
	ModSequence &Order();

	void SetModified(bool updateAllViews = true);

	bool UpdateSizes();
	void UpdateScrollSize();
	void UpdateScrollPos();
	void UpdateIndicator(bool updateAccessibility = true);
	void UpdateXInfoText();
	void UpdateColors();

	CString GetCursorDescription() const;

	int GetXScrollPos() const { return m_nXScroll; }
	int GetYScrollPos() const { return m_nYScroll; }
	int GetChannelWidth() const { return m_szCell.cx; }
	int GetRowHeight() const { return m_szCell.cy; }
	int GetSmoothScrollOffset() const;

	PATTERNINDEX GetCurrentPattern() const { return m_nPattern; }
	ROWINDEX GetCurrentRow() const { return m_Cursor.GetRow(); }
	CHANNELINDEX GetCurrentChannel() const { return m_Cursor.GetChannel(); }
	ORDERINDEX GetCurrentOrder() const { return m_nOrder; }
	void SetCurrentOrder(ORDERINDEX ord)
	{
		m_nOrder = ord;
		SendCtrlMessage(CTRLMSG_SETCURRENTORDER, ord);
	}
	// Get ModCommand at the pattern cursor position.
	ModCommand &GetCursorCommand() { return GetModCommand(m_Cursor); };
	const ModCommand& GetCursorCommand() const { return const_cast<CViewPattern *>(this)->GetModCommand(m_Cursor); };
	void SanitizeCursor();

	UINT GetColumnOffset(PatternCursor::Columns column) const;
	POINT GetPointFromPosition(PatternCursor cursor) const;
	PatternCursor GetPositionFromPoint(POINT pt) const;

	DragItem GetDragItem(CPoint point, RECT &rect) const;
	
	void StartRecordGroupDragging(const DragItem source);
	void ResetRecordGroupDragging() { m_initialDragRecordStatus.clear(); }
	bool IsDraggingRecordGroup() const { return !m_initialDragRecordStatus.empty(); }

	ROWINDEX GetRowsPerBeat() const;
	ROWINDEX GetRowsPerMeasure() const;

	// Invalidate functions (for redrawing areas of the pattern)
	void InvalidatePattern(bool invalidateChannelHeaders = false, bool invalidateRowHeaders = false);
	void InvalidateRow(ROWINDEX n = ROWINDEX_INVALID);
	void InvalidateArea(const PatternRect &rect) { InvalidateArea(rect.GetUpperLeft(), rect.GetLowerRight()); };
	void InvalidateArea(PatternCursor begin, PatternCursor end);
	void InvalidateSelection() { InvalidateArea(m_Selection); }
	void InvalidateCell(PatternCursor cursor);
	void InvalidateChannelsHeaders(CHANNELINDEX chn = CHANNELINDEX_INVALID);

	// Selection functions
	void SetCurSel(const PatternRect &rect) { SetCurSel(rect.GetUpperLeft(), rect.GetLowerRight()); };
	void SetCurSel(const PatternCursor &point) { SetCurSel(point, point); };
	void SetCurSel(PatternCursor beginSel, PatternCursor endSel);
	void SetSelToCursor() { SetCurSel(m_Cursor); };

	bool SetCurrentPattern(PATTERNINDEX pat, ROWINDEX row = ROWINDEX_INVALID);
	ROWINDEX SetCurrentRow(ROWINDEX row, bool wrap = false, bool updateHorizontalScrollbar = true);
	bool SetCurrentColumn(const PatternCursor &cursor) { return SetCurrentColumn(cursor.GetChannel(), cursor.GetColumnType()); };
	bool SetCurrentColumn(CHANNELINDEX channel, PatternCursor::Columns column = PatternCursor::firstColumn);
	// This should be used instead of consecutive calls to SetCurrentRow() then SetCurrentColumn()
	bool SetCursorPosition(const PatternCursor &cursor, bool wrap = false);
	bool DragToSel(const PatternCursor &cursor, bool scrollHorizontal, bool scrollVertical, bool noMove = false);
	bool SetPlayCursor(PATTERNINDEX pat, ROWINDEX row, uint32 tick);
	bool UpdateScrollbarPositions(bool updateHorizontalScrollbar = true);
	bool ShowEditWindow();
	UINT GetCurrentInstrument() const;
	void SelectBeatOrMeasure(bool selectBeat);
	// Move pattern cursor to left or right, respecting invisible columns.
	void MoveCursor(bool moveRight);

	bool TransposeSelection(int transp);
	bool DataEntry(bool up, bool coarse);

	bool PrepareUndo(const PatternRect &selection, const char *description) { return PrepareUndo(selection.GetUpperLeft(), selection.GetLowerRight(), description); };
	bool PrepareUndo(const PatternCursor &beginSel, const PatternCursor &endSel, const char *description);
	void UndoRedo(bool undo);

	bool InsertOrDeleteRows(CHANNELINDEX firstChn, CHANNELINDEX lastChn, bool globalEdit, bool deleteRows);
	void DeleteRows(CHANNELINDEX firstChn, CHANNELINDEX lastChn, bool globalEdit = false);
	void InsertRows(CHANNELINDEX firstChn, CHANNELINDEX lastChn, bool globalEdit = false);

	void OnDropSelection();

public:
	void DrawPatternData(HDC hdc, PATTERNINDEX nPattern, bool selEnable, bool isPlaying, ROWINDEX startRow, ROWINDEX numRows, CHANNELINDEX startChan, CRect &rcClient, int *pypaint);
	void DrawLetter(int x, int y, char letter, int sizex = 10, int ofsx = 0);
	void DrawLetter(int x, int y, wchar_t letter, int sizex = 10, int ofsx = 0);
#if MPT_CXX_AT_LEAST(20)
	void DrawLetter(int x, int y, char8_t letter, int sizex = 10, int ofsx = 0);
#endif
	void DrawNote(int x, int y, UINT note, CTuning *pTuning = nullptr);
	void DrawInstrument(int x, int y, UINT instr);
	void DrawVolumeCommand(int x, int y, const ModCommand &mc, bool drawDefaultVolume);
	void DrawChannelVUMeter(HDC hdc, int x, int y, UINT nChn);
	void UpdateAllVUMeters(Notification *pnotify);
	void DrawDragSel(HDC hdc);
	void OnDrawDragSel();
	// True if default volume should be drawn for a given cell.
	static bool DrawDefaultVolume(const ModCommand *m) { return (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_SHOWDEFAULTVOLUME) && m->volcmd == VOLCMD_NONE && m->command != CMD_VOLUME && m->instr != 0 && m->IsNote(); }

	void CursorJump(int distance, bool snap);

	void TempEnterNote(ModCommand::NOTE n, int vol = -1, bool fromMidi = false);
	void TempStopNote(ModCommand::NOTE note, const bool fromMidi = false, bool chordMode = false);
	void TempEnterChord(ModCommand::NOTE n);
	void TempStopChord(ModCommand::NOTE note) { TempStopNote(note, false, true); }
	void TempEnterIns(int val);
	void TempEnterOctave(int val);
	void TempStopOctave(int val);
	void TempEnterVol(int v);
	void TempEnterFX(ModCommand::COMMAND c, int v = -1);
	void TempEnterFXparam(int v);
	void EnterAftertouch(ModCommand::NOTE note, int atValue);

	int GetDefaultVolume(const ModCommand &m, ModCommand::INSTR lastInstr = 0) const;
	int GetBaseNote() const;
	ModCommand::NOTE GetNoteWithBaseOctave(int note) const;

	// Construct a chord from the chord presets. Returns number of notes in chord.
	int ConstructChord(int note, ModCommand::NOTE (&outNotes)[MPTChord::notesPerChord], ModCommand::NOTE baseNote);

	void QuantizeRow(PATTERNINDEX &pat, ROWINDEX &row) const;
	PATTERNINDEX GetPrevPattern() const;
	PATTERNINDEX GetNextPattern() const;

	void SetSpacing(int n);
	void OnClearField(const RowMask &mask, bool step, bool ITStyle = false);
	void SetSelectionInstrument(const INSTRUMENTINDEX instr, bool setEmptyInstrument);

	void FindInstrument();
	void JumpToPrevOrNextEntry(bool nextEntry, bool select);

	void TogglePluginEditor(int chan);

	void ExecutePaste(PatternClipboard::PasteModes mode);

	// Reset all channel variables
	void ResetChannel(CHANNELINDEX chn);

public:
	//{{AFX_VIRTUAL(CViewPattern)
	void OnDraw(CDC *) override;
	void OnInitialUpdate() override;
	BOOL OnScrollBy(CSize sizeScroll, BOOL bDoScroll = TRUE) override;
	BOOL PreTranslateMessage(MSG *pMsg) override;
	void UpdateView(UpdateHint hint, CObject *pObj = nullptr) override;
	LRESULT OnModViewMsg(WPARAM, LPARAM) override;
	LRESULT OnPlayerNotify(Notification *) override;
	INT_PTR OnToolHitTest(CPoint point, TOOLINFO *pTI) const override;
	//}}AFX_VIRTUAL

protected:
	//{{AFX_MSG(CViewPattern)
	afx_msg BOOL OnEraseBkgnd(CDC *) { return TRUE; }
	afx_msg void OnSize(UINT nType, int cx, int cy);
	afx_msg void OnDestroy();
	afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
	afx_msg void OnXButtonUp(UINT nFlags, UINT nButton, CPoint point);
	afx_msg void OnMouseMove(UINT, CPoint);
	afx_msg void OnLButtonUp(UINT, CPoint);
	afx_msg void OnLButtonDown(UINT, CPoint);
	afx_msg void OnLButtonDblClk(UINT, CPoint);
	afx_msg void OnRButtonDown(UINT, CPoint);
	afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar);
	afx_msg void OnSetFocus(CWnd *pOldWnd);
	afx_msg void OnKillFocus(CWnd *pNewWnd);
	afx_msg void OnEditCut();
	afx_msg void OnEditCopy();

	afx_msg void OnEditPaste() { ExecutePaste(PatternClipboard::pmOverwrite); };
	afx_msg void OnEditMixPaste() { ExecutePaste(PatternClipboard::pmMixPaste); };
	afx_msg void OnEditMixPasteITStyle() { ExecutePaste(PatternClipboard::pmMixPasteIT); };
	afx_msg void OnEditPasteFlood() { ExecutePaste(PatternClipboard::pmPasteFlood); };
	afx_msg void OnEditPushForwardPaste() { ExecutePaste(PatternClipboard::pmPushForward); };

	afx_msg void OnClearSelection(bool ITStyle = false, RowMask sb = RowMask());
	afx_msg void OnGrowSelection();
	afx_msg void OnShrinkSelection();
	afx_msg void OnEditSelectAll();
	afx_msg void OnEditSelectChannel();
	afx_msg void OnSelectCurrentChannel();
	afx_msg void OnSelectCurrentColumn();
	afx_msg void OnEditFind();
	afx_msg void OnEditGoto();
	afx_msg void OnEditFindNext();
	afx_msg void OnEditUndo();
	afx_msg void OnEditRedo();
	afx_msg void OnChannelReset();
	afx_msg void OnMuteFromClick();
	afx_msg void OnSoloFromClick();
	afx_msg void OnTogglePendingMuteFromClick();
	afx_msg void OnPendingSoloChnFromClick();
	afx_msg void OnPendingUnmuteAllChnFromClick();
	afx_msg void OnSoloChannel(CHANNELINDEX chn);
	afx_msg void OnMuteChannel(CHANNELINDEX chn);
	afx_msg void OnUnmuteAll();
	afx_msg void OnRecordSelect();
	afx_msg void OnSplitRecordSelect();
	afx_msg void OnDeleteRow();
	afx_msg void OnDeleteWholeRow();
	afx_msg void OnDeleteRowGlobal();
	afx_msg void OnDeleteWholeRowGlobal();
	afx_msg void OnInsertRow();
	afx_msg void OnInsertWholeRow();
	afx_msg void OnInsertRowGlobal();
	afx_msg void OnInsertWholeRowGlobal();
	afx_msg void OnSplitPattern();
	afx_msg void OnPatternStep();
	afx_msg void OnSwitchToOrderList();
	afx_msg void OnPrevOrder();
	afx_msg void OnNextOrder();
	afx_msg void OnPrevInstrument() { PostCtrlMessage(CTRLMSG_PAT_PREVINSTRUMENT); }
	afx_msg void OnNextInstrument() { PostCtrlMessage(CTRLMSG_PAT_NEXTINSTRUMENT); }
	afx_msg void OnPatternRecord() { PostCtrlMessage(CTRLMSG_SETRECORD, -1); }
	afx_msg void OnInterpolateVolume() { Interpolate(PatternCursor::volumeColumn); }
	afx_msg void OnInterpolateEffect() { Interpolate(PatternCursor::effectColumn); }
	afx_msg void OnInterpolateNote() { Interpolate(PatternCursor::noteColumn); }
	afx_msg void OnInterpolateInstr() { Interpolate(PatternCursor::instrColumn); }
	afx_msg void OnVisualizeEffect();
	afx_msg void OnTransposeUp() { TransposeSelection(1); }
	afx_msg void OnTransposeDown() { TransposeSelection(-1); }
	afx_msg void OnTransposeOctUp() { TransposeSelection(12000); }
	afx_msg void OnTransposeOctDown() { TransposeSelection(-12000); }
	afx_msg void OnTransposeCustom();
	afx_msg void OnTransposeCustomQuick();
	afx_msg void OnSetSelInstrument();
	afx_msg void OnAddChannelFront() { AddChannel(m_MenuCursor.GetChannel(), false); }
	afx_msg void OnAddChannelAfter() { AddChannel(m_MenuCursor.GetChannel(), true); };
	afx_msg void OnDuplicateChannel();
	afx_msg void OnResetChannelColors();
	afx_msg void OnTransposeChannel();
	afx_msg void OnRemoveChannel();
	afx_msg void OnRemoveChannelDialog();
	afx_msg void OnPatternProperties() { ShowPatternProperties(PATTERNINDEX_INVALID); }
	void ShowPatternProperties(PATTERNINDEX pat);
	void OnCursorCopy();
	void OnCursorPaste();
	afx_msg void OnPatternAmplify();
	afx_msg void OnUpdateUndo(CCmdUI *pCmdUI);
	afx_msg void OnUpdateRedo(CCmdUI *pCmdUI);
	afx_msg void OnSelectPlugin(UINT nID);
	afx_msg LRESULT OnUpdatePosition(WPARAM nOrd, LPARAM nRow);
	afx_msg LRESULT OnMidiMsg(WPARAM, LPARAM);
	afx_msg LRESULT OnRecordPlugParamChange(WPARAM, LPARAM);
	afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM);
	afx_msg void OnClearSelectionFromMenu();
	afx_msg void OnSelectInstrument(UINT nid);
	afx_msg void OnSelectPCNoteParam(UINT nid);
	afx_msg void OnRunScript();
	afx_msg void OnShowTimeAtRow();
	afx_msg void OnTogglePCNotePluginEditor();
	afx_msg void OnSetQuantize();
	afx_msg void OnLockPatternRows();
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()


public:
	afx_msg void OnInitMenu(CMenu *pMenu);

private:
	// Copy&Paste
	bool CopyPattern(PATTERNINDEX nPattern, const PatternRect &selection);
	bool PastePattern(PATTERNINDEX nPattern, const PatternCursor &pastePos, PatternClipboard::PasteModes mode);

	void SetSplitKeyboardSettings();
	bool HandleSplit(ModCommand &m, int note);
	bool IsNoteSplit(int note) const;

	CHANNELINDEX FindGroupRecordChannel(RecordGroup recordGroup, bool forceFreeChannel, CHANNELINDEX startChannel = 0) const;

	bool BuildChannelControlCtxMenu(HMENU hMenu, CInputHandler *ih) const;
	bool BuildPluginCtxMenu(HMENU hMenu, UINT nChn, const CSoundFile &sndFile) const;
	bool BuildRecordCtxMenu(HMENU hMenu, CInputHandler *ih, CHANNELINDEX nChn) const;
	bool BuildSoloMuteCtxMenu(HMENU hMenu, CInputHandler *ih, UINT nChn, const CSoundFile &sndFile) const;
	bool BuildRowInsDelCtxMenu(HMENU hMenu, CInputHandler *ih) const;
	bool BuildMiscCtxMenu(HMENU hMenu, CInputHandler *ih) const;
	bool BuildSelectionCtxMenu(HMENU hMenu, CInputHandler *ih) const;
	bool BuildGrowShrinkCtxMenu(HMENU hMenu, CInputHandler *ih) const;
	bool BuildInterpolationCtxMenu(HMENU hMenu, CInputHandler *ih) const;
	bool BuildInterpolationCtxMenu(HMENU hMenu, PatternCursor::Columns colType, CString label, UINT command) const;
	bool BuildEditCtxMenu(HMENU hMenu, CInputHandler *ih, CModDoc *pModDoc) const;
	bool BuildVisFXCtxMenu(HMENU hMenu, CInputHandler *ih) const;
	bool BuildTransposeCtxMenu(HMENU hMenu, CInputHandler *ih) const;
	bool BuildSetInstCtxMenu(HMENU hMenu, CInputHandler *ih) const;
	bool BuildAmplifyCtxMenu(HMENU hMenu, CInputHandler *ih) const;
	bool BuildPCNoteCtxMenu(HMENU hMenu, CInputHandler *ih) const;
	bool BuildTogglePlugEditorCtxMenu(HMENU hMenu, CInputHandler *ih) const;

	// Returns an ordered list of all channels in which a given column type is selected.
	CHANNELINDEX ListChansWhereColSelected(PatternCursor::Columns colType, std::vector<CHANNELINDEX> &chans) const;
	// Check if a column type is selected on any channel in the current selection.
	bool IsColumnSelected(PatternCursor::Columns colType) const;

	bool IsInterpolationPossible(PatternCursor::Columns colType) const;
	bool IsInterpolationPossible(ROWINDEX startRow, ROWINDEX endRow, CHANNELINDEX chan, PatternCursor::Columns colType) const;
	void Interpolate(PatternCursor::Columns type);
	PatternRect SweepPattern(bool (*startCond)(const ModCommand &), bool (*endCond)(const ModCommand &, const ModCommand &)) const;

	// Return true if recording live (i.e. editing while following playback).
	bool IsLiveRecord() const
	{
		const CMainFrame *mainFrm = CMainFrame::GetMainFrame();
		const CSoundFile *sndFile = GetSoundFile();
		if(mainFrm == nullptr || sndFile == nullptr)
		{
			return false;
		}
		//      (following song)      &&       (following in correct document)           &&    (playback is on)
		return m_Status[psFollowSong] && mainFrm->GetFollowSong(GetDocument()) == m_hWnd && !sndFile->IsPaused();
	};

	// Returns edit position.
	PatternEditPos GetEditPos(const CSoundFile &sndFile, const bool liveRecord) const;

	// Returns pointer to modcommand at given position.
	// If the position is not valid, a pointer to a dummy command is returned.
	ModCommand &GetModCommand(PatternCursor cursor);
	ModCommand &GetModCommand(CSoundFile &sndFile, const PatternEditPos &pos);

	// Returns true if pattern editing is enabled.
	bool IsEditingEnabled() const { return m_Status[psRecordingEnabled]; }

	// Like IsEditingEnabled(), but shows some notification when editing is not enabled.
	bool IsEditingEnabled_bmsg();

	// Play one pattern row and stop ("step mode")
	void PatternStep(ROWINDEX row = ROWINDEX_INVALID);

	// Add a channel.
	void AddChannel(CHANNELINDEX parent, bool afterCurrent);

	void DragChannel(CHANNELINDEX source, CHANNELINDEX target, CHANNELINDEX numChannels, bool duplicate);

public:
	afx_msg void OnRButtonDblClk(UINT nFlags, CPoint point);

private:
	void TogglePendingMute(CHANNELINDEX nChn);
	void PendingSoloChn(CHANNELINDEX nChn);

	template <typename Func>
	void ApplyToSelection(Func func);

	void PlayNote(ModCommand::NOTE note, ModCommand::INSTR instr, int volume, CHANNELINDEX channel);
	void PreviewNote(ROWINDEX row, CHANNELINDEX channel);

public:
	afx_msg void OnRButtonUp(UINT nFlags, CPoint point);

	HRESULT get_accName(VARIANT varChild, BSTR *pszName) override;
};

DECLARE_FLAGSET(CViewPattern::PatternStatus);

void getXParam(ModCommand::COMMAND command, PATTERNINDEX nPat, ROWINDEX nRow, CHANNELINDEX nChannel, const CSoundFile &sndFile, UINT &xparam, UINT &multiplier);

OPENMPT_NAMESPACE_END
