add last backwards-compatible version
This commit is contained in:
366
foobar2000/ATLHelpers/CEditWithButtons.h
Normal file
366
foobar2000/ATLHelpers/CEditWithButtons.h
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user