/////////////////////////////
// ECCSkelton
// Shell Service Utility
// CSHELLクラス定義ファイル
// Copyright (c) August, 2022
//        By Ysama
/////////////////////////////
///////////////////////////////
// Shell (COM) Service Utility
///////////////////////////////

#define		WM_SHELLCHANGE	WM_APP
#include <shlobj.h>
#include <shlwapi.h>
//明示的ライブラリーファイルの指定
#pragma comment (lib, "shell32.lib")
#pragma comment (lib, "shlwapi.lib")

class CSHELL
{
private:
	IShellFolder		*m_pDesktopFolder;	//デスクトップ（仮想）フォールダーインターフェース
	PIDLIST_ABSOLUTE	m_pidlDesktop;		//デスクトップの絶対アドレス
	PIDLIST_ABSOLUTE	m_pidlCurFolder;	//現在のフォールダーの絶対アドレス
	HWND				m_tvhWnd;			//TreeView（左）のウィンドウハンドル
	HWND				m_lvhWnd;			//ListView（右）のウィンドウハンドル
	ULONG				m_uRegisterId;		//Shell通知の登録ID
public:
	CSHELL(HWND);
	~CSHELL();
	void SetHandles(HWND, HWND);			//TreeView（左）とListView（右）のハンドルを設定
	IShellFolder* GetShellFolder() {return m_pDesktopFolder;}	//デスクトップシェルフォールダーの取得
	PIDLIST_ABSOLUTE GetDesktop() {return m_pidlDesktop;}		//デスクトップPIDLの取得
	void SetCurFolder(PIDLIST_ABSOLUTE);						//m_pidlCurFolderの設定
	PIDLIST_ABSOLUTE GetCurFolder();							//m_pidlCurFolderの取得
	bool InsertTreeItem(PIDLIST_ABSOLUTE, HTREEITEM, LPWSTR, SFGAOF);
	bool ViewEnumItem(PIDLIST_ABSOLUTE, HWND, BOOL, HTREEITEM, int CALLBACK (*)(LPARAM, LPARAM, LPARAM));
	bool ResetListViewColumn(IShellFolder*);
	bool InsertListItem(PIDLIST_ABSOLUTE, IShellFolder*, PITEMID_CHILD, SFGAOF, int);
	ULONG SetSHChangeNoticeRegister(HWND);								//ファイル、フォールダーの変更通知設定
	void NotifyShellChange(PIDLIST_ABSOLUTE, PIDLIST_ABSOLUTE, LONG);	//シェル通知処理;
	bool FileOperation(HWND, WCHAR, WCHAR*, WCHAR*, FILEOP_FLAGS);			//ファイル処理
};

CSHELL::CSHELL(HWND hWnd) {

	//単一スレッド（STA）のCOM libraryを初期化
	CoInitialize(NULL);
	//特殊フォールダー情報の取得
	if(SHGetDesktopFolder(&m_pDesktopFolder) != S_OK)
		MessageBoxW(NULL, L"Unable to use CSHELL class", L"Error", MB_OK | MB_ICONERROR);
	if(SHGetSpecialFolderLocation(hWnd, CSIDL_DESKTOP, &m_pidlDesktop) != S_OK)	//CSIDL = "Constant Special item ID List" CSIDL_DESKTOP = The "Windows Desktop" virtual folder 
		MessageBoxW(NULL, L"Unable to use CSHELL class", L"Error", MB_OK | MB_ICONERROR);
	m_pidlCurFolder = NULL;
}

CSHELL::~CSHELL() {

	SHChangeNotifyDeregister(m_uRegisterId);	//シェル通知を終了する
	if(m_pDesktopFolder)
		m_pDesktopFolder->Release();			//COMメモリーの開放
	CoUninitialize();							//【必須】COMの終了処理
}

//TreeView（左）とListView（右）のハンドルを設定
void CSHELL::SetHandles(HWND tvhWnd, HWND lvhWnd) {

	m_tvhWnd = tvhWnd;	//TreeView（左）のウィンドウハンドル
	m_lvhWnd = lvhWnd;	//ListView（右）のウィンドウハンドル
}

//m_pidlCurFolderの設定
void CSHELL::SetCurFolder(PIDLIST_ABSOLUTE pidl) {

	m_pidlCurFolder = pidl;
}

//m_pidlCurFolderの取得
PIDLIST_ABSOLUTE CSHELL::GetCurFolder() {

	return m_pidlCurFolder;
}

bool CSHELL::InsertTreeItem(PIDLIST_ABSOLUTE pidlAbsolute, HTREEITEM hitemParent, LPWSTR lpszName, SFGAOF attributes) {

	SHFILEINFOW		FileInfo;		//ファイルオブジェクトに関する情報を定義する構造体
	TVINSERTSTRUCTW	tvIs;			//ツリービュ―用挿入構造体
	//tvIsのitem情報を設定
	SHGetFileInfo((LPWSTR)pidlAbsolute, 0, &FileInfo, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_PIDL);
	tvIs.item.iImage = FileInfo.iIcon;			//iIcon−システムイメージリストのインデックス
	SHGetFileInfo((LPWSTR)pidlAbsolute, 0, &FileInfo, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_PIDL | SHGFI_OPENICON);
	tvIs.item.iSelectedImage = FileInfo.iIcon;	//iIcon−システムイメージリストの「選択時」インデックス
 	tvIs.hParent		= hitemParent;			//ツリービュー親のインデックス
	tvIs.item.mask		= TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE;	//使用するメンバー特定
	tvIs.item.pszText	= lpszName;				//項目のファイルまたはフォールダー名
	tvIs.item.lParam	= (LPARAM)pidlAbsolute;	//項目の絶対PIDLをlParamに記録
	//SFGAOF attributesのチェック
	if (attributes & SFGAO_GHOSTED) {		//「切り取り」時にitemのiconをグレーにする
		tvIs.item.mask	|= LVIF_STATE;
		tvIs.item.state	 = LVIS_CUT;
		tvIs.item.stateMask = LVIS_CUT;
	}
	if (attributes & SFGAO_SHARE) {			//itemが共有されている場合、「共有」状態のiconにする
		tvIs.item.mask	|= LVIF_STATE;
		tvIs.item.state	 = INDEXTOOVERLAYMASK(1);
		tvIs.item.stateMask = LVIS_OVERLAYMASK;
	}
	if (attributes & SFGAO_LINK) {			//itemがショートカットの場合、「ショートカット」状態のiconにする
		tvIs.item.mask	|= LVIF_STATE;
		tvIs.item.state	 = INDEXTOOVERLAYMASK(2);
		tvIs.item.stateMask = LVIS_OVERLAYMASK;
	}
	if (attributes & SFGAO_HASSUBFOLDER) {	//itemがsubfolderを持つ場合、「展開可能」の「+」を付ける
		tvIs.item.mask	|= TVIF_CHILDREN;
		tvIs.item.cChildren = 1;
	}
	return TreeView_InsertItem(m_tvhWnd, &tvIs);
}

bool CSHELL::ViewEnumItem(PIDLIST_ABSOLUTE pidlAbsolute, HWND hWnd, BOOL bInsertList, HTREEITEM hitem, int CALLBACK (*CompareFunc)(LPARAM, LPARAM, LPARAM)) {

	bool				success;
	int					i = 0;
	BOOL				bBind;
	WCHAR				szDisplayName[MAX_PATH];
	SFGAOF				attributes = 0;
	SHCONTF				flag;
	STRRET				strret;
	PIDLIST_ABSOLUTE	pidlNew;
	PITEMID_CHILD		pidlChild;
	IShellFolder		*pShellFolder;
	IEnumIDList			*pEnumIdList;
	//pidlAbsoluteがデスクトップの場合、エラーが発生するので一致確認し、一致する場合はm_pDesktopFolderを指定
	if(!m_pDesktopFolder->CompareIDs(0, pidlAbsolute, m_pidlDesktop)) {
		pShellFolder = m_pDesktopFolder;
		bBind = FALSE;
	}
	else {
		m_pDesktopFolder->BindToObject(pidlAbsolute, NULL, IID_PPV_ARGS(&pShellFolder));
		bBind = TRUE;
	}
	if(bInsertList) {	//TRUEの場合、リストビューを初期化（既存項目削除と列名）する
		ListView_DeleteAllItems(m_lvhWnd);
		ResetListViewColumn(pShellFolder);
	}
	flag = bInsertList ? SHCONTF_FOLDERS | SHCONTF_NONFOLDERS : SHCONTF_FOLDERS;	//リストビューは全て、ツリービュ―はフォールダーのみ
	if(pShellFolder->EnumObjects(hWnd, flag | SHCONTF_INCLUDEHIDDEN, &pEnumIdList) != S_OK) {	//IDリスト取得失敗の場合
		if(bBind)
			pShellFolder->Release();
		return FALSE;
	}
	while(pEnumIdList->Next(1, &pidlChild, NULL) == S_OK) {	//子のIDリスト
		pidlNew = ILCombine(pidlAbsolute, pidlChild);
		if(bInsertList) {	//リストビューへ追加
			attributes = SFGAO_GHOSTED | SFGAO_SHARE | SFGAO_LINK;
			pShellFolder->GetAttributesOf(1, (LPCITEMIDLIST *)&pidlChild, &attributes);
			success = (bool)InsertListItem(pidlNew, pShellFolder, pidlChild, attributes, i);
		}
		else {				//ツリービューへ追加
			attributes = SFGAO_HASSUBFOLDER | SFGAO_GHOSTED | SFGAO_SHARE | SFGAO_LINK;
			pShellFolder->GetAttributesOf(1, (LPCITEMIDLIST *)&pidlChild, &attributes);
			pShellFolder->GetDisplayNameOf(pidlChild, SHGDN_NORMAL, &strret);
			StrRetToBuf(&strret, pidlChild, szDisplayName, sizeof(szDisplayName) / sizeof(WCHAR));
			success = (bool)InsertTreeItem(pidlNew, hitem, szDisplayName, attributes);
		}
		i++;
		CoTaskMemFree(pidlChild);	//pEnumIdList->Next()で取得した子情報は解放する
	}
	if(bInsertList)	//リストビューアイテムのソート
		ListView_SortItems(m_lvhWnd, CompareFunc, (LPARAM)pShellFolder);
	pEnumIdList->Release();
	if(bBind)		//これがTRUEの場合、メモリーを解放する必要がある（Release要否のフラグ）
		pShellFolder->Release();
	return success;
}

//リストビューのリセット
bool CSHELL::ResetListViewColumn(IShellFolder *pShellFolder) {

	bool			success;
	int				i, nCol;
	WCHAR			szBuf[MAX_PATH];
	LVCOLUMN		lvColumn;
	SHELLDETAILS	shDetails;
	IShellFolder2	*pShellFolder2;
	//現在の列数を求めてすべて削除する
	nCol = Header_GetItemCount(ListView_GetHeader(m_lvhWnd));
	for(i = 0; i < nCol; i++)
		ListView_DeleteColumn(m_lvhWnd, 0);
	//ShellFolder2の取得に失敗
	if(pShellFolder->QueryInterface(IID_PPV_ARGS(&pShellFolder2)) != S_OK)
		return FALSE;
	
	for(i = 0; i < 4; i++) {
		if(pShellFolder2->GetDetailsOf(NULL, i, &shDetails) != S_OK)
			break;	//詳細情報の取得に失敗したら抜け出す
		//詳細情報（strret）をstr（WCHAR）化する
		StrRetToBuf(&shDetails.str, NULL, szBuf, sizeof(szBuf) / sizeof(WCHAR));
		//lvColumnにセットする
		lvColumn.mask		= LVCF_WIDTH | LVCF_TEXT;
		lvColumn.cx			= 150;	//x幅
		lvColumn.pszText	= szBuf;
		success = (bool)ListView_InsertColumn(m_lvhWnd, i, &lvColumn);
	}
	pShellFolder2->Release();
	return success;
}

bool CSHELL::InsertListItem(PIDLIST_ABSOLUTE pidlAbsolute, IShellFolder *pShellFolder, PITEMID_CHILD pidlChild, SFGAOF attributes, int i) {

	bool			success;
	int				j;
	WCHAR			szBuf[MAX_PATH];
	LVITEMW			lvItem;
	SHFILEINFOW		shFileInfo;
	SHELLDETAILS	shDetails;
	IShellFolder2	*pShellFolder2;
	//ShellFolder2の取得に失敗
	if (pShellFolder->QueryInterface(IID_PPV_ARGS(&pShellFolder2)) != S_OK)
		return FALSE;
	//1行（4列）の項目を追加してゆく
	for(j = 0; j < 4; j++) {
		pShellFolder2->GetDetailsOf(pidlChild, j, &shDetails);
		StrRetToBuf(&shDetails.str, pidlChild, szBuf, sizeof(szBuf) / sizeof(WCHAR));
		//LVITEMの設定
		lvItem.mask	 = LVIF_TEXT;
		lvItem.iItem	= i;
		lvItem.iSubItem = j;
		lvItem.pszText  = szBuf;
		if(!j) {	//第1列の場合（含LVITEMの追加設定）
			SHGetFileInfo((LPTSTR)pidlAbsolute, 0, &shFileInfo, sizeof(SHFILEINFOW), SHGFI_SYSICONINDEX | SHGFI_PIDL);
			lvItem.mask  |= LVIF_IMAGE | LVIF_PARAM;
			lvItem.iImage = shFileInfo.iIcon;
			lvItem.lParam = (LPARAM)pidlAbsolute;
			if(attributes & SFGAO_GHOSTED) {
				lvItem.mask	 |= LVIF_STATE;
				lvItem.state	 = LVIS_CUT;
				lvItem.stateMask = LVIS_CUT;
			}
			if(attributes & SFGAO_SHARE) {
				lvItem.mask	 	|= LVIF_STATE;
				lvItem.state	 = INDEXTOOVERLAYMASK(1);
				lvItem.stateMask = LVIS_OVERLAYMASK;
			}
			if(attributes & SFGAO_LINK) {
				lvItem.mask	 	|= LVIF_STATE;
				lvItem.state	 = INDEXTOOVERLAYMASK(2);
				lvItem.stateMask = LVIS_OVERLAYMASK;
			}
			success = (bool)ListView_InsertItem(m_lvhWnd, &lvItem);
			if(!success) break;
		}
		else {		//第1列以外の場合
			success = (bool)ListView_SetItem(m_lvhWnd, &lvItem);
			if(!success) break;
		}
	}
	pShellFolder2->Release();
	return success;
}

//シェル（ファイル、フォールダーの作成、削除、名称変更等）変更通知設定（通知終了はデストラクターで実施）
ULONG CSHELL::SetSHChangeNoticeRegister(HWND hWnd) {

	//Shell通知用構造体entryの設定（監視対象デスクトップ以下）と登録）		
	SHChangeNotifyEntry entry;	//変更通知設定用構造体
	entry.pidl = m_pidlDesktop;	//このPIDL（及びその下位）の変更通知を受け取る
	entry.fRecursive = TRUE;	//TRUEに設定することでPIDLの下位フォールダーの変更も通知される
	m_uRegisterId = SHChangeNotifyRegister(hWnd, SHCNRF_ShellLevel | SHCNRF_NewDelivery,
					SHCNE_CREATE | SHCNE_DELETE | SHCNE_RENAMEITEM | SHCNE_MKDIR |
					SHCNE_RMDIR | SHCNE_RENAMEFOLDER, WM_SHELLCHANGE, 1, &entry);
	return m_uRegisterId;
}

//シェル通知処理（リストビュー）
void CSHELL::NotifyShellChange(PIDLIST_ABSOLUTE pidlAbsolute1, PIDLIST_ABSOLUTE pidlAbsolute2, LONG lEvent) {

	int i, nCount;
	LVITEMW item;
	if(lEvent == SHCNE_CREATE) {			//ファイルが作成された場合
		SFGAOF				attributes;
		PITEMID_CHILD		pidlChild;
		PIDLIST_ABSOLUTE	pidlNew;
		IShellFolder		*pShellFolder;
 		//作成されたファイルのフォールダーオブジェクトの取得
		SHBindToParent(pidlAbsolute1, IID_PPV_ARGS(&pShellFolder), NULL);
		//最新の子のPIDL取得
		pidlNew = ILClone(pidlAbsolute1);	//pidlAbsolute1の複製を作成
		pidlChild = ILFindLastID(pidlNew);	//子のPIDL取得
		//作成されたファイルの属性を取得してリストビューに追加
		attributes = SFGAO_FILESYSTEM | SFGAO_GHOSTED | SFGAO_SHARE | SFGAO_LINK;
		pShellFolder->GetAttributesOf(1, (LPCITEMIDLIST *)&pidlChild, &attributes);
		InsertListItem(pidlNew, pShellFolder, pidlChild, attributes, -1);
 		//フォールダーオブジェクトの解放
		pShellFolder->Release();
		ILFree(pidlNew);					//作成した複製の開放
	}
	else if(lEvent == SHCNE_DELETE) {		//ファイルが削除された場合
		nCount = ListView_GetItemCount(m_lvhWnd);	//アイテム数を取得
		for(i = 0; i < nCount; i++) {
			item.mask		= LVIF_PARAM;
			item.iItem		= i;
			item.iSubItem	= 0;
			ListView_GetItem(m_lvhWnd, &item);
			//アイテムのpidlをを取得、調べて削除ファイルのアイテムを削除
			if(ILIsEqual((PIDLIST_ABSOLUTE)item.lParam, pidlAbsolute1)) {
				ListView_DeleteItem(m_lvhWnd, i);
				break;
			}
		}
	}
	else if (lEvent == SHCNE_RENAMEITEM) {	//ファイル名が変更された場合
		nCount = ListView_GetItemCount(m_lvhWnd);	//アイテム数を取得
		for(i = 0; i < nCount; i++) {
			item.mask		= LVIF_PARAM;
			item.iItem		= i;
			item.iSubItem	= 0;
			ListView_GetItem(m_lvhWnd, &item);
			//アイテムのpidlをを取得、調べて該当ファイルを名称変更
			if (ILIsEqual((PIDLIST_ABSOLUTE)item.lParam, pidlAbsolute1)) {
				TCHAR  szPath[256];
				LPTSTR lpszName;
				SHGetPathFromIDList(pidlAbsolute2, szPath);
				lpszName = PathFindFileName(szPath);
				item.mask		= LVIF_TEXT | LVIF_PARAM;
				item.iItem		= i;
				item.iSubItem	= 0;
				item.pszText	= lpszName;
				item.cchTextMax	= lstrlen(lpszName);
				item.lParam		= (LPARAM)ILClone(pidlAbsolute2);
				ListView_SetItem(m_lvhWnd, &item);
				break;
			}
		}
	}
}

//ファイル処理
bool CSHELL::FileOperation(HWND hWnd, WCHAR flag, WCHAR* From, WCHAR* To = NULL, FILEOP_FLAGS foflag = FOF_NOCONFIRMATION) {

	WCHAR fn[MAX_PATH] = {0};
	lstrcpyW(fn, From);
	SHFILEOPSTRUCTW Shfo;
	ZeroMemory(&Shfo, sizeof(Shfo));
	Shfo.hwnd	= hWnd;
	Shfo.pFrom	= fn;
	Shfo.pTo	= To;
	Shfo.fFlags	= foflag;
	switch(flag) {
	case L'M':
	case L'm':
		Shfo.wFunc = FO_MOVE;
		break;
	case L'C':
	case L'c':
		Shfo.wFunc = FO_COPY;
		break;
	case L'D':
	case L'd':
		Shfo.wFunc = FO_DELETE;
		break;
	case L'R':
	case L'r':
		Shfo.wFunc = FO_RENAME;
		break;
	default:
		break;
	}
	return SHFileOperation(&Shfo);
/*
int SHFileOperationA(
	LPSHFILEOPSTRUCT pFileOp   // SHFILEOPSTRUCT構造体
);
ファイルシステムオブジェクトのコピー・移動・名前の変更・削除を行ないます。
typedef struct _SHFILEOPSTRUCT {
	HWND  hwnd;						//ウィンドウハンドル
	UINT  wFunc;					//実行する操作（FO_MOVE, FO_COPY, FO_DELETE, FO_RENAME）
	LPCTSTR  pFrom;					//1つ以上のファイル名バッファアドレス（フルパス指定、"*"ワイルドカード指定可。0区切りで最後は2つ必要)
	LPCTSTR  pTo;					//目的ファイル名（コピー・移動先ディレクトリ名または変更後のファイル名。使用しない場合はNULL）
	FILEOP_FLAGS fFlags;			//フラグ（FOF_MULTIDESTFILE, FOF_SILENT, FOF_NOCONFIRMATION, FOF_SIMPLEPROGRESS）
	BOOL fAnyOperationsAborted;		// 結果（ユーザーによる中止 はTRUE、それ以外はFALSE）
	LPVOID  hNameMappings;			// ファイル名マッピングオブジェクト（FOF_WANTMAPPINGHANDLE フラグが指定された場合にのみ使用）
	LPCTSTR lpszProgressTitle;		// ダイアログのタイトル（Flags メンバで FOF_SIMPLEPROGRESS が指定されている場合にのみ有効）
} SHFILEOPSTRUCT, FAR *LPSHFILEOPSTRUCT;
SHFileOperation 関数が行なうファイル操作に関しての情報が格納されます。
*/}
