335 lines
12 KiB
C
335 lines
12 KiB
C
|
/*
|
||
|
* view_tre.h
|
||
|
* ----------
|
||
|
* Purpose: Tree view for managing open songs, sound files, file browser, ...
|
||
|
* 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 <vector>
|
||
|
#include <bitset>
|
||
|
|
||
|
OPENMPT_NAMESPACE_BEGIN
|
||
|
|
||
|
class CModDoc;
|
||
|
class CModTree;
|
||
|
class CSoundFile;
|
||
|
class CDLSBank;
|
||
|
|
||
|
struct ModTreeDocInfo
|
||
|
{
|
||
|
// Tree state variables
|
||
|
std::vector<std::vector<HTREEITEM>> tiOrders;
|
||
|
std::vector<HTREEITEM> tiSequences, tiPatterns;
|
||
|
CModDoc &modDoc;
|
||
|
HTREEITEM hSong = nullptr, hPatterns = nullptr, hSamples = nullptr, hInstruments = nullptr, hComments = nullptr, hOrders = nullptr, hEffects = nullptr;
|
||
|
|
||
|
// Module information
|
||
|
ORDERINDEX ordSel = ORDERINDEX_INVALID;
|
||
|
SEQUENCEINDEX seqSel = SEQUENCEINDEX_INVALID;
|
||
|
|
||
|
std::bitset<MAX_SAMPLES> samplesPlaying;
|
||
|
std::bitset<MAX_INSTRUMENTS> instrumentsPlaying;
|
||
|
|
||
|
ModTreeDocInfo(CModDoc &modDoc);
|
||
|
};
|
||
|
|
||
|
|
||
|
class CModTreeDropTarget: public COleDropTarget
|
||
|
{
|
||
|
protected:
|
||
|
CModTree *m_pModTree;
|
||
|
|
||
|
public:
|
||
|
CModTreeDropTarget() { m_pModTree = nullptr; }
|
||
|
BOOL Register(CModTree *pWnd);
|
||
|
|
||
|
public:
|
||
|
DROPEFFECT OnDragEnter(CWnd *pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point) override;
|
||
|
DROPEFFECT OnDragOver(CWnd *pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point) override;
|
||
|
BOOL OnDrop(CWnd *pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point) override;
|
||
|
};
|
||
|
|
||
|
|
||
|
class CModTree: public CTreeCtrl
|
||
|
{
|
||
|
protected:
|
||
|
enum TreeStatus
|
||
|
{
|
||
|
TREESTATUS_RDRAG = 0x01,
|
||
|
TREESTATUS_LDRAG = 0x02,
|
||
|
TREESTATUS_SINGLEEXPAND = 0x04,
|
||
|
TREESTATUS_DRAGGING = (TREESTATUS_RDRAG | TREESTATUS_LDRAG)
|
||
|
};
|
||
|
|
||
|
enum ModItemType : uint8
|
||
|
{
|
||
|
MODITEM_NULL = 0,
|
||
|
|
||
|
MODITEM_BEGIN_SONGITEMS,
|
||
|
MODITEM_ORDER = MODITEM_BEGIN_SONGITEMS,
|
||
|
MODITEM_PATTERN,
|
||
|
MODITEM_SAMPLE,
|
||
|
MODITEM_INSTRUMENT,
|
||
|
MODITEM_COMMENTS,
|
||
|
MODITEM_EFFECT,
|
||
|
MODITEM_SEQUENCE,
|
||
|
MODITEM_HDR_SONG,
|
||
|
MODITEM_HDR_ORDERS,
|
||
|
MODITEM_HDR_PATTERNS,
|
||
|
MODITEM_HDR_SAMPLES,
|
||
|
MODITEM_HDR_INSTRUMENTS,
|
||
|
MODITEM_HDR_EFFECTS,
|
||
|
MODITEM_END_SONGITEMS = MODITEM_HDR_EFFECTS,
|
||
|
|
||
|
MODITEM_HDR_INSTRUMENTLIB,
|
||
|
MODITEM_HDR_MIDILIB,
|
||
|
MODITEM_HDR_MIDIGROUP,
|
||
|
MODITEM_MIDIINSTRUMENT,
|
||
|
MODITEM_MIDIPERCUSSION,
|
||
|
MODITEM_INSLIB_FOLDER,
|
||
|
MODITEM_INSLIB_SAMPLE,
|
||
|
MODITEM_INSLIB_INSTRUMENT,
|
||
|
MODITEM_INSLIB_SONG,
|
||
|
MODITEM_DLSBANK_FOLDER,
|
||
|
MODITEM_DLSBANK_INSTRUMENT,
|
||
|
};
|
||
|
|
||
|
// Bit mask magic
|
||
|
enum
|
||
|
{
|
||
|
MIDILIB_SHIFT = 16,
|
||
|
MIDILIB_MASK = (1 << MIDILIB_SHIFT) - 1,
|
||
|
|
||
|
// Must be consistent with CCtrlPatterns::OnActivatePage
|
||
|
SEQU_SHIFT = 16,
|
||
|
SEQU_MASK = (1 << SEQU_SHIFT) - 1,
|
||
|
SEQU_INDICATOR = 0x80000000,
|
||
|
|
||
|
// Soundbank instrument identification (must be consistent with CViewInstrument::OnDragonDrop / CViewSample::OnDragonDrop)
|
||
|
DLS_TYPEPERC = 0x80000000,
|
||
|
DLS_INSTRMASK = 0x0000FFFF,
|
||
|
DLS_REGIONMASK = 0x7FFF0000, // Drum region
|
||
|
DLS_REGIONSHIFT = 16,
|
||
|
|
||
|
DLS_DRUM_FOLDER_LPARAM = 0x12345678,
|
||
|
};
|
||
|
static_assert((ORDERINDEX_INVALID & SEQU_MASK) == ORDERINDEX_INVALID, "ORDERINDEX doesn't fit in GetItemData() parameter");
|
||
|
static_assert((ORDERINDEX_MAX & SEQU_MASK) == ORDERINDEX_MAX, "ORDERINDEX doesn't fit in GetItemData() parameter");
|
||
|
static_assert((((SEQUENCEINDEX_INVALID << SEQU_SHIFT) & ~SEQU_INDICATOR) >> SEQU_SHIFT) == SEQUENCEINDEX_INVALID, "SEQUENCEINDEX doesn't fit in GetItemData() parameter");
|
||
|
|
||
|
struct ModItem
|
||
|
{
|
||
|
uint32 val1;
|
||
|
uint16 val2;
|
||
|
ModItemType type;
|
||
|
|
||
|
ModItem(ModItemType t = MODITEM_NULL, uint32 v1 = 0, uint16 v2 = 0) : val1(v1), val2(v2), type(t) { }
|
||
|
bool IsSongItem() const noexcept { return type >= MODITEM_BEGIN_SONGITEMS && type <= MODITEM_END_SONGITEMS; }
|
||
|
bool operator==(const ModItem &other) const noexcept { return val1 == other.val1 && val2 == other.val2 && type == other.type; }
|
||
|
bool operator!=(const ModItem &other) const noexcept { return !(*this == other); }
|
||
|
};
|
||
|
|
||
|
struct DlsItem : public ModItem
|
||
|
{
|
||
|
explicit DlsItem(uint16 instr) : ModItem(MODITEM_DLSBANK_INSTRUMENT, instr, 0) { }
|
||
|
DlsItem(uint16 instr, uint16 region) : ModItem(MODITEM_DLSBANK_INSTRUMENT, (instr & DLS_INSTRMASK) | ((region << DLS_REGIONSHIFT) & DLS_REGIONMASK) | DLS_TYPEPERC, 0) { }
|
||
|
|
||
|
uint32 GetRegion() const noexcept { return (val1 & DLS_REGIONMASK) >> DLS_REGIONSHIFT; }
|
||
|
uint32 GetInstr() const noexcept { return (val1 & DLS_INSTRMASK); }
|
||
|
bool IsPercussion() const noexcept { return ((val1 & DLS_TYPEPERC) == DLS_TYPEPERC); }
|
||
|
bool IsMelodic() const noexcept { return !IsPercussion(); }
|
||
|
|
||
|
static ModItem FromLPARAM(uint32 lparam) { return ModItem{MODITEM_DLSBANK_INSTRUMENT, lparam, 0}; }
|
||
|
static LPARAM ToLPARAM(uint16 instr, uint16 region, bool isPerc) { return (instr & DLS_INSTRMASK) | ((region << DLS_REGIONSHIFT) & DLS_REGIONMASK) | (isPerc ? DLS_TYPEPERC : 0); }
|
||
|
};
|
||
|
|
||
|
static CSoundFile *m_SongFile; // For browsing samples and instruments inside modules on disk
|
||
|
CModTreeDropTarget m_DropTarget;
|
||
|
CModTree *m_pDataTree = nullptr; // Pointer to instrument browser (lower part of tree view) - if it's a nullptr, this object is the instrument browser itself.
|
||
|
HWND m_hDropWnd = nullptr;
|
||
|
mpt::mutex m_WatchDirMutex;
|
||
|
HANDLE m_hSwitchWatchDir = nullptr;
|
||
|
mpt::PathString m_WatchDir;
|
||
|
HANDLE m_hWatchDirKillThread = nullptr;
|
||
|
std::thread m_WatchDirThread;
|
||
|
ModItem m_itemDrag;
|
||
|
DWORD m_dwStatus = 0;
|
||
|
CModDoc *m_selectedDoc = nullptr, *m_dragDoc = nullptr;
|
||
|
HTREEITEM m_hItemDrag = nullptr, m_hItemDrop = nullptr;
|
||
|
HTREEITEM m_hInsLib = nullptr, m_hMidiLib = nullptr;
|
||
|
HTREEITEM m_tiMidi[128];
|
||
|
HTREEITEM m_tiPerc[128];
|
||
|
std::vector<HTREEITEM> m_tiDLS;
|
||
|
std::map<const CModDoc *, ModTreeDocInfo> m_docInfo;
|
||
|
|
||
|
std::unique_ptr<CDLSBank> m_cachedBank;
|
||
|
mpt::PathString m_cachedBankName;
|
||
|
|
||
|
CString m_compareStrL, m_compareStrR; // Cache for ModTreeInsLibCompareNamesProc to avoid constant re-allocations
|
||
|
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN7)
|
||
|
DWORD m_stringCompareFlags = NORM_IGNORECASE | NORM_IGNOREWIDTH | SORT_DIGITSASNUMBERS;
|
||
|
#else
|
||
|
DWORD m_stringCompareFlags = NORM_IGNORECASE | NORM_IGNOREWIDTH;
|
||
|
#endif
|
||
|
|
||
|
// Instrument library
|
||
|
mpt::PathString m_InstrLibPath; // Current path to be explored
|
||
|
mpt::PathString m_InstrLibHighlightPath; // Folder to highlight in browser after a refresh
|
||
|
mpt::PathString m_SongFileName; // Name of open module, without path (== m_InstrLibPath).
|
||
|
mpt::PathString m_previousPath; // The folder from which we came from when navigating one folder up
|
||
|
std::vector<const char*> m_modExtensions; // cached in order to avoid querying too often when changing browsed folder
|
||
|
std::vector<mpt::PathString> m_MediaFoundationExtensions; // cached in order to avoid querying too often when changing browsed folder
|
||
|
bool m_showAllFiles = false;
|
||
|
|
||
|
bool m_doLabelEdit = false;
|
||
|
|
||
|
public:
|
||
|
CModTree(CModTree *pDataTree);
|
||
|
~CModTree();
|
||
|
|
||
|
// Attributes
|
||
|
public:
|
||
|
void Init();
|
||
|
bool InsLibSetFullPath(const mpt::PathString &libPath, const mpt::PathString &songFolder);
|
||
|
mpt::PathString InsLibGetFullPath(HTREEITEM hItem) const;
|
||
|
bool SetSoundFile(FileReader &file);
|
||
|
void RefreshMidiLibrary();
|
||
|
void RefreshDlsBanks();
|
||
|
void RefreshInstrumentLibrary();
|
||
|
void EmptyInstrumentLibrary();
|
||
|
void FillInstrumentLibrary(const TCHAR *selectedItem = nullptr);
|
||
|
void MonitorInstrumentLibrary();
|
||
|
ModItem GetModItem(HTREEITEM hItem);
|
||
|
BOOL SetMidiInstrument(UINT nIns, const mpt::PathString &fileName);
|
||
|
BOOL SetMidiPercussion(UINT nPerc, const mpt::PathString &fileName);
|
||
|
bool ExecuteItem(HTREEITEM hItem);
|
||
|
void DeleteTreeItem(HTREEITEM hItem);
|
||
|
static void PlayDLSItem(const CDLSBank &dlsBank, const DlsItem &item, ModCommand::NOTE note);
|
||
|
BOOL PlayItem(HTREEITEM hItem, ModCommand::NOTE nParam, int volume = -1);
|
||
|
BOOL OpenTreeItem(HTREEITEM hItem);
|
||
|
BOOL OpenMidiInstrument(DWORD dwItem);
|
||
|
void SetFullInstrumentLibraryPath(mpt::PathString path);
|
||
|
void InstrumentLibraryChDir(mpt::PathString dir, bool isSong);
|
||
|
bool GetDropInfo(DRAGONDROP &dropInfo, mpt::PathString &fullPath);
|
||
|
void OnOptionsChanged();
|
||
|
void AddDocument(CModDoc &modDoc);
|
||
|
void RemoveDocument(const CModDoc &modDoc);
|
||
|
void UpdateView(ModTreeDocInfo &info, UpdateHint hint);
|
||
|
void OnUpdate(CModDoc *pModDoc, UpdateHint hint, CObject *pHint);
|
||
|
bool CanDrop(HTREEITEM hItem, bool bDoDrop);
|
||
|
void UpdatePlayPos(CModDoc &modDoc, Notification *pNotify);
|
||
|
bool IsItemExpanded(HTREEITEM hItem);
|
||
|
void DeleteChildren(HTREEITEM hItem);
|
||
|
HTREEITEM GetNthChildItem(HTREEITEM hItem, int index) const;
|
||
|
HTREEITEM GetParentRootItem(HTREEITEM hItem) const;
|
||
|
|
||
|
bool IsSampleBrowser() const { return m_pDataTree == nullptr; }
|
||
|
CModTree *GetSampleBrowser() { return IsSampleBrowser() ? this : m_pDataTree; }
|
||
|
|
||
|
// Overrides
|
||
|
// ClassWizard generated virtual function overrides
|
||
|
//{{AFX_VIRTUAL(CModTree)
|
||
|
public:
|
||
|
BOOL PreTranslateMessage(MSG* pMsg) override;
|
||
|
//}}AFX_VIRTUAL
|
||
|
|
||
|
// Drag & Drop operations
|
||
|
public:
|
||
|
DROPEFFECT OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
|
||
|
DROPEFFECT OnDragOver(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
|
||
|
BOOL OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point);
|
||
|
|
||
|
protected:
|
||
|
int ModTreeInsLibCompareNamesGetItem(HTREEITEM item, CString &resultStr);
|
||
|
static int CALLBACK ModTreeInsLibCompareNamesProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
|
||
|
static int CALLBACK ModTreeInsLibCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
|
||
|
static int CALLBACK ModTreeDrumCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
|
||
|
HTREEITEM InsertInsLibItem(const TCHAR *name, int image, const TCHAR *selectIfMatch);
|
||
|
ModTreeDocInfo *GetDocumentInfoFromItem(HTREEITEM hItem);
|
||
|
CModDoc *GetDocumentFromItem(HTREEITEM hItem) { ModTreeDocInfo *info = GetDocumentInfoFromItem(hItem); return info ? &info->modDoc : nullptr; }
|
||
|
ModTreeDocInfo *GetDocumentInfoFromModDoc(CModDoc &modDoc);
|
||
|
|
||
|
size_t GetDLSBankIndexFromItem(HTREEITEM hItem) const;
|
||
|
CDLSBank *GetDLSBankFromItem(HTREEITEM hItem) const;
|
||
|
|
||
|
void InsertOrDupItem(bool insert);
|
||
|
void OnItemRightClick(HTREEITEM hItem, CPoint pt);
|
||
|
|
||
|
static bool HasEffectPlugins(const CSoundFile &sndFile);
|
||
|
static bool AllPluginsBypassed(const CSoundFile &sndFile, bool onlyEffects);
|
||
|
static void BypassAllPlugins(CSoundFile &sndFile, bool bypass, bool onlyEffects);
|
||
|
|
||
|
// Generated message map functions
|
||
|
protected:
|
||
|
//{{AFX_MSG(CModTree)
|
||
|
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
|
||
|
afx_msg void OnRButtonUp(UINT nFlags, CPoint point);
|
||
|
afx_msg void OnXButtonUp(UINT nFlags, UINT nButton, CPoint point);
|
||
|
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
|
||
|
afx_msg void OnBeginDrag(HTREEITEM, bool bLeft, LRESULT *pResult);
|
||
|
afx_msg void OnBeginLDrag(LPNMHDR, LRESULT *pResult);
|
||
|
afx_msg void OnBeginRDrag(LPNMHDR, LRESULT *pResult);
|
||
|
afx_msg void OnEndDrag(DWORD dwMask);
|
||
|
afx_msg void OnItemDblClk(LPNMHDR phdr, LRESULT *pResult);
|
||
|
afx_msg void OnItemReturn(LPNMHDR, LRESULT *pResult);
|
||
|
afx_msg void OnItemLeftClick(LPNMHDR pNMHDR, LRESULT *pResult);
|
||
|
afx_msg void OnItemRightClick(LPNMHDR, LRESULT *pResult);
|
||
|
afx_msg void OnItemExpanded(LPNMHDR pnmhdr, LRESULT *pResult);
|
||
|
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
|
||
|
afx_msg void OnRefreshTree();
|
||
|
afx_msg void OnExecuteItem();
|
||
|
afx_msg void OnPlayTreeItem();
|
||
|
afx_msg void OnDeleteTreeItem();
|
||
|
afx_msg void OnOpenTreeItem();
|
||
|
afx_msg void OnMuteTreeItem();
|
||
|
afx_msg void OnMuteOnlyEffects();
|
||
|
afx_msg void OnSoloTreeItem();
|
||
|
afx_msg void OnUnmuteAllTreeItem();
|
||
|
afx_msg void OnDuplicateTreeItem() { InsertOrDupItem(false); }
|
||
|
afx_msg void OnInsertTreeItem() { InsertOrDupItem(true); }
|
||
|
afx_msg void OnSwitchToTreeItem(); // hack for sequence items to avoid double-click action
|
||
|
afx_msg void OnCloseItem();
|
||
|
afx_msg void OnRenameItem();
|
||
|
afx_msg void OnBeginLabelEdit(NMHDR *nmhdr, LRESULT *result);
|
||
|
afx_msg void OnEndLabelEdit(NMHDR *nmhdr, LRESULT *result);
|
||
|
afx_msg void OnDropFiles(HDROP hDropInfo);
|
||
|
|
||
|
afx_msg void OnSetItemPath();
|
||
|
afx_msg void OnSaveItem();
|
||
|
afx_msg void OnSaveAll();
|
||
|
afx_msg void OnReloadItem();
|
||
|
afx_msg void OnReloadAll();
|
||
|
afx_msg void OnFindMissing();
|
||
|
|
||
|
afx_msg void OnAddDlsBank();
|
||
|
afx_msg void OnImportMidiLib();
|
||
|
afx_msg void OnExportMidiLib();
|
||
|
afx_msg void OnSoundBankProperties();
|
||
|
afx_msg void OnRefreshInstrLib();
|
||
|
afx_msg void OnShowDirectories();
|
||
|
afx_msg void OnShowAllFiles();
|
||
|
afx_msg void OnShowSoundFiles();
|
||
|
|
||
|
afx_msg void OnGotoInstrumentDir();
|
||
|
afx_msg void OnGotoSampleDir();
|
||
|
|
||
|
afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM);
|
||
|
LRESULT OnMidiMsg(WPARAM midiData, LPARAM);
|
||
|
//}}AFX_MSG
|
||
|
DECLARE_MESSAGE_MAP()
|
||
|
public:
|
||
|
afx_msg void OnKillFocus(CWnd *pNewWnd);
|
||
|
afx_msg void OnSetFocus(CWnd *pOldWnd);
|
||
|
};
|
||
|
|
||
|
|
||
|
OPENMPT_NAMESPACE_END
|