add last backwards-compatible version

This commit is contained in:
2021-12-14 00:33:46 -07:00
parent 68b10d413b
commit b0dd3f07f3
335 changed files with 4746 additions and 19627 deletions

View File

@@ -0,0 +1,23 @@
#pragma once
#include "ATLHelpersLean.h"
// #pragma message("Avoid using this header. #include ATLHelpersLean.h then individual helpers instead")
#include "../helpers/helpers.h"
#include "WTL-PP.h"
#include "misc.h"
#include "GDIUtils.h"
#include "WindowPositionUtils.h"
#include "CDialogResizeHelper.h"
#include "BumpableElem.h"
#include "inplace_edit.h"
#include "inplace_edit_v2.h"
#include "AutoComplete.h"
#include "Controls.h"

View File

@@ -0,0 +1,16 @@
#pragma once
#include "../SDK/foobar2000-winver.h"
#define _SECURE_ATL 1
#include "../SDK/foobar2000.h"
#include <atlbase.h>
#include <atltypes.h>
#include <atlstr.h>
#include <atlapp.h>
#include <atlctrls.h>
#include <atlwin.h>
#include <atlcom.h>
#include <atlcrack.h>

View File

@@ -0,0 +1,184 @@
#include "stdafx.h"
#include <ShlGuid.h> // CLSID_AutoComplete
#include "../helpers/COM_utils.h"
#include "../helpers/dropdown_helper.h"
namespace {
class CEnumString : public IEnumString {
public:
typedef pfc::chain_list_v2_t<pfc::array_t<TCHAR> > t_data;
CEnumString(const t_data & in) : m_data(in) {m_walk = m_data.first();}
CEnumString() {}
void AddString(const TCHAR * in) {
m_data.insert_last()->set_data_fromptr(in, _tcslen(in) + 1);
m_walk = m_data.first();
}
void AddStringU(const char * in, t_size len) {
pfc::array_t<TCHAR> & arr = * m_data.insert_last();
arr.set_size( pfc::stringcvt::estimate_utf8_to_wide( in, len ) );
pfc::stringcvt::convert_utf8_to_wide( arr.get_ptr(), arr.get_size(), in, len);
m_walk = m_data.first();
}
void AddStringU(const char * in) {
pfc::array_t<TCHAR> & arr = * m_data.insert_last();
arr.set_size( pfc::stringcvt::estimate_utf8_to_wide( in ) );
pfc::stringcvt::convert_utf8_to_wide_unchecked( arr.get_ptr(), in );
m_walk = m_data.first();
}
void ResetStrings() {m_walk.invalidate(); m_data.remove_all();}
typedef ImplementCOMRefCounter<CEnumString> TImpl;
COM_QI_BEGIN()
COM_QI_ENTRY(IUnknown)
COM_QI_ENTRY(IEnumString)
COM_QI_END()
HRESULT STDMETHODCALLTYPE Next( ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched) {
if (rgelt == NULL) return E_INVALIDARG;
ULONG done = 0;
while( done < celt && m_walk.is_valid()) {
rgelt[done] = CoStrDup(m_walk->get_ptr());
++m_walk; ++done;
}
if (pceltFetched != NULL) *pceltFetched = done;
return done == celt ? S_OK : S_FALSE;
}
static TCHAR * CoStrDup(const TCHAR * in) {
const size_t lenBytes = (_tcslen(in) + 1) * sizeof(TCHAR);
TCHAR * out = reinterpret_cast<TCHAR*>(CoTaskMemAlloc(lenBytes));
if (out) memcpy(out, in, lenBytes);
return out;
}
HRESULT STDMETHODCALLTYPE Skip(ULONG celt) {
while(celt > 0) {
if (m_walk.is_empty()) return S_FALSE;
--celt; ++m_walk;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE Reset() {
m_walk = m_data.first();
return S_OK;
}
HRESULT STDMETHODCALLTYPE Clone(IEnumString **ppenum) {
*ppenum = new TImpl(*this); return S_OK;
}
private:
t_data m_data;
t_data::const_iterator m_walk;
};
class CACList_History : public CEnumString {
public:
CACList_History(cfg_dropdown_history_mt * var) : m_var(var) {Reset();}
typedef ImplementCOMRefCounter<CACList_History> TImpl;
HRESULT STDMETHODCALLTYPE Reset() {
/*if (core_api::assert_main_thread())*/ {
ResetStrings();
pfc::string8 state; m_var->get_state(state);
for(const char * walk = state;;) {
const char * next = strchr(walk, cfg_dropdown_history_mt::separator);
t_size len = (next != NULL) ? next - walk : ~0;
AddStringU(walk, len);
if (next == NULL) break;
walk = next + 1;
}
}
return __super::Reset();
}
HRESULT STDMETHODCALLTYPE Clone(IEnumString **ppenum) {
*ppenum = new TImpl(*this); return S_OK;
}
private:
cfg_dropdown_history_mt * const m_var;
};
}
HRESULT InitializeSimpleAC(HWND edit, IUnknown * vals, DWORD opts) {
pfc::com_ptr_t<IAutoComplete> ac;
HRESULT hr;
hr = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_ALL, IID_IAutoComplete, (void**)ac.receive_ptr());
if (FAILED(hr)) {
PFC_ASSERT(!"Should not get here - CoCreateInstance/IAutoComplete fail!"); return hr;
}
hr = ac->Init(edit, vals, NULL, NULL);
if (FAILED(hr)) {
PFC_ASSERT(!"Should not get here - ac->Init fail!"); return hr;
}
pfc::com_ptr_t<IAutoComplete2> ac2;
hr = ac->QueryInterface( ac2.receive_ptr() );
if (FAILED(hr)) {
PFC_ASSERT(!"Should not get here - ac->QueryInterface fail!"); return hr;
}
hr = ac2->SetOptions(opts);
if (FAILED(hr)) {
PFC_ASSERT(!"Should not get here - ac2->SetOptions fail!"); return hr;
}
return S_OK;
}
pfc::com_ptr_t<IUnknown> CreateACList(pfc::const_iterator<pfc::string8> valueEnum) {
pfc::com_ptr_t<CEnumString> acl = new CEnumString::TImpl();
while(valueEnum.is_valid()) {
acl->AddStringU(*valueEnum); ++valueEnum;
}
return acl;
}
pfc::com_ptr_t<IUnknown> CreateACList(pfc::const_iterator<const char *> valueEnum) {
pfc::com_ptr_t<CEnumString> acl = new CEnumString::TImpl();
while(valueEnum.is_valid()) {
acl->AddStringU(*valueEnum); ++valueEnum;
}
return acl;
}
pfc::com_ptr_t<IUnknown> CreateACList(pfc::const_iterator<pfc::string> valueEnum) {
pfc::com_ptr_t<CEnumString> acl = new CEnumString::TImpl();
while(valueEnum.is_valid()) {
acl->AddStringU(valueEnum->ptr()); ++valueEnum;
}
return acl;
}
pfc::com_ptr_t<IUnknown> CreateACList() {
return new CEnumString::TImpl();
}
void CreateACList_AddItem(IUnknown * theList, const char * item) {
static_cast<CEnumString*>(theList)->AddStringU( item );
}
HRESULT InitializeEditAC(HWND edit, pfc::const_iterator<pfc::string8> valueEnum, DWORD opts) {
pfc::com_ptr_t<IUnknown> acl = CreateACList(valueEnum);
return InitializeSimpleAC(edit, acl.get_ptr(), opts);
}
HRESULT InitializeEditAC(HWND edit, const char * values, DWORD opts) {
pfc::com_ptr_t<CEnumString> acl = new CEnumString::TImpl();
for(const char * walk = values;;) {
const char * next = strchr(walk, cfg_dropdown_history_mt::separator);
if (next == NULL) {acl->AddStringU(walk, ~0); break;}
acl->AddStringU(walk, next - walk);
walk = next + 1;
}
return InitializeSimpleAC(edit, acl.get_ptr(), opts);
}
HRESULT InitializeDropdownAC(HWND comboBox, cfg_dropdown_history_mt & var, const char * initVal) {
var.on_init(comboBox, initVal);
COMBOBOXINFO ci = {}; ci.cbSize = sizeof(ci);
if (!GetComboBoxInfo(comboBox, &ci)) {
PFC_ASSERT(!"Should not get here - GetComboBoxInfo fail!");
return E_FAIL;
}
pfc::com_ptr_t<IUnknown> acl = new CACList_History::TImpl(&var);
return InitializeSimpleAC(ci.hwndItem, acl.get_ptr(), ACO_AUTOAPPEND|ACO_AUTOSUGGEST);
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include <ShlDisp.h>
class cfg_dropdown_history_mt;
HRESULT InitializeDropdownAC(HWND comboBox, cfg_dropdown_history_mt & var, const char * initVal);
HRESULT InitializeEditAC(HWND edit, const char * values, DWORD opts = ACO_AUTOAPPEND|ACO_AUTOSUGGEST);
HRESULT InitializeEditAC(HWND edit, pfc::const_iterator<pfc::string8> valueEnum, DWORD opts = ACO_AUTOAPPEND|ACO_AUTOSUGGEST);
HRESULT InitializeSimpleAC(HWND edit, IUnknown * vals, DWORD opts);
pfc::com_ptr_t<IUnknown> CreateACList(pfc::const_iterator<pfc::string8> valueEnum);
pfc::com_ptr_t<IUnknown> CreateACList(pfc::const_iterator<const char *> valueEnum);
pfc::com_ptr_t<IUnknown> CreateACList(pfc::const_iterator<pfc::string> valueEnum);
pfc::com_ptr_t<IUnknown> CreateACList();
void CreateACList_AddItem(IUnknown * theList, const char * item);

View File

@@ -0,0 +1,72 @@
#pragma once
#include "CFlashWindow.h"
#include "misc.h"
#include <utility>
template<typename TClass>
class ImplementBumpableElem : public TClass {
private:
typedef ImplementBumpableElem<TClass> TSelf;
public:
template<typename ... arg_t> ImplementBumpableElem( arg_t && ... arg ) : TClass(std::forward<arg_t>(arg) ... ) {_init(); }
BEGIN_MSG_MAP_EX(ImplementBumpableElem)
MSG_WM_DESTROY(OnDestroy)
CHAIN_MSG_MAP(__super)
END_MSG_MAP_HOOK()
void notify(const GUID & p_what, t_size p_param1, const void * p_param2, t_size p_param2size) {
if (p_what == ui_element_notify_visibility_changed && p_param1 == 0 && m_flash.m_hWnd != NULL) m_flash.Deactivate();
__super::notify(p_what, p_param1, p_param2, p_param2size);
}
static bool Bump() {
for(auto walk = instances.cfirst(); walk.is_valid(); ++walk) {
if ((*walk)->_bump()) return true;
}
return false;
}
~ImplementBumpableElem() throw() {
PFC_ASSERT(core_api::is_main_thread());
instances -= this;
}
private:
void OnDestroy() throw() {
m_selfDestruct = true;
m_flash.CleanUp();
SetMsgHandled(FALSE);
}
bool _bump() {
if (m_selfDestruct || m_hWnd == NULL) return false;
//PROBLEM: This assumes we're implementing service_base methods at this point. Explodes if called during constructors/destructors.
if (!this->m_callback->request_activation(this)) return false;
m_flash.Activate(*this);
this->set_default_focus();
return true;
}
void _init() {
m_selfDestruct = false;
PFC_ASSERT(core_api::is_main_thread());
instances += this;
}
static pfc::avltree_t<TSelf*> instances;
bool m_selfDestruct;
CFlashWindow m_flash;
};
template<typename TClass>
pfc::avltree_t<ImplementBumpableElem<TClass> *> ImplementBumpableElem<TClass>::instances;
template<typename TImpl, typename TInterface = ui_element_v2> class ui_element_impl_withpopup : public ui_element_impl<ImplementBumpableElem<TImpl>, TInterface> {
public:
t_uint32 get_flags() {return KFlagHavePopupCommand | KFlagSupportsBump;}
bool bump() {return ImplementBumpableElem<TImpl>::Bump();}
};
template<typename TImpl, typename TInterface = ui_element_v2> class ui_element_impl_visualisation : public ui_element_impl<ImplementBumpableElem<TImpl>, TInterface> {
public:
t_uint32 get_flags() {return KFlagsVisualisation | KFlagSupportsBump;}
bool bump() {return ImplementBumpableElem<TImpl>::Bump();}
};

View File

@@ -0,0 +1,267 @@
#pragma once
#include <functional>
#include "WTL-PP.h"
typedef CWinTraits<WS_CHILD|WS_TABSTOP,0> CButtonLiteTraits;
class CButtonLite : public CWindowImpl<CButtonLite, CWindow, CButtonLiteTraits > {
public:
BEGIN_MSG_MAP_EX(CButtonLite)
MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST, WM_MOUSELAST, MousePassThru);
MSG_WM_SETTEXT(OnSetText)
MSG_WM_PAINT( OnPaint )
MSG_WM_MOUSEMOVE(OnMouseMove)
MSG_WM_LBUTTONDOWN(OnLButtonDown)
MSG_WM_SETFOCUS(OnSetFocus)
MSG_WM_KILLFOCUS(OnKillFocus)
MSG_WM_KEYDOWN(OnKeyDown)
MSG_WM_KEYUP(OnKeyUp)
MSG_WM_CHAR(OnChar)
MSG_WM_ENABLE(OnEnable)
MESSAGE_HANDLER_EX(WM_GETDLGCODE, OnGetDlgCode)
MSG_WM_SETFONT(OnSetFont)
MSG_WM_GETFONT(OnGetFont)
MSG_WM_CREATE(OnCreate)
END_MSG_MAP()
std::function<void () > ClickHandler;
unsigned Measure() {
auto font = myGetFont();
LOGFONT lf;
font.GetLogFont( lf );
MakeBoldFont( lf );
CFont bold;
bold.CreateFontIndirect(&lf);
CWindowDC dc(*this);
auto oldFont = dc.SelectFont( bold );
CSize size (0,0);
CSize sizeSpace (0,0);
dc.GetTextExtent(m_textDrawMe, m_textDrawMe.GetLength(), &size);
dc.GetTextExtent(L" ", 1, &sizeSpace);
dc.SelectFont( oldFont );
return size.cx + sizeSpace.cx;
}
std::function< void (HWND) > TabCycleHandler;
std::function< HBRUSH (CDCHandle) > CtlColorHandler;
std::function< bool (HWND) > WantTabCheck;
CWindow WndCtlColorTarget;
// Rationale: sometimes you want a different text to be presented to accessibility APIs than actually drawn
// For an example, a clear button looks best with a multiplication sign, but the narrator should say "clear" or so when focused
void DrawAlternateText( const wchar_t * textDrawMe ) {
m_textDrawMe = textDrawMe;
}
protected:
CFontHandle m_font;
void OnSetFont(HFONT font, BOOL bRedraw) {
m_font = font; if (bRedraw) Invalidate();
}
HFONT OnGetFont() {
return m_font;
}
LRESULT OnGetDlgCode(UINT, WPARAM wp, LPARAM lp) {
if ( wp == VK_TAB && TabCycleHandler != NULL) {
if ( WantTabCheck == NULL || WantTabCheck(m_hWnd) ) {
TabCycleHandler( m_hWnd );
return DLGC_WANTTAB;
}
}
SetMsgHandled(FALSE); return 0;
}
void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) {
}
void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
switch(nChar) {
case VK_SPACE:
case VK_RETURN:
TogglePressed(true); break;
}
}
void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) {
switch(nChar) {
case VK_SPACE:
case VK_RETURN:
TogglePressed(false);
OnClicked();
break;
}
}
void OnSetFocus(CWindow wndOld) {
m_focused = true; Invalidate();
}
void OnKillFocus(CWindow wndFocus) {
m_focused = false; Invalidate();
}
CFontHandle myGetFont() {
auto f = GetFont();
if ( f == NULL ) f = (HFONT) GetStockObject(DEFAULT_GUI_FONT);
return f;
}
static void MakeBoldFont(LOGFONT & lf ) {
lf.lfWeight += 300;
if (lf.lfWeight > 1000 ) lf.lfWeight = 1000;
}
virtual void DrawBackground( CDCHandle dc, CRect rcClient ) {
HBRUSH brush = NULL;
if ( IsPressed() ) {
CTheme theme;
if (theme.OpenThemeData(*this, L"BUTTON" )) {
DrawThemeBackground(theme, dc, BP_PUSHBUTTON, PBS_PRESSED, rcClient, rcClient );
} else {
DrawFrameControl( dc, rcClient, DFC_BUTTON, DFCS_PUSHED );
}
} else if (m_hot) {
CTheme theme;
if (theme.OpenThemeData(*this, L"BUTTON")) {
DrawThemeBackground(theme, dc, BP_PUSHBUTTON, PBS_HOT, rcClient, rcClient);
} else {
DrawFrameControl(dc, rcClient, DFC_BUTTON, DFCS_HOT);
}
} else if ( CtlColorHandler ) {
brush = CtlColorHandler( dc );
} else {
CWindow target = WndCtlColorTarget;
if ( target == NULL ) target = GetParent();
brush = (HBRUSH) target.SendMessage(WM_CTLCOLORBTN, (WPARAM) dc.m_hDC, (LPARAM) this->m_hWnd );
}
if ( brush != NULL ) {
dc.FillRect( rcClient, brush );
dc.SetBkMode( TRANSPARENT );
}
}
virtual void OnPaint(CDCHandle) {
CPaintDC pdc(*this);
CRect rcClient;
if (! GetClientRect( &rcClient ) ) return;
auto font = myGetFont();
/*
CFont fontOverride;
if ( IsPressed() ) {
LOGFONT lf;
font.GetLogFont( lf );
MakeBoldFont( lf );
fontOverride.CreateFontIndirect( & lf );
font = fontOverride;
}
*/
HFONT oldFont = pdc.SelectFont( font );
DrawBackground( pdc.m_hDC, rcClient );
pdc.SetBkMode( TRANSPARENT );
if ( !IsWindowEnabled() ) {
pdc.SetTextColor( ::GetSysColor(COLOR_GRAYTEXT) );
} else if ( m_focused ) {
pdc.SetTextColor( ::GetSysColor(COLOR_HIGHLIGHT) );
}
pdc.DrawText( m_textDrawMe, m_textDrawMe.GetLength(), &rcClient, DT_VCENTER | DT_CENTER | DT_SINGLELINE | DT_NOPREFIX );
pdc.SelectFont( oldFont );
}
virtual void OnClicked() {
if ( ClickHandler ) {
ClickHandler();
} else {
GetParent().PostMessage( WM_COMMAND, MAKEWPARAM( this->GetDlgCtrlID(), BN_CLICKED ), (LPARAM) m_hWnd );
}
}
bool IsPressed() {return m_pressed; }
private:
int OnCreate(LPCREATESTRUCT lpCreateStruct) {
if ( lpCreateStruct->lpszName != nullptr ) this->m_textDrawMe = lpCreateStruct->lpszName;
SetMsgHandled(FALSE); return 0;
}
void OnEnable(BOOL bEnable) {
Invalidate(); SetMsgHandled(FALSE);
}
void ToggleHot( bool bHot ) {
if ( bHot != m_hot ) {
m_hot = bHot; Invalidate();
}
}
void OnMouseMove(UINT nFlags, CPoint point) {
const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2;
if ((nFlags & maskButtons) != 0) return;
CRect rcClient;
if (!GetClientRect(rcClient)) return;
if (!rcClient.PtInRect( point ) ) return;
ToggleHot( true );
SetCaptureEx([=](UINT cMsg, DWORD cFlags, CPoint cPoint) {
CRect rcClient;
if (!GetClientRect(rcClient)) return false;
if ( cMsg == WM_MOUSEWHEEL || cMsg == WM_MOUSEHWHEEL || (cFlags & maskButtons) != 0 || !rcClient.PtInRect(cPoint) ) {
ToggleHot(false);
SetMsgHandled( FALSE );
return false;
}
return true;
} );
}
void OnLButtonDown(UINT nFlags, CPoint point) {
const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2;
if ( ( nFlags & maskButtons ) != MK_LBUTTON ) return;
TogglePressed( true );
SetCaptureEx([=] (UINT cMsg, DWORD cFlags, CPoint cPoint) {
if (cMsg == WM_MOUSEWHEEL || cMsg == WM_MOUSEHWHEEL) {
TogglePressed(false);
SetMsgHandled(FALSE);
return false;
}
if ( cMsg == WM_LBUTTONUP ) {
if ( m_pressed ) OnClicked();
TogglePressed(false);
return false;
}
CRect rcClient;
if (!GetClientRect( rcClient )) return false;
if ( (cFlags & maskButtons) != (nFlags & maskButtons ) || ! rcClient.PtInRect( cPoint ) ) {
TogglePressed(false);
SetMsgHandled( FALSE ); return false;
}
return true;
} );
}
void TogglePressed( bool bPressed ) {
if ( bPressed != m_pressed ) {
m_pressed = bPressed; Invalidate();
}
}
int OnSetText(LPCTSTR lpstrText) {
m_textDrawMe = lpstrText;
Invalidate(); SetMsgHandled(FALSE);
return 0;
}
typedef std::function< bool(UINT, DWORD, CPoint) > CaptureProc_t;
void SetCaptureEx( CaptureProc_t proc ) {
m_captureProc = proc; SetCapture();
}
LRESULT MousePassThru(UINT msg, WPARAM wp, LPARAM lp) {
auto p = m_captureProc; // create local ref in case something in mid-captureproc clears it
if (p) {
CPoint pt(lp);
if (!p(msg, (DWORD)wp, pt)) {
::ReleaseCapture();
m_captureProc = nullptr;
}
return 0;
}
SetMsgHandled(FALSE);
return 0;
}
CaptureProc_t m_captureProc;
bool m_pressed = false, m_focused = false, m_hot = false;
CString m_textDrawMe;
};

View File

@@ -0,0 +1,141 @@
#include "stdafx.h"
#include "../helpers/win32_misc.h"
#include "CDialogResizeHelper.h"
bool CDialogResizeHelper::EvalRect(UINT id, CRect & out) const {
for(t_size walk = 0; walk < m_table.get_size(); ++walk) {
if (m_table[walk].id == id) {
CRect client; WIN32_OP_D( m_thisWnd.GetClientRect(client) );
return _EvalRect(walk, client.Size(), out);
}
}
return false;
}
bool CDialogResizeHelper::_EvalRect(t_size index, CSize wndSize, CRect & out) const {
CRect rc;
const Param & e = m_table[index];
if (m_origRects.query(e.id, rc)) {
int delta_x = wndSize.cx - m_rcOrigClient.right,
delta_y = wndSize.cy - m_rcOrigClient.bottom;
rc.left += pfc::rint32( e.snapLeft * delta_x );
rc.right += pfc::rint32( e.snapRight * delta_x );
rc.top += pfc::rint32(e.snapTop * delta_y );
rc.bottom += pfc::rint32(e.snapBottom * delta_y );
out = rc;
return true;
}
return false;
}
void CDialogResizeHelper::OnSize(UINT, CSize newSize)
{
if (m_thisWnd != NULL) {
HDWP hWinPosInfo = BeginDeferWindowPos( m_table.get_size() + (m_sizeGrip != NULL ? 1 : 0) );
for(t_size n = 0; n < m_table.get_size(); ++n) {
CRect rc;
if (_EvalRect(n, newSize, rc)) {
hWinPosInfo = DeferWindowPos(hWinPosInfo, m_thisWnd.GetDlgItem(m_table[n].id), 0, rc.left,rc.top,rc.Width(),rc.Height(),SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS);
}
}
if (m_sizeGrip != NULL)
{
RECT rc, rc_grip;
if (m_thisWnd.GetClientRect(&rc) && m_sizeGrip.GetWindowRect(&rc_grip)) {
DWORD flags = SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOCOPYBITS;
if (IsZoomed(m_thisWnd)) flags |= SWP_HIDEWINDOW;
else flags |= SWP_SHOWWINDOW;
hWinPosInfo = DeferWindowPos(hWinPosInfo, m_sizeGrip, NULL, rc.right - (rc_grip.right - rc_grip.left), rc.bottom - (rc_grip.bottom - rc_grip.top), 0, 0, flags );
}
}
EndDeferWindowPos(hWinPosInfo);
//RedrawWindow(m_thisWnd, NULL, NULL, RDW_UPDATENOW | RDW_ALLCHILDREN);
}
SetMsgHandled(FALSE);
}
void CDialogResizeHelper::OnGetMinMaxInfo(LPMINMAXINFO info) const {
CRect r;
const DWORD dwStyle = m_thisWnd.GetWindowLong(GWL_STYLE);
const DWORD dwExStyle = m_thisWnd.GetWindowLong(GWL_EXSTYLE);
if (max_x && max_y)
{
r.left = 0; r.right = max_x;
r.top = 0; r.bottom = max_y;
MapDialogRect(m_thisWnd,&r);
AdjustWindowRectEx(&r, dwStyle, FALSE, dwExStyle);
info->ptMaxTrackSize.x = r.right - r.left;
info->ptMaxTrackSize.y = r.bottom - r.top;
}
if (min_x && min_y)
{
r.left = 0; r.right = min_x;
r.top = 0; r.bottom = min_y;
MapDialogRect(m_thisWnd,&r);
AdjustWindowRectEx(&r, dwStyle, FALSE, dwExStyle);
info->ptMinTrackSize.x = r.right - r.left;
info->ptMinTrackSize.y = r.bottom - r.top;
}
}
void CDialogResizeHelper::OnInitDialog(CWindow thisWnd) {
m_origRects.remove_all();
m_thisWnd = thisWnd;
m_thisWnd.GetClientRect(&m_rcOrigClient);
for(t_size n = 0; n < m_table.get_size(); n++) {
CRect rc;
const UINT id = m_table[n].id;
if (GetChildWindowRect(m_thisWnd,id,&rc)) {
m_origRects.set(id, rc);
}
}
AddSizeGrip();
SetMsgHandled(FALSE);
}
void CDialogResizeHelper::OnDestroy() {
m_sizeGrip = NULL; m_thisWnd = NULL;
SetMsgHandled(FALSE);
}
void CDialogResizeHelper::AddSizeGrip()
{
if (m_thisWnd != NULL && m_sizeGrip == NULL)
{
if (m_thisWnd.GetWindowLong(GWL_STYLE) & WS_POPUP) {
m_sizeGrip = CreateWindowEx(0, WC_SCROLLBAR, _T(""), WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | SBS_SIZEGRIP | SBS_SIZEBOXBOTTOMRIGHTALIGN,
0, 0, CW_USEDEFAULT, CW_USEDEFAULT,
m_thisWnd, (HMENU)0, NULL, NULL);
if (m_sizeGrip != NULL)
{
RECT rc, rc_grip;
if (m_thisWnd.GetClientRect(&rc) && m_sizeGrip.GetWindowRect(&rc_grip)) {
m_sizeGrip.SetWindowPos(NULL, rc.right - (rc_grip.right - rc_grip.left), rc.bottom - (rc_grip.bottom - rc_grip.top), 0, 0, SWP_NOZORDER | SWP_NOSIZE);
}
}
}
}
}
void CDialogResizeHelper::InitTable(const Param* table, t_size tableSize) {
m_table.set_data_fromptr(table, tableSize);
}
void CDialogResizeHelper::InitTable(const ParamOld * table, t_size tableSize) {
m_table.set_size(tableSize);
for(t_size walk = 0; walk < tableSize; ++walk) {
const ParamOld in = table[walk];
Param entry = {};
entry.id = table[walk].id;
if (in.flags & dialog_resize_helper::X_MOVE) entry.snapLeft = entry.snapRight = 1;
else if (in.flags & dialog_resize_helper::X_SIZE) entry.snapRight = 1;
if (in.flags & dialog_resize_helper::Y_MOVE) entry.snapTop = entry.snapBottom = 1;
else if (in.flags & dialog_resize_helper::Y_SIZE) entry.snapBottom = 1;
m_table[walk] = entry;
}
}
void CDialogResizeHelper::InitMinMax(const CRect & range) {
min_x = range.left; min_y = range.top; max_x = range.right; max_y = range.bottom;
}

View File

@@ -0,0 +1,69 @@
#pragma once
#include "../helpers/dialog_resize_helper.h"
#include "WindowPositionUtils.h"
class CDialogResizeHelper : public CMessageMap {
public:
typedef dialog_resize_helper::param ParamOld;
struct Param {
t_uint32 id;
float snapLeft, snapTop, snapRight, snapBottom;
};
private:
void AddSizeGrip();
public:
inline void set_min_size(unsigned x,unsigned y) {min_x = x; min_y = y;}
inline void set_max_size(unsigned x,unsigned y) {max_x = x; max_y = y;}
BEGIN_MSG_MAP_EX(CDialogResizeHelper)
if (uMsg == WM_INITDIALOG) OnInitDialog(hWnd);
MSG_WM_SIZE(OnSize)
MSG_WM_DESTROY(OnDestroy)
MSG_WM_GETMINMAXINFO(OnGetMinMaxInfo)
END_MSG_MAP()
template<typename TParam, t_size paramCount> CDialogResizeHelper(const TParam (& src)[paramCount],CRect const& minMaxRange = CRect(0,0,0,0)) {
InitTable(src, paramCount);
InitMinMax(minMaxRange);
}
void InitTable(const Param* table, t_size tableSize);
void InitTable(const ParamOld * table, t_size tableSize);
void InitMinMax(const CRect & range);
bool EvalRect(UINT id, CRect & out) const;
private:
bool _EvalRect(t_size index, CSize wndSize, CRect & out) const;
void OnGetMinMaxInfo(LPMINMAXINFO lpMMI) const;
void OnSize(UINT nType, CSize size);
void OnInitDialog(CWindow thisWnd);
void OnDestroy();
pfc::array_t<Param> m_table;
pfc::map_t<UINT, CRect> m_origRects;
CRect m_rcOrigClient;
CWindow m_thisWnd, m_sizeGrip;
unsigned min_x,min_y,max_x,max_y;
};
template<typename TTracker> class CDialogResizeHelperTracking : public CDialogResizeHelper {
public:
template<typename TParam, t_size paramCount, typename TCfgVar> CDialogResizeHelperTracking(const TParam (& src)[paramCount],CRect const& minMaxRange, TCfgVar & cfgVar) : CDialogResizeHelper(src, minMaxRange), m_tracker(cfgVar) {}
BEGIN_MSG_MAP_EX(CDialogResizeHelperST)
CHAIN_MSG_MAP(CDialogResizeHelper)
CHAIN_MSG_MAP_MEMBER(m_tracker)
END_MSG_MAP()
private:
TTracker m_tracker;
};
typedef CDialogResizeHelperTracking<cfgDialogSizeTracker> CDialogResizeHelperST;
typedef CDialogResizeHelperTracking<cfgDialogPositionTracker> CDialogResizeHelperPT;
#define REDRAW_DIALOG_ON_RESIZE() if (uMsg == WM_SIZE) RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN);

View File

@@ -0,0 +1,3 @@
#include "stdafx.h"
#include "CEditWithButtons.h"
// All in the header - CPP present to make MS intellisense stop showing nonsensical errors

View File

@@ -0,0 +1,366 @@
#pragma once
#include <list>
#include <string>
#include <functional>
#include <memory>
#include "WTL-PP.h"
#include "CButtonLite.h"
class CEditWithButtons : public CEditPPHooks {
public:
CEditWithButtons(::ATL::CMessageMap * hookMM = nullptr, int hookMMID = 0) : CEditPPHooks(hookMM, hookMMID), m_fixedWidth() {}
enum {
MSG_CHECKCONDITIONS = WM_USER+13,
MSG_CHECKCONDITIONS_MAGIC1 = 0xaec66f0c,
MSG_CHECKCONDITIONS_MAGIC2 = 0x180c2f35,
};
BEGIN_MSG_MAP_EX(CEditWithButtons)
MSG_WM_SETFONT(OnSetFont)
MSG_WM_WINDOWPOSCHANGED(OnPosChanged)
MSG_WM_CTLCOLORBTN(OnColorBtn)
MESSAGE_HANDLER_EX(WM_GETDLGCODE, OnGetDlgCode)
MSG_WM_KEYDOWN(OnKeyDown)
MSG_WM_CHAR(OnChar)
// MSG_WM_SETFOCUS( OnSetFocus )
// MSG_WM_KILLFOCUS( OnKillFocus )
MSG_WM_ENABLE(OnEnable)
MSG_WM_SETTEXT( OnSetText )
MESSAGE_HANDLER_EX(WM_PAINT, CheckConditionsTrigger)
MESSAGE_HANDLER_EX(WM_CUT, CheckConditionsTrigger)
MESSAGE_HANDLER_EX(WM_PASTE, CheckConditionsTrigger)
MESSAGE_HANDLER_EX(MSG_CHECKCONDITIONS, OnCheckConditions)
CHAIN_MSG_MAP(CEditPPHooks)
END_MSG_MAP()
void SubclassWindow( HWND wnd ) {
CEditPPHooks::SubclassWindow( wnd );
this->ModifyStyle(0, WS_CLIPCHILDREN);
RefreshButtons();
}
typedef std::function<void () > handler_t;
typedef std::function<bool (const wchar_t*) > condition_t;
void AddMoreButton( std::function<void ()> f ) {
AddButton(L"\x2026", f);
}
void AddClearButton( const wchar_t * clearVal = L"") {
std::wstring clearValCopy ( clearVal );
auto handler = [this, clearValCopy] {
this->SetWindowText(clearValCopy.c_str());
};
auto condition = [clearValCopy] ( const wchar_t * txt ) -> bool {
return clearValCopy != txt;
};
// Present "clear" to accessibility APIs but actually draw a multiplication x sign
AddButton(L"clear", handler, condition, L"\x00D7");
}
void AddButton( const wchar_t * str, handler_t handler, condition_t condition = nullptr, const wchar_t * drawAlternateText = nullptr ) {
Button_t btn;
btn.handler = handler;
btn.title = str;
btn.condition = condition;
btn.visible = EvalCondition( btn, nullptr );
if ( drawAlternateText != nullptr ) {
btn.titleDraw = drawAlternateText;
}
m_buttons.push_back(std::move(btn) );
RefreshButtons();
}
static unsigned DefaultFixedWidth() {
return GetSystemMetrics(SM_CXVSCROLL) * 3 / 4;
}
void SetFixedWidth(unsigned fw = DefaultFixedWidth() ) {
m_fixedWidth = fw;
RefreshButtons();
}
CRect RectOfButton( const wchar_t * text ) {
for ( auto i = m_buttons.begin(); i != m_buttons.end(); ++ i ) {
if ( i->title == text && i->wnd != NULL ) {
CRect rc;
if (i->wnd.GetWindowRect ( rc )) return rc;
}
}
return CRect();
}
void Invalidate() {
__super::Invalidate();
for( auto i = m_buttons.begin(); i != m_buttons.end(); ++i ) {
if (i->wnd != NULL) i->wnd.Invalidate();
}
}
void SetShellFolderAutoComplete() {
SetShellAutoComplete(SHACF_FILESYS_DIRS);
}
void SetShellFileAutoComplete() {
SetShellAutoComplete(SHACF_FILESYS_ONLY);
}
void SetShellAutoComplete(DWORD flags) {
SHAutoComplete(*this, flags);
SetHasAutoComplete();
}
void SetHasAutoComplete(bool bValue = true) {
m_hasAutoComplete = bValue;
}
private:
LRESULT OnCheckConditions( UINT msg, WPARAM wp, LPARAM lp ) {
if ( msg == MSG_CHECKCONDITIONS && wp == MSG_CHECKCONDITIONS_MAGIC1 && lp == MSG_CHECKCONDITIONS_MAGIC2 ) {
this->RefreshConditions(nullptr);
} else {
SetMsgHandled(FALSE);
}
return 0;
}
bool HaveConditions() {
for( auto i = m_buttons.begin(); i != m_buttons.end(); ++i ) {
if ( i->condition ) return true;
}
return false;
}
void PostCheckConditions() {
if ( HaveConditions() ) {
PostMessage( MSG_CHECKCONDITIONS, MSG_CHECKCONDITIONS_MAGIC1, MSG_CHECKCONDITIONS_MAGIC2 );
}
}
LRESULT CheckConditionsTrigger( UINT, WPARAM, LPARAM ) {
PostCheckConditions();
SetMsgHandled(FALSE);
return 0;
}
int OnSetText(LPCTSTR lpstrText) {
PostCheckConditions();
SetMsgHandled(FALSE);
return 0;
}
void OnEnable(BOOL bEnable) {
for( auto i = m_buttons.begin(); i != m_buttons.end(); ++ i ) {
if ( i->wnd != NULL ) {
i->wnd.EnableWindow( bEnable );
}
}
SetMsgHandled(FALSE);
}
void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) {
if (nChar == VK_TAB ) {
return;
}
PostCheckConditions();
SetMsgHandled(FALSE);
}
void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
if ( nChar == VK_TAB ) {
return;
}
SetMsgHandled(FALSE);
}
bool canStealTab() {
if (m_hasAutoComplete) return false;
if (IsShiftPressed()) return false;
if (m_buttons.size() == 0) return false;
return true;
}
UINT OnGetDlgCode(UINT, WPARAM wp, LPARAM lp) {
if ( wp == VK_TAB && canStealTab() ) {
for (auto i = m_buttons.begin(); i != m_buttons.end(); ++ i ) {
if ( i->visible ) {
TabFocusThis(i->wnd);
return DLGC_WANTTAB;
}
}
}
SetMsgHandled(FALSE); return 0;
}
void OnSetFocus(HWND) {
this->ModifyStyleEx(0, WS_EX_CONTROLPARENT ); SetMsgHandled(FALSE);
}
void OnKillFocus(HWND) {
this->ModifyStyleEx(WS_EX_CONTROLPARENT, 0 ); SetMsgHandled(FALSE);
}
HBRUSH OnColorBtn(CDCHandle dc, CButton button) {
if ( (this->GetStyle() & ES_READONLY) != 0 || !this->IsWindowEnabled() ) {
return (HBRUSH) GetParent().SendMessage( WM_CTLCOLORSTATIC, (WPARAM) dc.m_hDC, (LPARAM) m_hWnd );
} else {
return (HBRUSH) GetParent().SendMessage( WM_CTLCOLOREDIT, (WPARAM) dc.m_hDC, (LPARAM) m_hWnd );
}
}
void OnPosChanged(LPWINDOWPOS lpWndPos) {
Layout(); SetMsgHandled(FALSE);
}
struct Button_t {
std::wstring title, titleDraw;
handler_t handler;
std::shared_ptr<CButtonLite> buttonImpl;
CWindow wnd;
bool visible;
condition_t condition;
};
void OnSetFont(CFontHandle font, BOOL bRedraw) {
CRect rc;
if (GetClientRect(&rc)) {
Layout(rc.Size(), font);
}
SetMsgHandled(FALSE);
}
void RefreshButtons() {
if ( m_hWnd != NULL && m_buttons.size() > 0 ) {
Layout();
}
}
void Layout( ) {
CRect rc;
if (GetClientRect(&rc)) {
Layout(rc.Size(), NULL);
}
}
static bool IsShiftPressed() {
return (GetKeyState(VK_SHIFT) & 0x8000) ? true : false;
}
CWindow FindDialog() {
return GetParent();
}
void TabFocusThis(HWND wnd) {
FindDialog().PostMessage(WM_NEXTDLGCTL, (WPARAM) wnd, TRUE );
}
void TabFocusPrevNext(bool bPrev) {
FindDialog().PostMessage(WM_NEXTDLGCTL, bPrev ? TRUE : FALSE, FALSE);
}
void TabCycleButtons(HWND wnd) {
for( auto i = m_buttons.begin(); i != m_buttons.end(); ++ i ) {
if ( i->wnd == wnd ) {
if (IsShiftPressed()) {
// back
for ( ;; ) {
if (i == m_buttons.begin()) {
TabFocusThis(m_hWnd); break;
} else {
--i;
if ( i->visible ) {
TabFocusThis(i->wnd);
break;
}
}
}
} else {
// forward
for ( ;; ) {
++i;
if (i == m_buttons.end()) {
TabFocusThis(m_hWnd);
TabFocusPrevNext(false);
break;
} else {
if ( i->visible ) {
TabFocusThis(i->wnd);
break;
}
}
}
}
return;
}
}
}
bool ButtonWantTab( HWND wnd ) {
if ( IsShiftPressed() ) return true;
if ( m_buttons.size() == 0 ) return false; // should not be possible
auto last = m_buttons.rbegin();
if ( wnd == last->wnd ) return false; // not for last button
return true;
}
bool EvalCondition( Button_t & btn, const wchar_t * newText ) {
if (!btn.condition) return true;
if ( newText != nullptr ) return btn.condition( newText );
TCHAR text[256] = {};
GetWindowText(text, 256);
text[255] = 0;
return btn.condition( text );
}
void RefreshConditions( const wchar_t * newText ) {
bool changed = false;
for( auto i = m_buttons.begin(); i != m_buttons.end(); ++i ) {
bool status = EvalCondition(*i, newText);
if ( status != i->visible ) {
i->visible = status; changed = true;
}
}
if ( changed ) {
Layout();
}
}
void Layout(CSize size, CFontHandle fontSetMe) {
if ( m_buttons.size() == 0 ) return;
int walk = size.cx;
HDWP dwp = BeginDeferWindowPos( (int) m_buttons.size() );
for( auto iter = m_buttons.rbegin(); iter != m_buttons.rend(); ++iter ) {
if (! iter->visible ) {
if (::GetFocus() == iter->wnd ) {
this->SetFocus();
}
::DeferWindowPos( dwp, iter->wnd, NULL, 0,0,0,0, SWP_NOZORDER | SWP_HIDEWINDOW | SWP_NOMOVE | SWP_NOSIZE );
continue;
}
if (iter->wnd == NULL) {
auto b = std::make_shared< CButtonLite > ( );
iter->buttonImpl = b;
b->Create( *this, NULL, iter->title.c_str() );
if ( iter->titleDraw.length() > 0 ) b->DrawAlternateText( iter->titleDraw.c_str() );
CFontHandle font = fontSetMe;
if ( font == NULL ) font = GetFont();
b->SetFont( font );
b->ClickHandler = iter->handler;
b->CtlColorHandler = [=] (CDCHandle dc) -> HBRUSH {
return this->OnColorBtn( dc, NULL );
};
b->TabCycleHandler = [=] (HWND wnd) {
TabCycleButtons(wnd);
};
b->WantTabCheck = [=] (HWND wnd) -> bool {
return ButtonWantTab(wnd);
};
if (! IsWindowEnabled( ) ) b->EnableWindow(FALSE);
iter->wnd = * b;
} else if ( fontSetMe ) {
iter->wnd.SetFont( fontSetMe );
}
unsigned delta = MeasureButton(*iter);
int left = walk - delta;
if ( iter->wnd != NULL ) {
CRect rc;
rc.top = 0;
rc.bottom = size.cy;
rc.left = left;
rc.right = walk;
::DeferWindowPos( dwp, iter->wnd, NULL, rc.left, rc.top, rc.Width(), rc.Height(), SWP_NOZORDER | SWP_SHOWWINDOW);
}
walk = left;
}
EndDeferWindowPos( dwp );
this->SetMargins(0, size.cx - walk, EC_RIGHTMARGIN );
}
unsigned MeasureButton(Button_t const & button ) {
if ( m_fixedWidth != 0 ) return m_fixedWidth;
return button.buttonImpl->Measure();
}
unsigned m_fixedWidth;
std::list< Button_t > m_buttons;
bool m_hasAutoComplete = false;
};

View File

@@ -0,0 +1,67 @@
#pragma once
typedef CWinTraits<WS_POPUP,WS_EX_TRANSPARENT|WS_EX_LAYERED|WS_EX_TOPMOST|WS_EX_TOOLWINDOW> CFlashWindowTraits;
class CFlashWindow : public CWindowImpl<CFlashWindow,CWindow,CFlashWindowTraits> {
public:
void Activate(CWindow parent) {
ShowAbove(parent);
m_tickCount = 0;
SetTimer(KTimerID, 500);
}
void Deactivate() throw() {
ShowWindow(SW_HIDE); KillTimer(KTimerID);
}
void ShowAbove(CWindow parent) {
if (m_hWnd == NULL) {
WIN32_OP( Create(NULL) != NULL );
}
CRect rect;
WIN32_OP_D( parent.GetWindowRect(rect) );
WIN32_OP_D( SetWindowPos(NULL,rect,SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW) );
m_parent = parent;
}
void CleanUp() throw() {
if (m_hWnd != NULL) DestroyWindow();
}
BEGIN_MSG_MAP_EX(CFlashWindow)
MSG_WM_CREATE(OnCreate)
MSG_WM_TIMER(OnTimer)
MSG_WM_DESTROY(OnDestroy)
END_MSG_MAP()
DECLARE_WND_CLASS_EX(TEXT("{2E124D52-131F-4004-A569-2316615BE63F}"),0,COLOR_HIGHLIGHT);
private:
void OnDestroy() throw() {
KillTimer(KTimerID);
}
enum {
KTimerID = 0x47f42dd0
};
void OnTimer(WPARAM id) {
if (id == KTimerID) {
switch(++m_tickCount) {
case 1:
ShowWindow(SW_HIDE);
break;
case 2:
ShowAbove(m_parent);
break;
case 3:
ShowWindow(SW_HIDE);
KillTimer(KTimerID);
break;
}
}
}
LRESULT OnCreate(LPCREATESTRUCT) throw() {
SetLayeredWindowAttributes(*this,0,128,LWA_ALPHA);
return 0;
}
CWindow m_parent;
t_uint32 m_tickCount;
};

View File

@@ -0,0 +1,47 @@
#pragma once
#include "../ATLHelpers/misc.h"
typedef CWinTraits<WS_POPUP,WS_EX_LAYERED> __COverlayWindowTraits;
class CIconOverlayWindow : public CWindowImpl<CIconOverlayWindow,CWindow,__COverlayWindowTraits> {
public:
DECLARE_WND_CLASS_EX(TEXT("{384298D0-4370-4f9b-9C36-49FC1A396DC7}"),0,(-1));
void AttachIcon(HICON p_icon) {m_icon = p_icon;}
bool HaveIcon() const {return m_icon != NULL;}
enum {
ColorKey = 0xc0ffee
};
BEGIN_MSG_MAP(CIconOverlayWindow)
MESSAGE_HANDLER(WM_CREATE,OnCreate);
MESSAGE_HANDLER(WM_PAINT,OnPaint);
MESSAGE_HANDLER(WM_ERASEBKGND,OnEraseBkgnd);
END_MSG_MAP()
private:
LRESULT OnCreate(UINT,WPARAM,LPARAM,BOOL&) {
::SetLayeredWindowAttributes(*this,ColorKey,0,LWA_COLORKEY);
return 0;
}
LRESULT OnEraseBkgnd(UINT,WPARAM p_wp,LPARAM,BOOL& bHandled) {
CRect rcClient;
WIN32_OP_D( GetClientRect(rcClient) );
CDCHandle((HDC)p_wp).FillSolidRect(rcClient,ColorKey);
return 1;
}
LRESULT OnPaint(UINT,WPARAM,LPARAM,BOOL& bHandled) {
if (m_icon != NULL) {
CPaintDC dc(*this);
CRect client;
WIN32_OP_D( GetClientRect(&client) );
dc.DrawIconEx(0,0,m_icon,client.right,client.bottom);
//CDCHandle(ps.hdc).DrawIcon(0,0,m_icon);
} else {
bHandled = FALSE;
}
return 0;
}
CIcon m_icon;
};

View File

@@ -0,0 +1,292 @@
#pragma once
#include "CIconOverlayWindow.h"
#include <utility>
template<typename TBase>
class CMiddleDragImpl : public TBase {
private:
typedef CMiddleDragImpl<TBase> TSelf;
enum {
KTimerID = 0x389675f8,
KTimerPeriod = 15,
};
public:
template<typename ... arg_t> CMiddleDragImpl( arg_t && ... arg ) : TBase(std::forward<arg_t>(arg) ... ) { m_active = false; m_accX = m_accY = 0; }
BEGIN_MSG_MAP(TSelf)
MESSAGE_HANDLER(WM_TIMER,OnTimer);
MESSAGE_HANDLER(WM_CAPTURECHANGED,OnCaptureChanged);
MESSAGE_HANDLER(WM_MBUTTONDOWN,OnMButtonDown);
MESSAGE_HANDLER(WM_MBUTTONDBLCLK,OnMButtonDown);
MESSAGE_HANDLER(WM_MBUTTONUP,OnMButtonUp);
MESSAGE_HANDLER(WM_LBUTTONDOWN,OnMouseAction);
MESSAGE_HANDLER(WM_RBUTTONDOWN,OnMouseAction);
MESSAGE_HANDLER(WM_LBUTTONDBLCLK,OnMouseAction);
MESSAGE_HANDLER(WM_RBUTTONDBLCLK,OnMouseAction);
MESSAGE_HANDLER(WM_LBUTTONUP,OnMouseAction);
MESSAGE_HANDLER(WM_RBUTTONUP,OnMouseAction);
MESSAGE_HANDLER(WM_XBUTTONDOWN,OnMouseAction);
MESSAGE_HANDLER(WM_XBUTTONDBLCLK,OnMouseAction);
MESSAGE_HANDLER(WM_XBUTTONUP,OnMouseAction);
MESSAGE_HANDLER(WM_MOUSEMOVE,OnMouseMove);
MESSAGE_HANDLER(WM_DESTROY,OnDestroyPassThru);
CHAIN_MSG_MAP(TBase);
END_MSG_MAP()
protected:
bool CanSmoothScroll() const {
return !m_active;
}
private:
bool m_active, m_dragged;
CPoint m_base;
CIconOverlayWindow m_overlay;
double m_accX, m_accY;
LRESULT OnDestroyPassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) {
if (m_overlay != NULL) m_overlay.DestroyWindow();
bHandled = FALSE;
return 0;
}
LRESULT OnMButtonUp(UINT,WPARAM,LPARAM p_lp,BOOL& bHandled) {
if (m_active /*&& m_dragged*/) {
EndDrag();
}
return 0;
}
LRESULT OnMButtonDown(UINT,WPARAM,LPARAM p_lp,BOOL& bHandled) {
if (m_active) {
EndDrag();
return 0;
}
EndDrag();
SetFocus();
CPoint pt(p_lp);
WIN32_OP_D( ClientToScreen(&pt) );
StartDrag(pt);
return 0;
}
LRESULT OnMouseMove(UINT,WPARAM,LPARAM p_lp,BOOL& bHandled) {
if (m_active) {
if (!m_dragged) {
CPoint pt(p_lp);
WIN32_OP_D( ClientToScreen(&pt) );
if (pt != m_base) {
m_dragged = true;
}
}
bHandled = TRUE;
} else {
bHandled = FALSE;
}
return 0;
}
LRESULT OnMouseAction(UINT,WPARAM,LPARAM,BOOL& bHandled) {
EndDrag();
bHandled = FALSE;
return 0;
}
void StartDrag(CPoint const & p_point) {
::SetCapture(NULL);
if (m_overlay == NULL) {
PFC_ASSERT( m_hWnd != NULL );
if (m_overlay.Create(*this) == NULL) {PFC_ASSERT(!"Should not get here!"); return;}
HANDLE temp;
WIN32_OP_D( (temp = LoadImage(core_api::get_my_instance(),MAKEINTRESOURCE(IDI_SCROLL),IMAGE_ICON,32,32,LR_DEFAULTCOLOR)) != NULL );
m_overlay.AttachIcon((HICON) temp);
}
//todo sanity checks - don't drag when the entire content is visible, perhaps use a different icon when only vertical or horizontal drag is possible
SetCursor(::LoadCursor(NULL,IDC_SIZEALL));
m_active = true;
m_dragged = false;
m_base = p_point;
m_accX = m_accY = 0;
SetCapture();
SetTimer(KTimerID,KTimerPeriod);
{
CSize radius(16,16);
CPoint center (p_point);
CRect rect(center - radius, center + radius);
m_overlay.SetWindowPos(HWND_TOPMOST,rect,SWP_SHOWWINDOW | SWP_NOACTIVATE);
}
}
void EndDrag() {
if (m_active) ::SetCapture(NULL);
}
void HandleEndDrag() {
if (m_active) {
m_active = false;
KillTimer(KTimerID);
if (m_overlay != NULL) m_overlay.ShowWindow(SW_HIDE);
}
}
LRESULT OnCaptureChanged(UINT,WPARAM,LPARAM,BOOL& bHandled) {
HandleEndDrag();
bHandled = FALSE;
return 0;
}
LRESULT OnTimer(UINT,WPARAM p_wp,LPARAM,BOOL& bHandled) {
switch(p_wp) {
case KTimerID:
this->MoveViewOriginDelta(ProcessMiddleDragDelta(CPoint((LPARAM)GetMessagePos()) - m_base));
return 0;
default:
bHandled = FALSE;
return 0;
}
}
static double myPow(double p_val,double p_exp) {
double ex = pow(pfc::abs_t(p_val),p_exp);
return p_val < 0 ? -ex : ex;
}
static double ProcessMiddleDragDeltaInternal(double p_delta) {
p_delta *= (double) KTimerPeriod / 25; /*originally calculated for 25ms timer interval*/
return myPow(p_delta * 0.05,2.0);
}
static double radiusHelper(double p_x,double p_y) {
return sqrt(p_x * p_x + p_y * p_y);
}
CPoint ProcessMiddleDragDelta(CPoint p_delta) {
const CSize dpi = QueryScreenDPIEx();
if (dpi.cx <= 0 || dpi.cy <= 0 || p_delta == CPoint(0,0)) return CPoint(0,0);
const double dpiMulX = 96.0 / (double) dpi.cx;
const double dpiMulY = 96.0 / (double) dpi.cy;
const double deltaTotal = ProcessMiddleDragDeltaInternal( radiusHelper( (double) p_delta.x * dpiMulX, (double) p_delta.y * dpiMulY ) );
double xVal = 0, yVal = 0;
if (p_delta.x == 0) {
yVal = deltaTotal;
} else if (p_delta.y == 0) {
xVal = deltaTotal;
} else {
const double ratio = (double) p_delta.x / (double) p_delta.y;
yVal = sqrt(pfc::sqr_t(deltaTotal) / (1.0 + pfc::sqr_t(ratio)));
xVal = yVal * ratio;
}
xVal = pfc::sgn_t( p_delta.x ) * pfc::abs_t(xVal);
yVal = pfc::sgn_t( p_delta.y ) * pfc::abs_t(yVal);
return CPoint(Round(xVal / dpiMulX, m_accX), Round(yVal / dpiMulY, m_accY));
}
static t_int32 Round(double val, double & acc) {
val += acc;
t_int32 ret = pfc::rint32(val);
acc = val - ret;
return ret;
}
};
template<typename TBase>
class CMiddleDragWrapper : public TBase {
private:
typedef CMiddleDragWrapper<TBase> TSelf;
public:
template<typename ... arg_t> CMiddleDragWrapper(arg_t && ... arg ) : TBase(std::forward<arg_t>(arg) ... ) { m_overflow = CPoint(0, 0); m_lineWidth = CSize(4, 16); }
BEGIN_MSG_MAP(TSelf)
MESSAGE_HANDLER(WM_CAPTURECHANGED,OnCaptureChanged);
CHAIN_MSG_MAP(TBase)
END_MSG_MAP()
void MoveViewOriginDelta(CPoint p_delta) {
MoveViewOriginDeltaLines( MiddleDrag_PixelsToLines( m_overflow, p_delta ) );
}
void MoveViewOriginDeltaLines(CPoint p_delta) {
FireScrollMessage(WM_HSCROLL,p_delta.x);
FireScrollMessage(WM_VSCROLL,p_delta.y);
}
void SetLineWidth(CSize p_width) {m_lineWidth = p_width;}
private:
void FireScrollMessage(UINT p_msg,int p_delta) {
UINT count = (UINT)pfc::abs_t(p_delta);
const UINT which = (p_msg == WM_HSCROLL ? SB_HORZ : SB_VERT);
SCROLLINFO si = {}; si.cbSize = sizeof(si); si.fMask = SIF_PAGE | SIF_RANGE;
if (!this->GetScrollInfo(which, &si) || si.nPage <= 0) return;
while(count >= si.nPage) {
const WPARAM code = p_delta < 0 ? SB_PAGEUP : SB_PAGEDOWN;
this->SendMessage(p_msg, code, 0);
count -= si.nPage;
}
const WPARAM code = p_delta < 0 ? SB_LINEUP : SB_LINEDOWN;
for(UINT walk = 0; walk < count; ++walk) this->SendMessage(p_msg, code, 0);
}
LRESULT OnCaptureChanged(UINT,WPARAM,LPARAM,BOOL& bHandled) {
m_overflow = CPoint(0,0);
bHandled = FALSE;
return 0;
}
static LONG LineToPixelsHelper(LONG & p_overflow,LONG p_pixels,LONG p_dpi,LONG p_lineWidth) {
const int lineWidth = MulDiv(p_lineWidth,p_dpi,96);
if (lineWidth == 0) return 0;
p_overflow += p_pixels;
LONG ret = p_overflow / lineWidth;
p_overflow -= ret * lineWidth;
return ret;
}
CPoint MiddleDrag_PixelsToLines(CPoint & p_overflow,CPoint p_pixels) {
const CSize dpi = QueryScreenDPIEx();
if (dpi.cx <= 0 || dpi.cy <= 0) return CPoint(0,0);
CPoint pt;
pt.x = LineToPixelsHelper(p_overflow.x,p_pixels.x,dpi.cx,m_lineWidth.cx);
pt.y = LineToPixelsHelper(p_overflow.y,p_pixels.y,dpi.cy,m_lineWidth.cy);
return pt;
}
CSize m_lineWidth;
CPoint m_overflow;
};
template<typename TBase = CWindow>
class CWindow_DummyMsgMap : public TBase , public CMessageMap {
public:
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
LRESULT& lResult, DWORD dwMsgMapID = 0) {return FALSE;}
};
typedef CMiddleDragImpl<CMiddleDragWrapper<CWindow_DummyMsgMap<> > > CMiddleDragImplSimple;
template<typename TBase = CWindow>
class CContainedWindow_MsgMap : public CContainedWindowT<CWindow_DummyMsgMap<TBase> > {
protected:
CContainedWindow_MsgMap() : CContainedWindowT<CWindow_DummyMsgMap<TBase> >(pfc::implicit_cast<CMessageMap*>(this)) {}
};
template<typename TBase>
class CMiddleDragImplCtrlHook : public CMiddleDragImpl<CMiddleDragWrapper<CContainedWindow_MsgMap<TBase> > > {
};
template<typename TBase> class CMiddleDragImplCtrlHookEx : public CMiddleDragImplCtrlHook<TBase> {
public:
CMiddleDragImplCtrlHookEx(CMessageMap * hook, DWORD hookMapID = 0) : m_hook(*hook), m_hookMapID(hookMapID) {}
BEGIN_MSG_MAP(CMiddleDragImplCtrlHookEx)
CHAIN_MSG_MAP(CMiddleDragImplCtrlHook<TBase>)
CHAIN_MSG_MAP_ALT_MEMBER(m_hook,m_hookMapID);
END_MSG_MAP()
private:
DWORD const m_hookMapID;
CMessageMap & m_hook;
};

View File

@@ -0,0 +1,166 @@
#pragma once
#include "misc.h"
void PaintSeparatorControl(CWindow wnd);
class CStaticSeparator : public CContainedWindowT<CStatic>, private CMessageMap {
public:
CStaticSeparator() : CContainedWindowT<CStatic>(this, 0) {}
BEGIN_MSG_MAP_EX(CSeparator)
MSG_WM_PAINT(OnPaint)
MSG_WM_SETTEXT(OnSetText)
END_MSG_MAP()
private:
int OnSetText(LPCTSTR lpstrText) {
Invalidate();
SetMsgHandled(FALSE);
return 0;
}
void OnPaint(CDCHandle) {
PaintSeparatorControl(*this);
}
};
template<typename TClass>
class CTextControl : public CWindowRegisteredT<TClass> {
public:
BEGIN_MSG_MAP_EX(CTextControl)
MSG_WM_SETFONT(OnSetFont)
MSG_WM_GETFONT(OnGetFont)
MSG_WM_SETTEXT(OnSetText)
CHAIN_MSG_MAP(__super)
END_MSG_MAP()
private:
HFONT OnGetFont() {
return m_font;
}
void OnSetFont(HFONT font, BOOL bRedraw) {
m_font = font;
if (bRedraw) Invalidate();
}
int OnSetText(LPCTSTR lpstrText) {
Invalidate();SetMsgHandled(FALSE); return 0;
}
CFontHandle m_font;
};
#ifndef VSCLASS_TEXTSTYLE
//
// TEXTSTYLE class parts and states
//
#define VSCLASS_TEXTSTYLE L"TEXTSTYLE"
enum TEXTSTYLEPARTS {
TEXT_MAININSTRUCTION = 1,
TEXT_INSTRUCTION = 2,
TEXT_BODYTITLE = 3,
TEXT_BODYTEXT = 4,
TEXT_SECONDARYTEXT = 5,
TEXT_HYPERLINKTEXT = 6,
TEXT_EXPANDED = 7,
TEXT_LABEL = 8,
TEXT_CONTROLLABEL = 9,
};
enum HYPERLINKTEXTSTATES {
TS_HYPERLINK_NORMAL = 1,
TS_HYPERLINK_HOT = 2,
TS_HYPERLINK_PRESSED = 3,
TS_HYPERLINK_DISABLED = 4,
};
enum CONTROLLABELSTATES {
TS_CONTROLLABEL_NORMAL = 1,
TS_CONTROLLABEL_DISABLED = 2,
};
#endif
class CStaticThemed : public CContainedWindowT<CStatic>, private CMessageMap {
public:
CStaticThemed() : CContainedWindowT<CStatic>(this, 0), m_id(), m_fallback() {}
BEGIN_MSG_MAP_EX(CStaticThemed)
MSG_WM_PAINT(OnPaint)
MSG_WM_THEMECHANGED(OnThemeChanged)
MSG_WM_SETTEXT(OnSetText)
END_MSG_MAP()
void SetThemePart(int id) {m_id = id; if (m_hWnd != NULL) Invalidate();}
private:
int OnSetText(LPCTSTR lpstrText) {
Invalidate();
SetMsgHandled(FALSE);
return 0;
}
void OnThemeChanged() {
m_theme.Release();
m_fallback = false;
}
void OnPaint(CDCHandle) {
if (m_fallback) {
SetMsgHandled(FALSE); return;
}
if (m_theme == NULL) {
m_theme.OpenThemeData(*this, L"TextStyle");
if (m_theme == NULL) {
m_fallback = true; SetMsgHandled(FALSE); return;
}
}
CPaintDC dc(*this);
TCHAR buffer[512] = {};
GetWindowText(buffer, _countof(buffer));
const int txLen = pfc::strlen_max_t(buffer, _countof(buffer));
CRect contentRect;
WIN32_OP_D( GetClientRect(contentRect) );
SelectObjectScope scopeFont(dc, GetFont());
dc.SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
dc.SetBkMode(TRANSPARENT);
if (txLen > 0) {
CRect rcText(contentRect);
DWORD flags = 0;
DWORD style = GetStyle();
if (style & SS_LEFT) flags |= DT_LEFT;
else if (style & SS_RIGHT) flags |= DT_RIGHT;
else if (style & SS_CENTER) flags |= DT_CENTER;
if (style & SS_ENDELLIPSIS) flags |= DT_END_ELLIPSIS;
HRESULT retval = DrawThemeText(m_theme, dc, m_id, 0, buffer, txLen, flags, 0, rcText);
PFC_ASSERT( SUCCEEDED( retval ) );
}
}
int m_id;
CTheme m_theme;
bool m_fallback;
};
class CStaticMainInstruction : public CStaticThemed {
public:
CStaticMainInstruction() { SetThemePart(TEXT_MAININSTRUCTION); }
};
class CSeparator : public CTextControl<CSeparator> {
public:
BEGIN_MSG_MAP_EX(CSeparator)
MSG_WM_PAINT(OnPaint)
MSG_WM_ENABLE(OnEnable)
CHAIN_MSG_MAP(__super)
END_MSG_MAP()
static const TCHAR * GetClassName() {
return _T("foobar2000:separator");
}
private:
void OnEnable(BOOL bEnable) {
Invalidate();
}
void OnPaint(CDCHandle dc) {
PaintSeparatorControl(*this);
}
};

View File

@@ -0,0 +1,253 @@
#pragma once
static HBITMAP CreateDIB24(CSize size) {
struct {
BITMAPINFOHEADER bmi;
} bi = {};
bi.bmi.biSize = sizeof(bi.bmi);
bi.bmi.biWidth = size.cx;
bi.bmi.biHeight = size.cy;
bi.bmi.biPlanes = 1;
bi.bmi.biBitCount = 24;
bi.bmi.biCompression = BI_RGB;
void * bitsPtr;
return CreateDIBSection(NULL, reinterpret_cast<const BITMAPINFO*>(&bi), DIB_RGB_COLORS,&bitsPtr,0,0);
}
static HBITMAP CreateDIB16(CSize size) {
struct {
BITMAPINFOHEADER bmi;
} bi = {};
bi.bmi.biSize = sizeof(bi.bmi);
bi.bmi.biWidth = size.cx;
bi.bmi.biHeight = size.cy;
bi.bmi.biPlanes = 1;
bi.bmi.biBitCount = 16;
bi.bmi.biCompression = BI_RGB;
void * bitsPtr;
return CreateDIBSection(NULL, reinterpret_cast<const BITMAPINFO*>(&bi), DIB_RGB_COLORS,&bitsPtr,0,0);
}
static HBITMAP CreateDIB8(CSize size, const COLORREF palette[256]) {
struct {
BITMAPINFOHEADER bmi;
COLORREF colors[256];
} bi = { };
for(int c = 0; c < 256; ++c ) bi.colors[c] = palette[c];
bi.bmi.biSize = sizeof(bi.bmi);
bi.bmi.biWidth = size.cx;
bi.bmi.biHeight = size.cy;
bi.bmi.biPlanes = 1;
bi.bmi.biBitCount = 8;
bi.bmi.biCompression = BI_RGB;
bi.bmi.biClrUsed = 256;
void * bitsPtr;
return CreateDIBSection(NULL, reinterpret_cast<const BITMAPINFO*>(&bi), DIB_RGB_COLORS,&bitsPtr,0,0);
}
static void CreateScaledFont(CFont & out, CFontHandle in, double scale) {
LOGFONT lf;
WIN32_OP_D( in.GetLogFont(lf) );
int temp = pfc::rint32(scale * lf.lfHeight);
if (temp == 0) temp = pfc::sgn_t(lf.lfHeight);
lf.lfHeight = temp;
WIN32_OP_D( out.CreateFontIndirect(&lf) != NULL );
}
static void CreateScaledFontEx(CFont & out, CFontHandle in, double scale, int weight) {
LOGFONT lf;
WIN32_OP_D( in.GetLogFont(lf) );
int temp = pfc::rint32(scale * lf.lfHeight);
if (temp == 0) temp = pfc::sgn_t(lf.lfHeight);
lf.lfHeight = temp;
lf.lfWeight = weight;
WIN32_OP_D( out.CreateFontIndirect(&lf) != NULL );
}
static void CreatePreferencesHeaderFont(CFont & out, CWindow source) {
CreateScaledFontEx(out, source.GetFont(), 1.3, FW_BOLD);
}
static void CreatePreferencesHeaderFont2(CFont & out, CWindow source) {
CreateScaledFontEx(out, source.GetFont(), 1.1, FW_BOLD);
}
template<typename TCtrl>
class CAltFontCtrl : public TCtrl {
public:
void Initialize(CWindow wnd, double scale, int weight) {
CreateScaledFontEx(m_font, wnd.GetFont(), scale, weight);
_initWnd(wnd);
}
void MakeHeader(CWindow wnd) {
CreatePreferencesHeaderFont(m_font, wnd);
_initWnd(wnd);
}
void MakeHeader2(CWindow wnd) {
CreatePreferencesHeaderFont2(m_font, wnd);
_initWnd(wnd);
}
private:
void _initWnd(CWindow wnd) {
SubclassWindow(wnd); SetFont(m_font);
}
CFont m_font;
};
class CFontScaled : public CFont {
public:
CFontScaled(HFONT _in, double scale) {
CreateScaledFont(*this, _in, scale);
}
};
class DCClipRgnScope {
public:
DCClipRgnScope(HDC dc) : m_dc(dc) {
m_dc.GetClipRgn(m_rgn);
}
~DCClipRgnScope() {
m_dc.SelectClipRgn(m_rgn);
}
HRGN OldVal() const throw() {return m_rgn;}
PFC_CLASS_NOT_COPYABLE_EX(DCClipRgnScope)
private:
CDCHandle m_dc;
CRgn m_rgn;
};
static HBRUSH MakeTempBrush(HDC dc, COLORREF color) throw() {
SetDCBrushColor(dc, color); return (HBRUSH) GetStockObject(DC_BRUSH);
}
class CDCBrush : public CBrushHandle {
public:
CDCBrush(HDC dc, COLORREF color) throw() {
m_dc = dc;
m_oldCol = m_dc.SetDCBrushColor(color);
m_hBrush = (HBRUSH) GetStockObject(DC_BRUSH);
}
~CDCBrush() throw() {
m_dc.SetDCBrushColor(m_oldCol);
}
PFC_CLASS_NOT_COPYABLE_EX(CDCBrush)
private:
CDCHandle m_dc;
COLORREF m_oldCol;
};
class CDCPen : public CPenHandle {
public:
CDCPen(HDC dc, COLORREF color) throw() {
m_dc = dc;
m_oldCol = m_dc.SetDCPenColor(color);
m_hPen = (HPEN) GetStockObject(DC_PEN);
}
~CDCPen() throw() {
m_dc.SetDCPenColor(m_oldCol);
}
private:
CDCHandle m_dc;
COLORREF m_oldCol;
};
class CBackBuffer : public CDC {
public:
CBackBuffer() : m_bitmapOld(NULL), m_curSize(0,0) {
CreateCompatibleDC(NULL);
ATLASSERT(m_hDC != NULL);
}
~CBackBuffer() {
Dispose();
}
void Attach(HBITMAP bmp, CSize size) {
Dispose();
m_bitmap.Attach(bmp); m_curSize = size;
m_bitmapOld = SelectBitmap(m_bitmap);
}
void Attach(HBITMAP bmp) {
CSize size;
bool state = CBitmapHandle(bmp).GetSize(size);
ATLASSERT(state);
Attach(bmp, size);
}
BOOL Allocate(CSize size, HDC dcCompatible = NULL) {
if (m_hDC == NULL) return FALSE;
if (m_curSize == size) return TRUE;
Dispose();
HBITMAP temp;
if (dcCompatible == NULL) {
temp = CreateDIB24(size);
} else {
temp = CreateCompatibleBitmap(dcCompatible, size.cx, size.cy);
}
if (temp == NULL) return FALSE;
Attach(temp);
return TRUE;
}
void Dispose() {
if (m_bitmap != NULL) {
SelectBitmap(m_bitmapOld); m_bitmapOld = NULL;
m_bitmap.DeleteObject();
}
m_curSize = CSize(0,0);
}
BOOL GetBitmapPtr(t_uint8 * & ptr, t_ssize & lineWidth) {
if (m_bitmap == NULL) return FALSE;
BITMAP bmp = {};
if (!m_bitmap.GetBitmap(bmp)) return FALSE;
lineWidth = bmp.bmWidthBytes;
ptr = reinterpret_cast<t_uint8*>(bmp.bmBits);
return TRUE;
}
CSize GetSize() const { return m_curSize; }
PFC_CLASS_NOT_COPYABLE_EX(CBackBuffer)
private:
CSize m_curSize;
CBitmap m_bitmap;
HBITMAP m_bitmapOld;
};
class CBackBufferScope : public CDCHandle {
public:
CBackBufferScope(HDC hDC, HDC hDCBB, const CRect & rcPaint) : CDCHandle(hDCBB), m_dcOrig(hDC), m_rcPaint(rcPaint)
{
GetClipRgn(m_clipRgnOld);
CRgn temp;
if (m_dcOrig.GetClipRgn(temp) == 1) {
if (m_clipRgnOld != NULL) temp.CombineRgn(m_clipRgnOld,RGN_AND);
SelectClipRgn(temp);
}
IntersectClipRect(rcPaint);
}
~CBackBufferScope()
{
m_dcOrig.BitBlt(m_rcPaint.left,m_rcPaint.top,m_rcPaint.Width(),m_rcPaint.Height(),m_hDC,m_rcPaint.left,m_rcPaint.top,SRCCOPY);
SelectClipRgn(m_clipRgnOld);
}
private:
const CRect m_rcPaint;
CDCHandle m_dcOrig;
CRgn m_clipRgnOld;
};
class SetTextColorScope {
public:
SetTextColorScope(HDC dc, COLORREF col) throw() : m_dc(dc) {
m_oldCol = SetTextColor(dc, col);
}
~SetTextColorScope() throw() {
SetTextColor(m_dc, m_oldCol);
}
PFC_CLASS_NOT_COPYABLE_EX(SetTextColorScope)
private:
HDC m_dc;
COLORREF m_oldCol;
};

View File

@@ -0,0 +1,441 @@
#pragma once
// Various WTL extensions that are not fb2k specific and can be reused in other WTL based software
#define ATLASSERT_SUCCESS(X) {auto RetVal = (X); ATLASSERT( RetVal ); }
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), m_active() {
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);
}
}
private:
bool m_active;
CWindow m_wnd;
};
#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; \
}
class CImageListContainer : public CImageList {
public:
CImageListContainer() {}
~CImageListContainer() {Destroy();}
private:
const CImageListContainer & operator=(const CImageListContainer&);
CImageListContainer(const CImageListContainer&);
};
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 CContainedWindowT<CEdit>, private CMessageMap {
public:
bool HandleCtrlA, NoEscSteal, NoEnterSteal;
CEditPPHooks(CMessageMap * hookMM = nullptr, int hookMMID = 0) : CContainedWindowT<CEdit>(this, 0), HandleCtrlA(true), NoEscSteal(), NoEnterSteal(), m_suppressChar(), 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 ) {
if ( wnd.GetWindowLong(GWL_STYLE) & ES_READONLY ) return;
int len = wnd.GetWindowTextLength();
if ( len <= 0 ) return;
TCHAR * buffer = new TCHAR [ len + 1 ];
if ( wnd.GetWindowText( buffer, len + 1 ) <= 0 ) {
delete[] buffer;
return;
}
buffer[len] = 0;
int selStart = len, selEnd = len;
wnd.GetSel(selStart, selEnd);
if ( selStart < 0 || selStart > len ) selStart = len; // sanity
if ( selEnd < selStart ) selEnd = selStart; // sanity
int work = selStart;
if ( work == selEnd ) {
// Only do our stuff if there is nothing yet selected. Otherwise first delete selection.
while( work > 0 && isWordDelimiter(buffer[work-1]) ) --work;
while( work > 0 && !isWordDelimiter(buffer[work-1] ) ) --work;
}
delete[] buffer;
if ( selEnd > work ) {
wnd.SetSel(work, selEnd, TRUE );
wnd.ReplaceSel( TEXT(""), TRUE );
}
}
private:
static bool isWordDelimiter( TCHAR c ) {
return (unsigned) c <= ' ' || c == ',' || c == '.' || c == ';' || c == ':';
}
void OnChar(UINT nChar, UINT, UINT nFlags) {
if (m_suppressChar != 0) {
UINT code = nFlags & 0xFF;
if (code == m_suppressChar) return;
}
SetMsgHandled(FALSE);
}
void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
m_suppressChar = 0;
if (HandleCtrlA) {
if (nChar == 'A') {
if (GetHotkeyModifierFlags() == MOD_CONTROL) {
m_suppressChar = nFlags & 0xFF;
this->SetSelAll(); return;
}
}
if ( nChar == VK_BACK ) {
if (GetHotkeyModifierFlags() == MOD_CONTROL) {
m_suppressChar = nFlags & 0xFF;
DeleteLastWord( *this ) ; return;
}
}
}
SetMsgHandled(FALSE);
}
UINT OnEditGetDlgCode(LPMSG lpMsg) {
if (lpMsg == NULL) {
SetMsgHandled(FALSE); return 0;
} else {
switch(lpMsg->message) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
switch(lpMsg->wParam) {
case VK_ESCAPE:
SetMsgHandled(!!NoEscSteal);
return 0;
case VK_RETURN:
SetMsgHandled(!!NoEnterSteal);
return 0;
default:
SetMsgHandled(FALSE); return 0;
}
default:
SetMsgHandled(FALSE); return 0;
}
}
}
UINT m_suppressChar;
CMessageMap * const m_hookMM;
const int m_hookMMID;
};
class CEditNoEscSteal : public CContainedWindowT<CEdit>, private CMessageMap {
public:
CEditNoEscSteal() : CContainedWindowT<CEdit>(this, 0) {}
BEGIN_MSG_MAP_EX(CEditNoEscSteal)
MSG_WM_GETDLGCODE(OnEditGetDlgCode)
END_MSG_MAP()
private:
UINT OnEditGetDlgCode(LPMSG lpMsg) {
if (lpMsg == NULL) {
SetMsgHandled(FALSE); return 0;
} else {
switch(lpMsg->message) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
switch(lpMsg->wParam) {
case VK_ESCAPE:
return 0;
default:
SetMsgHandled(FALSE); return 0;
}
default:
SetMsgHandled(FALSE); return 0;
}
}
}
};
class CEditNoEnterEscSteal : public CContainedWindowT<CEdit>, private CMessageMap {
public:
CEditNoEnterEscSteal() : CContainedWindowT<CEdit>(this, 0) {}
BEGIN_MSG_MAP_EX(CEditNoEscSteal)
MSG_WM_GETDLGCODE(OnEditGetDlgCode)
END_MSG_MAP()
private:
UINT OnEditGetDlgCode(LPMSG lpMsg) {
if (lpMsg == NULL) {
SetMsgHandled(FALSE); return 0;
} else {
switch(lpMsg->message) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
switch(lpMsg->wParam) {
case VK_ESCAPE:
case VK_RETURN:
return 0;
default:
SetMsgHandled(FALSE); return 0;
}
default:
SetMsgHandled(FALSE); return 0;
}
}
}
};
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() : theLock() {
#if _WIN32_WINNT < 0x600
auto dll = GetModuleHandle(_T("kernel32"));
Bind(AcquireSRWLockExclusive, dll, "AcquireSRWLockExclusive");
Bind(AcquireSRWLockShared, dll, "AcquireSRWLockShared");
Bind(ReleaseSRWLockExclusive, dll, "ReleaseSRWLockExclusive");
Bind(ReleaseSRWLockShared, dll, "ReleaseSRWLockShared");
#endif
}
bool HaveAPI() {
#if _WIN32_WINNT < 0x600
return AcquireSRWLockExclusive != NULL;
#else
return true;
#endif
}
void EnterShared() {
AcquireSRWLockShared( & theLock );
}
void EnterExclusive() {
AcquireSRWLockExclusive( & theLock );
}
void LeaveShared() {
ReleaseSRWLockShared( & theLock );
}
void LeaveExclusive() {
ReleaseSRWLockExclusive( &theLock );
}
private:
CSRWlock(const CSRWlock&);
void operator=(const CSRWlock&);
SRWLOCK theLock;
#if _WIN32_WINNT < 0x600
template<typename func_t> static void Bind(func_t & func, HMODULE dll, const char * name) {
func = reinterpret_cast<func_t>(GetProcAddress( dll, name ) );
}
VOID (WINAPI * AcquireSRWLockExclusive)(PSRWLOCK SRWLock);
VOID (WINAPI * AcquireSRWLockShared)(PSRWLOCK SRWLock);
VOID (WINAPI * ReleaseSRWLockExclusive)(PSRWLOCK SRWLock);
VOID (WINAPI * ReleaseSRWLockShared)(PSRWLOCK SRWLock);
#endif
};
#if _WIN32_WINNT < 0x600
class CSRWorCS {
public:
CSRWorCS() : cs() {
if (!srw.HaveAPI()) InitializeCriticalSection(&cs);
}
~CSRWorCS() {
if (!srw.HaveAPI()) DeleteCriticalSection(& cs );
}
void EnterShared() {
if (srw.HaveAPI()) srw.EnterShared();
else EnterCriticalSection(&cs);
}
void EnterExclusive() {
if (srw.HaveAPI()) srw.EnterExclusive();
else EnterCriticalSection(&cs);
}
void LeaveShared() {
if (srw.HaveAPI()) srw.LeaveShared();
else LeaveCriticalSection(&cs);
}
void LeaveExclusive() {
if (srw.HaveAPI()) srw.LeaveExclusive();
else LeaveCriticalSection(&cs);
}
private:
CSRWorCS(const CSRWorCS&);
void operator=(const CSRWorCS&);
CSRWlock srw;
CRITICAL_SECTION cs;
};
#else
typedef CSRWlock CSRWorCS;
#endif

View File

@@ -0,0 +1,323 @@
#pragma once
#include "../helpers/win32_misc.h"
static BOOL AdjustWindowRectHelper(CWindow wnd, CRect & rc) {
const DWORD style = wnd.GetWindowLong(GWL_STYLE), exstyle = wnd.GetWindowLong(GWL_EXSTYLE);
return AdjustWindowRectEx(&rc,style,(style & WS_POPUP) ? wnd.GetMenu() != NULL : FALSE, exstyle);
}
static void AdjustRectToScreenArea(CRect & rc, CRect rcParent) {
HMONITOR monitor = MonitorFromRect(rcParent,MONITOR_DEFAULTTONEAREST);
MONITORINFO mi = {sizeof(MONITORINFO)};
if (GetMonitorInfo(monitor,&mi)) {
const CRect clip = mi.rcWork;
if (rc.right > clip.right) rc.OffsetRect(clip.right - rc.right, 0);
if (rc.bottom > clip.bottom) rc.OffsetRect(0, clip.bottom - rc.bottom);
if (rc.left < clip.left) rc.OffsetRect(clip.left - rc.left, 0);
if (rc.top < clip.top) rc.OffsetRect(0, clip.top - rc.top);
}
}
static BOOL GetClientRectAsSC(CWindow wnd, CRect & rc) {
CRect temp;
if (!wnd.GetClientRect(temp)) return FALSE;
if (temp.IsRectNull()) return FALSE;
if (!wnd.ClientToScreen(temp)) return FALSE;
rc = temp;
return TRUE;
}
static BOOL CenterWindowGetRect(CWindow wnd, CWindow wndParent, CRect & out) {
CRect parent, child;
if (!wndParent.GetWindowRect(&parent) || !wnd.GetWindowRect(&child)) return FALSE;
{
CPoint origin = parent.CenterPoint();
origin.Offset( - child.Width() / 2, - child.Height() / 2);
child.OffsetRect( origin - child.TopLeft() );
}
AdjustRectToScreenArea(child, parent);
out = child;
return TRUE;
}
static BOOL CenterWindowAbove(CWindow wnd, CWindow wndParent) {
CRect rc;
if (!CenterWindowGetRect(wnd, wndParent, rc)) return FALSE;
return wnd.SetWindowPos(NULL,rc,SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}
static BOOL ShowWindowCentered(CWindow wnd,CWindow wndParent) {
CRect rc;
if (!CenterWindowGetRect(wnd, wndParent, rc)) return FALSE;
return wnd.SetWindowPos(HWND_TOP,rc,SWP_NOSIZE | SWP_SHOWWINDOW);
}
class cfgWindowSize : public cfg_var {
public:
cfgWindowSize(const GUID & p_guid) : cfg_var(p_guid), m_width(~0), m_height(~0) {}
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {
stream_writer_formatter<>(*p_stream,p_abort) << m_width << m_height;
}
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
stream_reader_formatter<>(*p_stream,p_abort) >> m_width >> m_height;
}
t_uint32 m_width, m_height;
};
class cfgWindowSizeTracker {
public:
cfgWindowSizeTracker(cfgWindowSize & p_var) : m_var(p_var), m_applied(false) {}
bool Apply(HWND p_wnd) {
bool retVal = false;
m_applied = false;
if (m_var.m_width != ~0 && m_var.m_height != ~0) {
CRect rect (0,0,m_var.m_width,m_var.m_height);
if (AdjustWindowRectHelper(p_wnd, rect)) {
SetWindowPos(p_wnd,NULL,0,0,rect.right-rect.left,rect.bottom-rect.top,SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER);
retVal = true;
}
}
m_applied = true;
return retVal;
}
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT & lResult) {
if (uMsg == WM_SIZE && m_applied) {
if (lParam != 0) {
m_var.m_width = (short)LOWORD(lParam); m_var.m_height = (short)HIWORD(lParam);
}
}
return FALSE;
}
private:
cfgWindowSize & m_var;
bool m_applied;
};
class cfgDialogSizeTracker : public cfgWindowSizeTracker {
public:
cfgDialogSizeTracker(cfgWindowSize & p_var) : cfgWindowSizeTracker(p_var) {}
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT & lResult) {
if (cfgWindowSizeTracker::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult)) return TRUE;
if (uMsg == WM_INITDIALOG) Apply(hWnd);
return FALSE;
}
};
class cfgDialogPositionData {
public:
cfgDialogPositionData() : m_width(sizeInvalid), m_height(sizeInvalid), m_posX(posInvalid), m_posY(posInvalid) {}
void OverrideDefaultSize(t_uint32 width, t_uint32 height) {
if (m_width == sizeInvalid && m_height == sizeInvalid) {
m_width = width; m_height = height; m_posX = m_posY = posInvalid;
m_dpiX = m_dpiY = 96;
}
}
void AddWindow(CWindow wnd) {
TryFetchConfig();
m_windows += wnd;
ApplyConfig(wnd);
}
void RemoveWindow(CWindow wnd) {
if (m_windows.contains(wnd)) {
StoreConfig(wnd); m_windows -= wnd;
}
}
void FetchConfig() {TryFetchConfig();}
private:
BOOL ApplyConfig(CWindow wnd) {
ApplyDPI();
CWindow wndParent = wnd.GetParent();
UINT flags = SWP_NOACTIVATE | SWP_NOZORDER;
CRect rc;
if (!GetClientRectAsSC(wnd,rc)) return FALSE;
if (m_width != sizeInvalid && m_height != sizeInvalid && (wnd.GetWindowLong(GWL_STYLE) & WS_SIZEBOX) != 0) {
rc.right = rc.left + m_width;
rc.bottom = rc.top + m_height;
} else {
flags |= SWP_NOSIZE;
}
if (wndParent != NULL) {
CRect rcParent;
if (GetParentWndRect(wndParent, rcParent)) {
if (m_posX != posInvalid && m_posY != posInvalid) {
rc.MoveToXY( rcParent.TopLeft() + CPoint(m_posX, m_posY) );
} else {
CPoint center = rcParent.CenterPoint();
rc.MoveToXY( center.x - rc.Width() / 2, center.y - rc.Height() / 2);
}
}
}
if (!AdjustWindowRectHelper(wnd, rc)) return FALSE;
DeOverlap(wnd, rc);
{
CRect rcAdjust(0,0,1,1);
if (wndParent != NULL) {
CRect temp;
if (wndParent.GetWindowRect(temp)) rcAdjust = temp;
}
AdjustRectToScreenArea(rc, rcAdjust);
}
return wnd.SetWindowPos(NULL, rc, flags);
}
struct DeOverlapState {
CWindow m_thisWnd;
CPoint m_topLeft;
bool m_match;
};
static BOOL CALLBACK MyEnumChildProc(HWND wnd, LPARAM param) {
DeOverlapState * state = reinterpret_cast<DeOverlapState*>(param);
if (wnd != state->m_thisWnd && IsWindowVisible(wnd) ) {
CRect rc;
if (GetWindowRect(wnd, rc)) {
if (rc.TopLeft() == state->m_topLeft) {
state->m_match = true; return FALSE;
}
}
}
return TRUE;
}
static bool DeOverlapTest(CWindow wnd, CPoint topLeft) {
DeOverlapState state = {};
state.m_thisWnd = wnd; state.m_topLeft = topLeft; state.m_match = false;
EnumThreadWindows(GetCurrentThreadId(), MyEnumChildProc, reinterpret_cast<LPARAM>(&state));
return state.m_match;
}
static int DeOverlapDelta() {
return pfc::max_t<int>(GetSystemMetrics(SM_CYCAPTION),1);
}
static void DeOverlap(CWindow wnd, CRect & rc) {
const int delta = DeOverlapDelta();
for(;;) {
if (!DeOverlapTest(wnd, rc.TopLeft())) break;
rc.OffsetRect(delta,delta);
}
}
BOOL StoreConfig(CWindow wnd) {
CRect rc;
if (!GetClientRectAsSC(wnd, rc)) return FALSE;
const CSize DPI = QueryScreenDPIEx();
m_dpiX = DPI.cx; m_dpiY = DPI.cy;
m_width = rc.Width(); m_height = rc.Height();
m_posX = m_posY = posInvalid;
CWindow parent = wnd.GetParent();
if (parent != NULL) {
CRect rcParent;
if (GetParentWndRect(parent, rcParent)) {
m_posX = rc.left - rcParent.left;
m_posY = rc.top - rcParent.top;
}
}
return TRUE;
}
void TryFetchConfig() {
for(auto walk = m_windows.cfirst(); walk.is_valid(); ++walk) {
if (StoreConfig(*walk)) break;
}
}
void ApplyDPI() {
const CSize screenDPI = QueryScreenDPIEx();
if (screenDPI.cx == 0 || screenDPI.cy == 0) {
PFC_ASSERT(!"Should not get here - something seriously wrong with the OS");
return;
}
if (m_dpiX != dpiInvalid && m_dpiX != screenDPI.cx) {
if (m_width != sizeInvalid) m_width = MulDiv(m_width, screenDPI.cx, m_dpiX);
if (m_posX != posInvalid) m_posX = MulDiv(m_posX, screenDPI.cx, m_dpiX);
}
if (m_dpiY != dpiInvalid && m_dpiY != screenDPI.cy) {
if (m_height != sizeInvalid) m_height = MulDiv(m_height, screenDPI.cy, m_dpiY);
if (m_posY != posInvalid) m_posY = MulDiv(m_posY, screenDPI.cy, m_dpiY);
}
m_dpiX = screenDPI.cx;
m_dpiY = screenDPI.cy;
}
CSize GrabDPI() const {
CSize DPI(96,96);
if (m_dpiX != dpiInvalid) DPI.cx = m_dpiX;
if (m_dpiY != dpiInvalid) DPI.cy = m_dpiY;
return DPI;
}
static BOOL GetParentWndRect(CWindow wndParent, CRect & rc) {
if (!wndParent.IsIconic()) {
return wndParent.GetWindowRect(rc);
}
WINDOWPLACEMENT pl = {sizeof(pl)};
if (!wndParent.GetWindowPlacement(&pl)) return FALSE;
rc = pl.rcNormalPosition;
return TRUE;
}
pfc::avltree_t<CWindow> m_windows;
public:
t_uint32 m_width, m_height;
t_int32 m_posX, m_posY;
t_uint32 m_dpiX, m_dpiY;
enum {
posInvalid = 0x80000000,
sizeInvalid = 0xFFFFFFFF,
dpiInvalid = 0,
};
};
FB2K_STREAM_READER_OVERLOAD(cfgDialogPositionData) {
stream >> value.m_width >> value.m_height;
try {
stream >> value.m_posX >> value.m_posY >> value.m_dpiX >> value.m_dpiY;
} catch(exception_io_data) {
value.m_posX = value.m_posY = cfgDialogPositionData::posInvalid;
value.m_dpiX = value.m_dpiY = cfgDialogPositionData::dpiInvalid;
}
return stream;
}
FB2K_STREAM_WRITER_OVERLOAD(cfgDialogPositionData) {
return stream << value.m_width << value.m_height << value.m_posX << value.m_posY << value.m_dpiX << value.m_dpiY;
}
class cfgDialogPosition : public cfgDialogPositionData, public cfg_var {
public:
cfgDialogPosition(const GUID & id) : cfg_var(id) {}
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {FetchConfig(); stream_writer_formatter<>(*p_stream, p_abort) << *pfc::implicit_cast<cfgDialogPositionData*>(this);}
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {stream_reader_formatter<>(*p_stream, p_abort) >> *pfc::implicit_cast<cfgDialogPositionData*>(this);}
};
class cfgDialogPositionTracker {
public:
cfgDialogPositionTracker(cfgDialogPosition & p_var) : m_var(p_var) {}
~cfgDialogPositionTracker() {Cleanup();}
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT & lResult) {
if (uMsg == WM_CREATE || uMsg == WM_INITDIALOG) {
Cleanup();
m_wnd = hWnd;
m_var.AddWindow(m_wnd);
} else if (uMsg == WM_DESTROY) {
PFC_ASSERT( hWnd == m_wnd );
Cleanup();
}
return FALSE;
}
private:
void Cleanup() {
if (m_wnd != NULL) {
m_var.RemoveWindow(m_wnd);
m_wnd = NULL;
}
}
cfgDialogPosition & m_var;
CWindow m_wnd;
};

View File

@@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{622E8B19-8109-4717-BD4D-9657AA78363E}</ProjectGuid>
<RootNamespace>foobar2000_ATL_helpers</RootNamespace>
<Keyword>Win32Proj</Keyword>
<WindowsTargetPlatformVersion>10.0.14393.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
<PlatformToolset>v141_xp</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141_xp</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)$(Configuration)\</OutDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Configuration)\</IntDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Configuration)\</OutDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>false</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<TreatSpecificWarningsAsErrors>4715</TreatSpecificWarningsAsErrors>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<Optimization>MinSpace</Optimization>
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BufferSecurityCheck>false</BufferSecurityCheck>
<FloatingPointModel>Fast</FloatingPointModel>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
<AdditionalOptions>/d2notypeopt %(AdditionalOptions)</AdditionalOptions>
<TreatSpecificWarningsAsErrors>4715</TreatSpecificWarningsAsErrors>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<StringPooling>true</StringPooling>
<OmitFramePointers>true</OmitFramePointers>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="AutoComplete.cpp" />
<ClCompile Include="CDialogResizeHelper.cpp" />
<ClCompile Include="CEditWithButtons.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="inplace_edit.cpp" />
<ClCompile Include="inplace_edit_v2.cpp" />
<ClCompile Include="misc.cpp" />
<ClCompile Include="stdafx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ui_element_helpers.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="ATLHelpers.h" />
<ClInclude Include="ATLHelpersLean.h" />
<ClInclude Include="AutoComplete.h" />
<ClInclude Include="BumpableElem.h" />
<ClInclude Include="CButtonLite.h" />
<ClInclude Include="CDialogResizeHelper.h" />
<ClInclude Include="CEditWithButtons.h" />
<ClInclude Include="CFlashWindow.h" />
<ClInclude Include="CIconOverlayWindow.h" />
<ClInclude Include="CMiddleDragImpl.h" />
<ClInclude Include="Controls.h" />
<ClInclude Include="GDIUtils.h" />
<ClInclude Include="inplace_edit.h" />
<ClInclude Include="inplace_edit_v2.h" />
<ClInclude Include="misc.h" />
<ClInclude Include="stdafx.h" />
<ClInclude Include="ui_element_helpers.h" />
<ClInclude Include="WindowPositionUtils.h" />
<ClInclude Include="WTL-PP.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="AutoComplete.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CDialogResizeHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="inplace_edit.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="inplace_edit_v2.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="stdafx.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="misc.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CEditWithButtons.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ui_element_helpers.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ATLHelpers.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="AutoComplete.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="BumpableElem.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CDialogResizeHelper.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="GDIUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="inplace_edit.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="inplace_edit_v2.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="stdafx.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="WTL-PP.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ATLHelpersLean.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="WindowPositionUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="misc.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CFlashWindow.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Controls.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CEditWithButtons.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CButtonLite.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CIconOverlayWindow.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CMiddleDragImpl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ui_element_helpers.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,477 @@
#include "stdafx.h"
#include "inplace_edit.h"
#include "AutoComplete.h"
#include "misc.h"
#include "WTL-PP.h"
#ifndef WM_MOUSEHWHEEL
#define WM_MOUSEHWHEEL 0x20E
#endif
using namespace InPlaceEdit;
namespace {
enum {
MSG_COMPLETION = WM_USER,
MSG_DISABLE_EDITING
};
static pfc::avltree_t<HWND> g_editboxes;
static HHOOK g_hook = NULL /*, g_keyHook = NULL*/;
static void GAbortEditing(HWND edit, t_uint32 code) {
CWindow parent = ::GetParent(edit);
parent.SendMessage(MSG_DISABLE_EDITING);
parent.PostMessage(MSG_COMPLETION, code, 0);
}
static void GAbortEditing(t_uint32 code) {
for(auto walk = g_editboxes.cfirst(); walk.is_valid(); ++walk ) {
GAbortEditing(*walk, code);
}
}
static bool IsSamePopup(CWindow wnd1, CWindow wnd2) {
return FindOwningPopup(wnd1) == FindOwningPopup(wnd2);
}
static void MouseEventTest(HWND target, CPoint pt, bool isWheel) {
for(auto walk = g_editboxes.cfirst(); walk.is_valid(); ++walk) {
CWindow edit ( *walk );
bool cancel = false;
if (target != edit && IsSamePopup(target, edit)) {
cancel = true;
} else if (isWheel) {
CWindow target2 = WindowFromPoint(pt);
if (target2 != edit && IsSamePopup(target2, edit)) {
cancel = true;
}
}
if (cancel) GAbortEditing(edit, KEditLostFocus);
}
}
static LRESULT CALLBACK GMouseProc(int nCode,WPARAM wParam,LPARAM lParam) {
if (nCode == HC_ACTION) {
const MOUSEHOOKSTRUCT * mhs = reinterpret_cast<const MOUSEHOOKSTRUCT *>(lParam);
switch(wParam) {
case WM_NCLBUTTONDOWN:
case WM_NCRBUTTONDOWN:
case WM_NCMBUTTONDOWN:
case WM_NCXBUTTONDOWN:
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_XBUTTONDOWN:
MouseEventTest(mhs->hwnd, mhs->pt, false);
break;
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
MouseEventTest(mhs->hwnd, mhs->pt, true);
break;
}
}
return CallNextHookEx(g_hook,nCode,wParam,lParam);
}
#if 0
static LRESULT CALLBACK GKeyboardProc(int code, WPARAM wp, LPARAM lp) {
if (code == HC_ACTION && (lp & (1<<31)) == 0) {
switch(wp) {
case VK_RETURN:
if (!IsKeyPressed(VK_LCONTROL) && !IsKeyPressed(VK_RCONTROL)) {
GAbortEditing(KEditEnter);
}
break;
case VK_TAB:
GAbortEditing(IsKeyPressed(VK_SHIFT) ? KEditShiftTab : KEditTab);
break;
case VK_ESCAPE:
GAbortEditing(KEditAborted);
break;
}
}
return CallNextHookEx(g_keyHook,code,wp,lp);
}
#endif
static void on_editbox_creation(HWND p_editbox) {
PFC_ASSERT( core_api::is_main_thread() );
g_editboxes.add(p_editbox);
if (g_hook == NULL) {
g_hook = SetWindowsHookEx(WH_MOUSE,GMouseProc,NULL,GetCurrentThreadId());
}
/*if (g_keyHook == NULL) {
g_keyHook = SetWindowsHookEx(WH_KEYBOARD, GKeyboardProc, NULL, GetCurrentThreadId());
}*/
}
static void UnhookHelper(HHOOK & hook) {
HHOOK v = pfc::replace_null_t(hook);
if (v != NULL) UnhookWindowsHookEx(v);
}
static void on_editbox_destruction(HWND p_editbox) {
PFC_ASSERT( core_api::is_main_thread() );
g_editboxes.remove_item(p_editbox);
if (g_editboxes.get_count() == 0) {
UnhookHelper(g_hook); /*UnhookHelper(g_keyHook);*/
}
}
class CInPlaceEditBox : public CContainedWindowSimpleT<CEdit> {
public:
CInPlaceEditBox() : m_selfDestruct(), m_suppressChar() {}
BEGIN_MSG_MAP_EX(CInPlaceEditBox)
//MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
MSG_WM_GETDLGCODE(OnGetDlgCode)
MSG_WM_KILLFOCUS(OnKillFocus)
MSG_WM_CHAR(OnChar)
MSG_WM_KEYDOWN(OnKeyDown)
END_MSG_MAP()
void OnCreation() {
m_typableScope.Set(m_hWnd);
on_editbox_creation(m_hWnd);
}
private:
void OnDestroy() {
m_selfDestruct = true;
m_typableScope.Set(NULL);
on_editbox_destruction(m_hWnd);
SetMsgHandled(FALSE);
}
int OnCreate(LPCREATESTRUCT lpCreateStruct) {
OnCreation();
SetMsgHandled(FALSE);
return 0;
}
UINT OnGetDlgCode(LPMSG lpMsg) {
if (lpMsg == NULL) {
return DLGC_WANTALLKEYS;
} else {
switch(lpMsg->message) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
switch(lpMsg->wParam) {
case VK_TAB:
case VK_ESCAPE:
case VK_RETURN:
return DLGC_WANTALLKEYS;
default:
SetMsgHandled(FALSE); return 0;
}
default:
SetMsgHandled(FALSE); return 0;
}
}
}
void OnKillFocus(CWindow wndFocus) {
ForwardCompletion(KEditLostFocus);
SetMsgHandled(FALSE);
}
void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) {
if (m_suppressChar != 0) {
UINT code = nFlags & 0xFF;
if (code == m_suppressChar) return;
}
SetMsgHandled(FALSE);
}
void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
m_suppressChar = nFlags & 0xFF;
switch(nChar) {
case VK_BACK:
if (GetHotkeyModifierFlags() == MOD_CONTROL) {
CEditPPHooks::DeleteLastWord( * this );
return;
}
break;
case 'A':
if (GetHotkeyModifierFlags() == MOD_CONTROL) {
this->SetSelAll(); return;
}
break;
case VK_RETURN:
if (!IsKeyPressed(VK_LCONTROL) && !IsKeyPressed(VK_RCONTROL)) {
ForwardCompletion(KEditEnter);
return;
}
break;
case VK_TAB:
ForwardCompletion(IsKeyPressed(VK_SHIFT) ? KEditShiftTab : KEditTab);
return;
case VK_ESCAPE:
ForwardCompletion(KEditAborted);
return;
}
m_suppressChar = 0;
SetMsgHandled(FALSE);
}
void ForwardCompletion(t_uint32 code) {
if (IsWindowEnabled()) {
CWindow owner = GetParent();
owner.SendMessage(MSG_DISABLE_EDITING);
owner.PostMessage(MSG_COMPLETION,code,0);
EnableWindow(FALSE);
}
}
CTypableWindowScope m_typableScope;
bool m_selfDestruct;
UINT m_suppressChar;
};
class InPlaceEditContainer : public CWindowImpl<InPlaceEditContainer> {
public:
DECLARE_WND_CLASS_EX(_T("{54340C80-248C-4b8e-8CD4-D624A8E9377B}"),0,-1);
HWND Create(CWindow parent) {
RECT rect_cropped;
{
RECT client;
WIN32_OP_D(parent.GetClientRect(&client));
IntersectRect(&rect_cropped,&client,&m_initRect);
}
const DWORD containerStyle = WS_BORDER|WS_CHILD;
AdjustWindowRect(&rect_cropped,containerStyle,FALSE);
WIN32_OP( __super::Create(parent,rect_cropped, NULL, containerStyle) != NULL );
try {
CRect rcClient;
WIN32_OP_D(GetClientRect(rcClient));
DWORD style = WS_CHILD|WS_VISIBLE;//parent is invisible now
if (m_flags & KFlagMultiLine) style |= WS_VSCROLL|ES_MULTILINE;
else style |= ES_AUTOHSCROLL;
if (m_flags & KFlagReadOnly) style |= ES_READONLY;
if (m_flags & KFlagAlignCenter) style |= ES_CENTER;
else if (m_flags & KFlagAlignRight) style |= ES_RIGHT;
else style |= ES_LEFT;
CEdit edit;
WIN32_OP( edit.Create(*this, rcClient, NULL, style, 0, ID_MYEDIT) != NULL );
edit.SetFont(parent.GetFont());
if (m_ACData.is_valid()) InitializeSimpleAC(edit, m_ACData.get_ptr(), m_ACOpts);
m_edit.SubclassWindow(edit);
m_edit.OnCreation();
uSetWindowText(m_edit,*m_content);
m_edit.SetSelAll();
} catch(...) {
PostMessage(MSG_COMPLETION,InPlaceEdit::KEditAborted,0);
return m_hWnd;
}
ShowWindow(SW_SHOW);
m_edit.SetFocus();
m_initialized = true;
PFC_ASSERT( m_hWnd != NULL );
return m_hWnd;
}
InPlaceEditContainer(const RECT & p_rect,t_uint32 p_flags,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify, IUnknown * ACData, DWORD ACOpts)
: m_content(p_content), m_notify(p_notify), m_completed(false), m_initialized(false), m_changed(false), m_disable_editing(false), m_initRect(p_rect), m_flags(p_flags), m_selfDestruct(), m_ACData(ACData), m_ACOpts(ACOpts)
{
}
enum {ID_MYEDIT = 666};
BEGIN_MSG_MAP_EX(InPlaceEditContainer)
MESSAGE_HANDLER_EX(WM_CTLCOLOREDIT, MsgForwardToParent)
MESSAGE_HANDLER_EX(WM_CTLCOLORSTATIC, MsgForwardToParent)
MESSAGE_HANDLER_EX(WM_MOUSEWHEEL, MsgLostFocus)
MESSAGE_HANDLER_EX(WM_MOUSEHWHEEL, MsgLostFocus)
MESSAGE_HANDLER_SIMPLE(MSG_DISABLE_EDITING, OnMsgDisableEditing)
MESSAGE_HANDLER_EX(MSG_COMPLETION, OnMsgCompletion)
COMMAND_HANDLER_EX(ID_MYEDIT, EN_CHANGE, OnEditChange)
MSG_WM_DESTROY(OnDestroy)
END_MSG_MAP()
HWND GetEditBox() const {return m_edit;}
private:
void OnDestroy() {m_selfDestruct = true;}
LRESULT MsgForwardToParent(UINT msg, WPARAM wParam, LPARAM lParam) {
return GetParent().SendMessage(msg, wParam, lParam);
}
LRESULT MsgLostFocus(UINT, WPARAM, LPARAM) {
PostMessage(MSG_COMPLETION,InPlaceEdit::KEditLostFocus,0);
return 0;
}
void OnMsgDisableEditing() {
ShowWindow(SW_HIDE);
GetParent().UpdateWindow();
m_disable_editing = true;
}
LRESULT OnMsgCompletion(UINT, WPARAM wParam, LPARAM lParam) {
PFC_ASSERT(m_initialized);
if ((wParam & KEditMaskReason) != KEditLostFocus) {
GetParent().SetFocus();
}
OnCompletion(wParam);
if (!m_selfDestruct) {
m_selfDestruct = true;
DestroyWindow();
}
return 0;
}
void OnEditChange(UINT, int, CWindow source) {
if (m_initialized && !m_disable_editing) {
uGetWindowText(source,*m_content);
m_changed = true;
}
}
private:
void OnCompletion(unsigned p_status) {
if (!m_completed) {
m_completed = true;
p_status &= KEditMaskReason;
unsigned code = p_status;
if (m_changed && p_status != KEditAborted) code |= KEditFlagContentChanged;
if (m_notify.is_valid()) m_notify->on_completion(code);
}
}
const pfc::rcptr_t<pfc::string_base> m_content;
const completion_notify_ptr m_notify;
bool m_completed;
bool m_initialized, m_changed;
bool m_disable_editing;
bool m_selfDestruct;
const CRect m_initRect;
const t_uint32 m_flags;
CInPlaceEditBox m_edit;
const pfc::com_ptr_t<IUnknown> m_ACData;
const DWORD m_ACOpts;
};
}
static void fail(completion_notify_ptr p_notify) {
completion_notify::g_signal_completion_async(p_notify,KEditAborted);
}
HWND InPlaceEdit::Start(HWND p_parentwnd,const RECT & p_rect,bool p_multiline,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify) {
return StartEx(p_parentwnd,p_rect,p_multiline ? KFlagMultiLine : 0, p_content,p_notify);
}
void InPlaceEdit::Start_FromListView(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify) {
Start_FromListViewEx(p_listview,p_item,p_subitem,p_linecount,0,p_content,p_notify);
}
bool InPlaceEdit::TableEditAdvance_ListView(HWND p_listview,unsigned p_column_base,unsigned & p_item,unsigned & p_column, unsigned p_item_count,unsigned p_column_count, unsigned p_whathappened) {
if (p_column >= p_column_count) return false;
pfc::array_t<t_size> orderRev;
{
pfc::array_t<unsigned> order;
const unsigned orderExCount = /*p_column_base + p_column_count*/ ListView_GetColumnCount(p_listview);
PFC_ASSERT( orderExCount >= p_column_base + p_column_count );
pfc::array_t<int> orderEx; orderEx.set_size(orderExCount);
if (!ListView_GetColumnOrderArray(p_listview,orderExCount,orderEx.get_ptr())) {
PFC_ASSERT(!"Should not get here - probably mis-calculated column count");
return false;
}
order.set_size(p_column_count);
for(unsigned walk = 0; walk < p_column_count; ++walk) order[walk] = orderEx[p_column_base + walk];
orderRev.set_size(p_column_count); order_helper::g_fill(orderRev);
pfc::sort_get_permutation_t(order,pfc::compare_t<unsigned,unsigned>,p_column_count,orderRev.get_ptr());
}
unsigned columnVisible = (unsigned)orderRev[p_column];
if (!TableEditAdvance(p_item,columnVisible,p_item_count,p_column_count,p_whathappened)) return false;
p_column = (unsigned)order_helper::g_find_reverse(orderRev.get_ptr(),columnVisible);
return true;
}
bool InPlaceEdit::TableEditAdvance(unsigned & p_item,unsigned & p_column, unsigned p_item_count,unsigned p_column_count, unsigned p_whathappened) {
if (p_item >= p_item_count || p_column >= p_column_count) return false;
int delta = 0;
switch(p_whathappened & KEditMaskReason) {
case KEditEnter:
delta = (int) p_column_count;
break;
case KEditTab:
delta = 1;
break;
case KEditShiftTab:
delta = -1;
break;
default:
return false;
}
while(delta > 0) {
p_column++;
if (p_column >= p_column_count) {
p_column = 0;
p_item++;
if (p_item >= p_item_count) return false;
}
delta--;
}
while(delta < 0) {
if (p_column == 0) {
if (p_item == 0) return false;
p_item--;
p_column = p_column_count;
}
p_column--;
delta++;
}
return true;
}
HWND InPlaceEdit::StartEx(HWND p_parentwnd,const RECT & p_rect,unsigned p_flags,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify, IUnknown * ACData , DWORD ACOpts) {
try {
PFC_ASSERT( (CWindow(p_parentwnd).GetWindowLong(GWL_STYLE) & WS_CLIPCHILDREN) != 0 );
return (new CWindowAutoLifetime<InPlaceEditContainer>(p_parentwnd,p_rect,p_flags,p_content,p_notify, ACData, ACOpts))->GetEditBox();
} catch(...) {
fail(p_notify);
return NULL;
}
}
void InPlaceEdit::Start_FromListViewEx(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,unsigned p_flags,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify) {
try {
ListView_EnsureVisible(p_listview,p_item,FALSE);
RECT itemrect;
WIN32_OP_D( ListView_GetSubItemRect(p_listview,p_item,p_subitem,LVIR_LABEL,&itemrect) );
const bool multiline = p_linecount > 1;
if (multiline) {
itemrect.bottom = itemrect.top + (itemrect.bottom - itemrect.top) * p_linecount;
}
StartEx(p_listview,itemrect,p_flags | (multiline ? KFlagMultiLine : 0),p_content,p_notify);
} catch(...) {
fail(p_notify);
}
}

View File

@@ -0,0 +1,132 @@
#pragma once
#include "../helpers/listview_helper.h"
namespace InPlaceEdit {
enum {
KEditAborted = 0,
KEditTab,
KEditShiftTab,
KEditEnter,
KEditLostFocus,
KEditMaskReason = 0xFF,
KEditFlagContentChanged = 0x100,
KFlagReadOnly = 1 << 0,
KFlagMultiLine = 1 << 1,
KFlagAlignCenter = 1 << 2,
KFlagAlignRight = 1 << 3,
};
HWND Start(HWND p_parentwnd,const RECT & p_rect,bool p_multiline,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify);
HWND StartEx(HWND p_parentwnd,const RECT & p_rect,unsigned p_flags,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify, IUnknown * ACData = NULL, DWORD ACOpts = 0);
void Start_FromListView(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify);
void Start_FromListViewEx(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,unsigned p_flags,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify);
bool TableEditAdvance(unsigned & p_item,unsigned & p_column, unsigned p_item_count,unsigned p_column_count, unsigned p_whathappened);
bool TableEditAdvance_ListView(HWND p_listview,unsigned p_column_base,unsigned & p_item,unsigned & p_column, unsigned p_item_count,unsigned p_column_count, unsigned p_whathappened);
class CTableEditHelper {
public:
void TableEdit_Start(HWND p_listview,unsigned p_item,unsigned p_column,unsigned p_itemcount,unsigned p_columncount,unsigned p_basecolumn,unsigned p_flags = 0) {
if (m_notify.is_valid() || p_columncount == 0 || p_itemcount == 0 || p_item >= p_itemcount || p_column >= p_columncount) return;
m_listview = p_listview;
m_item = p_item;
m_column = p_column;
m_itemcount = p_itemcount;
m_columncount = p_columncount;
m_basecolumn = p_basecolumn;
m_flags = p_flags;
__Start();
}
void TableEdit_Abort(bool p_forwardcontent) {
if (m_notify.is_valid()) {
m_notify->orphan();
m_notify.release();
if (p_forwardcontent && (m_flags & KFlagReadOnly) == 0) {
if (m_content.is_valid()) {
pfc::string8 temp(*m_content);
m_content.release();
TableEdit_SetItemText(m_item,m_column,temp);
}
} else {
m_content.release();
}
SetFocus(NULL);
TableEdit_Finished();
}
}
bool TableEdit_IsActive() const {
return m_notify.is_valid();
}
virtual bool TableEdit_GetItemText(unsigned p_item,unsigned p_column,pfc::string_base & p_out,unsigned & p_linecount) {
listview_helper::get_item_text(m_listview,p_item,p_column + m_basecolumn,p_out);
p_linecount = pfc::is_multiline(p_out) ? 5 : 1;
return true;
}
virtual void TableEdit_SetItemText(unsigned p_item,unsigned p_column,const char * p_text) {
listview_helper::set_item_text(m_listview,p_item,p_column + m_basecolumn,p_text);
}
virtual void TableEdit_Finished() {}
void on_task_completion(unsigned p_taskid,unsigned p_state) {
if (p_taskid == KTaskID) {
m_notify.release();
if (m_content.is_valid()) {
if (p_state & InPlaceEdit::KEditFlagContentChanged) {
TableEdit_SetItemText(m_item,m_column,*m_content);
}
m_content.release();
}
/*if (InPlaceEdit::TableEditAdvance(m_item,m_column,m_itemcount,m_columncount,p_state))*/
if (TableEdit_OnEditCompleted(m_item,m_column,p_state) &&
InPlaceEdit::TableEditAdvance_ListView(m_listview,m_basecolumn,m_item,m_column,m_itemcount,m_columncount,p_state)) {
__Start();
} else {
TableEdit_Finished();
}
}
}
~CTableEditHelper() {
if (m_notify.is_valid()) {
m_notify->orphan();
m_notify.release();
}
}
protected:
HWND TableEdit_GetListView() const {return m_listview;}
//return false to abort
virtual bool TableEdit_OnEditCompleted(unsigned item,unsigned column,unsigned state) {return true;}
private:
void __Start() {
listview_helper::select_single_item(m_listview,m_item);
m_content.new_t();
unsigned linecount = 1;
if (!TableEdit_GetItemText(m_item,m_column,*m_content,linecount)) return;
m_notify = completion_notify_create(this,KTaskID);
InPlaceEdit::Start_FromListViewEx(m_listview,m_item,m_column+m_basecolumn,linecount,m_flags,m_content,m_notify);
}
enum {
KTaskID = 0xc0ffee
};
HWND m_listview;
unsigned m_item,m_column;
unsigned m_itemcount,m_columncount,m_basecolumn;
unsigned m_flags;
pfc::rcptr_t<pfc::string8> m_content;
service_ptr_t<completion_notify_orphanable> m_notify;
};
}

View File

@@ -0,0 +1,147 @@
#include "stdafx.h"
#include "../helpers/win32_misc.h"
#include "inplace_edit.h"
#include "inplace_edit_v2.h"
#include "AutoComplete.h"
namespace InPlaceEdit {
t_size CTableEditHelperV2::ColumnToPosition(t_size col) const {
PFC_ASSERT( TableEdit_IsColumnEditable(col) );
pfc::array_t<t_size> colOrder; GrabColumnOrder(colOrder);
t_size skipped = 0;
for(t_size walk = 0; walk < colOrder.get_size(); ++walk) {
const t_size curCol = colOrder[walk];
if (TableEdit_IsColumnEditable(curCol)) {
if (curCol == col) return skipped;
++skipped;
}
}
PFC_ASSERT( !"Should not get here." );
return ~0;
}
t_size CTableEditHelperV2::PositionToColumn(t_size pos) const {
pfc::array_t<t_size> colOrder; GrabColumnOrder(colOrder);
t_size skipped = 0;
for(t_size walk = 0; walk < colOrder.get_size(); ++walk) {
const t_size curCol = colOrder[walk];
if (TableEdit_IsColumnEditable(curCol)) {
if (skipped == pos) return curCol;
++skipped;
}
}
PFC_ASSERT( !"Should not get here." );
return ~0;
}
t_size CTableEditHelperV2::EditableColumnCount() const {
const t_size total = TableEdit_GetColumnCount();
t_size found = 0;
for(t_size walk = 0; walk < total; ++walk) {
if (TableEdit_IsColumnEditable(walk)) found++;
}
return found;
}
bool CTableEditHelperV2::TableEdit_Advance(t_size & item, t_size & subItem, t_uint32 whathappened) {
//moo
unsigned _item((unsigned)item), _subItem((unsigned)ColumnToPosition(subItem));
if (!InPlaceEdit::TableEditAdvance(_item,_subItem,(unsigned) TableEdit_GetItemCount(), (unsigned) EditableColumnCount(), whathappened)) return false;
item = _item; subItem = PositionToColumn(_subItem);
return true;
}
void CTableEditHelperV2::TableEdit_Abort(bool forwardContent) {
if (this->have_task(KTaskID)) {
this->orphan_task(KTaskID);
if (forwardContent && (m_editFlags & KFlagReadOnly) == 0) {
if (m_editData.is_valid()) {
pfc::string8 temp(*m_editData);
TableEdit_SetField(m_editItem,m_editSubItem, temp);
}
}
m_editData.release();
SetFocus(NULL);
TableEdit_Finished();
}
}
void CTableEditHelperV2::TableEdit_Start(t_size item, t_size subItem) {
PFC_ASSERT( TableEdit_IsColumnEditable( subItem ) );
m_editItem = item; m_editSubItem = subItem;
_ReStart();
}
void CTableEditHelperV2::_ReStart() {
PFC_ASSERT( m_editItem < TableEdit_GetItemCount() );
PFC_ASSERT( m_editSubItem < TableEdit_GetColumnCount() );
TableEdit_SetItemFocus(m_editItem,m_editSubItem);
m_editData.new_t();
t_size lineCount = 1;
TableEdit_GetField(m_editItem, m_editSubItem, *m_editData, lineCount);
m_editFlags = TableEdit_GetEditFlags(m_editItem, m_editSubItem);
RECT rc = TableEdit_GetItemRect(m_editItem, m_editSubItem);
if (lineCount > 1) {
rc.bottom = rc.top + (rc.bottom - rc.top) * lineCount;
m_editFlags |= KFlagMultiLine;
}
pfc::com_ptr_t<IUnknown> acl;
if (!TableEdit_GetAutoComplete(m_editItem, m_editSubItem, acl)) acl.release();
InPlaceEdit::StartEx(TableEdit_GetParentWnd(), rc, m_editFlags, m_editData, create_task(KTaskID), acl.get_ptr(), ACO_AUTOSUGGEST);
}
void CTableEditHelperV2::on_task_completion(unsigned id, unsigned status) {
if (id == KTaskID) {
orphan_task(KTaskID);
if (m_editData.is_valid()) {
if (status & InPlaceEdit::KEditFlagContentChanged) {
TableEdit_SetField(m_editItem,m_editSubItem,*m_editData);
}
m_editData.release();
}
if (TableEdit_Advance(m_editItem,m_editSubItem,status)) {
_ReStart();
} else {
TableEdit_Finished();
}
}
}
void CTableEditHelperV2_ListView::TableEdit_GetColumnOrder(t_size * out, t_size count) const {
pfc::array_t<int> temp; temp.set_size(count);
WIN32_OP_D( ListView_GetColumnOrderArray( TableEdit_GetParentWnd(), count, temp.get_ptr() ) );
for(t_size walk = 0; walk < count; ++walk) out[walk] = temp[walk];
}
RECT CTableEditHelperV2_ListView::TableEdit_GetItemRect(t_size item, t_size subItem) const {
RECT rc;
WIN32_OP_D( ListView_GetSubItemRect(TableEdit_GetParentWnd(),item,subItem,LVIR_LABEL,&rc) );
return rc;
}
void CTableEditHelperV2_ListView::TableEdit_GetField(t_size item, t_size subItem, pfc::string_base & out, t_size & lineCount) {
listview_helper::get_item_text( TableEdit_GetParentWnd(), item, subItem, out);
lineCount = pfc::is_multiline(out) ? 5 : 1;
}
void CTableEditHelperV2_ListView::TableEdit_SetField(t_size item, t_size subItem, const char * value) {
WIN32_OP_D( listview_helper::set_item_text( TableEdit_GetParentWnd(), item, subItem, value) );
}
t_size CTableEditHelperV2_ListView::TableEdit_GetItemCount() const {
LRESULT temp;
WIN32_OP_D( ( temp = ListView_GetItemCount( TableEdit_GetParentWnd() ) ) >= 0 );
return (t_size) temp;
}
void CTableEditHelperV2_ListView::TableEdit_SetItemFocus(t_size item, t_size subItem) {
WIN32_OP_D( listview_helper::select_single_item( TableEdit_GetParentWnd(), item ) );
}
}

View File

@@ -0,0 +1,55 @@
#pragma once
#include "../helpers/listview_helper.h"
namespace InPlaceEdit {
class NOVTABLE CTableEditHelperV2 : protected completion_notify_receiver {
public:
virtual RECT TableEdit_GetItemRect(t_size item, t_size subItem) const = 0;
virtual void TableEdit_GetField(t_size item, t_size subItem, pfc::string_base & out, t_size & lineCount) = 0;
virtual void TableEdit_SetField(t_size item, t_size subItem, const char * value) = 0;
virtual HWND TableEdit_GetParentWnd() const = 0;
virtual bool TableEdit_Advance(t_size & item, t_size & subItem, t_uint32 whathappened);
virtual void TableEdit_Finished() {}
virtual t_size TableEdit_GetItemCount() const = 0;
virtual t_size TableEdit_GetColumnCount() const = 0;
virtual void TableEdit_SetItemFocus(t_size item, t_size subItem) = 0;
virtual bool TableEdit_IsColumnEditable(t_size subItem) const {return true;}
virtual void TableEdit_GetColumnOrder(t_size * out, t_size count) const {order_helper::g_fill(out,count);}
virtual t_uint32 TableEdit_GetEditFlags(t_size item, t_size subItem) const {return 0;}
virtual bool TableEdit_GetAutoComplete(t_size item, t_size subItem, pfc::com_ptr_t<IUnknown> & out) {return false;}
void TableEdit_Start(t_size item, t_size subItem);
void TableEdit_Abort(bool forwardContent);
bool TableEdit_IsActive() const {return have_task(KTaskID);}
protected:
void on_task_completion(unsigned p_id,unsigned p_status);
private:
t_size ColumnToPosition(t_size col) const;
t_size PositionToColumn(t_size pos) const;
t_size EditableColumnCount() const;
void GrabColumnOrder(pfc::array_t<t_size> & buffer) const {buffer.set_size(TableEdit_GetColumnCount()); TableEdit_GetColumnOrder(buffer.get_ptr(), buffer.get_size());}
void _ReStart();
t_size m_editItem, m_editSubItem;
t_uint32 m_editFlags;
pfc::rcptr_t<pfc::string8> m_editData;
static const unsigned KTaskID = 0x6f0a3de6;
};
class NOVTABLE CTableEditHelperV2_ListView : public CTableEditHelperV2 {
public:
RECT TableEdit_GetItemRect(t_size item, t_size subItem) const;
void TableEdit_GetField(t_size item, t_size subItem, pfc::string_base & out, t_size & lineCount);
void TableEdit_SetField(t_size item, t_size subItem, const char * value);
t_size TableEdit_GetColumnCount() const {return (t_size) ListView_GetColumnCount(TableEdit_GetParentWnd());}
t_size TableEdit_GetItemCount() const;
void TableEdit_SetItemFocus(t_size item, t_size subItem);
void TableEdit_GetColumnOrder(t_size * out, t_size count) const;
};
}

View File

@@ -0,0 +1,100 @@
#include "stdafx.h"
#include "Controls.h"
#include "../helpers/win32_misc.h"
void PaintSeparatorControl(CWindow wnd) {
CPaintDC dc(wnd);
TCHAR buffer[512] = {};
wnd.GetWindowText(buffer, _countof(buffer));
const int txLen = pfc::strlen_max_t(buffer, _countof(buffer));
CRect contentRect;
WIN32_OP_D( wnd.GetClientRect(contentRect) );
dc.SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
dc.SetBkMode(TRANSPARENT);
{
CBrushHandle brush = (HBRUSH) wnd.GetParent().SendMessage(WM_CTLCOLORSTATIC, (WPARAM) (HDC) dc, (LPARAM) wnd.m_hWnd);
if (brush != NULL) dc.FillRect(contentRect, brush);
}
SelectObjectScope scopeFont(dc, wnd.GetFont());
if (txLen > 0) {
CRect rcText(contentRect);
if ( !wnd.IsWindowEnabled() ) {
dc.SetTextColor( GetSysColor(COLOR_GRAYTEXT) );
}
WIN32_OP_D( dc.DrawText(buffer,txLen,rcText,DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | DT_LEFT ) > 0);
// WIN32_OP_D( dc.GrayString(NULL, NULL, (LPARAM) buffer, txLen, rcText.left, rcText.top, rcText.Width(), rcText.Height() ) );
}
SIZE txSize, probeSize;
const TCHAR probe[] = _T("#");
if (dc.GetTextExtent(buffer,txLen,&txSize) && dc.GetTextExtent(probe, _countof(probe), &probeSize)) {
int spacing = txSize.cx > 0 ? (probeSize.cx / 4) : 0;
if (txSize.cx + spacing < contentRect.Width()) {
const CPoint center = contentRect.CenterPoint();
CRect rcEdge(contentRect.left + txSize.cx + spacing, center.y, contentRect.right, contentRect.bottom);
WIN32_OP_D( dc.DrawEdge(rcEdge, EDGE_ETCHED, BF_TOP) );
}
}
}
void ui_element_instance_standard_context_menu(service_ptr_t<ui_element_instance> p_elem, LPARAM p_pt) {
CPoint pt;
bool fromKeyboard;
if (p_pt == -1) {
fromKeyboard = true;
if (!p_elem->edit_mode_context_menu_get_focus_point(pt)) {
CRect rc;
WIN32_OP_D( GetWindowRect(p_elem->get_wnd(), rc) );
pt = rc.CenterPoint();
}
} else {
fromKeyboard = false;
pt = p_pt;
}
if (p_elem->edit_mode_context_menu_test(pt,fromKeyboard)) {
const unsigned idBase = 1;
CMenu menu;
WIN32_OP( menu.CreatePopupMenu() );
p_elem->edit_mode_context_menu_build(pt,fromKeyboard,menu,idBase);
int cmd;
{
CMenuSelectionReceiver_UiElement receiver(p_elem,idBase);
cmd = menu.TrackPopupMenu(TPM_RIGHTBUTTON|TPM_NONOTIFY|TPM_RETURNCMD,pt.x,pt.y,receiver);
}
if (cmd > 0) p_elem->edit_mode_context_menu_command(pt,fromKeyboard,cmd,idBase);
}
}
void ui_element_instance_standard_context_menu_eh(service_ptr_t<ui_element_instance> p_elem, LPARAM p_pt) {
try {
ui_element_instance_standard_context_menu(p_elem, p_pt);
} catch(std::exception const & e) {
console::complain("Context menu failure", e);
}
}
#if _WIN32_WINNT >= 0x501
void HeaderControl_SetSortIndicator(CHeaderCtrl header, int column, bool isUp) {
const int total = header.GetItemCount();
for(int walk = 0; walk < total; ++walk) {
HDITEM item = {}; item.mask = HDI_FORMAT;
if (header.GetItem(walk,&item)) {
DWORD newFormat = item.fmt;
newFormat &= ~( HDF_SORTUP | HDF_SORTDOWN );
if (walk == column) {
newFormat |= isUp ? HDF_SORTUP : HDF_SORTDOWN;
}
if (newFormat != item.fmt) {
item.fmt = newFormat;
header.SetItem(walk,&item);
}
}
}
}
#endif

View File

@@ -0,0 +1,440 @@
#pragma once
#include "../helpers/win32_misc.h"
#include "WTL-PP.h"
#include <utility>
class CMenuSelectionReceiver : public CWindowImpl<CMenuSelectionReceiver> {
public:
CMenuSelectionReceiver(HWND p_parent) {
WIN32_OP( Create(p_parent) != NULL );
}
~CMenuSelectionReceiver() {
if (m_hWnd != NULL) DestroyWindow();
}
typedef CWindowImpl<CMenuSelectionReceiver> _baseClass;
DECLARE_WND_CLASS_EX(TEXT("{DF0087DB-E765-4283-BBAB-6AB2E8AB64A1}"),0,0);
BEGIN_MSG_MAP(CMenuSelectionReceiver)
MESSAGE_HANDLER(WM_MENUSELECT,OnMenuSelect)
END_MSG_MAP()
protected:
virtual bool QueryHint(unsigned p_id,pfc::string_base & p_out) {
return false;
}
private:
LRESULT OnMenuSelect(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) {
if (p_lp != 0) {
if (HIWORD(p_wp) & MF_POPUP) {
m_status.release();
} else {
pfc::string8 msg;
UINT cmd = LOWORD(p_wp);
if ( cmd == 0 || !QueryHint(cmd,msg)) {
m_status.release();
} else {
if (m_status.is_empty()) {
if (!static_api_ptr_t<ui_control>()->override_status_text_create(m_status)) m_status.release();
}
if (m_status.is_valid()) {
m_status->override_text(msg);
}
}
}
} else {
m_status.release();
}
return 0;
}
service_ptr_t<ui_status_text_override> m_status;
PFC_CLASS_NOT_COPYABLE(CMenuSelectionReceiver,CMenuSelectionReceiver);
};
class CMenuDescriptionMap : public CMenuSelectionReceiver {
public:
CMenuDescriptionMap(HWND p_parent) : CMenuSelectionReceiver(p_parent) {}
void Set(unsigned p_id,const char * p_description) {m_content.set(p_id,p_description);}
protected:
bool QueryHint(unsigned p_id,pfc::string_base & p_out) {
return m_content.query(p_id,p_out);
}
private:
pfc::map_t<unsigned,pfc::string8> m_content;
};
class CMenuDescriptionHybrid : public CMenuSelectionReceiver {
public:
CMenuDescriptionHybrid(HWND parent) : CMenuSelectionReceiver(parent) {}
void Set(unsigned id, const char * desc) {m_content.set(id, desc);}
void SetCM(contextmenu_manager::ptr mgr, unsigned base, unsigned max) {
m_cmMgr = mgr; m_cmMgr_base = base; m_cmMgr_max = max;
}
protected:
bool QueryHint(unsigned p_id,pfc::string_base & p_out) {
if (m_cmMgr.is_valid() && p_id >= m_cmMgr_base && p_id < m_cmMgr_max) {
return m_cmMgr->get_description_by_id(p_id - m_cmMgr_base,p_out);
}
return m_content.query(p_id,p_out);
}
private:
pfc::map_t<unsigned,pfc::string8> m_content;
contextmenu_manager::ptr m_cmMgr; unsigned m_cmMgr_base, m_cmMgr_max;
};
inline pfc::string_base & operator<<(pfc::string_base & p_fmt,const CPoint & p_point) {
return p_fmt << "(" << p_point.x << "," << p_point.y << ")";
}
inline pfc::string_base & operator<<(pfc::string_base & p_fmt,const CRect & p_rect) {
return p_fmt << "(" << p_rect.left << "," << p_rect.top << "," << p_rect.right << "," << p_rect.bottom << ")";
}
template<typename TClass>
class CAddDummyMessageMap : public TClass {
public:
BEGIN_MSG_MAP(CAddDummyMessageMap<TClass>)
END_MSG_MAP()
};
template<typename _parentClass> class CWindowFixSEH : public _parentClass { public:
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) {
__try {
return _parentClass::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult, dwMsgMapID);
} __except(uExceptFilterProc(GetExceptionInformation())) { return FALSE; /* should not get here */ }
}
template<typename ... arg_t> CWindowFixSEH( arg_t && ... arg ) : _parentClass( std::forward<arg_t>(arg) ... ) {}
};
template<typename TClass>
class CWindowAutoLifetime : public CWindowFixSEH<TClass> {
public:
typedef CWindowFixSEH<TClass> TBase;
template<typename ... arg_t> CWindowAutoLifetime(HWND parent, arg_t && ... arg) : TBase( std::forward<arg_t>(arg) ... ) {Init(parent);}
private:
void Init(HWND parent) {WIN32_OP(this->Create(parent) != NULL);}
void OnFinalMessage(HWND wnd) {PFC_ASSERT_NO_EXCEPTION( TBase::OnFinalMessage(wnd) ); PFC_ASSERT_NO_EXCEPTION(delete this);}
};
template<typename TClass>
class ImplementModelessTracking : public TClass {
public:
template<typename ... arg_t> ImplementModelessTracking(arg_t && ... arg ) : TClass(std::forward<arg_t>(arg) ... ) {}
BEGIN_MSG_MAP_EX(ImplementModelessTracking)
MSG_WM_INITDIALOG(OnInitDialog)
MSG_WM_DESTROY(OnDestroy)
CHAIN_MSG_MAP(TClass)
END_MSG_MAP_HOOK()
private:
BOOL OnInitDialog(CWindow, LPARAM) {m_modeless.Set( m_hWnd ); SetMsgHandled(FALSE); return FALSE; }
void OnDestroy() {m_modeless.Set(NULL); SetMsgHandled(FALSE); }
CModelessDialogEntry m_modeless;
};
namespace fb2k {
template<typename dialog_t, typename ... arg_t> dialog_t * newDialogEx( HWND parent, arg_t && ... arg ) {
return new CWindowAutoLifetime<ImplementModelessTracking< dialog_t > > ( parent, std::forward<arg_t>(arg) ... );
}
template<typename dialog_t, typename ... arg_t> dialog_t * newDialog(arg_t && ... arg) {
return new CWindowAutoLifetime<ImplementModelessTracking< dialog_t > > (core_api::get_main_window(), std::forward<arg_t>(arg) ...);
}
}
class CMenuSelectionReceiver_UiElement : public CMenuSelectionReceiver {
public:
CMenuSelectionReceiver_UiElement(service_ptr_t<ui_element_instance> p_owner,unsigned p_id_base) : CMenuSelectionReceiver(p_owner->get_wnd()), m_owner(p_owner), m_id_base(p_id_base) {}
protected:
bool QueryHint(unsigned p_id,pfc::string_base & p_out) {
return m_owner->edit_mode_context_menu_get_description(p_id,m_id_base,p_out);
}
private:
const unsigned m_id_base;
const service_ptr_t<ui_element_instance> m_owner;
};
void ui_element_instance_standard_context_menu(service_ptr_t<ui_element_instance> p_elem, LPARAM p_pt);
void ui_element_instance_standard_context_menu_eh(service_ptr_t<ui_element_instance> p_elem, LPARAM p_pt);
#if _WIN32_WINNT >= 0x501
void HeaderControl_SetSortIndicator(CHeaderCtrl header, int column, bool isUp);
#endif
class CTypableWindowScope {
public:
CTypableWindowScope() : m_wnd() {}
~CTypableWindowScope() {Set(NULL);}
void Set(HWND wnd) {
try {
if (m_wnd != NULL) {
static_api_ptr_t<ui_element_typable_window_manager>()->remove(m_wnd);
}
m_wnd = wnd;
if (m_wnd != NULL) {
static_api_ptr_t<ui_element_typable_window_manager>()->add(m_wnd);
}
} catch(exception_service_not_found) {
m_wnd = NULL;
}
}
private:
HWND m_wnd;
PFC_CLASS_NOT_COPYABLE_EX(CTypableWindowScope);
};
template<typename TBase> class CContainedWindowSimpleT : public CContainedWindowT<TBase>, public CMessageMap {
public:
CContainedWindowSimpleT() : CContainedWindowT<TBase>(this) {}
BEGIN_MSG_MAP(CContainedWindowSimpleT)
END_MSG_MAP()
};
static bool window_service_trait_defer_destruction(const service_base *) {return true;}
//! Special service_impl_t replacement for service classes that also implement ATL/WTL windows.
template<typename _t_base>
class window_service_impl_t : public implement_service_query< CWindowFixSEH<_t_base> > {
private:
typedef window_service_impl_t<_t_base> t_self;
typedef implement_service_query< CWindowFixSEH<_t_base> > t_base;
public:
BEGIN_MSG_MAP_EX(window_service_impl_t)
MSG_WM_DESTROY(OnDestroyPassThru)
CHAIN_MSG_MAP(__super)
END_MSG_MAP_HOOK()
int FB2KAPI service_release() throw() {
int ret = --m_counter;
if (ret == 0) {
if (window_service_trait_defer_destruction(this) && !InterlockedExchange(&m_delayedDestroyInProgress,1)) {
PFC_ASSERT_NO_EXCEPTION( service_impl_helper::release_object_delayed(this); );
} else if (m_hWnd != NULL) {
if (!m_destroyWindowInProgress) { // don't double-destroy in weird scenarios
PFC_ASSERT_NO_EXCEPTION( ::DestroyWindow(m_hWnd) );
}
} else {
PFC_ASSERT_NO_EXCEPTION( delete this );
}
}
return ret;
}
int FB2KAPI service_add_ref() throw() {return ++m_counter;}
template<typename ... arg_t>
window_service_impl_t( arg_t && ... arg ) : t_base( std::forward<arg_t>(arg) ... ) {};
private:
void OnDestroyPassThru() {
SetMsgHandled(FALSE); m_destroyWindowInProgress = true;
}
void OnFinalMessage(HWND p_wnd) {
t_base::OnFinalMessage(p_wnd);
service_ptr_t<service_base> bump(this);
}
volatile bool m_destroyWindowInProgress = false;
volatile LONG m_delayedDestroyInProgress = 0;
pfc::refcounter m_counter;
};
static void AppendMenuPopup(HMENU menu, UINT flags, CMenu & popup, const TCHAR * label) {
PFC_ASSERT( flags & MF_POPUP );
WIN32_OP( CMenuHandle(menu).AppendMenu(flags, popup, label) );
popup.Detach();
}
class CMessageMapDummy : public CMessageMap {
public:
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
LRESULT& lResult, DWORD dwMsgMapID) {return FALSE;}
};
class CPopupTooltipMessage {
public:
CPopupTooltipMessage(DWORD style = TTS_BALLOON | TTS_NOPREFIX) : m_style(style | WS_POPUP), m_toolinfo(), m_shutDown() {}
void ShowFocus(const TCHAR * message, CWindow wndParent) {
Show(message, wndParent); wndParent.SetFocus();
}
void Show(const TCHAR * message, CWindow wndParent) {
if (m_shutDown || (message == NULL && m_tooltip.m_hWnd == NULL)) return;
Initialize();
Hide();
if (message != NULL) {
CRect rect;
WIN32_OP_D( wndParent.GetWindowRect(rect) );
ShowInternal(message, wndParent, rect);
}
}
void ShowEx(const TCHAR * message, CWindow wndParent, CRect rect) {
if (m_shutDown) return;
Initialize();
Hide();
ShowInternal(message, wndParent, rect);
}
void Hide() {
if (m_tooltip.m_hWnd != NULL && m_tooltip.GetToolCount() > 0) {
m_tooltip.TrackActivate(&m_toolinfo,FALSE);
m_tooltip.DelTool(&m_toolinfo);
}
}
void CleanUp() {
if (m_tooltip.m_hWnd != NULL) {
m_tooltip.DestroyWindow();
}
}
void ShutDown() {
m_shutDown = true; CleanUp();
}
private:
void ShowInternal(const TCHAR * message, CWindow wndParent, CRect rect) {
PFC_ASSERT( !m_shutDown );
PFC_ASSERT( message != NULL );
PFC_ASSERT( wndParent != NULL );
if ( _tcschr( message, '\n') != nullptr ) {
m_tooltip.SetMaxTipWidth( rect.Width() );
}
m_toolinfo.cbSize = sizeof(m_toolinfo);
m_toolinfo.uFlags = TTF_TRACK|TTF_IDISHWND|TTF_ABSOLUTE|TTF_TRANSPARENT|TTF_CENTERTIP;
m_toolinfo.hwnd = wndParent;
m_toolinfo.uId = 0;
m_toolinfo.lpszText = const_cast<TCHAR*>(message);
m_toolinfo.hinst = NULL; //core_api::get_my_instance();
if (m_tooltip.AddTool(&m_toolinfo)) {
m_tooltip.TrackPosition(rect.CenterPoint().x,rect.bottom);
m_tooltip.TrackActivate(&m_toolinfo,TRUE);
}
}
void Initialize() {
if (m_tooltip.m_hWnd == NULL) {
WIN32_OP( m_tooltip.Create( NULL , NULL, NULL, m_style) );
}
}
CContainedWindowSimpleT<CToolTipCtrl> m_tooltip;
TOOLINFO m_toolinfo;
const DWORD m_style;
bool m_shutDown;
};
template<typename T> class CDialogWithTooltip : public CDialogImpl<T> {
public:
BEGIN_MSG_MAP(CDialogWithTooltip)
MSG_WM_DESTROY(OnDestroy)
END_MSG_MAP()
void ShowTip(UINT id, const TCHAR * label) {
m_tip.Show(label, GetDlgItem(id));
}
void ShowTip(HWND child, const TCHAR * label) {
m_tip.Show(label, child);
}
void ShowTipF(UINT id, const TCHAR * label) {
m_tip.ShowFocus(label, GetDlgItem(id));
}
void ShowTipF(HWND child, const TCHAR * label) {
m_tip.ShowFocus(label, child);
}
void HideTip() {m_tip.Hide();}
private:
void OnDestroy() {m_tip.ShutDown(); SetMsgHandled(FALSE); }
CPopupTooltipMessage m_tip;
};
static void ListView_FixContextMenuPoint(CListViewCtrl list,CPoint & coords) {
if (coords == CPoint(-1,-1)) {
int selWalk = -1;
CRect rcClient; WIN32_OP_D(list.GetClientRect(rcClient));
for(;;) {
selWalk = list.GetNextItem(selWalk, LVNI_SELECTED);
if (selWalk < 0) {
CRect rc;
WIN32_OP_D( list.GetWindowRect(&rc) );
coords = rc.CenterPoint();
return;
}
CRect rcItem, rcVisible;
WIN32_OP_D( list.GetItemRect(selWalk, &rcItem, LVIR_BOUNDS) );
if (rcVisible.IntersectRect(rcItem, rcClient)) {
coords = rcVisible.CenterPoint();
WIN32_OP_D( list.ClientToScreen(&coords) );
return;
}
}
}
}
template<typename TDialog> class preferences_page_instance_impl : public TDialog {
public:
preferences_page_instance_impl(HWND parent, preferences_page_callback::ptr callback) : TDialog(callback) {WIN32_OP(this->Create(parent) != NULL);}
HWND get_wnd() {return this->m_hWnd;}
};
static bool window_service_trait_defer_destruction(const preferences_page_instance *) {return false;}
template<typename TDialog> class preferences_page_impl : public preferences_page_v3 {
public:
preferences_page_instance::ptr instantiate(HWND parent, preferences_page_callback::ptr callback) {
return new window_service_impl_t<preferences_page_instance_impl<TDialog> >(parent, callback);
}
};
class CEmbeddedDialog : public CDialogImpl<CEmbeddedDialog> {
public:
CEmbeddedDialog(CMessageMap * owner, DWORD msgMapID, UINT dialogID) : m_owner(*owner), IDD(dialogID), m_msgMapID(msgMapID) {}
BEGIN_MSG_MAP(CEmbeddedDialog)
CHAIN_MSG_MAP_ALT_MEMBER(m_owner, m_msgMapID)
END_MSG_MAP()
const DWORD m_msgMapID;
const UINT IDD;
CMessageMap & m_owner;
};
// here because of window_service_impl_t
template<typename TImpl, typename TInterface = ui_element> class ui_element_impl : public TInterface {
public:
GUID get_guid() { return TImpl::g_get_guid(); }
GUID get_subclass() { return TImpl::g_get_subclass(); }
void get_name(pfc::string_base & out) { TImpl::g_get_name(out); }
ui_element_instance::ptr instantiate(HWND parent, ui_element_config::ptr cfg, ui_element_instance_callback::ptr callback) {
PFC_ASSERT(cfg->get_guid() == get_guid());
service_nnptr_t<ui_element_instance_impl_helper> item = new window_service_impl_t<ui_element_instance_impl_helper>(cfg, callback);
item->initialize_window(parent);
return item;
}
ui_element_config::ptr get_default_configuration() { return TImpl::g_get_default_configuration(); }
ui_element_children_enumerator_ptr enumerate_children(ui_element_config::ptr cfg) { return NULL; }
bool get_description(pfc::string_base & out) { out = TImpl::g_get_description(); return true; }
private:
class ui_element_instance_impl_helper : public TImpl {
public:
ui_element_instance_impl_helper(ui_element_config::ptr cfg, ui_element_instance_callback::ptr callback) : TImpl(cfg, callback) {}
GUID get_guid() { return TImpl::g_get_guid(); }
GUID get_subclass() { return TImpl::g_get_subclass(); }
HWND get_wnd() { return *this; }
};
public:
typedef ui_element_instance_impl_helper TInstance;
static TInstance const & instanceGlobals() { return *reinterpret_cast<const TInstance*>(NULL); }
};

View File

@@ -0,0 +1,8 @@
// stdafx.cpp : source file that includes just the standard includes
// foobar2000_ATL_helpers.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information
#include "stdafx.h"
// TODO: reference any additional headers you need in STDAFX.H
// and not in this file

View File

@@ -0,0 +1,15 @@
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#pragma once
#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later.
#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows.
#endif
// TODO: reference additional headers your program requires here
#include "ATLHelpersLean.h"

View File

@@ -0,0 +1,353 @@
#include "stdafx.h"
#if FOOBAR2000_TARGET_VERSION >= 79
#include "ui_element_helpers.h"
#include "../helpers/IDataObjectUtils.h"
#include "misc.h"
namespace ui_element_helpers {
ui_element_instance_ptr instantiate_dummy(HWND p_parent,ui_element_config::ptr cfg, ui_element_instance_callback_ptr p_callback) {
service_ptr_t<ui_element> ptr;
if (!find(ptr,pfc::guid_null)) uBugCheck();
return ptr->instantiate(p_parent,cfg,p_callback);
}
ui_element_instance_ptr instantiate(HWND p_parent,ui_element_config::ptr cfg,ui_element_instance_callback_ptr p_callback) {
try {
service_ptr_t<ui_element> ptr;
if (!find(ptr,cfg->get_guid())) throw exception_io_data("UI Element Not Found");
auto ret = ptr->instantiate(p_parent,cfg,p_callback);
if (ret.is_empty()) throw std::runtime_error("Null UI Element returned");
return std::move(ret);
} catch(std::exception const & e) {
console::complain("UI Element instantiation failure",e);
return instantiate_dummy(p_parent,cfg,p_callback);
}
}
ui_element_instance_ptr update(ui_element_instance_ptr p_element,HWND p_parent,ui_element_config::ptr cfg,ui_element_instance_callback_ptr p_callback) {
if (p_element.is_valid() && cfg->get_guid() == p_element->get_guid()) {
p_element->set_configuration(cfg);
return p_element;
} else {
return instantiate(p_parent,cfg,p_callback);
}
}
bool find(service_ptr_t<ui_element> & p_out,const GUID & p_guid) {
return service_by_guid(p_out,p_guid);
}
ui_element_children_enumerator_ptr enumerate_children(ui_element_config::ptr cfg) {
service_ptr_t<ui_element> ptr;
if (!find(ptr,cfg->get_guid())) return NULL;
try {
return ptr->enumerate_children(cfg);
} catch(exception_io_data) {
return NULL;
}
}
};
void ui_element_helpers::replace_with_new_element(ui_element_instance_ptr & p_item,const GUID & p_guid,HWND p_parent,ui_element_instance_callback_ptr p_callback) {
abort_callback_dummy l_abort;
ui_element_config::ptr cfg;
try {
if (p_item.is_empty()) {
service_ptr_t<ui_element> ptr;
if (!find(ptr,p_guid)) throw exception_io_data("UI Element Not Found");
cfg = ptr->get_default_configuration();
p_item = ptr->instantiate(p_parent,cfg,p_callback);
} else if (p_item->get_guid() != p_guid) {
service_ptr_t<ui_element> ptr;
if (!find(ptr,p_guid)) throw exception_io_data("UI Element Not Found");
cfg = ptr->import(p_item->get_configuration());
//p_item.release();
if (cfg.is_empty()) cfg = ptr->get_default_configuration();
p_item = ptr->instantiate(p_parent,cfg,p_callback);
}
} catch(std::exception const & e) {
console::complain("UI Element instantiation failure",e);
if (cfg.is_empty()) cfg = ui_element_config::g_create_empty();
p_item = instantiate_dummy(p_parent,cfg,p_callback);
}
}
namespace {
class CMyMenuSelectionReceiver : public CMenuSelectionReceiver {
public:
CMyMenuSelectionReceiver(HWND p_wnd,ui_element_helpers::ui_element_edit_tools * p_host,ui_element_instance_ptr p_client,unsigned p_client_id,unsigned p_host_base,unsigned p_client_base) : CMenuSelectionReceiver(p_wnd), m_host(p_host), m_client(p_client), m_client_id(p_client_id), m_host_base(p_host_base), m_client_base(p_client_base) {}
bool QueryHint(unsigned p_id,pfc::string_base & p_out) {
if (p_id >= m_client_base) {
return m_client->edit_mode_context_menu_get_description(p_id,m_client_base,p_out);
} else if (p_id >= m_host_base) {
return m_host->host_edit_mode_context_menu_get_description(m_client_id,p_id,m_host_base,p_out);
} else {
const char * msg = ui_element_helpers::ui_element_edit_tools::description_from_menu_command(p_id);
if (msg == NULL) return false;
p_out = msg;
return true;
}
}
private:
ui_element_helpers::ui_element_edit_tools * const m_host;
ui_element_instance_ptr const m_client;
unsigned const m_client_id,m_host_base,m_client_base;
};
};
namespace HostHelperIDs {
enum {ID_LABEL, ID_REPLACE = 1, ID_ADD_NEW, ID_CUT, ID_COPY, ID_PASTE, ID_CUSTOM_BASE};
}
void ui_element_helpers::ui_element_edit_tools::standard_edit_context_menu(LPARAM p_point,ui_element_instance_ptr p_item,unsigned p_id,HWND p_parent) {
using namespace HostHelperIDs;
static_api_ptr_t<ui_element_common_methods> api;
POINT pt;
bool fromkeyboard = false;
if (p_point == (LPARAM)(-1)) {
fromkeyboard = true;
if (!p_item->edit_mode_context_menu_get_focus_point(pt)) {
CRect rect;
WIN32_OP_D( CWindow(p_item->get_wnd()).GetWindowRect(&rect) );
pt = rect.CenterPoint();
}
} else {
pt.x = (short)LOWORD(p_point);
pt.y = (short)HIWORD(p_point);
}
CMenu menu;
WIN32_OP( menu.CreatePopupMenu() );
const GUID sourceItemGuid = p_item->get_guid();
const bool sourceItemEmpty = !!(sourceItemGuid == pfc::guid_null);
if (sourceItemEmpty) {
WIN32_OP_D( menu.AppendMenu(MF_STRING,ID_ADD_NEW,TEXT(AddNewUIElementCommand)) );
WIN32_OP_D( menu.SetMenuDefaultItem(ID_ADD_NEW) );
} else {
service_ptr_t<ui_element> elem;
pfc::string8 name;
if (find(elem,sourceItemGuid)) {
elem->get_name(name);
} else {
name = "<unknown element>";
}
WIN32_OP_D( menu.AppendMenu(MF_STRING | MF_DISABLED,ID_LABEL,pfc::stringcvt::string_os_from_utf8(name)) );
WIN32_OP_D( menu.AppendMenu(MF_SEPARATOR,(UINT_PTR)0,TEXT("")) );
WIN32_OP_D( menu.AppendMenu(MF_STRING,ID_REPLACE,TEXT(ReplaceUIElementCommand)) );
}
WIN32_OP_D( menu.AppendMenu(MF_SEPARATOR,(UINT_PTR)0,TEXT("")) );
//menu.AppendMenu(MF_STRING,ID_REPLACE,TEXT(ReplaceUIElementCommand));
WIN32_OP_D( menu.AppendMenu(MF_STRING | (sourceItemEmpty ? (MF_DISABLED|MF_GRAYED) : 0),ID_CUT,TEXT(CutUIElementCommand)) );
WIN32_OP_D( menu.AppendMenu(MF_STRING | (sourceItemEmpty ? (MF_DISABLED|MF_GRAYED) : 0),ID_COPY,TEXT(CopyUIElementCommand)) );
WIN32_OP_D( menu.AppendMenu(MF_STRING | (api->is_paste_available() ? 0 : (MF_DISABLED|MF_GRAYED)),ID_PASTE,TEXT(PasteUIElementCommand)) );
unsigned custom_walk = ID_CUSTOM_BASE;
unsigned custom_base_host = ~0, custom_base_client = ~0;
if (host_edit_mode_context_menu_test(p_id,pt,fromkeyboard)) {
menu.AppendMenu(MF_SEPARATOR,(UINT_PTR)0,TEXT(""));
custom_base_host = custom_walk;
host_edit_mode_context_menu_build(p_id,pt,fromkeyboard,menu,custom_walk);
}
{
if (p_item->edit_mode_context_menu_test(pt,fromkeyboard)) {
WIN32_OP_D( menu.AppendMenu(MF_SEPARATOR,(UINT_PTR)0,TEXT("")) );
custom_base_client = custom_walk;
p_item->edit_mode_context_menu_build(pt,fromkeyboard,menu,custom_walk);
}
}
int cmd;
{
ui_element_highlight_scope s(p_item->get_wnd());
CMyMenuSelectionReceiver receiver(p_parent,this,p_item,p_id,custom_base_host,custom_base_client);
cmd = menu.TrackPopupMenu(TPM_RIGHTBUTTON|TPM_NONOTIFY|TPM_RETURNCMD,pt.x,pt.y,receiver);
}
if (cmd > 0) {
switch(cmd) {
case ID_REPLACE:
case ID_ADD_NEW:
replace_dialog(p_item->get_wnd(),p_id,p_item->get_guid());
break;
case ID_COPY:
api->copy(p_item);
break;
case ID_CUT:
api->copy(p_item);
p_item.release();
host_replace_element(p_id,pfc::guid_null);
break;
case ID_PASTE:
host_paste_element(p_id);
break;
default:
if ((unsigned)cmd >= custom_base_client) {
p_item->edit_mode_context_menu_command(pt,fromkeyboard,(unsigned)cmd,custom_base_client);
} else if ((unsigned)cmd >= custom_base_host) {
host_edit_mode_context_menu_command(p_id,pt,fromkeyboard,(unsigned)cmd,custom_base_host);
}
break;
}
}
}
void ui_element_helpers::ui_element_edit_tools::on_elem_replace(unsigned p_id, GUID const & newGuid) {
m_replace_dialog.Detach();
if ( newGuid != pfc::guid_null ) {
host_replace_element(p_id,newGuid);
}
}
void ui_element_helpers::ui_element_edit_tools::replace_dialog(HWND p_parent,unsigned p_id,const GUID & p_current) {
_release_replace_dialog();
auto ks = m_killSwitch;
auto reply = ui_element_replace_dialog_notify::create( [=] ( GUID newGUID ) {
if ( !*ks) {
on_elem_replace(p_id, newGUID );
}
} );
HWND dlg = ui_element_common_methods_v3::get()->replace_element_dialog_start( p_parent, p_current, reply );
m_replace_dialog.Attach( dlg );
}
const char * ui_element_helpers::ui_element_edit_tools::description_from_menu_command(unsigned p_id) {
using namespace HostHelperIDs;
switch(p_id) {
case ID_REPLACE:
return ReplaceUIElementDescription;
case ID_CUT:
return CutUIElementDescription;
case ID_COPY:
return CopyUIElementDescription;
case ID_PASTE:
return PasteUIElementDescription;
case ID_ADD_NEW:
return AddNewUIElementDescription;
default:
return NULL;
}
}
void ui_element_helpers::ui_element_edit_tools::_release_replace_dialog() {
if (m_replace_dialog.m_hWnd != NULL) {
m_replace_dialog.DestroyWindow();
}
}
bool ui_element_helpers::enforce_min_max_info(CWindow p_window,ui_element_min_max_info const & p_info) {
CRect rect;
WIN32_OP_D( p_window.GetWindowRect(&rect) );
t_uint32 width = (t_uint32) rect.Width();
t_uint32 height = (t_uint32) rect.Height();
bool changed = false;
if (width < p_info.m_min_width) {changed = true; width = p_info.m_min_width;}
if (width > p_info.m_max_width) {changed = true; width = p_info.m_max_width;}
if (height < p_info.m_min_height) {changed = true; height = p_info.m_min_height;}
if (height > p_info.m_max_height) {changed = true; height = p_info.m_max_height;}
if (changed) {
WIN32_OP_D( p_window.SetWindowPos(NULL,0,0,width,height,SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER) );
}
return changed;
}
void ui_element_helpers::handle_WM_GETMINMAXINFO(LPARAM p_lp,const ui_element_min_max_info & p_myinfo) {
MINMAXINFO * info = reinterpret_cast<MINMAXINFO *>(p_lp);
/*console::formatter() << "handle_WM_GETMINMAXINFO";
console::formatter() << p_myinfo.m_min_width << ", " << p_myinfo.m_min_height;
console::formatter() << info->ptMinTrackSize << ", " << info->ptMaxTrackSize;*/
pfc::max_acc(info->ptMinTrackSize.x,(LONG)p_myinfo.m_min_width);
pfc::max_acc(info->ptMinTrackSize.y,(LONG)p_myinfo.m_min_height);
if ((LONG) p_myinfo.m_max_width >= 0) pfc::min_acc(info->ptMaxTrackSize.x, (LONG) p_myinfo.m_max_width);
if ((LONG) p_myinfo.m_max_height >= 0) pfc::min_acc(info->ptMaxTrackSize.y, (LONG) p_myinfo.m_max_height);
//console::formatter() << info->ptMinTrackSize << ", " << info->ptMaxTrackSize;
}
bool ui_element_helpers::ui_element_edit_tools::host_paste_element(unsigned p_id) {
pfc::com_ptr_t<IDataObject> obj;
if (SUCCEEDED(OleGetClipboard(obj.receive_ptr()))) {
DWORD effect;
ui_element_config::ptr cfg;
if (static_api_ptr_t<ui_element_common_methods>()->parse_dataobject(obj,cfg,effect)) {
host_replace_element(p_id, cfg);
IDataObjectUtils::SetDataObjectDWORD(obj, RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT), effect);
IDataObjectUtils::PasteSucceeded(obj,effect);
if (effect == DROPEFFECT_MOVE) OleSetClipboard(NULL);
return true;
}
}
return false;
}
bool ui_element_helpers::recurse_for_elem_config(ui_element_config::ptr root, ui_element_config::ptr & out, const GUID & toFind) {
const GUID rootID = root->get_guid();
if (rootID == toFind) {
out = root; return true;
}
ui_element::ptr elem;
if (!find(elem, rootID)) return false;
ui_element_children_enumerator::ptr children;
try {
children = elem->enumerate_children(root);
} catch(exception_io_data) {return false;}
if (children.is_empty()) return false;
const t_size childrenTotal = children->get_count();
for(t_size walk = 0; walk < childrenTotal; ++walk) {
if (recurse_for_elem_config(children->get_item(walk), out, toFind)) return true;
}
return false;
}
bool ui_element_helpers::ui_element_instance_host_base::grabTopPriorityVisibleChild(ui_element_instance_ptr & out, t_size & outWhich, double & outPriority) {
double bestPriority = 0;
ui_element_instance_ptr best;
t_size bestWhich = ~0;
const t_size count = host_get_children_count();
for (t_size walk = 0; walk < count; ++walk) if (this->host_is_child_visible(walk) ) {
ui_element_instance_ptr item = host_get_child(walk);
if (item.is_valid() ) {
const double priority = item->get_focus_priority();
if (best.is_empty() || childPriorityCompare(walk, priority, bestPriority)) {
best = item; bestPriority = priority; bestWhich = walk;
}
}
}
if (best.is_empty()) return false;
out = best; outPriority = bestPriority; outWhich = bestWhich; return true;
}
bool ui_element_helpers::ui_element_instance_host_base::grabTopPriorityChild(ui_element_instance_ptr & out, t_size & outWhich, double & outPriority, const GUID & subclass) {
double bestPriority = 0;
ui_element_instance_ptr best;
t_size bestWhich = ~0;
const t_size count = host_get_children_count();
for (t_size walk = 0; walk < count; ++walk) {
ui_element_instance_ptr item = host_get_child(walk);
if (item.is_valid()) {
double priority;
if (item->get_focus_priority_subclass(priority, subclass)) {
if (best.is_empty() || childPriorityCompare(walk, priority, bestPriority)) {
best = item; bestPriority = priority; bestWhich = walk;
}
}
}
}
if (best.is_empty()) return false;
out = best; outPriority = bestPriority; outWhich = bestWhich; return true;
}
#endif // FOOBAR2000_TARGET_VERSION >= 79

View File

@@ -0,0 +1,374 @@
#pragma once
// ====================================================================================================
// ui_element_helpers
// A framework for creating UI Elements that host other elements.
// All foo_ui_std elements that host other elements - such as splitters or tabs - are based on this.
// Note that API 79 (v1.4) or newer is required, earlier did not provide ui_element_common_methods_v3.
// ====================================================================================================
#if FOOBAR2000_TARGET_VERSION >= 79
#include <memory>
#include <functional>
namespace ui_element_helpers {
template<typename t_receiver> class ui_element_instance_callback_multi_impl : public ui_element_instance_callback_v3 {
public:
ui_element_instance_callback_multi_impl(t_size id, t_receiver * p_receiver) : m_receiver(p_receiver), m_id(id) {}
void on_min_max_info_change() {
if (m_receiver != NULL) m_receiver->on_min_max_info_change();
}
bool query_color(const GUID & p_what,t_ui_color & p_out) {
if (m_receiver != NULL) return m_receiver->query_color(p_what,p_out);
else return false;
}
bool request_activation(service_ptr_t<class ui_element_instance> p_item) {
if (m_receiver) return m_receiver->request_activation(m_id);
else return false;
}
bool is_edit_mode_enabled() {
if (m_receiver) return m_receiver->is_edit_mode_enabled();
else return false;
}
void request_replace(service_ptr_t<class ui_element_instance> p_item) {
if (m_receiver) m_receiver->request_replace(m_id);
}
t_ui_font query_font_ex(const GUID & p_what) {
if (m_receiver) return m_receiver->query_font_ex(p_what);
else return NULL;
}
t_size notify(ui_element_instance * source, const GUID & what, t_size param1, const void * param2, t_size param2size) {
if (m_receiver) return m_receiver->host_notify(source, what, param1, param2, param2size);
else return 0;
}
void orphan() {m_receiver = NULL;}
bool is_elem_visible(service_ptr_t<class ui_element_instance> elem) {
if (m_receiver) return m_receiver->is_elem_visible(m_id);
else return false;
}
void override_id(t_size id) {m_id = id;}
void on_alt_pressed(bool) {}
private:
t_size m_id;
t_receiver * m_receiver;
};
class ui_element_instance_callback_receiver_multi {
public:
virtual void on_min_max_info_change() {}
virtual bool query_color(const GUID & p_what,t_ui_color & p_out) {return false;}
virtual bool request_activation(t_size which) {return false;}
virtual bool is_edit_mode_enabled() {return false;}
virtual void request_replace(t_size which) {}
virtual t_ui_font query_font_ex(const GUID&) {return NULL;}
virtual bool is_elem_visible(t_size which) {return true;}
virtual t_size host_notify(ui_element_instance * source, const GUID & what, t_size param1, const void * param2, t_size param2size) {return 0;}
void ui_element_instance_callback_handle_remove(bit_array const & mask, t_size const oldCount) {
t_callback_list newCallbacks;
t_size newWalk = 0;
for(t_size walk = 0; walk < oldCount; ++walk) {
if (mask[walk]) {
t_callback_ptr ptr;
if (m_callbacks.query(walk,ptr)) {
ptr->orphan(); m_callbacks.remove(walk);
}
} else {
if (newWalk != walk) {
t_callback_ptr ptr;
if (m_callbacks.query(walk,ptr)) {
m_callbacks.remove(walk);
ptr->override_id(newWalk);
m_callbacks.set(newWalk,ptr);
}
}
++newWalk;
}
}
}
void ui_element_instance_callback_handle_reorder(const t_size * order, t_size count) {
t_callback_list newCallbacks;
for(t_size walk = 0; walk < count; ++walk) {
t_callback_ptr ptr;
if (m_callbacks.query(order[walk],ptr)) {
ptr->override_id(walk);
newCallbacks.set(walk,ptr);
m_callbacks.remove(order[walk]);
}
}
PFC_ASSERT( m_callbacks.get_count() == 0 );
ui_element_instance_callback_release_all();
m_callbacks = newCallbacks;
}
ui_element_instance_callback_ptr ui_element_instance_callback_get_ptr(t_size which) {
t_callback_ptr ptr;
if (!m_callbacks.query(which,ptr)) {
ptr = new service_impl_t<t_callback>(which,this);
m_callbacks.set(which,ptr);
}
return ptr;
}
ui_element_instance_callback_ptr ui_element_instance_callback_create(t_size which) {
ui_element_instance_callback_release(which);
t_callback_ptr ptr = new service_impl_t<t_callback>(which,this);
m_callbacks.set(which,ptr);
return ptr;
}
void ui_element_instance_callback_release_all() {
for(t_callback_list::const_iterator walk = m_callbacks.first(); walk.is_valid(); ++walk) {
walk->m_value->orphan();
}
m_callbacks.remove_all();
}
void ui_element_instance_callback_release(t_size which) {
t_callback_ptr ptr;
if (m_callbacks.query(which,ptr)) {
ptr->orphan();
m_callbacks.remove(which);
}
}
protected:
~ui_element_instance_callback_receiver_multi() {
ui_element_instance_callback_release_all();
}
ui_element_instance_callback_receiver_multi() {}
private:
typedef ui_element_instance_callback_receiver_multi t_self;
typedef ui_element_instance_callback_multi_impl<t_self> t_callback;
typedef service_ptr_t<t_callback> t_callback_ptr;
typedef pfc::map_t<t_size,t_callback_ptr> t_callback_list;
t_callback_list m_callbacks;
};
//! Parses container tree to find configuration of specified element inside a layout configuration.
bool recurse_for_elem_config(ui_element_config::ptr root, ui_element_config::ptr & out, const GUID & toFind);
ui_element_instance_ptr create_root_container(HWND p_parent,ui_element_instance_callback_ptr p_callback);
ui_element_instance_ptr instantiate(HWND p_parent,ui_element_config::ptr cfg,ui_element_instance_callback_ptr p_callback);
ui_element_instance_ptr instantiate_dummy(HWND p_parent,ui_element_config::ptr cfg,ui_element_instance_callback_ptr p_callback);
ui_element_instance_ptr update(ui_element_instance_ptr p_element,HWND p_parent,ui_element_config::ptr cfg,ui_element_instance_callback_ptr p_callback);
bool find(service_ptr_t<ui_element> & p_out,const GUID & p_guid);
ui_element_children_enumerator_ptr enumerate_children(ui_element_config::ptr cfg);
void replace_with_new_element(ui_element_instance_ptr & p_item,const GUID & p_guid,HWND p_parent,ui_element_instance_callback_ptr p_callback);
class ui_element_highlight_scope {
public:
ui_element_highlight_scope(HWND wndElem) {
m_highlight = ui_element_common_methods_v3::get()->highlight_element( wndElem );
}
~ui_element_highlight_scope() {
DestroyWindow(m_highlight);
}
private:
ui_element_highlight_scope(const ui_element_highlight_scope&) = delete;
void operator=(const ui_element_highlight_scope&) = delete;
HWND m_highlight;
};
//! Helper class; provides edit-mode context menu functionality and interacts with "Replace UI Element" dialog. \n
//! Do not use directly - derive from ui_element_instance_host_base instead.
class ui_element_edit_tools {
public:
//! Override me
virtual void host_replace_element(unsigned p_id, ui_element_config::ptr cfg) {}
//! Override me
virtual void host_replace_element(unsigned p_id,const GUID & p_newguid) {}
//! Override me optionally if you customize edit mode context menu
virtual bool host_edit_mode_context_menu_test(unsigned p_childid,const POINT & p_point,bool p_fromkeyboard) {return false;}
//! Override me optionally if you customize edit mode context menu
virtual void host_edit_mode_context_menu_build(unsigned p_childid,const POINT & p_point,bool p_fromkeyboard,HMENU p_menu,unsigned & p_id_base) {}
//! Override me optionally if you customize edit mode context menu
virtual void host_edit_mode_context_menu_command(unsigned p_childid,const POINT & p_point,bool p_fromkeyboard,unsigned p_id,unsigned p_id_base) {}
//! Override me optionally if you customize edit mode context menu
virtual bool host_edit_mode_context_menu_get_description(unsigned p_childid,unsigned p_id,unsigned p_id_base,pfc::string_base & p_out) {return false;}
//! Initiates "Replace UI Element" dialog for one of your sub-elements.
void replace_dialog(HWND p_parent,unsigned p_id,const GUID & p_current);
//! Shows edit mode context menu for your element.
void standard_edit_context_menu(LPARAM p_point,ui_element_instance_ptr p_item,unsigned p_id,HWND p_parent);
static const char * description_from_menu_command(unsigned p_id);
bool host_paste_element(unsigned p_id);
BEGIN_MSG_MAP(ui_element_edit_tools)
MESSAGE_HANDLER(WM_DESTROY,OnDestroy)
END_MSG_MAP()
protected:
ui_element_edit_tools() {}
private:
void on_elem_replace(unsigned p_id,GUID const & newElem);
void _release_replace_dialog();
LRESULT OnDestroy(UINT,WPARAM,LPARAM,BOOL& bHandled) {bHandled = FALSE; *m_killSwitch = true; _release_replace_dialog(); return 0;}
CWindow m_replace_dialog;
std::shared_ptr<bool> m_killSwitch = std::make_shared<bool>();
ui_element_edit_tools( const ui_element_edit_tools & ) = delete;
void operator=( const ui_element_edit_tools & ) = delete;
};
//! Base class for ui_element_instances that host other elements.
class ui_element_instance_host_base : public ui_element_instance, protected ui_element_instance_callback_receiver_multi, protected ui_element_edit_tools {
protected:
// Any derived class must pass their messages to us, by CHAIN_MSG_MAP(ui_element_instance_host_base)
BEGIN_MSG_MAP(ui_element_instance_host_base)
CHAIN_MSG_MAP(ui_element_edit_tools)
MESSAGE_HANDLER(WM_SETTINGCHANGE,OnSettingChange);
END_MSG_MAP()
//override me
virtual ui_element_instance_ptr host_get_child(t_size which) = 0;
//override me
virtual t_size host_get_children_count() = 0;
//override me (tabs)
virtual void host_bring_to_front(t_size which) {}
//override me
virtual void on_min_max_info_change() {m_callback->on_min_max_info_change();}
//override me
virtual void host_replace_child(t_size which) = 0;
virtual bool host_is_child_visible(t_size which) {return true;}
void host_child_visibility_changed(t_size which, bool state) {
if (m_callback->is_elem_visible_(this)) {
ui_element_instance::ptr item = host_get_child(which);
if (item.is_valid()) item->notify(ui_element_notify_visibility_changed,state ? 1 : 0,NULL,0);
}
}
bool is_elem_visible(t_size which) {
if (!m_callback->is_elem_visible_(this)) return false;
return this->host_is_child_visible(which);
}
GUID get_subclass() {return ui_element_subclass_containers;}
double get_focus_priority() {
ui_element_instance_ptr item; double priority; t_size which;
if (!grabTopPriorityVisibleChild(item,which,priority)) return 0;
return priority;
}
void set_default_focus() {
ui_element_instance_ptr item; double priority; t_size which;
if (!grabTopPriorityVisibleChild(item,which,priority)) {
this->set_default_focus_fallback();
} else {
host_bring_to_front(which);
item->set_default_focus();
}
}
bool get_focus_priority_subclass(double & p_out,const GUID & p_subclass) {
ui_element_instance_ptr item; double priority; t_size which;
if (!grabTopPriorityChild(item,which,priority,p_subclass)) return false;
p_out = priority;
return true;
}
bool set_default_focus_subclass(const GUID & p_subclass) {
ui_element_instance_ptr item; double priority; t_size which;
if (!grabTopPriorityChild(item,which,priority,p_subclass)) return false;
host_bring_to_front(which);
return item->set_default_focus_subclass(p_subclass);
}
void notify(const GUID & p_what, t_size p_param1, const void * p_param2, t_size p_param2size) {
if (p_what == ui_element_notify_visibility_changed) {
const t_size total = host_get_children_count();
for(t_size walk = 0; walk < total; ++walk) {
if (this->host_is_child_visible(walk)) {
ui_element_instance_ptr item = host_get_child(walk);
if (item.is_valid()) item->notify(p_what,p_param1,p_param2,p_param2size);
}
}
} else if (p_what == ui_element_notify_get_element_labels) {
handleGetLabels(p_param1, p_param2, p_param2size);
} else {
const t_size total = host_get_children_count();
for(t_size walk = 0; walk < total; ++walk) {
ui_element_instance_ptr item = host_get_child(walk);
if (item.is_valid()) item->notify(p_what,p_param1,p_param2,p_param2size);
}
}
}
bool query_color(const GUID & p_what,t_ui_color & p_out) {return m_callback->query_color(p_what,p_out);}
bool request_activation(t_size which) {
if (!m_callback->request_activation(this)) return false;
host_bring_to_front(which);
return true;
}
bool is_edit_mode_enabled() {return m_callback->is_edit_mode_enabled();}
t_ui_font query_font_ex(const GUID& id) {return m_callback->query_font_ex(id);}
void request_replace(t_size which) {
host_replace_child(which);
}
private:
void handleGetLabelsChild(ui_element_instance::ptr child, t_size which, t_size param1, const void * param2, t_size param2size) {
if (child->get_subclass() == ui_element_subclass_containers) {
child->notify(ui_element_notify_get_element_labels, param1, param2, param2size);
} else if (child->get_guid() != pfc::guid_null && child->get_wnd() != NULL && this->host_is_child_visible(which)) {
FB2K_DYNAMIC_ASSERT(param2 != NULL);
reinterpret_cast<ui_element_notify_get_element_labels_callback*>(const_cast<void*>(param2))->set_visible_element(child);
}
}
void handleGetLabels(t_size param1, const void * param2, t_size param2size) {
const t_size childrenTotal = host_get_children_count();
for(t_size childWalk = 0; childWalk < childrenTotal; ++childWalk) {
ui_element_instance_ptr item = host_get_child(childWalk);
if (item.is_valid()) handleGetLabelsChild(item, childWalk, param1, param2, param2size);
}
}
LRESULT OnSettingChange(UINT msg,WPARAM wp,LPARAM lp,BOOL& bHandled) {
bHandled = FALSE;
const t_size total = host_get_children_count();
for(t_size walk = 0; walk < total; ++walk) {
ui_element_instance::ptr item = host_get_child(walk);
if (item.is_valid()) {
::SendMessage(item->get_wnd(),msg,wp,lp);
}
}
return 0;
}
t_size whichChild(ui_element_instance_ptr child) {
const t_size count = host_get_children_count();
for(t_size walk = 0; walk < count; ++walk) {
if (child == host_get_child(walk)) return walk;
}
return ~0;
}
bool childPriorityCompare(t_size which, double priority, double bestPriority) {
if (host_is_child_visible(which)) return priority >= bestPriority;
else return priority > bestPriority;
}
bool grabTopPriorityVisibleChild(ui_element_instance_ptr & out,t_size & outWhich,double & outPriority);
bool grabTopPriorityChild(ui_element_instance_ptr & out,t_size & outWhich,double & outPriority,const GUID & subclass);
protected:
ui_element_instance_host_base(ui_element_instance_callback::ptr callback) : m_callback(callback) {}
const ui_element_instance_callback::ptr m_callback;
};
bool enforce_min_max_info(CWindow p_window,ui_element_min_max_info const & p_info);
void handle_WM_GETMINMAXINFO(LPARAM p_lp,const ui_element_min_max_info & p_myinfo);
};
#endif // FOOBAR2000_TARGET_VERSION >= 79