diff foosdk/sdk/libPPUI/wtl-pp.h @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/foosdk/sdk/libPPUI/wtl-pp.h	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,443 @@
+#pragma once
+// Various WTL extensions that are not fb2k specific and can be reused in other WTL based software
+
+#include <Uxtheme.h>
+#include <functional>
+
+#define ATLASSERT_SUCCESS(X) {auto RetVal = (X); ATLASSERT( RetVal ); (void) RetVal; }
+
+#ifdef SubclassWindow // mitigate windowsx.h clash
+#undef SubclassWindow
+#endif
+
+class NoRedrawScope {
+public:
+	NoRedrawScope(HWND p_wnd) throw() : m_wnd(p_wnd) {
+		m_wnd.SetRedraw(FALSE);
+	}
+	~NoRedrawScope() throw() {
+		m_wnd.SetRedraw(TRUE);
+	}
+private:
+	CWindow m_wnd;
+};
+
+class NoRedrawScopeEx {
+public:
+	NoRedrawScopeEx(HWND p_wnd) throw() : m_wnd(p_wnd) {
+		if (m_wnd.IsWindowVisible()) {
+			m_active = true;
+			m_wnd.SetRedraw(FALSE);
+		}
+	}
+	~NoRedrawScopeEx() throw() {
+		if (m_active) {
+			m_wnd.SetRedraw(TRUE);
+			m_wnd.RedrawWindow(NULL,NULL,RDW_INVALIDATE|RDW_ERASE|RDW_ALLCHILDREN);
+		}
+	}
+	NoRedrawScopeEx(const NoRedrawScopeEx&) = delete;
+	void operator=(const NoRedrawScopeEx&) = delete;
+private:
+	bool m_active = false;
+	CWindow m_wnd;
+};
+
+class NoRedrawControl {
+public:
+	CWindow m_wnd;
+
+	void operator++() {
+		m_count++;
+		if (m_wnd.IsWindowVisible()) {
+			m_active = true;
+			m_wnd.SetRedraw(FALSE);
+		}
+	}
+	void operator--() {
+		if (--m_count == 0 && m_active) {
+			m_wnd.SetRedraw(TRUE);
+			m_wnd.RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN);
+			m_active = false;
+		}
+	}
+	int m_count = 0;
+	bool m_active = false;
+
+	NoRedrawControl(HWND wnd = NULL) : m_wnd(wnd) {}
+	void operator=(const NoRedrawControl&) = delete;
+	NoRedrawControl(const NoRedrawControl&) = delete;
+};
+
+LRESULT RelayEraseBkgnd(HWND p_from, HWND p_to, HDC p_dc);
+void InjectParentEraseHandler(HWND);
+void InjectEraseHandler(HWND, HWND sendTo);
+void InjectParentCtlColorHandler(HWND);
+void BounceNextDlgCtl(HWND wnd, HWND wndTo);
+
+
+
+#define MSG_WM_ERASEBKGND_PARENT() \
+	if (uMsg == WM_ERASEBKGND) { \
+		lResult = ::RelayEraseBkgnd(hWnd, ::GetParent(hWnd), (HDC)wParam); \
+		return TRUE; \
+	}
+
+#define MSG_WM_ERASEBKGND_TO(wndTarget) \
+	if (uMsg == WM_ERASEBKGND) { \
+		lResult = ::RelayEraseBkgnd(hWnd, wndTarget, (HDC)wParam); \
+		return TRUE; \
+	}
+
+#define MSG_WM_TIMER_EX(timerId, func) \
+	if (uMsg == WM_TIMER && (UINT_PTR)wParam == timerId) \
+	{ \
+		SetMsgHandled(TRUE); \
+		func(); \
+		lResult = 0; \
+		if(IsMsgHandled()) \
+			return TRUE; \
+	}
+
+#define MESSAGE_HANDLER_SIMPLE(msg, func) \
+	if(uMsg == msg) \
+	{ \
+		SetMsgHandled(TRUE); \
+		func(); \
+		lResult = 0; \
+		if(IsMsgHandled()) \
+			return TRUE; \
+	}
+
+// void OnSysCommandHelp()
+#define MSG_WM_SYSCOMMAND_HELP(func) \
+	if (uMsg == WM_SYSCOMMAND && wParam == SC_CONTEXTHELP) \
+	{ \
+		SetMsgHandled(TRUE); \
+		func(); \
+		lResult = 0; \
+		if(IsMsgHandled()) \
+			return TRUE; \
+	}
+
+//BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID)
+#define END_MSG_MAP_HOOK() \
+			break; \
+		default: \
+			return __super::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult, dwMsgMapID); \
+		} \
+		return FALSE; \
+	}
+
+
+// Obsolete, use CImageListManaged instead
+class CImageListContainer : public CImageList {
+public:
+	CImageListContainer() {}
+	~CImageListContainer() {Destroy();}
+
+	void operator=(const CImageListContainer&) = delete;
+	CImageListContainer(const CImageListContainer&) = delete;
+};
+
+
+template<bool managed> class CThemeT {
+public:
+	CThemeT(HTHEME source = NULL) : m_theme(source) {}
+
+	~CThemeT() {
+		Release();
+	}
+
+	HTHEME OpenThemeData(HWND wnd,LPCWSTR classList) {
+		Release();
+		return m_theme = ::OpenThemeData(wnd, classList);
+	}
+
+	void Release() {
+		HTHEME releaseme = pfc::replace_null_t(m_theme);
+		if (managed && releaseme != NULL) CloseThemeData(releaseme);
+	}
+
+	operator HTHEME() const {return m_theme;}
+	HTHEME m_theme;
+};
+typedef CThemeT<false> CThemeHandle;
+typedef CThemeT<true> CTheme;
+
+
+class CCheckBox : public CButton {
+public:
+	void ToggleCheck(bool state) {SetCheck(state ? BST_CHECKED : BST_UNCHECKED);}
+	bool IsChecked() const {return GetCheck() == BST_CHECKED;}
+
+	CCheckBox(HWND hWnd = NULL) : CButton(hWnd) { }
+	CCheckBox & operator=(HWND wnd) {m_hWnd = wnd; return *this; }
+};
+
+class CEditPPHooks : public CWindowImpl<CEditPPHooks, CEdit> {
+public:
+	bool HandleCtrlA = true, NoEscSteal = false, NoEnterSteal = false, WantAllKeys = false;
+
+	std::function<void ()> onEnterKey;
+	std::function<void ()> onEscKey;
+	
+	CEditPPHooks(CMessageMap * hookMM = nullptr, int hookMMID = 0) : m_hookMM(hookMM), m_hookMMID(hookMMID) {}
+
+	BEGIN_MSG_MAP_EX(CEditPPHooks)
+		MSG_WM_KEYDOWN(OnKeyDown)
+		MSG_WM_CHAR(OnChar)
+		MSG_WM_GETDLGCODE(OnEditGetDlgCode)
+
+		if ( m_hookMM != nullptr ) {
+
+			CHAIN_MSG_MAP_ALT_MEMBER( ( * m_hookMM ), m_hookMMID );
+
+		}
+
+	END_MSG_MAP()
+
+	static void DeleteLastWord( CEdit wnd, bool bForward = false );
+private:
+	static bool isSpecial( wchar_t c ) {
+		return (unsigned) c < ' ';
+	}
+	static bool isWordDelimiter( wchar_t c ) {
+		return c == ' ' || c == ',' || c == '.' || c == ';' || c == ':';
+	}
+	void OnChar(UINT nChar, UINT, UINT nFlags) {
+		if (m_suppressChar != 0) {
+			if (nChar == m_suppressChar) return;
+		}
+		if (m_suppressScanCode != 0) {
+			UINT code = nFlags & 0xFF;
+			if (code == m_suppressScanCode) return;
+		}
+		SetMsgHandled(FALSE);
+	}
+	void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
+		(void)nRepCnt;
+		m_suppressChar = 0;
+		m_suppressScanCode = 0;
+		if (HandleCtrlA) {
+			if (nChar == 'A') {
+				if (GetHotkeyModifierFlags() == MOD_CONTROL) {
+					m_suppressScanCode = nFlags & 0xFF;
+					this->SetSelAll(); return;
+				}
+			}
+			if ( nChar == VK_BACK ) {
+				if (GetHotkeyModifierFlags() == MOD_CONTROL) {
+					m_suppressScanCode = nFlags & 0xFF;
+					DeleteLastWord( *this ) ; return;
+				}
+			}
+			if ( nChar == VK_DELETE ) {
+				if (GetHotkeyModifierFlags() == MOD_CONTROL) {
+					m_suppressScanCode = nFlags & 0xFF;
+					DeleteLastWord( *this, true ) ; return;
+				}
+			}
+			if ( nChar == VK_RETURN && onEnterKey ) {
+				m_suppressChar = nChar;
+				onEnterKey(); return;
+			}
+			if ( nChar == VK_ESCAPE && onEscKey ) {
+				m_suppressChar = nChar;
+				onEscKey(); return;
+			}
+		}
+		SetMsgHandled(FALSE);
+	}
+	UINT OnEditGetDlgCode(LPMSG lpMsg) {
+		if (WantAllKeys) return DLGC_WANTALLKEYS;
+		if (lpMsg == NULL) {
+			SetMsgHandled(FALSE); return 0;
+		} else {
+			switch(lpMsg->message) {
+			case WM_KEYDOWN:
+			case WM_SYSKEYDOWN:
+				switch(lpMsg->wParam) {
+				case VK_ESCAPE:
+					if (onEscKey) {
+						return DLGC_WANTMESSAGE;
+					}
+					SetMsgHandled(!!NoEscSteal);
+					return 0;
+				case VK_RETURN:
+					if (onEnterKey) {
+						return DLGC_WANTMESSAGE;
+					}
+					SetMsgHandled(!!NoEnterSteal);
+					return 0;
+				default:
+					SetMsgHandled(FALSE); return 0;
+				}
+			default:
+				SetMsgHandled(FALSE); return 0;
+
+			}
+		}
+	}
+	UINT m_suppressChar = 0, m_suppressScanCode = 0;
+	CMessageMap * const m_hookMM;
+	const int m_hookMMID;
+};
+
+
+class CEditNoEscSteal : public CEdit {
+public:
+	void SubclassWindow(HWND wnd) {
+		this->Attach(wnd);
+		SubclassThisWindow(wnd);
+	}
+	static void SubclassThisWindow(HWND wnd) {
+		SetWindowSubclass(wnd, proc, 0, 0);
+	}
+private:
+	static LRESULT CALLBACK proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR, DWORD_PTR) {
+		if ( uMsg == WM_GETDLGCODE ) {
+			auto lpMsg = reinterpret_cast<LPMSG>(lParam);
+			if (lpMsg != NULL) {
+				switch(lpMsg->message) {
+				case WM_KEYDOWN:
+				case WM_SYSKEYDOWN:
+					switch(lpMsg->wParam) {
+					case VK_ESCAPE:
+						return 0;
+					}
+				}
+			}
+		}
+		return DefSubclassProc(hWnd, uMsg, wParam, lParam);
+	}
+};
+
+class CEditNoEnterEscSteal : public CEdit {
+public:
+	void SubclassWindow(HWND wnd) {
+		this->Attach(wnd);
+		SubclassThisWindow(wnd);
+	}
+	static void SubclassThisWindow(HWND wnd) {
+		SetWindowSubclass(wnd, proc, 0, 0);
+	}
+private:
+	static LRESULT CALLBACK proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR, DWORD_PTR) {
+		if ( uMsg == WM_GETDLGCODE ) {
+			auto lpMsg = reinterpret_cast<LPMSG>(lParam);
+			if (lpMsg != NULL) {
+				switch(lpMsg->message) {
+				case WM_KEYDOWN:
+				case WM_SYSKEYDOWN:
+					switch(lpMsg->wParam) {
+					case VK_RETURN:
+					case VK_ESCAPE:
+						return 0;
+					}
+				}
+			}
+		}
+		return DefSubclassProc(hWnd, uMsg, wParam, lParam);
+	}
+};
+
+
+
+class CWindowClassUnregisterScope {
+public:
+	CWindowClassUnregisterScope() : name() {}
+	const TCHAR * name;
+	void Set(const TCHAR * n) {ATLASSERT( name == NULL ); name = n; }
+	bool IsActive() const {return name != NULL;}
+	void CleanUp() {
+		const TCHAR * n = name; name = NULL;
+		if (n != NULL) ATLASSERT_SUCCESS( UnregisterClass(n, (HINSTANCE)&__ImageBase) );
+	}
+	~CWindowClassUnregisterScope() {CleanUp();}
+};
+
+
+// CWindowRegisteredT
+// Minimalistic wrapper for registering own window classes that can be created by class name, included in dialogs and such.
+// Usage:
+// class myClass : public CWindowRegisteredT<myClass> {...};
+// Call myClass::Register() before first use
+template<typename TClass, typename TBaseClass = CWindow>
+class CWindowRegisteredT : public TBaseClass, public CMessageMap {
+public:
+	static UINT GetClassStyle() {
+		return CS_VREDRAW | CS_HREDRAW;
+	}
+	static HCURSOR GetCursor() {
+		return ::LoadCursor(NULL, IDC_ARROW);
+	}
+
+	BEGIN_MSG_MAP_EX(CWindowRegisteredT)
+	END_MSG_MAP()
+
+
+	static void Register() {
+		static CWindowClassUnregisterScope scope;
+		if (!scope.IsActive()) {
+			WNDCLASS wc = {};
+			wc.style = TClass::GetClassStyle();
+			wc.cbWndExtra = sizeof(void*);
+			wc.lpszClassName = TClass::GetClassName();
+			wc.lpfnWndProc = myWindowProc;
+			wc.hInstance = (HINSTANCE)&__ImageBase;
+			wc.hCursor = TClass::GetCursor();
+			ATLASSERT_SUCCESS( RegisterClass(&wc) != 0 );
+			scope.Set(wc.lpszClassName);
+		}
+	}
+protected:
+	virtual ~CWindowRegisteredT() {}
+private:
+	static LRESULT CALLBACK myWindowProc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp) {
+		TClass * i = NULL;
+		if (msg == WM_NCCREATE) {
+			i = new TClass;
+			i->Attach(wnd);
+			::SetWindowLongPtr(wnd, 0, reinterpret_cast<LONG_PTR>(i));
+		} else {
+			i = reinterpret_cast<TClass*>( ::GetWindowLongPtr(wnd, 0) );
+		}
+		LRESULT r;
+		if (i == NULL || !i->ProcessWindowMessage(wnd, msg, wp, lp, r)) r = ::DefWindowProc(wnd, msg, wp, lp);
+		if (msg == WM_NCDESTROY) {
+			::SetWindowLongPtr(wnd, 0, 0);
+			delete i;
+		}
+		return r;
+	}
+};
+
+
+
+
+class CSRWlock {
+public:
+	CSRWlock() { }
+	
+	static bool HaveAPI() { return true; }
+
+	void EnterShared() {
+		AcquireSRWLockShared( & theLock );
+	}
+	void EnterExclusive() {
+		AcquireSRWLockExclusive( & theLock );
+	}
+	void LeaveShared() {
+		ReleaseSRWLockShared( & theLock );
+	}
+	void LeaveExclusive() {
+		ReleaseSRWLockExclusive( &theLock );
+	}
+
+private:
+	CSRWlock(const CSRWlock&) = delete;
+	void operator=(const CSRWlock&) = delete;
+
+	SRWLOCK theLock = {};
+};