latest SDK

This commit is contained in:
2021-12-14 00:28:25 -07:00
commit 68b10d413b
492 changed files with 80542 additions and 0 deletions

77
libPPUI/AutoComplete.cpp Normal file
View File

@@ -0,0 +1,77 @@
#include "stdafx.h"
#include "AutoComplete.h"
#include "pp-COM-macros.h"
#include <ShlGuid.h> // CLSID_AutoComplete
#include "CEnumString.h"
using PP::CEnumString;
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, '\n');
if (next == NULL) { acl->AddStringU(walk, ~0); break; }
acl->AddStringU(walk, next - walk);
walk = next + 1;
}
return InitializeSimpleAC(edit, acl.get_ptr(), opts);
}

12
libPPUI/AutoComplete.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include <ShlDisp.h>
HRESULT InitializeEditAC(HWND edit, pfc::const_iterator<pfc::string8> valueEnum, DWORD opts = ACO_AUTOAPPEND | ACO_AUTOSUGGEST);
HRESULT InitializeEditAC(HWND edit, const char * values, 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);

272
libPPUI/CButtonLite.h Normal file
View File

@@ -0,0 +1,272 @@
#pragma once
#include <functional>
#include <vsstyle.h>
#include "WTL-PP.h"
#include "win32_op.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;
WIN32_OP_D(font.GetLogFont(lf));
MakeBoldFont( lf );
CFont bold;
WIN32_OP_D(bold.CreateFontIndirect(&lf));
CWindowDC dc(*this);
auto oldFont = dc.SelectFont( bold );
CSize size (0,0);
{
CString measure;
measure = L"#";
measure += m_textDrawMe;
WIN32_OP_D(dc.GetTextExtent(measure, measure.GetLength(), &size));
}
dc.SelectFont( oldFont );
return size.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 ) {
bool wasPressed = m_pressed;
TogglePressed(false);
if ( wasPressed ) OnClicked();
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,194 @@
#include "stdafx.h"
#include "CDialogResizeHelper.h"
#include "win32_op.h"
static BOOL GetChildWindowRect2(HWND wnd, HWND wndChild, RECT* child) {
RECT temp;
if (wndChild == NULL) return FALSE;
if (!GetWindowRect(wndChild, &temp)) return FALSE;
if (!MapWindowPoints(NULL, wnd, (POINT*)&temp, 2)) return FALSE;
*child = temp;
return TRUE;
}
static BOOL GetChildWindowRect(HWND wnd, UINT id, RECT* child) {
return GetChildWindowRect2(wnd, GetDlgItem(wnd, id), child);
}
bool CDialogResizeHelper::EvalRect(UINT id, CRect & out) const {
for( auto iter = m_runtime.begin(); iter != m_runtime.end(); ++ iter ) {
if ( iter->initData.id == id ) {
out = _EvalRect(*iter, this->CurrentSize() );
return true;
}
}
return false;
}
CRect CDialogResizeHelper::_EvalRect(runtime_t const & rt, CSize wndSize) const {
CRect rc = rt.origRect;
int delta_x = wndSize.cx - rt.origWindowSize.cx,
delta_y = wndSize.cy - rt.origWindowSize.cy;
const Param & e = rt.initData;
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);
return rc;
}
CWindow CDialogResizeHelper::ResolveWnd(runtime_t const & rt) const {
if ( rt.userHWND != NULL ) return rt.userHWND;
return m_thisWnd.GetDlgItem( rt.initData.id );
}
CSize CDialogResizeHelper::CurrentSize() const {
CRect rc;
WIN32_OP_D( m_thisWnd.GetClientRect(rc) );
return rc.Size();
}
void CDialogResizeHelper::OnSize(UINT, CSize newSize)
{
if (m_thisWnd != NULL) {
HDWP hWinPosInfo = BeginDeferWindowPos((int)(m_runtime.size() + (m_sizeGrip != NULL ? 1 : 0)));
for (auto iter = m_runtime.begin(); iter != m_runtime.end(); ++ iter) {
CRect rc = _EvalRect(*iter, newSize);
hWinPosInfo = DeferWindowPos(hWinPosInfo, ResolveWnd(*iter), 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_thisWnd = thisWnd;
const auto origSize = CurrentSize();
for( auto initIter = m_initData.begin(); initIter != m_initData.end(); ++ initIter ) {
CRect rc;
if (GetChildWindowRect(m_thisWnd, initIter->id, &rc)) {
runtime_t rt;
rt.origRect = rc;
rt.initData = * initIter;
rt.origWindowSize = origSize;
m_runtime.push_back( std::move(rt) );
}
}
AddSizeGrip();
SetMsgHandled(FALSE);
}
void CDialogResizeHelper::OnDestroy() {
m_runtime.clear();
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, size_t tableSize) {
m_initData.assign( table, table+tableSize );
}
void CDialogResizeHelper::InitTable(const ParamOld * table, size_t tableSize) {
m_initData.resize(tableSize);
for (size_t walk = 0; walk < tableSize; ++walk) {
const ParamOld in = table[walk];
Param entry = {};
entry.id = table[walk].id;
if (in.flags & CDialogResizeHelperCompat::X_MOVE) entry.snapLeft = entry.snapRight = 1;
else if (in.flags & CDialogResizeHelperCompat::X_SIZE) entry.snapRight = 1;
if (in.flags & CDialogResizeHelperCompat::Y_MOVE) entry.snapTop = entry.snapBottom = 1;
else if (in.flags & CDialogResizeHelperCompat::Y_SIZE) entry.snapBottom = 1;
m_initData[walk] = entry;
}
}
void CDialogResizeHelper::InitMinMax(const CRect & range) {
min_x = range.left; min_y = range.top; max_x = range.right; max_y = range.bottom;
}
void CDialogResizeHelper::AddControl(Param const & initData, CWindow wnd) {
CRect rc;
if ( wnd == NULL ) {
PFC_ASSERT( initData.id != 0 );
WIN32_OP_D(GetChildWindowRect(m_thisWnd, initData.id, rc));
} else {
WIN32_OP_D(GetChildWindowRect2(m_thisWnd, wnd, rc));
}
runtime_t rt;
rt.initData = initData;
rt.userHWND = wnd;
rt.origRect = rc;
rt.origWindowSize = this->CurrentSize();
m_runtime.push_back( std::move(rt) );
}
bool CDialogResizeHelper::RemoveControl(CWindow wnd) {
return pfc::remove_if_t( m_runtime, [wnd] (runtime_t const & rt) {
return rt.userHWND == wnd;
} ) > 0;
}
bool CDialogResizeHelper::HaveControl(CWindow wnd) const {
for( auto i = m_runtime.begin(); i != m_runtime.end(); ++ i ) {
if ( i->userHWND == wnd ) return true;
}
return false;
}

View File

@@ -0,0 +1,67 @@
#pragma once
#include "CDialogResizeHelperCompat.h"
#include <vector>
class CDialogResizeHelper : public CMessageMap {
public:
typedef CDialogResizeHelperCompat::param ParamOld;
struct Param {
uint32_t 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, size_t paramCount> CDialogResizeHelper(const TParam(&src)[paramCount], CRect const& minMaxRange = CRect(0, 0, 0, 0)) {
InitTable(src, paramCount);
InitMinMax(minMaxRange);
}
void InitTable(const Param* table, size_t tableSize);
void InitTable(const ParamOld * table, size_t tableSize);
void InitMinMax(const CRect & range);
bool EvalRect(UINT id, CRect & out) const;
//! initData.id may be null, if so specify a non null window handle.
void AddControl( Param const & initData, CWindow wnd = NULL );
bool RemoveControl( CWindow wnd );
bool HaveControl(CWindow wnd) const;
private:
struct runtime_t {
HWND userHWND = 0;
CRect origRect;
CSize origWindowSize;
Param initData = {};
};
CSize CurrentSize() const;
CWindow ResolveWnd( runtime_t const & rt ) const;
CRect _EvalRect(runtime_t const & rt, CSize wndSize) const;
void OnGetMinMaxInfo(LPMINMAXINFO lpMMI) const;
void OnSize(UINT nType, CSize size);
void OnInitDialog(CWindow thisWnd);
void OnDestroy();
std::vector<runtime_t> m_runtime;
std::vector<Param> m_initData;
CWindow m_thisWnd, m_sizeGrip;
unsigned min_x, min_y, max_x, max_y;
};

View File

@@ -0,0 +1,16 @@
#pragma once
class CDialogResizeHelperCompat {
public:
struct param {
unsigned short id;
unsigned short flags;
};
enum {
X_MOVE = 1, X_SIZE = 2, Y_MOVE = 4, Y_SIZE = 8,
XY_MOVE = X_MOVE | Y_MOVE, XY_SIZE = X_SIZE | Y_SIZE,
X_MOVE_Y_SIZE = X_MOVE | Y_SIZE, X_SIZE_Y_MOVE = X_SIZE | Y_MOVE,
};
};

View File

@@ -0,0 +1,178 @@
#include "stdafx.h"
#include "CEditWithButtons.h"
void CEditWithButtons::AddMoreButton(std::function<void()> f) {
AddButton(L"more", f, nullptr, L"\x2026");
}
void CEditWithButtons::AddClearButton(const wchar_t * clearVal, bool bHandleEsc) {
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");
if (bHandleEsc) {
this->onEscKey = handler;
}
}
void CEditWithButtons::AddButton(const wchar_t * str, handler_t handler, condition_t condition, const wchar_t * drawAlternateText) {
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();
}
CRect CEditWithButtons::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 CEditWithButtons::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 CEditWithButtons::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 CEditWithButtons::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 CEditWithButtons::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 CEditWithButtons::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 | SWP_NOCOPYBITS);
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 | SWP_NOCOPYBITS);
}
walk = left;
}
EndDeferWindowPos(dwp);
this->SetMargins(0, size.cx - walk, EC_RIGHTMARGIN);
}
unsigned CEditWithButtons::MeasureButton(Button_t const & button) {
if (m_fixedWidth != 0) return m_fixedWidth;
return button.buttonImpl->Measure();
}

213
libPPUI/CEditWithButtons.h Normal file
View File

@@ -0,0 +1,213 @@
#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 );
void AddClearButton( const wchar_t * clearVal = L"", bool bHandleEsc = false);
void AddButton( const wchar_t * str, handler_t handler, condition_t condition = nullptr, const wchar_t * drawAlternateText = nullptr );
static unsigned DefaultFixedWidth() {return GetSystemMetrics(SM_CXVSCROLL) * 3 / 4;}
void SetFixedWidth(unsigned fw = DefaultFixedWidth() ) {
m_fixedWidth = fw;
RefreshButtons();
}
CRect RectOfButton( const wchar_t * text );
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;
}
void RefreshConditions(const wchar_t * newText = nullptr);
private:
LRESULT OnCheckConditions( UINT msg, WPARAM wp, LPARAM lp ) {
if ( msg == MSG_CHECKCONDITIONS && wp == MSG_CHECKCONDITIONS_MAGIC1 && lp == MSG_CHECKCONDITIONS_MAGIC2 ) {
this->RefreshConditions();
} 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);
bool ButtonWantTab( HWND wnd );
bool EvalCondition( Button_t & btn, const wchar_t * newText );
void Layout(CSize size, CFontHandle fontSetMe);
unsigned MeasureButton(Button_t const & button );
unsigned m_fixedWidth;
std::list< Button_t > m_buttons;
bool m_hasAutoComplete = false;
};

99
libPPUI/CEnumString.h Normal file
View File

@@ -0,0 +1,99 @@
#pragma once
#include <memory>
#include "pp-COM-macros.h"
namespace PP {
class CEnumString : public IEnumString {
public:
typedef pfc::chain_list_v2_t<pfc::array_t<TCHAR> > t_data;
typedef std::shared_ptr<t_data> shared_t;
CEnumString(t_data && in) {
m_shared = std::make_shared<t_data>(std::move(in));
Reset();
}
CEnumString(const t_data & in) {
m_shared = std::make_shared<t_data>(in);
Reset();
}
CEnumString() {
m_shared = std::make_shared< t_data >();
}
void SetStrings(t_data && data) {
*m_shared = std::move(data);
Reset();
}
static pfc::array_t<TCHAR> stringToBuffer(const char * in) {
pfc::array_t<TCHAR> arr;
arr.set_size(pfc::stringcvt::estimate_utf8_to_wide(in));
pfc::stringcvt::convert_utf8_to_wide_unchecked(arr.get_ptr(), in);
return arr;
}
void AddString(const TCHAR * in) {
m_shared->insert_last()->set_data_fromptr(in, _tcslen(in) + 1);
Reset();
}
void AddStringU(const char * in, t_size len) {
pfc::array_t<TCHAR> & arr = *m_shared->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);
Reset();
}
void AddStringU(const char * in) {
*m_shared->insert_last() = stringToBuffer(in);
Reset();
}
void ResetStrings() {
m_shared->remove_all();
Reset();
}
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_shared->first();
return S_OK;
}
HRESULT STDMETHODCALLTYPE Clone(IEnumString **ppenum) {
*ppenum = new TImpl(*this); return S_OK;
}
private:
shared_t m_shared;
t_data::const_iterator m_walk;
};
}

69
libPPUI/CFlashWindow.h Normal file
View File

@@ -0,0 +1,69 @@
#pragma once
#include "win32_op.h"
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;
uint32_t m_tickCount;
};

15
libPPUI/CHeaderCtrlEx.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
class CHeaderCtrlEx : public CHeaderCtrl {
public:
CHeaderCtrlEx(HWND wnd = NULL) : CHeaderCtrl(wnd) {}
CHeaderCtrlEx const & operator=(HWND wnd) { m_hWnd = wnd; return *this; }
// Column sort marker operations
// If they appear to have no effect, you're probably missing Common Controls 6 manifest, see link-CommonControls6.h
DWORD GetItemFormat(int iItem);
void SetItemFormat(int iItem, DWORD flags);
void SetItemSort(int iItem, int direction);
void SetSingleItemSort(int iItem, int direction);
void ClearSort();
};

View File

@@ -0,0 +1,47 @@
#pragma once
#include "win32_op.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_EX(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;
};

744
libPPUI/CListAccessible.cpp Normal file
View File

@@ -0,0 +1,744 @@
#include "stdafx.h"
#include "CListAccessible.h"
#include "pp-COM-macros.h"
static size_t IndexFromChildId(LONG id) {return (size_t)(id-1);}
static LONG ChildIdFromIndex(size_t index) {return (LONG)(index+1);}
class IEnumVARIANT_selection : public ImplementCOMRefCounter<IEnumVARIANT> {
public:
IEnumVARIANT_selection(const LONG * data,size_t dataCount, ULONG pos = 0) : m_pos(pos) {
m_data.set_data_fromptr(data,dataCount);
}
COM_QI_BEGIN()
COM_QI_ENTRY(IEnumVARIANT)
COM_QI_ENTRY(IUnknown)
COM_QI_END()
// IEnumVARIANT methods
STDMETHOD(Next)(ULONG celt, VARIANT *rgVar, ULONG *pCeltFetched);
STDMETHOD(Skip)(ULONG celt);
STDMETHOD(Reset)();
STDMETHOD(Clone)(IEnumVARIANT **ppEnum);
private:
pfc::array_t<LONG> m_data;
ULONG m_pos;
};
HRESULT IEnumVARIANT_selection::Next(ULONG celt, VARIANT *rgVar, ULONG *pceltFetched) {
if (rgVar == NULL) return E_INVALIDARG;
if (pceltFetched) *pceltFetched = 0;
ULONG n;
for (n = 0; n < celt; n++) VariantInit(&rgVar[n]);
for (n = 0; n < celt && m_pos+n < m_data.get_size(); n++) {
rgVar[n].vt = VT_I4;
rgVar[n].lVal = m_data[m_pos+n];
}
if (pceltFetched) *pceltFetched = n;
m_pos += n;
return n == celt ? S_OK : S_FALSE;
}
HRESULT IEnumVARIANT_selection::Skip(ULONG celt) {
m_pos += celt;
if (m_pos > m_data.get_size()) {
m_pos = (ULONG)m_data.get_size();
return S_FALSE;
}
return S_OK;
}
HRESULT IEnumVARIANT_selection::Reset() {
m_pos = 0;
return S_OK;
}
HRESULT IEnumVARIANT_selection::Clone(IEnumVARIANT **ppEnum)
{
if (ppEnum == NULL)
return E_INVALIDARG;
IEnumVARIANT * var;
try {
var = new IEnumVARIANT_selection(m_data.get_ptr(), m_data.get_size(), m_pos);
} catch(std::bad_alloc) {return E_OUTOFMEMORY;}
var->AddRef();
*ppEnum = var;
return S_OK;
}
namespace {
class WeakRef {
public:
WeakRef( CListAccessible * ptr_, std::shared_ptr<bool> ks_ ) : ptr(ptr_), ks(ks_) {}
CListAccessible * operator->() const { return ptr; }
bool IsEmpty() const {
return * ks;
}
private:
CListAccessible * ptr;
std::shared_ptr<bool> ks;
};
}
class IAccessible_CListControl : public ImplementCOMRefCounter<IAccessible> {
public:
IAccessible_CListControl(WeakRef owner) : m_owner(owner) {}
COM_QI_BEGIN()
COM_QI_ENTRY(IUnknown)
COM_QI_ENTRY(IDispatch)
COM_QI_ENTRY(IAccessible)
COM_QI_END()
//IDispatch
STDMETHOD(GetTypeInfoCount)(UINT * pcTInfo);
STDMETHOD(GetTypeInfo)(UINT iTInfo, LCID lcid, ITypeInfo ** ppTInfo);
STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
STDMETHOD(Invoke)(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pDispParams, VARIANT * pVarResult, EXCEPINFO * pExcepInfo, UINT * puArgErr);
//IAccessible
STDMETHOD(get_accParent)(IDispatch **ppdispParent); // required
STDMETHOD(get_accChildCount)(long *pcountChildren);
STDMETHOD(get_accChild)(VARIANT varChild, IDispatch **ppdispChild);
STDMETHOD(get_accName)(VARIANT varChild, BSTR *pszName); // required
STDMETHOD(get_accValue)(VARIANT varChild, BSTR *pszValue);
STDMETHOD(get_accDescription)(VARIANT varChild, BSTR *pszDescription);
STDMETHOD(get_accRole)(VARIANT varChild, VARIANT *pvarRole); // required
STDMETHOD(get_accState)(VARIANT varChild, VARIANT *pvarState); // required
STDMETHOD(get_accHelp)(VARIANT varChild, BSTR *pszHelp);
STDMETHOD(get_accHelpTopic)(BSTR *pszHelpFile, VARIANT varChild, long *pidTopic);
STDMETHOD(get_accKeyboardShortcut)(VARIANT varChild, BSTR *pszKeyboardShortcut);
STDMETHOD(get_accFocus)(VARIANT *pvarChild);
STDMETHOD(get_accSelection)(VARIANT *pvarChildren);
STDMETHOD(get_accDefaultAction)(VARIANT varChild, BSTR *pszDefaultAction);
STDMETHOD(accSelect)(long flagsSelect, VARIANT varChild);
STDMETHOD(accLocation)(long *pxLeft, long *pyTop, long *pcxWidth, long *pcyHeight, VARIANT varChild); // required
STDMETHOD(accNavigate)(long navDir, VARIANT varStart, VARIANT *pvarEndUpAt);
STDMETHOD(accHitTest)(long xLeft, long yTop, VARIANT *pvarChild);
STDMETHOD(accDoDefaultAction)(VARIANT varChild);
STDMETHOD(put_accName)(VARIANT varChild, BSTR szName);
STDMETHOD(put_accValue)(VARIANT varChild, BSTR szValue);
private:
const WeakRef m_owner;
};
void CListAccessible::AccInitialize(CWindow wnd) {
m_wnd = wnd;
}
void CListAccessible::AccCleanup() {
if (m_interface.is_valid()) {
NotifyWinEvent(EVENT_OBJECT_DESTROY, m_wnd, OBJID_CLIENT, CHILDID_SELF);
m_interface.release();
}
m_wnd = NULL;
}
LRESULT CListAccessible::AccGetObject(WPARAM wp,LPARAM lp) {
const WPARAM dwFlags = wp;
const LPARAM dwObjId = lp;
if (dwObjId == OBJID_CLIENT)
{
if (m_interface.is_empty())
{
try {
m_interface = new IAccessible_CListControl( WeakRef(this, m_killSwitch) );
} catch(...) {
//bah
return DefWindowProc(m_wnd,WM_GETOBJECT,wp,lp);
}
NotifyWinEvent(EVENT_OBJECT_CREATE, m_wnd, OBJID_CLIENT, CHILDID_SELF);
}
return LresultFromObject(IID_IAccessible, dwFlags, m_interface.get_ptr());
}
else return DefWindowProc(m_wnd,WM_GETOBJECT,wp,lp);
}
void CListAccessible::AccItemNamesChanged(pfc::bit_array const & mask) {
this->AccRefreshItems(mask, EVENT_OBJECT_NAMECHANGE);
}
void CListAccessible::AccReloadItems(pfc::bit_array const & mask) {
this->AccRefreshItems(mask, EVENT_OBJECT_NAMECHANGE);
}
void CListAccessible::AccStateChange(pfc::bit_array const & mask) {
this->AccRefreshItems(mask, EVENT_OBJECT_STATECHANGE);
}
void CListAccessible::AccStateChange(size_t index) {
this->AccStateChange(pfc::bit_array_one(index));
}
void CListAccessible::AccRefreshItems(pfc::bit_array const & affected, UINT what) {
if (m_wnd == NULL || m_interface.is_empty()) return;
//if (GetFocus() != m_hWnd) return;
const size_t total = AccGetItemCount();
for (size_t walk = affected.find_first(true, 0, total); walk < total; walk = affected.find_next(true, walk, total)) {
NotifyWinEvent(what, m_wnd, OBJID_CLIENT, ChildIdFromIndex(walk));
}
}
void CListAccessible::AccItemLayoutChanged() {
if (m_wnd == NULL || m_interface.is_empty()) return;
NotifyWinEvent(EVENT_OBJECT_REORDER, m_wnd, OBJID_CLIENT, CHILDID_SELF);
}
void CListAccessible::AccFocusItemChanged(size_t index) {
if (m_wnd == NULL || m_interface.is_empty()) return;
if (GetFocus() != m_wnd) return;
NotifyWinEvent(EVENT_OBJECT_FOCUS, m_wnd, OBJID_CLIENT, index != SIZE_MAX ? ChildIdFromIndex(index) : CHILDID_SELF);
}
void CListAccessible::AccFocusOtherChanged(size_t index) {
if (m_wnd == NULL || m_interface.is_empty()) return;
if (GetFocus() != m_wnd) return;
const size_t itemCount = this->AccGetItemCount();
index += itemCount;
NotifyWinEvent(EVENT_OBJECT_FOCUS, m_wnd, OBJID_CLIENT, index != SIZE_MAX ? ChildIdFromIndex(index) : CHILDID_SELF);
}
void CListAccessible::AccSelectionChanged(const pfc::bit_array & affected, const pfc::bit_array & state) {
if (m_wnd == NULL || m_interface.is_empty()) return;
//if (GetFocus() != m_wnd) return;
const size_t itemCount = this->AccGetItemCount();
const size_t limit = 20;
if (affected.calc_count(true, 0, itemCount, limit) == limit) {
NotifyWinEvent(EVENT_OBJECT_SELECTIONWITHIN, m_wnd, OBJID_CLIENT, CHILDID_SELF);
} else for(size_t walk = affected.find_first(true,0,itemCount); walk < itemCount; walk = affected.find_next(true,walk,itemCount)) {
NotifyWinEvent(state[walk] ? EVENT_OBJECT_SELECTIONADD : EVENT_OBJECT_SELECTIONREMOVE, m_wnd, OBJID_CLIENT, ChildIdFromIndex(walk));
}
}
void CListAccessible::AccLocationChange() {
if (m_wnd == NULL || m_interface.is_empty()) return;
NotifyWinEvent(EVENT_OBJECT_LOCATIONCHANGE,m_wnd,OBJID_CLIENT, CHILDID_SELF);
}
HRESULT IAccessible_CListControl::GetTypeInfoCount(UINT * pcTInfo) {
return E_NOTIMPL;
}
HRESULT IAccessible_CListControl::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo ** ppTInfo) {
return E_NOTIMPL;
}
HRESULT IAccessible_CListControl::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) {
return E_NOTIMPL;
}
HRESULT IAccessible_CListControl::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pDispParams, VARIANT * pVarResult, EXCEPINFO * pExcepInfo, UINT * puArgErr) {
return E_NOTIMPL;
}
HRESULT IAccessible_CListControl::get_accParent(IDispatch **ppdispParent) // required
{
if (ppdispParent == NULL) return E_INVALIDARG;
if (m_owner.IsEmpty()) return E_FAIL;
*ppdispParent = NULL;
HRESULT hResult = AccessibleObjectFromWindow(m_owner->AccGetWnd(), OBJID_WINDOW, IID_IDispatch, (void**)ppdispParent);
return (hResult == S_OK) ? S_OK : S_FALSE;
}
HRESULT IAccessible_CListControl::get_accChildCount(long *pcountChildren) {
if (pcountChildren == NULL) return E_INVALIDARG;
if (m_owner.IsEmpty()) return E_FAIL;
*pcountChildren = (long)( m_owner->AccGetItemCount() + m_owner->AccGetOtherCount() );
return S_OK;
}
HRESULT IAccessible_CListControl::get_accChild(VARIANT varChild, IDispatch **ppdispChild) {
if (varChild.vt != VT_I4) return E_INVALIDARG;
if (ppdispChild == NULL) return E_INVALIDARG;
if (varChild.lVal == CHILDID_SELF) {
*ppdispChild = this; AddRef();
return S_OK;
} else {
if (m_owner.IsEmpty()) return E_FAIL;
const size_t index = IndexFromChildId(varChild.lVal);
const size_t itemCount = m_owner->AccGetItemCount();
if (index < itemCount) {
return S_FALSE;
} else if (index < itemCount + m_owner->AccGetOtherCount()) {
CWindow wnd = m_owner->AccGetOtherChildWnd(index - itemCount);
if (wnd == NULL) return S_FALSE;
if (AccessibleObjectFromWindow(wnd, OBJID_WINDOW, IID_IDispatch, (void**)ppdispChild) != S_OK) return S_FALSE;
return S_OK;
} else {
return E_INVALIDARG;
}
}
}
HRESULT IAccessible_CListControl::get_accName(VARIANT varChild, BSTR *pszName) // required
{
if (varChild.vt != VT_I4) return E_INVALIDARG;
if (pszName == NULL) return E_INVALIDARG;
if (m_owner.IsEmpty()) return E_FAIL;
pfc::string8_fastalloc name;
if (varChild.lVal == CHILDID_SELF) {
m_owner->AccGetName(name);
} else {
const size_t index = IndexFromChildId(varChild.lVal);
const size_t itemCount = m_owner->AccGetItemCount();
if (index < itemCount) {
m_owner->AccGetItemName(index,name);
} else if (index < itemCount + m_owner->AccGetOtherCount()) {
m_owner->AccGetOtherName(index - itemCount, name);
} else {
return E_INVALIDARG;
}
}
*pszName = SysAllocString(pfc::stringcvt::string_wide_from_utf8(name));
return S_OK;
}
HRESULT IAccessible_CListControl::get_accValue(VARIANT varChild, BSTR *pszValue) {
if (varChild.vt != VT_I4)
return E_INVALIDARG;
if (pszValue == NULL)
return E_INVALIDARG;
return S_FALSE;
}
HRESULT IAccessible_CListControl::get_accDescription(VARIANT varChild, BSTR *pszDescription) {
if (varChild.vt != VT_I4) return E_INVALIDARG;
if (pszDescription == NULL) return E_INVALIDARG;
if (varChild.lVal == CHILDID_SELF) return S_FALSE;
if (m_owner.IsEmpty()) return E_FAIL;
*pszDescription = NULL;
const size_t itemCount = m_owner->AccGetItemCount();
const size_t index = IndexFromChildId(varChild.lVal);
pfc::string_formatter temp;
if (index < itemCount) {
if (!m_owner->AccGetItemDescription(index, temp)) return S_FALSE;
} else if (index < itemCount + m_owner->AccGetOtherCount()) {
if (!m_owner->AccGetOtherDescription(index - itemCount, temp)) return S_FALSE;
} else {
return E_INVALIDARG;
}
*pszDescription = SysAllocString(pfc::stringcvt::string_os_from_utf8(temp));
return S_OK;
}
HRESULT IAccessible_CListControl::get_accRole(VARIANT varChild, VARIANT *pvarRole) // required
{
if (varChild.vt != VT_I4) return E_INVALIDARG;
if (pvarRole == NULL) return E_INVALIDARG;
if (m_owner.IsEmpty()) return E_FAIL;
VariantClear(pvarRole);
pvarRole->vt = VT_I4;
if (varChild.lVal == CHILDID_SELF) {
pvarRole->lVal = ROLE_SYSTEM_LIST;
} else {
const size_t itemCount = m_owner->AccGetItemCount();
const size_t index = IndexFromChildId(varChild.lVal);
if (index < itemCount) {
pvarRole->lVal = m_owner->AccGetItemRole( index );
} else if (index < itemCount + m_owner->AccGetOtherCount()) {
pvarRole->lVal = m_owner->AccGetOtherRole(index - itemCount);
} else {
return E_INVALIDARG;
}
}
return S_OK;
}
HRESULT IAccessible_CListControl::get_accState(VARIANT varChild, VARIANT *pvarState) // required
{
if (varChild.vt != VT_I4) return E_INVALIDARG;
if (pvarState == NULL) return E_INVALIDARG;
if (m_owner.IsEmpty()) return E_FAIL;
VariantClear(pvarState);
pvarState->vt = VT_I4;
if (varChild.lVal == CHILDID_SELF) {
pvarState->lVal = /*STATE_SYSTEM_NORMAL*/ 0;
if (GetFocus() == m_owner->AccGetWnd())
pvarState->lVal |= STATE_SYSTEM_FOCUSED;
}
else
{
const size_t index = IndexFromChildId(varChild.lVal);
const size_t itemCount = m_owner->AccGetItemCount();
if (index < itemCount) {
pvarState->lVal = STATE_SYSTEM_MULTISELECTABLE | STATE_SYSTEM_SELECTABLE;
if (GetFocus() == m_owner->AccGetWnd()) {
pvarState->lVal |= STATE_SYSTEM_FOCUSABLE;
if (m_owner->AccGetFocusItem() == index) pvarState->lVal |= STATE_SYSTEM_FOCUSED;
}
if (m_owner->AccIsItemSelected(index)) pvarState->lVal |= STATE_SYSTEM_SELECTED;
if (!m_owner->AccIsItemVisible(index)) pvarState->lVal |= STATE_SYSTEM_OFFSCREEN | STATE_SYSTEM_INVISIBLE;
if (m_owner->AccIsItemChecked(index)) pvarState->lVal |= STATE_SYSTEM_CHECKED;
} else if (index < itemCount + m_owner->AccGetOtherCount()) {
const size_t indexO = index - itemCount;
pvarState->lVal = 0;
if (m_owner->AccIsOtherFocusable(indexO) && GetFocus() == m_owner->AccGetWnd()) {
pvarState->lVal |= STATE_SYSTEM_FOCUSABLE;
if (m_owner->AccGetFocusOther() == indexO) pvarState->lVal |= STATE_SYSTEM_FOCUSED;
}
if (!m_owner->AccIsOtherVisible(indexO)) pvarState->lVal |= STATE_SYSTEM_OFFSCREEN | STATE_SYSTEM_INVISIBLE;
} else {
return E_INVALIDARG;
}
}
return S_OK;
}
HRESULT IAccessible_CListControl::get_accHelp(VARIANT varChild, BSTR *pszHelp)
{
if (varChild.vt != VT_I4)
return E_INVALIDARG;
if (pszHelp == NULL)
return E_INVALIDARG;
return S_FALSE;
}
HRESULT IAccessible_CListControl::get_accHelpTopic(BSTR *pszHelpFile, VARIANT varChild, long *pidTopic)
{
if (varChild.vt != VT_I4)
return E_INVALIDARG;
if (pszHelpFile == NULL)
return E_INVALIDARG;
if (pidTopic == NULL)
return E_INVALIDARG;
return S_FALSE;
}
HRESULT IAccessible_CListControl::get_accKeyboardShortcut(VARIANT varChild, BSTR *pszKeyboardShortcut)
{
if (pszKeyboardShortcut == NULL)
return E_INVALIDARG;
return S_FALSE;
}
HRESULT IAccessible_CListControl::get_accFocus(VARIANT *pvarChild)
{
if (pvarChild == NULL) return E_INVALIDARG;
if (m_owner.IsEmpty()) return E_FAIL;
VariantClear(pvarChild);
if (GetFocus() != m_owner->AccGetWnd())
pvarChild->vt = VT_EMPTY;
else {
pvarChild->vt = VT_I4;
size_t index = m_owner->AccGetFocusItem();
if (index != ~0) {
pvarChild->lVal = ChildIdFromIndex(index);
} else {
index = m_owner->AccGetFocusOther();
if (index != ~0) {
pvarChild->lVal = ChildIdFromIndex(index + m_owner->AccGetItemCount());
} else {
pvarChild->lVal = CHILDID_SELF;
}
}
}
return S_OK;
}
HRESULT IAccessible_CListControl::get_accSelection(VARIANT *pvarChildren)
{
if (pvarChildren == NULL) return E_INVALIDARG;
if (m_owner.IsEmpty()) return E_FAIL;
VariantClear(pvarChildren);
try {
const size_t itemCount = m_owner->AccGetItemCount();
size_t selCount = 0;
pfc::bit_array_bittable mask(itemCount);
for(size_t walk = 0; walk < itemCount; ++walk) {
bool state = m_owner->AccIsItemSelected(walk);
mask.set(walk,state);
if (state) selCount++;
}
if (selCount == 0) {
pvarChildren->vt = VT_EMPTY;
} else if (selCount == 1) {
pvarChildren->vt = VT_I4;
pvarChildren->lVal = ChildIdFromIndex(mask.find_first(true, 0, itemCount));
} else {
pfc::array_t<LONG> data; data.set_size(selCount); size_t dataWalk = 0;
for(size_t walk = mask.find_first(true,0,itemCount); walk < itemCount; walk = mask.find_next(true,walk,itemCount)) {
data[dataWalk++] = ChildIdFromIndex(walk);
}
IEnumVARIANT * ptr = new IEnumVARIANT_selection(data.get_ptr(),data.get_size());
ptr->AddRef();
pvarChildren->vt = VT_UNKNOWN;
pvarChildren->punkVal = ptr;
}
} catch(std::bad_alloc) {
return E_OUTOFMEMORY;
}
return S_OK;
}
HRESULT IAccessible_CListControl::get_accDefaultAction(VARIANT varChild, BSTR *pszDefaultAction)
{
if (varChild.vt != VT_I4) return E_INVALIDARG;
if (pszDefaultAction == NULL) return E_INVALIDARG;
if (m_owner.IsEmpty()) return E_FAIL;
*pszDefaultAction = NULL;
if (varChild.lVal == CHILDID_SELF) {
return S_FALSE;
} else {
pfc::string_formatter temp;
const size_t index = IndexFromChildId(varChild.lVal);
const size_t itemCount = m_owner->AccGetItemCount();
if (index < itemCount) {
if (!m_owner->AccGetItemDefaultAction(temp)) return S_FALSE;
} else if (index < itemCount + m_owner->AccGetOtherCount()) {
if (!m_owner->AccGetOtherDefaultAction(index - itemCount, temp)) return false;
} else {
return E_INVALIDARG;
}
*pszDefaultAction = SysAllocString(pfc::stringcvt::string_os_from_utf8(temp));
return S_OK;
}
}
HRESULT IAccessible_CListControl::accSelect(long flagsSelect, VARIANT varChild)
{
if (varChild.vt != VT_EMPTY && varChild.vt != VT_I4) return E_INVALIDARG;
if (m_owner.IsEmpty()) return E_FAIL;
if (varChild.vt == VT_EMPTY || varChild.lVal == CHILDID_SELF)
{
switch (flagsSelect)
{
case SELFLAG_TAKEFOCUS:
m_owner->AccGetWnd().SetFocus();
return S_OK;
default:
return DISP_E_MEMBERNOTFOUND;
}
}
else
{
const size_t count = m_owner->AccGetItemCount();
const size_t index = IndexFromChildId(varChild.lVal);
if (index < count) {
if (flagsSelect & SELFLAG_TAKESELECTION)
{
if (flagsSelect & (SELFLAG_ADDSELECTION | SELFLAG_REMOVESELECTION | SELFLAG_EXTENDSELECTION))
return E_INVALIDARG;
m_owner->AccSetSelection(pfc::bit_array_true(), pfc::bit_array_one(index));
}
else if (flagsSelect & SELFLAG_EXTENDSELECTION)
{
if (flagsSelect & SELFLAG_TAKESELECTION)
return E_INVALIDARG;
size_t focus = m_owner->AccGetFocusItem();
if (focus == ~0) return S_FALSE;
bool state;
if (flagsSelect & SELFLAG_ADDSELECTION)
{
if (flagsSelect & SELFLAG_REMOVESELECTION)
return E_INVALIDARG;
state = true;
}
else if (flagsSelect & SELFLAG_REMOVESELECTION)
{
if (flagsSelect & SELFLAG_ADDSELECTION)
return E_INVALIDARG;
state = false;
}
else
{
state = m_owner->AccIsItemSelected(focus);
}
m_owner->AccSetSelection(pfc::bit_array_range(min(index, focus), max(index, focus)-min(index, focus)+1), pfc::bit_array_val(state));
}
else if (flagsSelect & SELFLAG_ADDSELECTION)
{
if (flagsSelect & (SELFLAG_REMOVESELECTION | SELFLAG_EXTENDSELECTION))
return E_INVALIDARG;
m_owner->AccSetSelection(pfc::bit_array_one(index), pfc::bit_array_true());
}
else if (flagsSelect & SELFLAG_REMOVESELECTION)
{
if (flagsSelect & (SELFLAG_ADDSELECTION | SELFLAG_EXTENDSELECTION))
return E_INVALIDARG;
m_owner->AccSetSelection(pfc::bit_array_one(index), pfc::bit_array_false());
}
if (flagsSelect & SELFLAG_TAKEFOCUS) {
m_owner->AccSetFocusItem(index);
}
} else if (index < count + m_owner->AccGetOtherCount()) {
const size_t indexO = index - count;
if (flagsSelect & SELFLAG_TAKEFOCUS) {
m_owner->AccSetFocusOther(indexO);
}
} else {
return E_INVALIDARG;
}
return S_OK;
}
}
HRESULT IAccessible_CListControl::accLocation(long *pxLeft, long *pyTop, long *pcxWidth, long *pcyHeight, VARIANT varChild) // required
{
if (varChild.vt != VT_I4) return E_INVALIDARG;
if (pxLeft == NULL || pyTop == NULL || pcxWidth == NULL || pcyHeight == NULL) return E_INVALIDARG;
if (m_owner.IsEmpty()) return E_FAIL;
CRect rc;
const CWindow ownerWnd = m_owner->AccGetWnd();
if (varChild.lVal == CHILDID_SELF) {
if (!ownerWnd.GetClientRect(&rc)) return E_FAIL;
} else {
const size_t index = IndexFromChildId(varChild.lVal);
const size_t itemCount = m_owner->AccGetItemCount();
if (index < itemCount) {
if (!m_owner->AccGetItemRect(index,rc)) return S_FALSE;
} else if (index < itemCount + m_owner->AccGetOtherCount()) {
if (!m_owner->AccGetOtherRect(index - itemCount, rc)) return S_FALSE;
} else {
return E_INVALIDARG;
}
}
if (!ownerWnd.ClientToScreen(rc)) return E_FAIL;
*pxLeft = rc.left;
*pyTop = rc.top;
*pcxWidth = rc.right-rc.left;
*pcyHeight = rc.bottom-rc.top;
return S_OK;
}
HRESULT IAccessible_CListControl::accNavigate(long navDir, VARIANT varStart, VARIANT *pvarEndUpAt)
{
if (varStart.vt != VT_I4 && varStart.vt != VT_EMPTY) return E_INVALIDARG;
if (pvarEndUpAt == NULL) return E_INVALIDARG;
if (m_owner.IsEmpty()) return E_FAIL;
VariantClear(pvarEndUpAt);
pvarEndUpAt->vt = VT_EMPTY;
if (varStart.vt == VT_EMPTY || varStart.lVal == CHILDID_SELF)
{
switch (navDir)
{
case NAVDIR_LEFT:
case NAVDIR_RIGHT:
case NAVDIR_UP:
case NAVDIR_DOWN:
case NAVDIR_PREVIOUS:
case NAVDIR_NEXT:
// leave empty
break;
case NAVDIR_FIRSTCHILD:
if (m_owner->AccGetItemCount() > 0)
{
pvarEndUpAt->vt = VT_I4;
pvarEndUpAt->lVal = ChildIdFromIndex(0);
}
break;
case NAVDIR_LASTCHILD:
if (m_owner->AccGetItemCount() > 0)
{
pvarEndUpAt->vt = VT_I4;
pvarEndUpAt->lVal = ChildIdFromIndex(m_owner->AccGetItemCount()-1);
}
break;
}
}
else
{
const size_t index = IndexFromChildId(varStart.lVal);
const size_t itemCount = m_owner->AccGetItemCount();
if (index >= itemCount) {
if (index < itemCount + m_owner->AccGetOtherCount()) return S_FALSE;
else return E_INVALIDARG;
}
switch (navDir)
{
case NAVDIR_LEFT:
case NAVDIR_RIGHT:
case NAVDIR_FIRSTCHILD:
case NAVDIR_LASTCHILD:
// leave empty
break;
case NAVDIR_UP:
case NAVDIR_PREVIOUS:
if (index > 0)
{
pvarEndUpAt->vt = VT_I4;
pvarEndUpAt->lVal = ChildIdFromIndex(index-1);
}
break;
case NAVDIR_DOWN:
case NAVDIR_NEXT:
if (index+1 < itemCount)
{
pvarEndUpAt->vt = VT_I4;
pvarEndUpAt->lVal = ChildIdFromIndex(index+1);
}
break;
}
}
return (pvarEndUpAt->vt != VT_EMPTY) ? S_OK : S_FALSE;
}
HRESULT IAccessible_CListControl::accHitTest(long xLeft, long yTop, VARIANT *pvarChild)
{
if (pvarChild == NULL) return E_INVALIDARG;
if (m_owner.IsEmpty()) return E_FAIL;
VariantClear(pvarChild);
CPoint pt (xLeft, yTop);
const CWindow ownerWnd = m_owner->AccGetWnd();
if (!ownerWnd.ScreenToClient(&pt)) return E_FAIL;
CRect rcClient;
if (!ownerWnd.GetClientRect(&rcClient)) return E_FAIL;
if (PtInRect(&rcClient, pt)) {
size_t index = m_owner->AccItemHitTest(pt);
if (index != ~0) {
pvarChild->vt = VT_I4;
pvarChild->lVal = ChildIdFromIndex(index);
} else {
index = m_owner->AccOtherHitTest(pt);
if (index != ~0) {
pvarChild->vt = VT_I4;
pvarChild->lVal = ChildIdFromIndex(m_owner->AccGetItemCount() + index);
CWindow wnd = m_owner->AccGetOtherChildWnd(index);
if (wnd != NULL) {
IDispatch * obj;
if (AccessibleObjectFromWindow(wnd,OBJID_WINDOW, IID_IDispatch, (void**)&obj) == S_OK) {
pvarChild->vt = VT_DISPATCH;
pvarChild->pdispVal = obj;
}
}
} else {
pvarChild->vt = VT_I4;
pvarChild->lVal = CHILDID_SELF;
}
}
} else {
pvarChild->vt = VT_EMPTY;
}
return S_OK;
}
HRESULT IAccessible_CListControl::accDoDefaultAction(VARIANT varChild)
{
if (varChild.vt != VT_I4) return E_INVALIDARG;
if (m_owner.IsEmpty()) return E_FAIL;
if (varChild.lVal == CHILDID_SELF) return S_FALSE;
const size_t index = IndexFromChildId(varChild.lVal);
const size_t itemCount = m_owner->AccGetItemCount();
if (index < itemCount) {
if (!m_owner->AccExecuteItemDefaultAction(index)) return S_FALSE;
} else if (index < itemCount + m_owner->AccGetOtherCount()) {
if (!m_owner->AccExecuteOtherDefaultAction(index - itemCount)) return S_FALSE;
} else {
return E_INVALIDARG;
}
return S_OK;
}
HRESULT IAccessible_CListControl::put_accName(VARIANT varChild, BSTR szName) {
return DISP_E_MEMBERNOTFOUND;
}
HRESULT IAccessible_CListControl::put_accValue(VARIANT varChild, BSTR szValue) {
return DISP_E_MEMBERNOTFOUND;
}
void CListAccessible::AccGetName(pfc::string_base & out) const {
auto str = pfc::getWindowText(m_wnd);
if ( str.length() > 0 ) out = str;
else out = "List Control";
}

292
libPPUI/CListAccessible.h Normal file
View File

@@ -0,0 +1,292 @@
#pragma once
#include <memory>
#pragma comment(lib, "oleacc.lib")
#include "CListControl-Cell.h"
#include "CListControlWithSelection.h"
//! Internal class interfacing with Windows accessibility APIs. \n
//! This class is not tied to any specific control and requires most of its methods to be overridden. \n
//! With CListControl you want to use CListControlAccImpl<> template instead of using CListAccessible directly.
class CListAccessible {
public:
void AccInitialize(CWindow wnd);
void AccCleanup();
LRESULT AccGetObject(WPARAM wp,LPARAM lp);
CWindow AccGetWnd() const {return m_wnd;}
virtual size_t AccGetItemCount() const {return 0;}
virtual LONG AccGetItemRole( size_t index ) const {return ROLE_SYSTEM_LISTITEM;}
virtual void AccGetItemName(size_t index, pfc::string_base & out) const {out = "";}
virtual void AccGetName(pfc::string_base & out) const;
virtual size_t AccGetFocusItem() const {return ~0;}
virtual bool AccIsItemSelected(size_t index) const {return false;}
virtual bool AccIsItemChecked( size_t index ) const { return false; }
virtual bool AccIsItemVisible(size_t index) const {return false;}
virtual bool AccGetItemDefaultAction(pfc::string_base & out) const {return false;}
virtual bool AccExecuteItemDefaultAction(size_t index) {return false;}
virtual void AccSetSelection(pfc::bit_array const & affected, pfc::bit_array const & state) {}
virtual void AccSetFocusItem(size_t index) {}
virtual bool AccGetItemRect(size_t index, CRect & out) const {return false;}
virtual bool AccGetItemDescription(size_t index, pfc::string_base & out) const {return false;}
virtual size_t AccItemHitTest(CPoint const & pt) const {return ~0;}
virtual DWORD AccGetOtherRole(size_t index) {return 0;}
virtual size_t AccGetOtherCount() const {return 0;}
virtual void AccGetOtherName(size_t index, pfc::string_base & out) const {out = "";}
virtual size_t AccGetFocusOther() const {return ~0;}
virtual void AccSetFocusOther(size_t index) {}
virtual bool AccIsOtherVisible(size_t index) const {return false;}
virtual bool AccGetOtherDescription(size_t index, pfc::string_base & out) const {return false;}
virtual size_t AccOtherHitTest(CPoint const & pt) const {return ~0;}
virtual bool AccIsOtherFocusable(size_t index) const {return false;}
virtual bool AccGetOtherDefaultAction(size_t index, pfc::string_base & out) const {return false;}
virtual bool AccExecuteOtherDefaultAction(size_t index) {return false;}
virtual bool AccGetOtherRect(size_t index, CRect & out) const {return false;}
virtual CWindow AccGetOtherChildWnd(size_t index) const {return NULL;}
void AccItemNamesChanged( pfc::bit_array const & mask);
void AccReloadItems(pfc::bit_array const & mask );
void AccRefreshItems(pfc::bit_array const & mask, UINT what);
void AccStateChange(pfc::bit_array const & mask);
void AccStateChange(size_t index);
void AccItemLayoutChanged();
void AccFocusItemChanged(size_t index);
void AccFocusOtherChanged(size_t index);
void AccSelectionChanged(const pfc::bit_array & affected, const pfc::bit_array & status);
void AccLocationChange();
protected:
~CListAccessible() { * m_killSwitch = true; }
private:
CWindow m_wnd;
std::shared_ptr<bool> m_killSwitch = std::make_shared<bool>();
pfc::com_ptr_t<IAccessible> m_interface;
};
//! Basic wrapper implementing CListAccessible methods on top of CListControl. Leaves many methods to be overridden by calle
template<typename TBaseClass> class CListControlAccImpl : public TBaseClass, protected CListAccessible {
public:
template<typename ... arg_t> CListControlAccImpl( arg_t && ... arg ) : TBaseClass(std::forward<arg_t>(arg) ... ) {}
BEGIN_MSG_MAP_EX(CListControlAccImpl)
MESSAGE_HANDLER(WM_GETOBJECT,OnGetObject)
MESSAGE_HANDLER(WM_DESTROY,OnDestroyPassThru)
MESSAGE_HANDLER(WM_CREATE,OnCreatePassThru)
CHAIN_MSG_MAP(TBaseClass)
END_MSG_MAP()
public:
void ReloadData() {
TBaseClass::ReloadData();
AccItemLayoutChanged();
}
void ReloadItems(pfc::bit_array const & mask) {
TBaseClass::ReloadItems(mask);
AccReloadItems(mask);
}
protected:
size_t AccGetItemCount() const {return this->GetItemCount();}
size_t AccGetFocusItem() const {return this->GetFocusItem();}
bool AccIsItemSelected(size_t index) const {return this->IsItemSelected(index);}
bool AccIsItemVisible(size_t index) const {
if (index >= AccGetItemCount()) return false;
return this->IsRectVisible(this->GetItemRect(index));
}
bool AccExecuteItemDefaultAction(size_t index) {if (index < AccGetItemCount()) {this->ExecuteDefaultAction(index);return true;} else return false;}
bool AccGetItemRect(size_t index, CRect & out) const {
if (index >= AccGetItemCount()) return false;
out = this->GetItemRect(index);
return true;
}
size_t AccItemHitTest(CPoint const & pt) const {
size_t item;
if (!this->ItemFromPoint(pt,item)) return ~0;
return item;
}
void OnViewOriginChange(CPoint p_delta) override {
TBaseClass::OnViewOriginChange(p_delta);
AccLocationChange();
}
/* overrideme, optional
void AccGetName(pfc::string_base & out) const;
bool AccGetItemDefaultAction(pfc::string_base & out) const;
*/
LONG AccRoleAt(size_t idx, size_t sub) const {
auto cell = this->GetCellType(idx, sub);
if (cell == nullptr) return 0;
return cell->AccRole();
}
bool isCellText(size_t idx, size_t sub) const {
switch (AccRoleAt(idx, sub)) {
case ROLE_SYSTEM_TEXT:
case ROLE_SYSTEM_STATICTEXT:
case ROLE_SYSTEM_LISTITEM:
return true;
default:
return false;
}
}
bool useCellForDescription(size_t idx, size_t sub) const {
return sub > 0 && isCellText(idx, sub);
}
bool AccGetItemDescription(size_t index, pfc::string_base& out) const {
pfc::string_formatter ret, temp, temp2;
const size_t total = this->GetColumnCount();
for (size_t walk = 0; walk < total; ) {
if (useCellForDescription(index, walk) && this->GetSubItemText(index, walk, temp) && temp.length() > 0) {
if (ret.length() > 0) ret << "; ";
this->GetColumnText(walk, temp2);
if (temp2.length() > 0) ret << temp2 << ": ";
ret << temp;
}
size_t d = this->GetSubItemSpan(index, walk);
if (d < 1) d = 1;
walk += d;
}
bool rv = (ret.length() > 0);
if (rv) out = ret;
return ret;
}
void AccGetItemNameAlt(size_t index, pfc::string_base & out) const {
pfc::string_formatter ret, temp;
const size_t total = this->GetColumnCount();
for (size_t walk = 0; walk < total; ) {
if (this->isCellText(index, walk) && this->GetSubItemText(index, walk, temp) && temp.length() > 0) {
if (ret.length() > 0) ret << "; ";
ret << temp;
}
size_t d = this->GetSubItemSpan(index, walk);
if (d < 1) d = 1;
walk += d;
}
out = ret;
}
// Item name by default taken from column 0, override this if you supply another
void AccGetItemName(size_t index, pfc::string_base & out) const {
this->GetSubItemText(index, 0, out);
}
void AccSetSelection(pfc::bit_array const & affected, pfc::bit_array const & state) override {
this->SetSelection(affected, state);
}
void AccSetFocusItem(size_t index) override {
this->SetFocusItem(index);
}
void OnFocusChangedGroup(int iGroup) override {
TBaseClass::OnFocusChangedGroup(iGroup);
AccFocusOtherChanged((size_t)iGroup);
}
void OnFocusChanged(size_t f) override {
TBaseClass::OnFocusChanged(f);
AccFocusItemChanged(f);
}
void OnSelectionChanged(pfc::bit_array const & affected, pfc::bit_array const & status) override {
TBaseClass::OnSelectionChanged(affected, status);
AccSelectionChanged(affected, status);
}
CWindow AccGetOtherChildWnd(size_t index) const {return index == 0 ? CWindow(this->GetHeaderCtrl()) : CWindow(NULL) ;}
virtual DWORD AccGetOtherRole(size_t index) {return index > 0 ? ROLE_SYSTEM_GROUPING : ROLE_SYSTEM_WINDOW;}//FIXME?
virtual bool AccGetOtherDescription(size_t index, pfc::string_base & out) const {return false;}//FIXME??
size_t AccGetOtherCount() const {return 1 + this->GetGroupCount();}
void AccGetOtherName(size_t index, pfc::string_base & out) const override {
if (index == 0) out = "Columns Header";
else if (!this->GetGroupHeaderText((int)index, out)) out = "";
}
size_t AccGetFocusOther() const override {
int focus = this->GetGroupFocus();
if (focus > 0) return (size_t) focus;
else return SIZE_MAX;
}
void AccSetFocusOther(size_t index) override {
if (index > 0) this->SetGroupFocus((int)index);
}
bool AccIsOtherVisible(size_t index) const override {
if (index == 0) return true;
CRect rc;
if (!this->GetGroupHeaderRect((int)index,rc)) return false;
return IsRectVisible(rc);
}
size_t AccOtherHitTest(CPoint const & pt) const {
{
CPoint s(pt);
if (this->ClientToScreen(&s)) {
CRect rc;
auto hdr = this->GetHeaderCtrl();
if (hdr != NULL && hdr.GetWindowRect(rc)) {
if (rc.PtInRect(s)) {
return 0;
}
}
}
}
int group;
if (this->GroupHeaderFromPoint(pt,group)) return (size_t) group;
return ~0;
}
bool AccIsOtherFocusable(size_t index) const {return index > 0;}
bool AccGetOtherDefaultAction(size_t index, pfc::string_base & out) const {return false;}
bool AccExecuteOtherDefaultAction(size_t index) {return false;}
bool AccGetOtherRect(size_t index, CRect & out) const {
if (index == 0) {
CRect rc, client;
auto hdr = this->GetHeaderCtrl();
if ( hdr == NULL ) return false;
if (!hdr.GetWindowRect(rc)) return false;
if (!this->ScreenToClient(rc)) return false;
if (!this->GetClientRect(client)) return false;
return !! out.IntersectRect(rc, client);
} else {
return this->GetGroupHeaderRect((int)index, out);
}
}
LONG AccGetItemRole( size_t index ) const override {
auto type = this->GetCellType( index, 0 );
if ( type != nullptr ) {
return type->AccRole();
}
return ROLE_SYSTEM_LISTITEM;
}
void SetCellCheckState(size_t item, size_t subItem, bool value) override {
__super::SetCellCheckState(item, subItem, value);
this->AccStateChange(item);
}
bool AccIsItemChecked( size_t index ) const override {
auto type = this->GetCellType( index, 0 );
if ( type != nullptr && type->IsToggle() ) {
return this->GetCellCheckState( index, 0 );
}
return false;
}
private:
bool IsRectVisible(CRect const & rc) const {
return !!CRect().IntersectRect(this->GetClientRectHook(),rc);
}
LRESULT OnCreatePassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) {
bHandled = FALSE;
try { AccInitialize(*this); } catch(...) {AccCleanup();}
return 0;
}
LRESULT OnDestroyPassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) {
bHandled = FALSE;
AccCleanup();
return 0;
}
LRESULT OnGetObject(UINT,WPARAM wp, LPARAM lp, BOOL & ) {
return this->AccGetObject(wp,lp);
}
};
class CListControlWithSelectionImpl;
typedef CListControlAccImpl< CListControlWithSelectionImpl > CListControlWithSelectionAccImpl;

View File

@@ -0,0 +1,39 @@
#pragma once
#include <uxtheme.h> // HTHEME
class CListCell {
public:
typedef uint32_t cellState_t;
enum {
cellState_none = 0,
cellState_hot = 1 << 0,
cellState_pressed = 1 << 1,
cellState_disabled = 1 << 2,
};
struct DrawContentArg_t {
DWORD hdrFormat;
cellState_t cellState = 0;
CRect subItemRect;
CDCHandle dc;
const wchar_t * text = nullptr;
bool allowColors = true;
CRect rcHot;
CRect rcText;
HTHEME theme = NULL;
uint32_t colorHighlight = 0;
CWindow thisWnd;
};
virtual void DrawContent( DrawContentArg_t const & arg ) = 0;
virtual const char * Theme() { return nullptr; }
virtual bool ApplyTextStyle( LOGFONT & font, double scale, uint32_t state );
virtual bool IsInteractive() { return false; }
virtual bool SuppressRowSelect() { return false; }
virtual bool IsToggle() { return false; }
virtual bool IsRadioToggle() { return false; }
virtual bool AllowTypeFind() { return true; }
virtual CRect HotRect( CRect rc ) { return rc; }
virtual HCURSOR HotCursor() { return NULL; }
virtual bool AllowDrawThemeText() { return false; }
virtual LONG AccRole();
};

View File

@@ -0,0 +1,13 @@
#pragma once
#include "CListControl-Cells.h"
// Wrapper for old code using cell type enum constants
#define cell_text &PFC_SINGLETON(CListCell_Text)
#define cell_multitext &PFC_SINGLETON(CListCell_MultiText)
#define cell_hyperlink &PFC_SINGLETON(CListCell_Hyperlink)
#define cell_button &PFC_SINGLETON(CListCell_Button)
#define cell_button_lite &PFC_SINGLETON(CListCell_ButtonLite)
#define cell_button_glyph &PFC_SINGLETON(CListCell_ButtonGlyph)
#define cell_checkbox &PFC_SINGLETON(CListCell_Checkbox)
#define cell_radiocheckbox &PFC_SINGLETON(CListCell_RadioCheckbox)

View File

@@ -0,0 +1,298 @@
#include "stdafx.h"
#include "CListControl.h"
#include "CListControlHeaderImpl.h"
#include "CListControl-Cells.h"
#include "PaintUtils.h"
#include "GDIUtils.h"
#include <vsstyle.h>
LONG CListCell::AccRole() {
return ROLE_SYSTEM_LISTITEM;
}
void RenderCheckbox( HTHEME theme, CDCHandle dc, CRect rcCheckBox, unsigned stateFlags, bool bRadio ) {
const int part = bRadio ? BP_RADIOBUTTON : BP_CHECKBOX;
const bool bDisabled = (stateFlags & CListCell::cellState_disabled) != 0;
const bool bPressed = (stateFlags & CListCell::cellState_pressed ) != 0;
const bool bHot = ( stateFlags & CListCell::cellState_hot ) != 0;
if (theme != NULL && IsThemePartDefined(theme, part, 0)) {
int state = 0;
if (bDisabled) {
state = bPressed ? CBS_CHECKEDDISABLED : CBS_DISABLED;
} else if ( bHot ) {
state = bPressed ? CBS_CHECKEDHOT : CBS_HOT;
} else {
state = bPressed ? CBS_CHECKEDNORMAL : CBS_NORMAL;
}
CSize size;
if (SUCCEEDED(GetThemePartSize(theme, dc, part, state, rcCheckBox, TS_TRUE, &size))) {
if (size.cx <= rcCheckBox.Width() && size.cy <= rcCheckBox.Height()) {
CRect rc = rcCheckBox;
rc.left += ( rc.Width() - size.cx ) / 2;
rc.top += ( rc.Height() - size.cy ) / 2;
rc.right = rc.left + size.cx;
rc.bottom = rc.top + size.cy;
DrawThemeBackground(theme, dc, part, state, rc, &rc);
return;
}
}
}
int stateEx = bRadio ? DFCS_BUTTONRADIO : DFCS_BUTTONCHECK;
if ( bPressed ) stateEx |= DFCS_CHECKED;
if ( bDisabled ) stateEx |= DFCS_INACTIVE;
else if ( bHot ) stateEx |= DFCS_HOT;
DrawFrameControl(dc, rcCheckBox, DFC_BUTTON, stateEx);
}
void RenderButton( HTHEME theme, CDCHandle dc, CRect rcButton, CRect rcUpdate, uint32_t cellState ) {
const int part = BP_PUSHBUTTON;
enum {
stNormal = PBS_NORMAL,
stHot = PBS_HOT,
stDisabled = PBS_DISABLED,
stPressed = PBS_PRESSED,
};
int state = 0;
if (cellState & CListCell::cellState_disabled) state = stDisabled;
if ( cellState & CListCell::cellState_pressed ) state = stPressed;
else if ( cellState & CListCell::cellState_hot ) state = stHot;
else state = stNormal;
CRect rcClient = rcButton;
if (theme != NULL && IsThemePartDefined(theme, part, 0)) {
DrawThemeBackground(theme, dc, part, state, rcClient, &rcUpdate);
} else {
int stateEx = DFCS_BUTTONPUSH;
switch (state) {
case stPressed: stateEx |= DFCS_PUSHED; break;
case stDisabled: stateEx |= DFCS_INACTIVE; break;
}
DrawFrameControl(dc, rcClient, DFC_BUTTON, stateEx);
}
}
bool CListCell::ApplyTextStyle( LOGFONT & font, double scale, uint32_t ) {
if ( scale != 1.0 ) {
font.lfHeight = pfc::rint32( font.lfHeight * scale );
return true;
} else {
return false;
}
}
void CListCell_Text::DrawContent( DrawContentArg_t const & arg ) {
const auto fgWas = arg.dc.GetTextColor();
CDCHandle dc = arg.dc;
if (arg.cellState & cellState_disabled) {
dc.SetTextColor(GetSysColor(COLOR_GRAYTEXT));
}
CRect clip = arg.rcText;
const t_uint32 format = PaintUtils::DrawText_TranslateHeaderAlignment(arg.hdrFormat);
dc.DrawText( arg.text, (int)wcslen(arg.text), clip, format | DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER );
dc.SetTextColor(fgWas);
}
void CListCell_TextColors::DrawContent( DrawContentArg_t const & arg ) {
CDCHandle dc = arg.dc;
CRect clip = arg.rcText;
const uint32_t fgWas = dc.GetTextColor();
const t_uint32 format = PaintUtils::DrawText_TranslateHeaderAlignment(arg.hdrFormat);
const t_uint32 bk = dc.GetBkColor();
const t_uint32 fg = fgWas;
const t_uint32 hl = (arg.allowColors ? arg.colorHighlight : fg);
const t_uint32 colors[3] = { PaintUtils::BlendColor(bk, fg, 33), fg, hl };
PaintUtils::TextOutColorsEx(dc, arg.text, clip, format, colors);
dc.SetTextColor(fgWas);
}
void CListCell_MultiText::DrawContent( DrawContentArg_t const & arg ) {
CDCHandle dc = arg.dc;
const int textLen = (int) wcslen( arg.text );
CRect clip = arg.rcText;
const t_uint32 format = PaintUtils::DrawText_TranslateHeaderAlignment(arg.hdrFormat) | DT_NOPREFIX | DT_VCENTER ;
CRect rcDraw = clip;
dc.DrawText(arg.text, textLen, rcDraw, format | DT_CALCRECT);
auto txSize = rcDraw.Size();
rcDraw = clip;
if ( txSize.cy < rcDraw.Height() ) {
int sub = rcDraw.Height() - txSize.cy;
rcDraw.top += sub/2;
rcDraw.bottom = rcDraw.top + txSize.cy;
}
dc.DrawText(arg.text, textLen, rcDraw, format);
}
bool CListCell_Hyperlink::ApplyTextStyle( LOGFONT & font, double scale, uint32_t state ) {
bool rv = __super::ApplyTextStyle(font, scale, state);
if ( state & cellState_hot ) {
font.lfUnderline = TRUE;
rv = true;
}
return rv;
}
HCURSOR CListCell_Hyperlink::HotCursor() {
return LoadCursor(NULL, IDC_HAND);
}
LONG CListCell_Hyperlink::AccRole() {
return ROLE_SYSTEM_LINK;
}
void CListCell_Hyperlink::DrawContent( DrawContentArg_t const & arg ) {
CDCHandle dc = arg.dc;
const uint32_t fgWas = dc.GetTextColor();
const t_uint32 format = PaintUtils::DrawText_TranslateHeaderAlignment(arg.hdrFormat);
if (arg.allowColors) dc.SetTextColor( arg.colorHighlight );
const t_uint32 bk = dc.GetBkColor();
CRect rc = arg.rcText;
dc.DrawText(arg.text, (int) wcslen(arg.text), rc, format | DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER );
dc.SetTextColor(fgWas);
}
LONG CListCell_Button::AccRole() {
return ROLE_SYSTEM_PUSHBUTTON;
}
void CListCell_Button::DrawContent( DrawContentArg_t const & arg ) {
CDCHandle dc = arg.dc;
const bool bPressed = (arg.cellState & cellState_pressed) != 0;
const bool bHot = (arg.cellState & cellState_hot) != 0;
if ( !m_lite || bHot || bPressed ) {
RenderButton( arg.theme, dc, arg.rcHot, arg.rcHot, arg.cellState );
}
CRect clip = arg.rcText;
dc.DrawText(arg.text, (int) wcslen(arg.text), clip, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_CENTER);
}
bool CListCell_ButtonGlyph::ApplyTextStyle( LOGFONT & font, double scale, uint32_t state ) {
return __super::ApplyTextStyle(font, scale * 1.3, state);
}
static CRect CheckBoxRect(CRect rc) {
if (rc.Width() > rc.Height()) {
rc.right = rc.left + rc.Height();
}
return rc;
}
LONG CListCell_Checkbox::AccRole() {
return m_radio ? ROLE_SYSTEM_RADIOBUTTON : ROLE_SYSTEM_CHECKBUTTON;
}
CRect CListCell_Checkbox::HotRect( CRect rc ) {
return CheckBoxRect( rc );
}
void CListCell_Checkbox::DrawContent( DrawContentArg_t const & arg ) {
CDCHandle dc = arg.dc;
const bool bPressed = (arg.cellState & cellState_pressed) != 0;
const bool bHot = (arg.cellState & cellState_hot) != 0;
CRect clip = arg.rcText;
const uint32_t fgWas = dc.GetTextColor();
if (arg.subItemRect.Width() > arg.subItemRect.Height() ) {
CRect rcCheckbox = arg.subItemRect;
rcCheckbox.right = rcCheckbox.left + rcCheckbox.Height();
RenderCheckbox(arg.theme, dc, rcCheckbox, arg.cellState, m_radio );
CRect rcText = arg.subItemRect;
rcText.left = rcCheckbox.right;
if (arg.cellState & cellState_disabled) {
dc.SetTextColor(GetSysColor(COLOR_GRAYTEXT));
}
dc.DrawText(arg.text, (int) wcslen(arg.text), rcText, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_LEFT);
} else {
RenderCheckbox(arg.theme, dc, arg.subItemRect, arg.cellState, m_radio );
}
dc.SetTextColor(fgWas);
}
void CListCell_Text_FixedColor::DrawContent(DrawContentArg_t const & arg) {
if (arg.allowColors) {
SetTextColorScope scope(arg.dc, m_col);
__super::DrawContent(arg);
} else {
__super::DrawContent(arg);
}
}
void CListCell_Combo::DrawContent(DrawContentArg_t const & arg) {
CDCHandle dc = arg.dc;
const bool bDisabled = (arg.cellState & CListCell::cellState_disabled) != 0;
const bool bPressed = (arg.cellState & cellState_pressed) != 0;
const bool bHot = (arg.cellState & cellState_hot) != 0;
const int part = CP_DROPDOWNBUTTONRIGHT;
const HTHEME theme = arg.theme;
if (theme != NULL && IsThemePartDefined(theme, part, 0)) {
int state = CBXSR_NORMAL;
if (bDisabled) {
state = CBXSR_DISABLED;
} else if (bPressed) {
state = CBXSR_PRESSED;
} else if (bHot) {
state = CBXSR_HOT;
}
CSize size;
CRect rcCombo = arg.subItemRect;
CRect rcText = arg.rcText;
int w = rcCombo.Height()*3/4;
if (w < rcCombo.Width()) {
rcCombo.left = rcCombo.right - w;
DrawThemeBackground(theme, dc, part, state, rcCombo, &rcCombo);
if (rcCombo.left < rcText.right ) rcText.right = rcCombo.left;
}
DrawContentArg_t arg2 = arg;
arg2.rcText = rcText;
PFC_SINGLETON(CListCell_Text).DrawContent(arg2);
}
}
LONG CListCell_Combo::AccRole() {
return ROLE_SYSTEM_DROPLIST;
}

View File

@@ -0,0 +1,94 @@
#pragma once
#include "CListControl-Cell.h"
class CListCell_Interactive : public CListCell {
public:
bool IsInteractive() override { return true; }
};
class CListCell_Text : public CListCell {
public:
void DrawContent( DrawContentArg_t const & ) override;
bool AllowDrawThemeText() override { return true; }
};
class CListCell_TextColors : public CListCell_Text {
public:
void DrawContent( DrawContentArg_t const & ) override;
};
class CListCell_MultiText : public CListCell {
public:
void DrawContent( DrawContentArg_t const & ) override;
};
class CListCell_Hyperlink : public CListCell_Interactive {
public:
void DrawContent( DrawContentArg_t const & ) override;
bool ApplyTextStyle( LOGFONT & font, double scale, uint32_t state ) override;
HCURSOR HotCursor() override;
LONG AccRole() override;
bool SuppressRowSelect() override { return true; }
};
class CListCell_Button : public CListCell_Interactive {
public:
void DrawContent( DrawContentArg_t const & ) override;
const char * Theme() override { return "BUTTON"; }
bool AllowTypeFind() override { return false; }
LONG AccRole() override;
bool SuppressRowSelect() override { return true; }
protected:
bool m_lite = false;
};
class CListCell_ButtonLite : public CListCell_Button {
public:
CListCell_ButtonLite() { m_lite = true; }
};
class CListCell_ButtonGlyph : public CListCell_ButtonLite {
public:
bool ApplyTextStyle( LOGFONT & font, double scale, uint32_t state ) override;
};
class CListCell_Checkbox : public CListCell_Interactive {
public:
void DrawContent( DrawContentArg_t const & ) override;
const char * Theme() override { return "BUTTON"; }
bool IsToggle() override { return true; }
CRect HotRect( CRect rc ) override;
bool IsRadioToggle() override { return m_radio; }
LONG AccRole() override;
protected:
bool m_radio = false;
};
class CListCell_RadioCheckbox : public CListCell_Checkbox {
public:
CListCell_RadioCheckbox() { m_radio = true; }
static CListCell_RadioCheckbox instance;
};
class CListCell_Combo : public CListCell_Interactive {
public:
void DrawContent(DrawContentArg_t const &) override;
const char * Theme() override { return "COMBOBOX"; }
LONG AccRole() override;
};
void RenderButton( HTHEME theme, CDCHandle dc, CRect rcButton, CRect rcUpdate, uint32_t cellState );
void RenderCheckbox( HTHEME theme, CDCHandle dc, CRect rcCheckBox, unsigned stateFlags, bool bRadio );
class CListCell_Text_FixedColor : public CListCell_Text {
const COLORREF m_col;
public:
CListCell_Text_FixedColor(COLORREF col) : m_col(col) {}
void DrawContent(DrawContentArg_t const & arg) override;
};

989
libPPUI/CListControl.cpp Normal file
View File

@@ -0,0 +1,989 @@
#include "stdafx.h"
#include "CListControl.h"
#include "PaintUtils.h"
#include "CListControlUserOptions.h"
#include "GDIUtils.h"
CListControlUserOptions * CListControlUserOptions::instance = nullptr;
CRect CListControlImpl::GetClientRectHook() const {
CRect temp; if (!GetClientRect(temp)) temp.SetRectEmpty(); return temp;
}
bool CListControlImpl::UserEnabledSmoothScroll() const {
auto i = CListControlUserOptions::instance;
if ( i != nullptr ) return i->useSmoothScroll();
return false;
}
LRESULT CListControlImpl::SetFocusPassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) {
SetFocus();
bHandled = FALSE;
return 0;
}
void CListControlImpl::EnsureVisibleRectAbs(const CRect & p_rect) {
const CRect rcView = GetVisibleRectAbs();
const CRect rcItem = p_rect;
int deltaX = 0, deltaY = 0;
const bool centerOnItem = m_ensureVisibleUser;
if (rcItem.top < rcView.top || rcItem.bottom > rcView.bottom) {
if (rcItem.Height() > rcView.Height()) {
deltaY = rcItem.top - rcView.top;
} else {
if (centerOnItem) {
deltaY = rcItem.CenterPoint().y - rcView.CenterPoint().y;
} else {
if (rcItem.bottom > rcView.bottom) deltaY = rcItem.bottom - rcView.bottom;
else deltaY = rcItem.top - rcView.top;
}
}
}
if (rcItem.left < rcView.left || rcItem.right > rcView.right) {
if (rcItem.Width() > rcView.Width()) {
if (rcItem.left > rcView.left || rcItem.right < rcView.right) deltaX = rcItem.left - rcView.left;
} else {
if (centerOnItem) {
deltaX = rcItem.CenterPoint().x - rcView.CenterPoint().x;
} else {
if (rcItem.right > rcView.right) deltaX = rcItem.right - rcView.right;
else deltaX = rcItem.left - rcView.left;
}
}
}
if (deltaX != 0 || deltaY != 0) {
MoveViewOriginDelta(CPoint(deltaX,deltaY));
}
}
void CListControlImpl::EnsureItemVisible(t_size p_item, bool bUser) {
m_ensureVisibleUser = bUser;
EnsureVisibleRectAbs(GetItemRectAbs(p_item));
m_ensureVisibleUser = false;
}
void CListControlImpl::EnsureHeaderVisible(int p_group) {
CRect rect;
if (GetGroupHeaderRectAbs(p_group,rect)) EnsureVisibleRectAbs(rect);
}
void CListControlImpl::RefreshSlider(bool p_vertical) {
const CRect viewArea = GetViewAreaRectAbs();
const CRect rcVisible = GetVisibleRectAbs();
SCROLLINFO si = {};
si.cbSize = sizeof(si);
si.fMask = SIF_PAGE|SIF_RANGE|SIF_POS;
if (AllowScrollbar(p_vertical)) {
if (p_vertical) {
si.nPage = rcVisible.Height();
si.nMin = viewArea.top;
si.nMax = viewArea.bottom - 1;
si.nPos = rcVisible.top;
} else {
si.nPage = rcVisible.Width();
si.nMin = viewArea.left;
si.nMax = viewArea.right - 1;
si.nPos = rcVisible.left;
}
}
SetScrollInfo(p_vertical ? SB_VERT : SB_HORZ, &si);
}
void CListControlImpl::RefreshSliders() {
//PROBLEM: while lots of data can be reused across those, it has to be recalculated inbetween because view area etc may change when scroll info changes
RefreshSlider(false); RefreshSlider(true);
}
int CListControlImpl::GetScrollThumbPos(int which) {
SCROLLINFO si = {};
si.cbSize = sizeof(si);
si.fMask = SIF_TRACKPOS;
GetScrollInfo(which,&si);
return si.nTrackPos;
}
namespace {
class ResolveGroupHelper {
public:
ResolveGroupHelper(const CListControlImpl & p_control) : m_control(p_control) {}
int operator[](t_size p_index) const {return m_control.GetItemGroup(p_index);}
private:
const CListControlImpl & m_control;
};
}
bool CListControlImpl::ResolveGroupRange(int p_id,t_size & p_base,t_size & p_count) const {
return pfc::binarySearch<>::runGroup(ResolveGroupHelper(*this),0,GetItemCount(),p_id,p_base,p_count);
//return pfc::bsearch_range_t(GetItemCount(),ResolveGroupHelper(*this),pfc::compare_t<int,int>,p_id,p_base,p_count);
}
static int HandleScroll(WORD p_code,int p_offset,int p_page, int p_line, int p_bottom, int p_thumbpos) {
switch(p_code) {
case SB_LINEUP:
return p_offset - p_line;
case SB_LINEDOWN:
return p_offset + p_line;
case SB_BOTTOM:
return p_bottom - p_page;
case SB_TOP:
return 0;
case SB_PAGEUP:
return p_offset - p_page;
case SB_PAGEDOWN:
return p_offset + p_page;
case SB_THUMBPOSITION:
return p_thumbpos;
case SB_THUMBTRACK:
return p_thumbpos;
default:
return p_offset;
}
}
static CPoint ClipPointToRect(CPoint const & p_pt,CRect const & p_rect) {
return CPoint(pfc::clip_t(p_pt.x,p_rect.left,p_rect.right),pfc::clip_t(p_pt.y,p_rect.top,p_rect.bottom));
}
void CListControlImpl::MoveViewOriginNoClip(CPoint p_target) {
UpdateWindow();
const CPoint old = m_viewOrigin;
m_viewOrigin = p_target;
if (m_viewOrigin != old) {
if (m_viewOrigin.x != old.x) SetScrollPos(SB_HORZ,m_viewOrigin.x);
if (m_viewOrigin.y != old.y) SetScrollPos(SB_VERT,m_viewOrigin.y);
const CPoint delta = old - m_viewOrigin;
if (FixedOverlayPresent()) Invalidate();
else {
DWORD flags = SW_INVALIDATE | SW_ERASE;
const DWORD smoothScrollMS = 50;
if (this->UserEnabledSmoothScroll() && this->CanSmoothScroll()) {
flags |= SW_SMOOTHSCROLL | (smoothScrollMS << 16);
}
ScrollWindowEx(delta.x,delta.y,GetClientRectHook(),NULL,0,0,flags );
}
OnViewOriginChange(m_viewOrigin - old);
}
}
CPoint CListControlImpl::ClipViewOrigin(CPoint p_origin) const {
return ClipPointToRect(p_origin,GetValidViewOriginArea());
}
void CListControlImpl::MoveViewOrigin(CPoint p_target) {
MoveViewOriginNoClip(ClipViewOrigin(p_target));
}
#ifndef SPI_GETWHEELSCROLLCHARS
#define SPI_GETWHEELSCROLLCHARS 0x006C
#endif
int CListControlImpl::HandleWheel(int & p_accum,int p_delta, bool bHoriz) {
if ( m_suppressMouseWheel ) return 0;
UINT scrollLines = 1;
SystemParametersInfo(bHoriz ? SPI_GETWHEELSCROLLCHARS : SPI_GETWHEELSCROLLLINES,0,&scrollLines,0);
if (scrollLines == ~0) {
p_accum = 0;
int rv = -pfc::sgn_t(p_delta);
CRect client = GetClientRectHook();
if (bHoriz) rv *= client.Width();
else rv *= client.Height();
return rv;
}
const int itemHeight = GetItemHeight();
const int extraScale = 10000;
p_accum += p_delta * extraScale;
if ((int)scrollLines < 1) scrollLines = 1;
int multiplier = (WHEEL_DELTA * extraScale) / (scrollLines * itemHeight);
if (multiplier<1) multiplier = 1;
int delta = pfc::rint32( (double) p_accum / (double) multiplier );
p_accum -= delta * multiplier;
return -delta;
/*
if (p_accum<=-multiplier || p_accum>=multiplier) {
int direction;
int ov = p_accum;
if (ov<0) {
direction = -1;
ov = -ov;
p_accum = - ((-p_accum)%multiplier);
} else {
p_accum %= multiplier;
direction = 1;
}
return - (direction * (ov + multiplier - 1) ) / multiplier;
} else {
return 0;
}
*/
}
LRESULT CListControlImpl::OnVWheel(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) {
const CRect client = GetClientRectHook(), view = this->GetViewAreaRectAbs();
int deltaPixels = HandleWheel(m_wheelAccumY,(short)HIWORD(p_wp), false);
const bool canVScroll = client.Height() < view.Height();
const bool canHScroll = client.Width() < view.Width();
CPoint ptDelta;
if ( canVScroll && canHScroll && GetHotkeyModifierFlags() == MOD_SHIFT) {
ptDelta = CPoint(deltaPixels, 0); // default to horizontal scroll if shift is pressed
} else if (canVScroll) {
ptDelta = CPoint(0,deltaPixels);
} else if (canHScroll) {
ptDelta = CPoint(deltaPixels,0);
}
if ( ptDelta != CPoint(0,0) ) {
MoveViewOriginDelta(ptDelta);
}
return 0;
}
LRESULT CListControlImpl::OnHWheel(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) {
const CRect client = GetClientRectHook();
int deltaPixels = HandleWheel(m_wheelAccumX,(short)HIWORD(p_wp), true);
MoveViewOriginDelta(CPoint(-deltaPixels,0));
return 0;
}
LRESULT CListControlImpl::OnVScroll(UINT,WPARAM p_wp,LPARAM,BOOL&) {
int target = HandleScroll(LOWORD(p_wp),m_viewOrigin.y,GetVisibleRectAbs().Height(),GetItemHeight(),GetViewAreaRectAbs().bottom,GetScrollThumbPos(SB_VERT));
MoveViewOrigin(CPoint(m_viewOrigin.x,target));
return 0;
}
LRESULT CListControlImpl::OnHScroll(UINT,WPARAM p_wp,LPARAM,BOOL&) {
int target = HandleScroll(LOWORD(p_wp),m_viewOrigin.x,GetVisibleRectAbs().Width(),GetItemHeight() /*fixme*/,GetViewAreaRectAbs().right,GetScrollThumbPos(SB_HORZ));
MoveViewOrigin(CPoint(target,m_viewOrigin.y));
return 0;
}
LRESULT CListControlImpl::OnGesture(UINT,WPARAM,LPARAM lParam,BOOL& bHandled) {
if (!this->m_gestureAPI.IsAvailable()) {
bHandled = FALSE;
return 0;
}
HGESTUREINFO hGesture = (HGESTUREINFO) lParam;
GESTUREINFO gestureInfo = {sizeof(gestureInfo)};
if (m_gestureAPI.GetGestureInfo(hGesture, &gestureInfo)) {
//console::formatter() << "WM_GESTURE " << pfc::format_hex( gestureInfo.dwFlags ) << " " << (int)gestureInfo.dwID << " X:" << gestureInfo.ptsLocation.x << " Y:" << gestureInfo.ptsLocation.y << " arg:" << (__int64) gestureInfo.ullArguments;
CPoint pt( gestureInfo.ptsLocation.x, gestureInfo.ptsLocation.y );
switch(gestureInfo.dwID) {
case GID_BEGIN:
m_gesturePoint = pt;
break;
case GID_END:
break;
case GID_PAN:
MoveViewOriginDelta( this->m_gesturePoint - pt);
m_gesturePoint = pt;
break;
}
}
m_gestureAPI.CloseGestureInfoHandle(hGesture);
bHandled = TRUE;
return 0;
}
LRESULT CListControlImpl::OnSize(UINT,WPARAM,LPARAM p_lp,BOOL&) {
OnSizeAsync_Trigger();
RefreshSliders();
return 0;
}
void CListControlImpl::RenderBackground( CDCHandle dc, CRect const & rc ) {
PaintUtils::FillRectSimple(dc,rc,GetSysColorHook(colorBackground));
}
void CListControlImpl::PaintContent(CRect rcPaint, HDC dc) {
CDCHandle renderDC(dc);
CMemoryDC bufferDC(renderDC,rcPaint);
renderDC = bufferDC;
this->RenderBackground(renderDC, rcPaint);
{
const CPoint pt = GetViewOffset();
OffsetWindowOrgScope offsetScope(renderDC, pt);
CRect renderRect = rcPaint; renderRect.OffsetRect(pt);
RenderRect(renderRect,renderDC);
}
}
void CListControlImpl::OnPrintClient(HDC dc, UINT uFlags) {
CRect rcClient; this->GetClientRect( rcClient );
PaintContent( rcClient, dc );
}
LRESULT CListControlImpl::OnPaint(UINT,WPARAM,LPARAM,BOOL&) {
CPaintDC paintDC(*this);
PaintContent( paintDC.m_ps.rcPaint, paintDC.m_hDC );
return 0;
}
namespace {
class comparator_rect {
public:
static int compare(const CRect & p_rect1,const CRect & p_rect2) {
if (p_rect1.bottom <= p_rect2.top) return -1;
else if (p_rect1.top >= p_rect2.bottom) return 1;
else return 0;
}
};
static int RectPointCompare(const CRect & p_item1,const int p_y) {
if (p_item1.bottom <= p_y) return -1;
else if (p_item1.top > p_y) return 1;
else return 0;
}
class RectSearchHelper_Items {
public:
RectSearchHelper_Items(const CListControlImpl & p_control) : m_control(p_control) {}
CRect operator[](t_size p_index) const {
return m_control.GetItemRectAbs(p_index);
}
private:
const CListControlImpl & m_control;
};
class RectSearchHelper_Groups {
public:
RectSearchHelper_Groups(const CListControlImpl & p_control) : m_control(p_control) {}
CRect operator[](t_size p_index) const {
CRect rect;
if (!m_control.GetGroupHeaderRectAbs((int)(p_index + 1),rect)) rect.SetRectEmpty();
return rect;
}
private:
const CListControlImpl & m_control;
};
}
bool CListControlImpl::GetItemRange(const CRect & p_rect,t_size & p_base,t_size & p_count) const {
CRect temp(p_rect); temp.OffsetRect( GetViewOffset() );
return GetItemRangeAbs(temp, p_base, p_count);
}
bool CListControlImpl::GetItemRangeAbsInclHeaders(const CRect & p_rect,t_size & p_base,t_size & p_count) const {
CRect temp(p_rect);
temp.bottom += this->GetGroupHeaderHeight();
return GetItemRangeAbs(temp, p_base, p_count);
}
bool CListControlImpl::GetItemRangeAbs(const CRect & p_rect,t_size & p_base,t_size & p_count) const {
if (p_rect.right < 0 || p_rect.left >= GetItemWidth()) return false;
return pfc::binarySearch<comparator_rect>::runGroup(RectSearchHelper_Items(*this),0,GetItemCount(),p_rect,p_base,p_count);
}
void CListControlImpl::RenderRect(const CRect & p_rect,CDCHandle p_dc) {
const CRect rectAbs = p_rect;
t_size base, count;
if (GetItemRangeAbs(rectAbs,base,count)) {
for(t_size walk = 0; walk < count; ++walk) {
CRect rcUpdate, rcItem = GetItemRectAbs(base+walk);
if (rcUpdate.IntersectRect(rcItem,p_rect)) {
DCStateScope dcState(p_dc);
if (p_dc.IntersectClipRect(rcUpdate) != NULLREGION) {
try {
RenderItem(base+walk,rcItem,rcUpdate,p_dc);
} catch(std::exception const & e) {
(void) e;
// console::complain("List Control: Item rendering failure", e);
}
}
}
}
}
if (pfc::binarySearch<comparator_rect>::runGroup(RectSearchHelper_Groups(*this),0,GetGroupCount(),rectAbs,base,count)) {
for(t_size walk = 0; walk < count; ++walk) {
CRect rcHeader, rcUpdate;
const int id = (int)(base+walk+1);
if (GetGroupHeaderRectAbs(id,rcHeader) && rcUpdate.IntersectRect(rcHeader,p_rect)) {
DCStateScope dcState(p_dc);
if (p_dc.IntersectClipRect(rcUpdate) != NULLREGION) {
try {
RenderGroupHeader(id,rcHeader,rcUpdate,p_dc);
} catch(std::exception const & e) {
(void) e;
// console::complain("List Control: Group header rendering failure", e);
}
}
}
}
}
RenderOverlay(p_rect,p_dc);
}
CRect CListControlImpl::GetItemRect(t_size p_item) const {
CRect rcItem = GetItemRectAbs(p_item);
rcItem.OffsetRect( - GetViewOffset() );
return rcItem;
}
bool CListControlImpl::GetGroupHeaderRect(int p_group,CRect & p_rect) const {
if (!GetGroupHeaderRectAbs(p_group,p_rect)) return false;
p_rect.OffsetRect( - GetViewOffset() );
return true;
}
int CListControlImpl::GetViewAreaHeight() const {
const t_size itemCount = GetItemCount();
int subAreaBase = 0;
if (itemCount > 0) {
subAreaBase = GetItemRectAbs(itemCount - 1).bottom;
}
return subAreaBase;
}
CRect CListControlImpl::GetItemRectAbs(t_size p_item) const {
CRect rcItem;
const int itemHeight = GetItemHeight(), itemWidth = GetItemWidth(), groupHeight = GetGroupHeaderHeight(), itemGroup = GetItemGroup(p_item);
rcItem.top = (int)p_item * itemHeight + groupHeight * itemGroup;
rcItem.bottom = rcItem.top + itemHeight;
rcItem.left = 0;
rcItem.right = rcItem.left + itemWidth;
return rcItem;
}
bool CListControlImpl::GetGroupHeaderRectAbs(int p_group,CRect & p_rect) const {
if (p_group == 0) return false;
t_size itemBase, itemCount;
if (!ResolveGroupRange(p_group,itemBase,itemCount)) return false;
const int itemHeight = GetItemHeight(), itemWidth = GetItemWidth(), groupHeight = GetGroupHeaderHeight();
p_rect.bottom = (int) itemBase * itemHeight + groupHeight * p_group;
p_rect.top = p_rect.bottom - groupHeight;
p_rect.left = 0;
p_rect.right = p_rect.left + itemWidth;
return true;
}
CRect CListControlImpl::GetViewAreaRectAbs() const {
return CRect(0,0,GetViewAreaWidth(),GetViewAreaHeight());
}
CRect CListControlImpl::GetViewAreaRect() const {
CRect rc = GetViewAreaRectAbs();
rc.OffsetRect( - GetViewOffset() );
CRect ret; ret.IntersectRect(rc,GetClientRectHook());
return ret;
}
t_size CListControlImpl::GetGroupCount() const {
const t_size itemCount = GetItemCount();
if (itemCount > 0) {
return (t_size) GetItemGroup(itemCount-1);
} else {
return 0;
}
}
void CListControlImpl::UpdateGroupHeader(int p_id) {
CRect rect;
if (GetGroupHeaderRect(p_id,rect)) {
InvalidateRect(rect);
}
}
static void AddUpdateRect(HRGN p_rgn,CRect const & p_rect) {
CRgn temp; temp.CreateRectRgnIndirect(p_rect);
CRgnHandle(p_rgn).CombineRgn(temp,RGN_OR);
}
void CListControlImpl::OnItemsReordered( const size_t * order, size_t count ) {
PFC_ASSERT( count == GetItemCount() );
ReloadItems( pfc::bit_array_order_changed(order) );
}
void CListControlImpl::UpdateItems(const pfc::bit_array & p_mask) {
t_size base,count;
if (GetItemRangeAbs(GetVisibleRectAbs(),base,count)) {
const t_size max = base+count;
CRgn updateRgn; updateRgn.CreateRectRgn(0,0,0,0);
bool found = false;
for(t_size walk = p_mask.find_first(true,base,max); walk < max; walk = p_mask.find_next(true,walk,max)) {
found = true;
AddUpdateRect(updateRgn,GetItemRect(walk));
}
if (found) {
InvalidateRgn(updateRgn);
}
}
}
void CListControlImpl::UpdateItemsAndHeaders(const pfc::bit_array & p_mask) {
t_size base,count;
int groupWalk = 0;
if (GetItemRangeAbsInclHeaders(GetVisibleRectAbs(),base,count)) {
const t_size max = base+count;
CRgn updateRgn; updateRgn.CreateRectRgn(0,0,0,0);
bool found = false;
for(t_size walk = p_mask.find_first(true,base,max); walk < max; walk = p_mask.find_next(true,walk,max)) {
found = true;
const int groupId = GetItemGroup(walk);
if (groupId != groupWalk) {
if (groupId > 0) {
CRect rect;
if (GetGroupHeaderRect(groupId,rect)) {
AddUpdateRect(updateRgn,rect);
}
}
groupWalk = groupId;
}
AddUpdateRect(updateRgn,GetItemRect(walk));
}
if (found) {
InvalidateRgn(updateRgn);
}
}
}
CRect CListControlImpl::GetValidViewOriginArea() const {
const CRect rcView = GetViewAreaRectAbs();
const CRect rcClient = GetClientRectHook();
CRect rcArea = rcView;
rcArea.right -= pfc::min_t(rcView.Width(),rcClient.Width());
rcArea.bottom -= pfc::min_t(rcView.Height(),rcClient.Height());
return rcArea;
}
void CListControlImpl::OnViewAreaChanged(CPoint p_originOverride) {
const CPoint oldViewOrigin = m_viewOrigin;
m_viewOrigin = ClipPointToRect(p_originOverride,GetValidViewOriginArea());
RefreshSliders();
Invalidate();
if (oldViewOrigin != m_viewOrigin) {
OnViewOriginChange(m_viewOrigin - oldViewOrigin);
}
}
bool CListControlImpl::ItemFromPointAbs(CPoint const & p_pt,t_size & p_item) const {
if (p_pt.x < 0 || p_pt.x >= GetItemWidth()) return false;
t_size dummy;
return GetItemRangeAbs(CRect(p_pt,p_pt + CPoint(1,1)),p_item,dummy);
}
bool CListControlImpl::GroupHeaderFromPointAbs(CPoint const & p_pt,int & p_group) const {
if (p_pt.x < 0 || p_pt.x >= GetItemWidth()) return false;
t_size result;
if (!pfc::binarySearch<comparator_rect>::run(RectSearchHelper_Groups(*this),0,GetGroupCount(),CRect(p_pt,p_pt + CSize(1,1)),result)) return false;
//if (!pfc::bsearch_t(GetGroupCount(),RectSearchHelper_Groups(*this),RectCompare,CRect(p_pt,p_pt),result)) return false;
p_group = (int) (result + 1);
return true;
}
void CListControlImpl::OnThemeChanged() {
m_themeCache.remove_all();
}
CTheme & CListControlImpl::themeFor(const char * what) {
bool bNew;
auto & ret = this->m_themeCache.find_or_add_ex( what, bNew );
if (bNew) ret.OpenThemeData(*this, pfc::stringcvt::string_wide_from_utf8(what));
return ret;
}
LRESULT CListControlImpl::OnCreatePassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) {
::SetWindowTheme(*this, _T("explorer"), NULL);
OnViewAreaChanged();
if (m_gestureAPI.IsAvailable()) {
GESTURECONFIG config = {GID_PAN, GC_PAN_WITH_SINGLE_FINGER_VERTICALLY|GC_PAN_WITH_INERTIA, GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY | GC_PAN_WITH_GUTTER};
m_gestureAPI.SetGestureConfig( *this, 0, 1, &config, sizeof(GESTURECONFIG));
}
bHandled = FALSE;
return 0;
}
bool CListControlImpl::IsSameItemOrHeaderAbs(const CPoint & p_point1, const CPoint & p_point2) const {
t_size item1, item2; int group1, group2;
if (ItemFromPointAbs(p_point1, item1)) {
if (ItemFromPointAbs(p_point2,item2)) {
return item1 == item2;
} else {
return false;
}
}
if (GroupHeaderFromPointAbs(p_point1, group1)) {
if (GroupHeaderFromPointAbs(p_point2, group2)) {
return group1 == group2;
} else {
return false;
}
}
return false;
}
void CListControlImpl::OnSizeAsync_Trigger() {
if (!m_sizeAsyncPending) {
if (PostMessage(MSG_SIZE_ASYNC,0,0)) {
m_sizeAsyncPending = true;
} else {
PFC_ASSERT(!"Shouldn't get here!");
//should not happen
ListHandleResize();
}
}
}
void CListControlImpl::ListHandleResize() {
MoveViewOriginDelta(CPoint(0,0));
m_sizeAsyncPending = false;
}
void CListControlImpl::AddGroupHeaderToUpdateRgn(HRGN p_rgn, int id) const {
if (id > 0) {
CRect rcHeader;
if (GetGroupHeaderRect(id,rcHeader)) AddUpdateRect(p_rgn,rcHeader);
}
}
void CListControlImpl::AddItemToUpdateRgn(HRGN p_rgn, t_size p_index) const {
if (p_index < this->GetItemCount()) {
AddUpdateRect(p_rgn,GetItemRect(p_index));
}
}
COLORREF CListControlImpl::GetSysColorHook(int colorIndex) const {
return GetSysColor(colorIndex);
}
LRESULT CListControlImpl::OnEraseBkgnd(UINT,WPARAM wp,LPARAM,BOOL&) {
#ifndef CListControl_ScrollWindowFix
const CRect rcClient = GetClientRectHook();
PaintUtils::FillRectSimple((HDC)wp,rcClient,GetColor(ui_color_background));
#endif
return 1;
}
t_size CListControlImpl::InsertIndexFromPointEx(const CPoint & pt, bool & bInside) const {
bInside = false;
t_size insertMark = ~0;
t_size itemIdx; int groupId;
CPoint test(pt); test += GetViewOffset();
test.x = GetItemWidth() / 2;
if (test.y >= GetViewAreaHeight()) {
return GetItemCount();
} else if (ItemFromPointAbs(test,itemIdx)) {
const CRect rc = GetItemRectAbs(itemIdx);
if (test.y > rc.top + MulDiv(rc.Height(),2,3)) itemIdx++;
else if (test.y >= rc.top + MulDiv(rc.Height(),1,3)) bInside = true;
return itemIdx;
} else if (GroupHeaderFromPointAbs(test,groupId)) {
t_size base,count;
if (ResolveGroupRange(groupId,base,count)) {
return base;
}
}
return ~0;
}
t_size CListControlImpl::InsertIndexFromPoint(const CPoint & pt) const {
bool dummy; return InsertIndexFromPointEx(pt,dummy);
}
COLORREF CListControlImpl::BlendGridColor( COLORREF bk ) {
return BlendGridColor( bk, PaintUtils::DetermineTextColor( bk ) );
}
COLORREF CListControlImpl::BlendGridColor( COLORREF bk, COLORREF tx ) {
return PaintUtils::BlendColor(bk, tx, 10);
}
COLORREF CListControlImpl::GridColor() {
return BlendGridColor( GetSysColorHook(colorBackground), GetSysColorHook(colorText) );
}
void CListControlImpl::RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,size_t p_item, uint32_t bkColor) {
switch( this->m_rowStyle ) {
case rowStylePlaylistDelimited:
PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item+GetItemGroup(p_item),bkColor);
{
auto blend = BlendGridColor(bkColor);
CDCPen pen(p_dc, blend);
SelectObjectScope scope(p_dc, pen);
p_dc.MoveTo( p_itemRect.right-1, p_itemRect.top );
p_dc.LineTo( p_itemRect.right-1, p_itemRect.bottom );
}
break;
case rowStylePlaylist:
PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item+GetItemGroup(p_item),bkColor);
break;
case rowStyleGrid:
PaintUtils::FillRectSimple(p_dc, p_itemRect, bkColor );
{
auto blend = BlendGridColor(bkColor);
CDCBrush brush(p_dc, blend);
p_dc.FrameRect(&p_itemRect, brush);
}
break;
case rowStyleFlat:
PaintUtils::FillRectSimple(p_dc, p_itemRect, bkColor );
break;
}
}
void CListControlImpl::RenderGroupHeaderBackground(CDCHandle p_dc,const CRect & p_headerRect,int p_group) {
const t_uint32 bkColor = GetSysColorHook(colorBackground);
t_size index = 0;
t_size base, count;
if (p_group > 0 && ResolveGroupRange(p_group,base,count)) {
index = base + (t_size) p_group - 1;
}
switch( this->m_rowStyle ) {
default:
PaintUtils::FillRectSimple( p_dc, p_headerRect, bkColor );
break;
case rowStylePlaylistDelimited:
case rowStylePlaylist:
PaintUtils::RenderItemBackground(p_dc,p_headerRect,index,bkColor);
break;
}
}
void CListControlImpl::RenderItem(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc) {
this->RenderItemBackground(p_dc, p_itemRect, p_item, GetSysColorHook(colorBackground) );
DCStateScope backup(p_dc);
p_dc.SetBkMode(TRANSPARENT);
p_dc.SetBkColor(GetSysColorHook(colorBackground));
p_dc.SetTextColor(GetSysColorHook(colorText));
RenderItemText(p_item,p_itemRect,p_updateRect,p_dc, true);
}
void CListControlImpl::RenderGroupHeader(int p_group,const CRect & p_headerRect,const CRect & p_updateRect,CDCHandle p_dc) {
this->RenderGroupHeaderBackground(p_dc, p_headerRect, p_group );
DCStateScope backup(p_dc);
p_dc.SetBkMode(TRANSPARENT);
p_dc.SetBkColor(GetSysColorHook(colorBackground));
p_dc.SetTextColor(GetSysColorHook(colorHighlight));
RenderGroupHeaderText(p_group,p_headerRect,p_updateRect,p_dc);
}
CListControlFontOps::CListControlFontOps() : m_font((HFONT)::GetStockObject(DEFAULT_GUI_FONT)), m_itemHeight(), m_groupHeaderHeight() {
UpdateGroupHeaderFont();
CalculateHeights();
}
void CListControlFontOps::UpdateGroupHeaderFont() {
try {
m_groupHeaderFont = NULL;
LOGFONT lf = {};
WIN32_OP_D( m_font.GetLogFont(lf) );
lf.lfHeight = pfc::rint32( (double) lf.lfHeight * GroupHeaderFontScale() );
lf.lfWeight = GroupHeaderFontWeight(lf.lfWeight);
WIN32_OP_D( m_groupHeaderFont.CreateFontIndirect(&lf) != NULL );
} catch(std::exception const & e) {
(void) e;
// console::print(e.what());
m_groupHeaderFont = (HFONT)::GetStockObject(DEFAULT_GUI_FONT);
}
}
void CListControlFontOps::CalculateHeights() {
const t_uint32 spacing = MulDiv(4, m_dpi.cy, 96);
m_itemHeight = GetFontHeight( m_font ) + spacing;
m_groupHeaderHeight = GetFontHeight( m_groupHeaderFont ) + spacing;
}
void CListControlFontOps::SetFont(HFONT font,bool bUpdateView) {
m_font = font;
UpdateGroupHeaderFont(); CalculateHeights();
OnSetFont(bUpdateView);
if (bUpdateView && m_hWnd != NULL) OnViewAreaChanged();
}
LRESULT CListControlFontOps::OnSetFont(UINT,WPARAM wp,LPARAM,BOOL&) {
SetFont((HFONT)wp);
return 0;
}
LRESULT CListControlFontOps::OnGetFont(UINT,WPARAM,LPARAM,BOOL&) {
return (LRESULT)(HFONT)m_font;
}
void CListControlImpl::SetCaptureEx(CaptureProc_t proc) {
this->m_captureProc = proc; SetCapture();
}
LRESULT CListControlImpl::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;
}
LRESULT CListControlImpl::OnGetDlgCode(UINT, WPARAM wp, LPARAM) {
switch(wp) {
case VK_RETURN:
return m_dlgWantEnter ? DLGC_WANTMESSAGE : 0;
default:
SetMsgHandled(FALSE);
return 0;
}
}
void CListControlImpl::CreateInDialog(CWindow wndDialog, UINT replaceControlID ) {
CWindow lstReplace = wndDialog.GetDlgItem(replaceControlID);
PFC_ASSERT( lstReplace != NULL );
auto status = lstReplace.SendMessage(WM_GETDLGCODE, VK_RETURN );
m_dlgWantEnter = (status & DLGC_WANTMESSAGE);
CRect rc;
CWindow wndPrev = wndDialog.GetNextDlgTabItem(lstReplace, TRUE);
WIN32_OP_D( lstReplace.GetWindowRect(&rc) );
WIN32_OP_D( wndDialog.ScreenToClient(rc) );
WIN32_OP_D( lstReplace.DestroyWindow() );
WIN32_OP_D( this->Create(wndDialog, &rc, 0, 0, WS_EX_STATICEDGE, replaceControlID) );
if (wndPrev != NULL ) this->SetWindowPos(wndPrev, 0,0,0,0, SWP_NOSIZE | SWP_NOMOVE );
this->SetFont(wndDialog.GetFont());
}
void CListControlImpl::defer(std::function<void() > f) {
m_deferred.push_back( f );
if (!m_defferredMsgPending) {
if ( PostMessage(MSG_EXEC_DEFERRED) ) m_defferredMsgPending = true;
}
}
LRESULT CListControlImpl::OnExecDeferred(UINT, WPARAM, LPARAM) {
for ( ;; ) {
auto i = m_deferred.begin();
if ( i == m_deferred.end() ) break;
auto op = std::move(*i);
m_deferred.erase(i); // erase first, execute later - avoid erratic behavior if op alters the list
op();
}
m_defferredMsgPending = false;
return 0;
}
// ========================================================================================
// Mouse wheel vs drag&drop hacks
// Install MouseHookProc for the duration of DoDragDrop and handle the input from there
// ========================================================================================
static HHOOK g_hook = NULL;
static CListControlImpl * g_dragDropInstance = nullptr;
LRESULT CALLBACK CListControlImpl::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode == HC_ACTION && g_dragDropInstance != nullptr) {
switch (wParam) {
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
g_dragDropInstance->MouseWheelFromHook((UINT)wParam, lParam);
break;
}
}
return CallNextHookEx(g_hook, nCode, wParam, lParam);
}
bool CListControlImpl::MouseWheelFromHook(UINT msg, LPARAM data) {
MOUSEHOOKSTRUCTEX const * mhs = reinterpret_cast<MOUSEHOOKSTRUCTEX const *> ( data );
if ( ::WindowFromPoint(mhs->pt) != m_hWnd ) return false;
LRESULT dummyResult = 0;
WPARAM wp = mhs->mouseData;
LPARAM lp = MAKELPARAM( mhs->pt.x, mhs->pt.y );
// If we get here, m_suppressMouseWheel should be true per our DoDragDrop()
pfc::vartoggle_t<bool> scope(m_suppressMouseWheel, false);
this->ProcessWindowMessage( m_hWnd, msg, wp, lp, dummyResult );
return true;
}
HRESULT CListControlImpl::DoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect) {
HRESULT ret = E_FAIL;
// Should not get here with non null g_dragDropInstance - means we have a recursive call
PFC_ASSERT(g_dragDropInstance == nullptr);
if ( g_dragDropInstance == nullptr ) {
// futureproofing: kill mouse wheel message processing if we get them delivered the regular way while this is in progress
pfc::vartoggle_t<bool> scope(m_suppressMouseWheel, true);
g_dragDropInstance = this;
g_hook = SetWindowsHookEx(WH_MOUSE, MouseHookProc, NULL, GetCurrentThreadId());
try {
ret = ::DoDragDrop(pDataObj, pDropSource, dwOKEffects, pdwEffect);
} catch (...) {
}
g_dragDropInstance = nullptr;
UnhookWindowsHookEx(pfc::replace_null_t(g_hook));
}
return ret;
}
void CListControlImpl::OnKillFocus(CWindow) {
if (m_captureProc) {
ReleaseCapture();
m_captureProc = nullptr;
}
SetMsgHandled(FALSE);
}
void CListControlImpl::OnWindowPosChanged(LPWINDOWPOS arg) {
if ( arg->flags & SWP_HIDEWINDOW ) {
if (m_captureProc) {
ReleaseCapture();
m_captureProc = nullptr;
}
}
SetMsgHandled(FALSE);
}
void CListControlHeaderImpl::RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,size_t item, uint32_t bkColor) {
if ( ! this->DelimitColumns() ) {
__super::RenderItemBackground(p_dc, p_itemRect, item, bkColor);
} else {
auto cnt = this->GetColumnCount();
uint32_t x = 0;
for( size_t walk = 0; walk < cnt; ) {
auto span = this->GetSubItemSpan( item, walk );
PFC_ASSERT( span > 0 );
uint32_t width = 0;
for( size_t walk2 = 0; walk2 < span; ++ walk2 ) {
width += this->GetSubItemWidth( walk + walk2 );
}
CRect rc = p_itemRect;
rc.left = x;
x += width;
rc.right = x;
__super::RenderItemBackground(p_dc, rc, item, bkColor);
walk += span;
}
}
}

328
libPPUI/CListControl.h Normal file
View File

@@ -0,0 +1,328 @@
#pragma once
// ================================================================================
// Main CListControl implementation
//
// For ready-to-use CListControl specializations,
// see CListControlSimple.h and CListControlOwnerData.h
// ================================================================================
#pragma comment(lib, "uxtheme.lib")
#include <functional>
#include <list>
#include <vector>
#include <set>
#include <string>
#include "CMiddleDragImpl.h"
#include "wtl-pp.h"
#include "gesture.h"
#include "gdiplus_helpers.h"
#define CListControl_ScrollWindowFix
#ifdef CListControl_ScrollWindowFix
#define WS_EX_COMPOSITED_CListControl 0
#else
#define WS_EX_COMPOSITED_CListControl WS_EX_COMPOSITED
#endif
typedef std::function< bool ( UINT, DWORD, CPoint ) > CaptureProc_t;
typedef CWinTraits<WS_VSCROLL | WS_HSCROLL | WS_TABSTOP | WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, WS_EX_COMPOSITED_CListControl> CListControlTraits;
class CListControlImpl : public CWindowImpl<CListControlImpl,CWindow,CListControlTraits> {
public:
CListControlImpl() {}
DECLARE_WND_CLASS_EX(TEXT("{4B94B650-C2D8-40de-A0AD-E8FADF62D56C}"),CS_DBLCLKS,COLOR_WINDOW);
// Wrapper around CWindowImpl::Create().
// Creates CListControl replacing another dialog control with the specified ID.
// Note that m_dlgWantEnter is set to false by this method, as it's typically unwanted in dialogs.
void CreateInDialog( CWindow wndDialog, UINT replaceControlID );
enum {
MSG_SIZE_ASYNC = WM_USER + 13,
MSG_EXEC_DEFERRED,
UserMsgBase
};
BEGIN_MSG_MAP_EX(CListControlImpl)
MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST, WM_MOUSELAST, MousePassThru);
MESSAGE_HANDLER_EX(MSG_EXEC_DEFERRED, OnExecDeferred);
MESSAGE_HANDLER(WM_PAINT,OnPaint);
MSG_WM_PRINTCLIENT(OnPrintClient);
MESSAGE_HANDLER(WM_VSCROLL,OnVScroll);
MESSAGE_HANDLER(WM_HSCROLL,OnHScroll);
MESSAGE_HANDLER(WM_SIZE,OnSize);
MESSAGE_HANDLER(WM_MOUSEHWHEEL,OnHWheel);
MESSAGE_HANDLER(WM_MOUSEWHEEL,OnVWheel);
MESSAGE_HANDLER(WM_LBUTTONDOWN,SetFocusPassThru);
MESSAGE_HANDLER(WM_RBUTTONDOWN,SetFocusPassThru);
MESSAGE_HANDLER(WM_MBUTTONDOWN,SetFocusPassThru);
MESSAGE_HANDLER(WM_LBUTTONDBLCLK,SetFocusPassThru);
MESSAGE_HANDLER(WM_RBUTTONDBLCLK,SetFocusPassThru);
MESSAGE_HANDLER(WM_MBUTTONDBLCLK,SetFocusPassThru);
MESSAGE_HANDLER(WM_CREATE,OnCreatePassThru);
MESSAGE_HANDLER(WM_ERASEBKGND,OnEraseBkgnd);
MESSAGE_HANDLER(MSG_SIZE_ASYNC,OnSizeAsync);
MESSAGE_HANDLER(WM_GESTURE, OnGesture)
MSG_WM_THEMECHANGED(OnThemeChanged)
MSG_WM_KILLFOCUS(OnKillFocus)
MSG_WM_WINDOWPOSCHANGED(OnWindowPosChanged)
MESSAGE_HANDLER_EX( WM_GETDLGCODE, OnGetDlgCode )
END_MSG_MAP()
virtual void ReloadData() { OnViewAreaChanged(); }
virtual void ReloadItems( pfc::bit_array const & mask ) { UpdateItems( mask); }
//! Hookable function called in response to reordering of items. Redraws the view and updates internal data to reflect the change.
virtual void OnItemsReordered( const size_t * order, size_t count );
//! Hookable function called in response to removal of items. Redraws the view and updates internal data to reflect the change.
virtual void OnItemsRemoved( pfc::bit_array const & mask, size_t oldCount ) { ReloadData(); }
//! Hookable function called in response to insertion of items. Redraws the view and updates internal data to reflect the change.
virtual void OnItemsInserted( size_t at, size_t count, bool bSelect ) { ReloadData(); }
void ReloadItem(size_t i) { ReloadItems( pfc::bit_array_one(i) ); }
void OnViewAreaChanged() {OnViewAreaChanged(GetViewOrigin());}
void OnViewAreaChanged(CPoint p_originOverride);
void UpdateGroupHeader(int p_id);
void UpdateItems(const pfc::bit_array & p_mask);
void UpdateItemsAndHeaders(const pfc::bit_array & p_mask);
void UpdateItem(t_size p_item) {UpdateItems(pfc::bit_array_one(p_item));}
void UpdateItemsAll() {Invalidate();}
void EnsureItemVisible(t_size p_item, bool bUser = false);
void EnsureHeaderVisible(int p_group);
virtual void EnsureVisibleRectAbs(const CRect & p_rect);
CRect GetItemRect(t_size p_item) const;
bool GetGroupHeaderRect(int p_group,CRect & p_rect) const;
CRect GetItemRectAbs(t_size p_item) const;
bool GetGroupHeaderRectAbs(int p_group,CRect & p_rect) const;
CPoint GetViewOrigin() const {return m_viewOrigin;}
CPoint GetViewOffset() const {return GetViewOrigin() - GetClientOrigin();}
int GetViewAreaWidth() const {return GetItemWidth();}
int GetViewAreaHeight() const;
CRect GetViewAreaRectAbs() const;
CRect GetViewAreaRect() const;
CRect GetValidViewOriginArea() const;
t_size GetGroupCount() const;
bool GetItemRangeAbs(const CRect & p_rect,t_size & p_base,t_size & p_count) const;
bool GetItemRangeAbsInclHeaders(const CRect & p_rect,t_size & p_base,t_size & p_count) const;
bool GetItemRange(const CRect & p_rect,t_size & p_base,t_size & p_count) const;
void MoveViewOriginNoClip(CPoint p_target);
void MoveViewOrigin(CPoint p_target);
CPoint ClipViewOrigin(CPoint p_origin) const;
void MoveViewOriginDelta(CPoint p_delta) {MoveViewOrigin( GetViewOrigin() + p_delta );}
void MoveViewOriginDeltaNoClip(CPoint p_delta) {MoveViewOriginNoClip( GetViewOrigin() + p_delta );}
bool ItemFromPoint(CPoint const & p_pt,t_size & p_item) const {return ItemFromPointAbs(p_pt + GetViewOffset(),p_item);}
bool GroupHeaderFromPoint(CPoint const & p_pt,int & p_group) const {return GroupHeaderFromPointAbs(p_pt + GetViewOffset(),p_group);}
bool ItemFromPointAbs(CPoint const & p_pt,t_size & p_item) const;
bool GroupHeaderFromPointAbs(CPoint const & p_pt,int & p_group) const;
bool ResolveGroupRange(int p_id,t_size & p_base,t_size & p_count) const;
virtual int GetGroupHeaderHeight() const {return 0;}
virtual int GetItemHeight() const {return 0;}
virtual int GetItemWidth() const {return 0;}
virtual t_size GetItemCount() const {return 0;}
virtual int GetItemGroup(t_size p_item) const {return 0;}
//override optionally
virtual void RenderItem(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc);
//override optionally
virtual void RenderGroupHeader(int p_group,const CRect & p_headerRect,const CRect & p_updateRect,CDCHandle p_dc);
//called by default RenderItem implementation
virtual void RenderItemText(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc, bool allowColors) {}
//called by default RenderItem implementation
virtual void RenderGroupHeaderText(int p_group,const CRect & p_headerRect,const CRect & p_updateRect,CDCHandle p_dc) {}
virtual void RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,size_t item, uint32_t bkColor);
virtual void RenderGroupHeaderBackground(CDCHandle p_dc,const CRect & p_headerRect,int iGroup);
virtual void RenderBackground( CDCHandle dc, CRect const & rc );
virtual void OnViewOriginChange(CPoint p_delta) {}
virtual void RenderOverlay(const CRect & p_updaterect,CDCHandle p_dc) {}
virtual bool FixedOverlayPresent() {return false;}
virtual CRect GetClientRectHook() const;
enum {
colorText = COLOR_WINDOWTEXT,
colorBackground = COLOR_WINDOW,
colorHighlight = COLOR_HOTLIGHT,
colorSelection = COLOR_HIGHLIGHT,
};
virtual COLORREF GetSysColorHook( int colorIndex ) const;
//! Called by CListControlWithSelectionBase.
virtual void OnItemClicked(t_size item, CPoint pt) {}
//! Called by CListControlWithSelectionBase.
virtual void OnGroupHeaderClicked(int groupId, CPoint pt) {}
//! Return true to indicate that some area of the control has a special purpose and clicks there should not trigger changes in focus/selection.
virtual bool OnClickedSpecialHitTest(CPoint pt) { return false; }
virtual bool OnClickedSpecial(DWORD status, CPoint pt) {return false;}
virtual bool AllowScrollbar(bool vertical) const {return true;}
CPoint GetClientOrigin() const {return GetClientRectHook().TopLeft();}
CRect GetVisibleRectAbs() const {
CRect view = GetClientRectHook();
view.OffsetRect( GetViewOrigin() - view.TopLeft() );
return view;
}
bool IsSameItemOrHeaderAbs(const CPoint & p_point1, const CPoint & p_point2) const;
void AddItemToUpdateRgn(HRGN p_rgn, t_size p_index) const;
void AddGroupHeaderToUpdateRgn(HRGN p_rgn, int id) const;
t_size InsertIndexFromPoint(const CPoint & p_pt) const;
//! Translate point to insert location for drag and drop. \n
//! Made virtual so it can be specialized to allow only specific drop locations.
virtual t_size InsertIndexFromPointEx(const CPoint & pt, bool & bInside) const;
virtual void ListHandleResize();
//! Can smooth-scroll *now* ? Used to suppress smooth scroll on temporary basis due to specific user operations in progress
virtual bool CanSmoothScroll() const { return true; }
//! Is smooth scroll enabled by user?
virtual bool UserEnabledSmoothScroll() const;
virtual bool ToggleSelectedItemsHook(pfc::bit_array const & mask) { return false; }
void SetCaptureEx(CaptureProc_t proc);
void SetCaptureMsgHandled(BOOL v) { this->SetMsgHandled(v); }
SIZE GetDPI() const { return this->m_dpi;}
// Should this control take enter key in dialogs or not?
// Default to true for compatibility with existing code - but when used in dialogs, you'll want it set to false to hit [OK] with enter.
// Note that CreateInDialog() sets this to false. Change it later if you want enter key presses to reach this control in a dialog.
bool m_dlgWantEnter = true;
bool WantReturn() const { return m_dlgWantEnter; }
void SetWantReturn(bool v) { m_dlgWantEnter = v; }
enum {
rowStyleGrid = 0,
rowStyleFlat,
rowStylePlaylist,
rowStylePlaylistDelimited,
};
void SetPlaylistStyle() {SetRowStyle(rowStylePlaylist);}
void SetRowStyle(unsigned v) { this->m_rowStyle = v; if (m_hWnd) Invalidate(); }
void SetFlatStyle() {SetRowStyle(rowStyleFlat);}
unsigned m_rowStyle = rowStylePlaylistDelimited;
bool DelimitColumns() const { return m_rowStyle == rowStyleGrid || m_rowStyle == rowStylePlaylistDelimited; }
static COLORREF BlendGridColor( COLORREF bk, COLORREF tx );
static COLORREF BlendGridColor( COLORREF bk );
COLORREF GridColor();
private:
void RenderRect(const CRect & p_rect,CDCHandle p_dc);
int HandleWheel(int & p_accum,int p_delta, bool bHoriz);
void OnKillFocus(CWindow);
void OnWindowPosChanged(LPWINDOWPOS);
void PaintContent(CRect rcPaint, HDC dc);
void OnPrintClient(HDC dc, UINT uFlags);
LRESULT OnPaint(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnVScroll(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnHScroll(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnSize(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnSizeAsync(UINT,WPARAM,LPARAM,BOOL&) {ListHandleResize();return 0;}
LRESULT OnVWheel(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnHWheel(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnGesture(UINT,WPARAM,LPARAM,BOOL&);
LRESULT SetFocusPassThru(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnCreatePassThru(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnEraseBkgnd(UINT,WPARAM,LPARAM,BOOL&);
LRESULT MousePassThru(UINT, WPARAM, LPARAM);
LRESULT OnGetDlgCode(UINT, WPARAM, LPARAM);
void OnThemeChanged();
int GetScrollThumbPos(int which);
void RefreshSliders();
void RefreshSlider(bool p_vertical);
void OnSizeAsync_Trigger();
static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam);
bool MouseWheelFromHook(UINT msg, LPARAM data);
bool m_suppressMouseWheel = false;
int m_wheelAccumX = 0, m_wheelAccumY = 0;
CPoint m_viewOrigin = CPoint(0,0);
bool m_sizeAsyncPending = false;
CPoint m_gesturePoint;
protected:
pfc::map_t<pfc::string8, CTheme, pfc::comparator_strcmp> m_themeCache;
CTheme & themeFor( const char * what );
CTheme & theme() { return themeFor("LISTVIEW");}
const SIZE m_dpi = QueryScreenDPIEx();
CGestureAPI m_gestureAPI;
bool m_ensureVisibleUser = false;
CaptureProc_t m_captureProc;
void defer( std::function<void () > f );
LRESULT OnExecDeferred(UINT, WPARAM, LPARAM);
// Overlays our stuff on top of generic DoDragDrop call.
// Currently catches mouse wheel messages in mid-drag&drop and handles them in our view.
HRESULT DoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect);
private:
bool m_defferredMsgPending = false;
std::list<std::function<void ()> > m_deferred;
};
class CListControlFontOps : public CListControlImpl {
private:
typedef CListControlImpl TParent;
public:
CListControlFontOps();
BEGIN_MSG_MAP_EX(CListControlFontOps)
MESSAGE_HANDLER(WM_GETFONT,OnGetFont);
MESSAGE_HANDLER(WM_SETFONT,OnSetFont);
CHAIN_MSG_MAP(TParent);
END_MSG_MAP()
CFontHandle GetFont() const { return m_font; }
void SetFont(HFONT font, bool bUpdateView = true);
protected:
CFontHandle GetGroupHeaderFont() const {return (HFONT)m_groupHeaderFont;}
virtual double GroupHeaderFontScale() const { return 1.25; }
virtual int GroupHeaderFontWeight(int origVal) const {
//return pfc::min_t<int>(FW_BLACK, origVal + 200);
return origVal;
}
//! Overridden implementations should always forward the call to the base class.
virtual void OnSetFont(bool bUpdatingView) {}
int GetGroupHeaderHeight() const {return m_groupHeaderHeight;}
int GetItemHeight() const {return m_itemHeight;}
private:
LRESULT OnSetFont(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnGetFont(UINT,WPARAM,LPARAM,BOOL&);
void UpdateGroupHeaderFont();
void CalculateHeights();
int m_itemHeight, m_groupHeaderHeight;
CFontHandle m_font;
CFont m_groupHeaderFont;
};
#include "CListControlHeaderImpl.h"
#include "CListControlTruncationTooltipImpl.h"
typedef CMiddleDragImpl<CListControlTruncationTooltipImpl> CListControl;

View File

@@ -0,0 +1,22 @@
#pragma once
// ================================================================================
// CListControlComplete
// ================================================================================
// Simplified declaration of the base class that most CListControl users will need.
// The other base classes are used directly mainly by old code predating libPPUI.
// ================================================================================
#include "CListControlWithSelection.h"
#include "CListControl_EditImpl.h"
#include "CListAccessible.h"
// ================================================================================
// CListControlWithSelectionImpl = list control with selection/focus
// CListControl_EditImpl = inplace editbox implementation
// CListControlAccImpl = accessibility API implementation (screen reader interop)
// ================================================================================
typedef CListControlAccImpl<CListControl_EditImpl<CListControlWithSelectionImpl> > CListControlComplete;
// CListControlReadOnly : no inplace edit functionality (CListControl_EditImpl)
typedef CListControlAccImpl<CListControlWithSelectionImpl> CListControlReadOnly;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,224 @@
#pragma once
class CListCell;
class CListControlHeaderImpl : public CListControlFontOps {
private:
typedef CListControlFontOps TParent;
public:
CListControlHeaderImpl() {}
BEGIN_MSG_MAP_EX(CListControlHeaderImpl)
MSG_WM_CTLCOLORSTATIC(OnCtlColorStatic);
MESSAGE_HANDLER(WM_KEYDOWN,OnKeyDown);
MESSAGE_HANDLER(WM_SYSKEYDOWN,OnKeyDown);
MESSAGE_HANDLER_EX(WM_SIZE,OnSizePassThru);
NOTIFY_CODE_HANDLER(HDN_ITEMCHANGED,OnHeaderItemChanged);
NOTIFY_CODE_HANDLER(HDN_ENDDRAG,OnHeaderEndDrag);
NOTIFY_CODE_HANDLER(HDN_ITEMCLICK,OnHeaderItemClick);
NOTIFY_CODE_HANDLER(HDN_DIVIDERDBLCLICK,OnDividerDoubleClick);
MSG_WM_SETCURSOR(OnSetCursor);
MSG_WM_MOUSEMOVE(OnMouseMove)
MSG_WM_DESTROY(OnDestroy)
MSG_WM_ENABLE(OnEnable)
CHAIN_MSG_MAP(TParent)
END_MSG_MAP()
typedef uint32_t cellState_t;
typedef CListCell * cellType_t;
int GetHeaderItemWidth( int which );
void InitializeHeaderCtrl(DWORD flags = HDS_FULLDRAG);
void InitializeHeaderCtrlSortable() {InitializeHeaderCtrl(HDS_FULLDRAG | HDS_BUTTONS);}
CHeaderCtrl GetHeaderCtrl() const {return m_header;}
void SetSortIndicator( size_t whichColumn, bool isUp );
void ClearSortIndicator();
bool IsHeaderEnabled() const {return m_header.m_hWnd != NULL;}
void ResetColumns(bool update = true);
enum {
columnWidthMax = (uint32_t)INT32_MAX,
columnWidthAuto = UINT32_MAX,
columnWidthAutoUseContent = UINT32_MAX-1,
};
void AddColumn(const char * label, uint32_t widthPixels, DWORD fmtFlags = HDF_LEFT,bool update = true);
//! Extended AddColumn, specifies width in pixels @ 96DPI instead of screen-specific pixels
void AddColumnEx( const char * label, uint32_t widthPixelsAt96DPI, DWORD fmtFlags = HDF_LEFT, bool update = true );
//! Extended AddColumn, specifies width in Dialog Length Units (DLU), assumes parent of this list to be a dialog window.
void AddColumnDLU( const char * label, uint32_t widthDLU, DWORD fmtFlags = HDF_LEFT, bool update = true );
//! Extended AddColumn, specifies width as floating-point value of pixels at 96DPI. \n
//! For DPI-safe storage of user's column widths.
void AddColumnF( const char * label, float widthF, DWORD fmtFlags = HDF_LEFT, bool update = true );
void AddColumnAutoWidth( const char * label, DWORD fmtFlags = HDF_LEFT, bool bUpdate = true) { AddColumn(label, columnWidthAuto, fmtFlags, bUpdate); }
bool DeleteColumn(size_t index, bool updateView = true);
void DeleteColumns( pfc::bit_array const & mask, bool updateView = true);
void ResizeColumn(t_size index, t_uint32 widthPixels, bool updateView = true);
void SetColumn( size_t which, const char * title, DWORD fmtFlags = HDF_LEFT, bool updateView = true);
void GetColumnText(size_t which, pfc::string_base & out) const;
uint32_t GetOptimalColumnWidth( size_t index ) const;
uint32_t GetOptimalColumnWidthFixed( const char * fixedText) const;
uint32_t GetColumnsBlankWidth( size_t colExclude = SIZE_MAX ) const;
void SizeColumnToContent( size_t which, uint32_t minWidth );
void SizeColumnToContentFillBlank( size_t which );
//! If creating a custom headerless multi column scheme, override these to manipulate your columns
virtual size_t GetColumnCount() const;
virtual uint32_t GetSubItemWidth(size_t subItem) const;
//! Returns column width as a floating-point value of pixels at 96DPI. \n
//! For DPI-safe storage of user's column widths.
float GetColumnWidthF( size_t subItem ) const;
//! Indicate how many columns a specific row/column cell spans\n
//! This makes sense only if the columns can't be user-reordered
virtual size_t GetSubItemSpan(size_t row, size_t column) const;
t_size GetSubItemOrder(t_size subItem) const;
int GetItemWidth() const override;
protected:
CRect GetClientRectHook() const override;
void RenderBackground(CDCHandle dc, CRect const& rc) override;
struct GetOptimalWidth_Cache {
//! For temporary use.
pfc::string8_fastalloc m_stringTemp, m_stringTempUnfuckAmpersands;
//! For temporary use.
pfc::stringcvt::string_wide_from_utf8_t<pfc::alloc_fast_aggressive> m_convertTemp;
//! Our DC for measuring text. Correct font pre-selected.
CDCHandle m_dc;
t_uint32 GetStringTempWidth();
};
void UpdateHeaderLayout();
void OnViewOriginChange(CPoint p_delta);
void SetHeaderFont(HFONT font);
void RenderItemText(t_size item,const CRect & itemRect,const CRect & updateRect,CDCHandle dc, bool allowColors);
void RenderGroupHeaderText(int id,const CRect & headerRect,const CRect & updateRect,CDCHandle dc);
//! Converts an item/subitem rect to a rect in which the text should be rendered, removing spacing to the left/right of the text.
virtual CRect GetItemTextRectHook(size_t item, size_t subItem, CRect const & itemRect) const;
CRect GetItemTextRect(CRect const & itemRect) const;
//! Override for custom spacing to the left/right of the text in each column.
virtual t_uint32 GetColumnSpacing() const {return MulDiv(4,m_dpi.cx,96);}
//! Override for column-header-click sorting.
virtual void OnColumnHeaderClick(t_size index) {}
//! Override to supply item labels.
virtual bool GetSubItemText(t_size item, t_size subItem, pfc::string_base & out) const {return false;}
//! Override if you support groups.
virtual bool GetGroupHeaderText(int id, pfc::string_base & out) const {return false;}
//! Override optionally.
virtual void RenderSubItemText(t_size item, t_size subItem,const CRect & subItemRect,const CRect & updateRect,CDCHandle dc, bool allowColors);
virtual void OnColumnsChanged();
virtual t_uint32 GetOptimalSubItemWidth(t_size item, t_size subItem, GetOptimalWidth_Cache & cache) const;
virtual t_uint32 GetOptimalGroupHeaderWidth(int which) const;
bool GetItemAtPointAbsEx( CPoint pt, size_t & outItem, size_t & outSubItem ) const;
cellType_t GetCellTypeAtPointAbs( CPoint pt ) const;
virtual cellType_t GetCellType( size_t item, size_t subItem ) const;
virtual bool AllowTypeFindInCell( size_t item, size_t subItem ) const;
virtual bool GetCellTypeSupported() const { return false; } // optimization hint, some expensive checks can be suppressed if cell types are not used for this view
virtual bool GetCellCheckState( size_t item, size_t subItem ) const { return false; }
virtual void SetCellCheckState(size_t item, size_t subItem, bool value);
virtual bool ToggleSelectedItemsHook(const pfc::bit_array & mask);
void RenderSubItemTextInternal(size_t subItem, const CRect & subItemRect, CDCHandle dc, const char * text, bool allowColors);
t_uint32 GetOptimalColumnWidth(t_size which, GetOptimalWidth_Cache & cache) const;
t_uint32 GetOptimalSubItemWidthSimple(t_size item, t_size subItem) const;
void AutoColumnWidths(const pfc::bit_array & mask,bool expandLast = false);
void AutoColumnWidths() {AutoColumnWidths(pfc::bit_array_true());}
void AutoColumnWidth(t_size which) {AutoColumnWidths(pfc::bit_array_one(which));}
virtual bool OnColumnHeaderDrag(t_size index, t_size newOrder);
void OnItemClicked(t_size item, CPoint pt) override;
virtual void OnSubItemClicked(t_size item, t_size subItem,CPoint pt);
bool OnClickedSpecialHitTest(CPoint pt) override;
bool OnClickedSpecial(DWORD status, CPoint pt) override;
static bool CellTypeUsesSpecialHitTests( cellType_t ct );
CRect GetSubItemRectAbs(t_size item,t_size subItem) const;
CRect GetSubItemRect(t_size item,t_size subItem) const;
t_size SubItemFromPointAbs(CPoint pt) const;
static bool CellTypeReactsToMouseOver( cellType_t ct );
virtual CRect CellHotRect( size_t item, size_t subItem, cellType_t ct, CRect rcCell );
CRect CellHotRect( size_t item, size_t subItem, cellType_t ct );
virtual double CellTextScale(size_t item, size_t subItem) { return 1; }
virtual bool IsSubItemGrayed(size_t item, size_t subItem) { return !this->IsWindowEnabled(); }
// HDF_* constants for this column, override when not using list header control. Used to control text alignment.
virtual DWORD GetColumnFormat(t_size which) const;
void SetColumnFormat(t_size which,DWORD format);
void SetColumnSort(t_size which, bool isUp);
std::vector<int> GetColumnOrderArray() const;
bool AllowScrollbar(bool vertical) const override;
void RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,size_t item, uint32_t bkColor) override;
void ColumnWidthFix(); // Call RecalcItemWidth() / ProcessAutoWidth()
void ReloadData() override;
private:
void OnEnable(BOOL) { Invalidate(); }
HBRUSH OnCtlColorStatic(CDCHandle dc, CStatic wndStatic);
void ProcessColumnsChange() { OnColumnsChanged();}
LRESULT OnSizePassThru(UINT,WPARAM,LPARAM);
LRESULT OnHeaderItemClick(int,LPNMHDR,BOOL&);
LRESULT OnDividerDoubleClick(int,LPNMHDR,BOOL&);
LRESULT OnHeaderItemChanged(int,LPNMHDR,BOOL&);
LRESULT OnHeaderEndDrag(int,LPNMHDR,BOOL&);
LRESULT OnKeyDown(UINT,WPARAM,LPARAM,BOOL&);
void OnDestroy();
BOOL OnSetCursor(CWindow wnd, UINT nHitTest, UINT message);
void OnMouseMove(UINT nFlags, CPoint point);
void RecalcItemWidth(); // FIXED width math
void ProcessAutoWidth(); // DYNAMIC width math
bool m_ownColumnsChange = false;
int m_itemWidth = 0;
int m_clientWidth = 0;
CHeaderCtrl m_header;
CStatic m_headerLine;
bool HaveAutoWidthColumns() const;
bool HaveAutoWidthContentColumns() const;
struct colRuntime_t {
bool m_autoWidth = false;
bool m_autoWidthContent = false;
int m_widthPixels = 0;
uint32_t m_userWidth = 0;
std::string m_text;
bool autoWidth() const { return m_userWidth > columnWidthMax; }
bool autoWidthContent() const { return m_userWidth == columnWidthAutoUseContent; }
bool autoWidthPlain() const { return m_userWidth == columnWidthAuto; }
};
std::vector<colRuntime_t> m_colRuntime;
//for group headers
GdiplusScope m_gdiPlusScope;
void SetPressedItem(size_t row, size_t column);
void ClearPressedItem() {SetPressedItem(SIZE_MAX, SIZE_MAX);}
void SetHotItem( size_t row, size_t column );
void ClearHotItem() { SetHotItem(SIZE_MAX, SIZE_MAX); }
size_t m_pressedItem = SIZE_MAX, m_pressedSubItem = SIZE_MAX;
size_t m_hotItem = SIZE_MAX, m_hotSubItem = SIZE_MAX;
};

View File

@@ -0,0 +1,206 @@
#pragma once
// ================================================================================
// CListControlOwnerData
// ================================================================================
// Forwards all list data retrieval and manipulation calls to a host object.
// Not intended for subclassing, just implement IListControlOwnerDataSource methods
// in your class.
// ================================================================================
#include "CListControlComplete.h"
class CListControlOwnerData;
class IListControlOwnerDataSource {
public:
typedef const CListControlOwnerData * ctx_t;
virtual size_t listGetItemCount( ctx_t ) = 0;
virtual pfc::string8 listGetSubItemText( ctx_t, size_t item, size_t subItem ) = 0;
virtual bool listCanReorderItems( ctx_t ) { return false; }
virtual bool listReorderItems( ctx_t, const size_t*, size_t) {return false;}
virtual bool listRemoveItems( ctx_t, pfc::bit_array const & ) {return false;}
virtual void listItemAction(ctx_t, size_t) {}
virtual void listSubItemClicked( ctx_t, size_t, size_t ) {}
virtual bool listCanSelectItem( ctx_t, size_t ) { return true; }
virtual pfc::string8 listGetEditField(ctx_t ctx, size_t item, size_t subItem, size_t & lineCount) {
lineCount = 1; return listGetSubItemText( ctx, item, subItem);
}
virtual void listSetEditField(ctx_t ctx, size_t item, size_t subItem, const char * val) {}
virtual uint32_t listGetEditFlags(ctx_t ctx, size_t item, size_t subItem) {return 0;}
typedef InPlaceEdit::CTableEditHelperV2::autoComplete_t autoComplete_t;
typedef InPlaceEdit::CTableEditHelperV2::combo_t combo_t;
virtual autoComplete_t listGetAutoComplete(ctx_t, size_t item, size_t subItem) {return autoComplete_t();}
virtual combo_t listGetCombo(ctx_t, size_t item, size_t subItem) { return combo_t(); }
virtual bool listIsColumnEditable( ctx_t, size_t ) { return false; }
virtual bool listKeyDown(ctx_t, UINT nChar, UINT nRepCnt, UINT nFlags) { return false; }
virtual bool listKeyUp(ctx_t, UINT nChar, UINT nRepCnt, UINT nFlags) { return false; }
// Allow type-find in this view?
// Called prior to a typefind pass attempt, you can either deny entirely, or prepare any necessary data and allow it.
virtual bool listAllowTypeFind( ctx_t ) { return true; }
// Allow type-find in a specific item/column?
virtual bool listAllowTypeFindHere( ctx_t, size_t item, size_t subItem ) { return true ;}
virtual void listColumnHeaderClick(ctx_t, size_t subItem) {}
virtual void listBeforeDrawItemText( ctx_t, size_t item, CDCHandle dc ) {}
virtual bool listIsSubItemGrayed(ctx_t, size_t item, size_t subItem) { return false; }
virtual void listSelChanged(ctx_t) {}
virtual void listFocusChanged(ctx_t) {}
virtual void listColumnsChanged(ctx_t) {}
virtual bool listEditCanAdvanceHere(ctx_t, size_t item, size_t subItem, uint32_t whatHappened) {(void) item; (void) subItem, (void) whatHappened; return true;}
};
class IListControlOwnerDataCells {
public:
typedef const CListControlOwnerData * cellsCtx_t;
virtual CListControl::cellType_t listCellType( cellsCtx_t, size_t item, size_t subItem ) = 0;
virtual size_t listCellSpan( cellsCtx_t, size_t item, size_t subItem ) {return 1;}
virtual bool listCellCheckState( cellsCtx_t, size_t item, size_t subItem ) {return false; }
virtual void listCellSetCheckState( cellsCtx_t, size_t item, size_t subItem, bool state ) {}
};
class CListControlOwnerData : public CListControlComplete {
IListControlOwnerDataSource * const m_host;
public:
CListControlOwnerData( IListControlOwnerDataSource * h) : m_host(h) {}
BEGIN_MSG_MAP_EX(CListControlOwnerData)
MSG_WM_KEYDOWN(OnKeyDown)
MSG_WM_KEYUP(OnKeyUp)
MSG_WM_SYSKEYDOWN(OnKeyDown)
MSG_WM_SYSKEYUP(OnKeyUp)
CHAIN_MSG_MAP(CListControlComplete)
END_MSG_MAP()
using CListControl_EditImpl::TableEdit_Abort;
using CListControl_EditImpl::TableEdit_Start;
using CListControl_EditImpl::TableEdit_IsActive;
bool CanSelectItem( size_t idx ) const override {
return m_host->listCanSelectItem( this, idx );
}
size_t GetItemCount() const override {
return m_host->listGetItemCount( this );
}
bool GetSubItemText(size_t item, size_t subItem, pfc::string_base & out) const override {
out = m_host->listGetSubItemText( this, item, subItem );
return true;
}
void OnSubItemClicked( size_t item, size_t subItem, CPoint pt ) override {
__super::OnSubItemClicked(item, subItem, pt); // needed to toggle checkboxes etc
m_host->listSubItemClicked( this, item, subItem );
}
uint32_t QueryDragDropTypes() const override {
return (m_host->listCanReorderItems(this)) ? dragDrop_reorder : 0;
}
void RequestReorder( const size_t * order, size_t count) override {
if ( ! m_host->listReorderItems( this, order, count )) return;
this->OnItemsReordered( order, count );
}
void RequestRemoveSelection() override {
auto mask = this->GetSelectionMask();
size_t oldCount = GetItemCount();
if ( ! m_host->listRemoveItems( this, mask ) ) return;
this->OnItemsRemoved( mask, oldCount );
}
void ExecuteDefaultAction( size_t idx ) override {
m_host->listItemAction( this, idx );
}
size_t EvalTypeFind() override {
if (! m_host->listAllowTypeFind(this) ) return SIZE_MAX;
return __super::EvalTypeFind();
}
bool AllowTypeFindInCell( size_t item, size_t subItem ) const {
return __super::AllowTypeFindInCell( item, subItem ) && m_host->listAllowTypeFindHere( this, item, subItem );
}
protected:
void OnFocusChanged(size_t newFocus) override {
__super::OnFocusChanged(newFocus);
m_host->listFocusChanged(this);
}
void OnSelectionChanged(pfc::bit_array const & affected, pfc::bit_array const & status) {
__super::OnSelectionChanged(affected, status);
m_host->listSelChanged(this);
}
void RenderItemText(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc, bool allowColors) {
m_host->listBeforeDrawItemText(this, p_item, p_dc );
__super::RenderItemText(p_item, p_itemRect, p_updateRect, p_dc, allowColors);
}
void TableEdit_SetField(t_size item, t_size subItem, const char * value) override {
m_host->listSetEditField(this, item, subItem, value);
}
void TableEdit_GetField(t_size item, t_size subItem, pfc::string_base & out, t_size & lineCount) override {
lineCount = 1;
out = m_host->listGetEditField(this, item, subItem, lineCount);
}
t_uint32 TableEdit_GetEditFlags(t_size item, t_size subItem) const override {
return m_host->listGetEditFlags( this, item, subItem );
}
combo_t TableEdit_GetCombo(size_t item, size_t subItem) override {
return m_host->listGetCombo(this, item, subItem);
}
autoComplete_t TableEdit_GetAutoCompleteEx(t_size item, t_size subItem) override {
return m_host->listGetAutoComplete( this, item, subItem );
}
bool TableEdit_IsColumnEditable(t_size subItem) const override {
return m_host->listIsColumnEditable( this, subItem );
}
void OnColumnHeaderClick(t_size index) override {
m_host->listColumnHeaderClick(this, index);
}
void OnColumnsChanged() override {
__super::OnColumnsChanged();
m_host->listColumnsChanged(this);
}
bool IsSubItemGrayed(size_t item, size_t subItem) override {
return __super::IsSubItemGrayed(item, subItem) || m_host->listIsSubItemGrayed(this, item, subItem);
}
private:
bool TableEdit_CanAdvanceHere( size_t item, size_t subItem, uint32_t whatHappened ) const override {
return m_host->listEditCanAdvanceHere(this, item, subItem, whatHappened);
}
void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
bool handled = m_host->listKeyDown(this, nChar, nRepCnt, nFlags);
SetMsgHandled( !! handled );
}
void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) {
bool handled = m_host->listKeyUp(this, nChar, nRepCnt, nFlags);
SetMsgHandled( !! handled );
}
};
class CListControlOwnerDataCells : public CListControlOwnerData {
IListControlOwnerDataCells * const m_cells;
public:
CListControlOwnerDataCells( IListControlOwnerDataSource * source, IListControlOwnerDataCells * cells ) : CListControlOwnerData(source), m_cells(cells) {}
bool GetCellTypeSupported() const override {return true; }
bool GetCellCheckState( size_t item, size_t subItem ) const override {
return m_cells->listCellCheckState( this, item, subItem );
}
void SetCellCheckState( size_t item, size_t subItem, bool value ) override {
m_cells->listCellSetCheckState( this, item, subItem, value );
__super::SetCellCheckState(item, subItem, value);
}
cellType_t GetCellType( size_t item, size_t subItem ) const override {
return m_cells->listCellType( this, item, subItem );
}
size_t GetSubItemSpan(size_t row, size_t column) const override {
return m_cells->listCellSpan( this, row, column );
}
};

View File

@@ -0,0 +1,137 @@
#pragma once
// ================================================================================
// CListControlSimple
// Simplified CListControl interface; a ready-to-use class that can be instantiated
// without subclassing.
// Use when you don't need advanced features such as buttons or editing.
// Maintains its own data.
// ================================================================================
#include "CListControlComplete.h"
#include <functional>
#include <vector>
#include <map>
#include <string>
class CListControlSimple : public CListControlReadOnly {
public:
// Events
std::function<void()> onReordered; // if not set, list reordering is disabled
std::function<void()> onRemoved; // if not set, list item removal is disabled
std::function<void(size_t)> onItemAction;
std::function<void()> onSelChange;
size_t GetItemCount() const override {
return m_lines.size();
}
void SetItemCount( size_t count ) {
m_lines.resize( count );
ReloadData();
}
void SetItemText(size_t item, size_t subItem, const char * text, bool bRedraw = true) {
if ( item < m_lines.size() ) {
m_lines[item].text[subItem] = text;
if ( bRedraw ) ReloadItem( item );
}
}
bool GetSubItemText(size_t item, size_t subItem, pfc::string_base & out) const override {
if ( item < m_lines.size() ) {
auto & l = m_lines[item].text;
auto iter = l.find( subItem );
if ( iter != l.end() ) {
out = iter->second.c_str();
return true;
}
}
return false;
}
uint32_t QueryDragDropTypes() const override {
return (onReordered != nullptr) ? dragDrop_reorder : 0;
}
void RequestReorder( const size_t * order, size_t count) override {
if ( onReordered == nullptr ) return;
pfc::reorder_t( m_lines, order, count );
this->OnItemsReordered( order, count );
onReordered();
}
void RequestRemoveSelection() override {
if (onRemoved == nullptr) return;
auto mask = this->GetSelectionMask();
size_t oldCount = m_lines.size();
pfc::remove_mask_t( m_lines, mask );
this->OnItemsRemoved( mask, oldCount );
onRemoved();
}
void ExecuteDefaultAction( size_t idx ) override {
if (onItemAction != nullptr) onItemAction(idx);
}
void SetItemUserData( size_t item, size_t user ) {
if ( item < m_lines.size() ) {
m_lines[item].user = user;
}
}
size_t GetItemUserData( size_t item ) const {
size_t ret = 0;
if ( item < m_lines.size() ) {
ret = m_lines[item].user;
}
return ret;
}
void RemoveAllItems() {
RemoveItems(pfc::bit_array_true());
}
void RemoveItems( pfc::bit_array const & mask ) {
const auto oldCount = m_lines.size();
pfc::remove_mask_t( m_lines, mask );
this->OnItemsRemoved( mask, oldCount );
}
void RemoveItem( size_t which ) {
RemoveItems( pfc::bit_array_one( which ) );
}
size_t InsertItem( size_t insertAt, const char * textCol0 = nullptr ) {
if ( insertAt > m_lines.size() ) {
insertAt = m_lines.size();
}
{
line_t data;
if ( textCol0 != nullptr ) data.text[0] = textCol0;
m_lines.insert( m_lines.begin() + insertAt, std::move(data) );
}
this->OnItemsInserted( insertAt, 1, false );
return insertAt;
}
size_t AddItem( const char * textCol0 = nullptr ) {
return InsertItem( SIZE_MAX, textCol0 );
}
size_t InsertItems( size_t insertAt, size_t count ) {
if ( insertAt > m_lines.size() ) {
insertAt = m_lines.size();
}
{
line_t val;
m_lines.insert( m_lines.begin(), count, val );
}
this->OnItemsInserted( insertAt, count, false );
return insertAt;
}
protected:
void OnSelectionChanged(pfc::bit_array const & affected, pfc::bit_array const & status) {
__super::OnSelectionChanged(affected, status);
if ( onSelChange ) onSelChange();
}
private:
struct line_t {
std::map<size_t, std::string> text;
size_t user = 0;
};
std::vector<line_t> m_lines;
};

View File

@@ -0,0 +1,209 @@
#include "stdafx.h"
#include "CListControl.h"
#include "PaintUtils.h"
LRESULT CListControlTruncationTooltipImpl::OnTTShow(int,LPNMHDR,BOOL&) {
SetTimer(KTooltipTimer,KTooltipTimerDelay);
return 0;
}
LRESULT CListControlTruncationTooltipImpl::OnTTPop(int,LPNMHDR,BOOL&) {
KillTimer(KTooltipTimer);
return 0;
}
LRESULT CListControlTruncationTooltipImpl::OnTTGetDispInfo(int,LPNMHDR p_hdr,BOOL&) {
LPNMTTDISPINFO info = (LPNMTTDISPINFO)p_hdr;
info->lpszText = const_cast<TCHAR*>(this->m_tooltipText.get_ptr());
info->hinst = 0;
info->uFlags = 0;
return 0;
}
LRESULT CListControlTruncationTooltipImpl::OnDestroyPassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) {
if (m_tooltip.m_hWnd != NULL) m_tooltip.DestroyWindow();
KillTimer(KTooltipTimer);
bHandled = FALSE; return 0;
}
CListControlTruncationTooltipImpl::CListControlTruncationTooltipImpl()
: m_toolinfo()
, m_tooltipRect(0,0,0,0)
{
}
void CListControlTruncationTooltipImpl::TooltipRemove() {
m_tooltipRect = CRect(0,0,0,0);
if (m_tooltip.m_hWnd != NULL) {
m_tooltip.TrackActivate(&m_toolinfo,FALSE);
}
}
void CListControlTruncationTooltipImpl::TooltipRemoveCheck() {
CPoint pt = GetCursorPos();
if (ScreenToClient(&pt)) {
TooltipRemoveCheck( MAKELPARAM( pt.x, pt.y ) );
}
}
void CListControlTruncationTooltipImpl::TooltipRemoveCheck(LPARAM pos) {
if (!m_tooltipRect.IsRectEmpty()) {
CPoint pt(pos);
if (!GetClientRectHook().PtInRect(pt)) {
TooltipRemove();
} else {
ClientToScreen(&pt);
if (!m_tooltipRect.PtInRect(pt)) {
TooltipRemove();
}
}
}
}
LRESULT CListControlTruncationTooltipImpl::OnTimer(UINT,WPARAM wp,LPARAM,BOOL& bHandled) {
switch(wp) {
case KTooltipTimer:
TooltipRemoveCheck();
return 0;
default:
bHandled = FALSE;
return 0;
}
}
LRESULT CListControlTruncationTooltipImpl::OnMouseMovePassThru(UINT,WPARAM,LPARAM lp,BOOL& bHandled) {
TooltipRemoveCheck(lp);
{
TRACKMOUSEEVENT ev = {sizeof(ev)};
ev.dwFlags = TME_HOVER;
ev.hwndTrack = *this;
ev.dwHoverTime = HOVER_DEFAULT;
TrackMouseEvent(&ev);
}
bHandled = FALSE;
return 0;
}
bool CListControlTruncationTooltipImpl::IsRectPartiallyObscuredAbs(CRect const & r) const {
CRect cl = this->GetClientRectHook(); cl.OffsetRect( this->GetViewOffset() );
return r.right > cl.right || r.top < cl.top || r.bottom > cl.bottom;
}
bool CListControlTruncationTooltipImpl::IsRectFullyVisibleAbs(CRect const & r) {
CRect cl = this->GetClientRectHook(); cl.OffsetRect( this->GetViewOffset() );
return r.left >= cl.left && r.right <= cl.right && r.top >= cl.top && r.bottom <= cl.bottom;
}
bool CListControlTruncationTooltipImpl::GetTooltipData(CPoint pt, pfc::string_base & outText, CRect & outRC, CFontHandle & outFont) const {
t_size item; int group;
if (ItemFromPointAbs(pt, item)) {
const CRect itemRectAbs = this->GetItemRectAbs(item);
/*if (this->IsHeaderEnabled()) */{
t_uint32 cbase = 0;
auto orderArray = this->GetColumnOrderArray();
for (t_size _cwalk = 0; _cwalk < orderArray.size(); ++_cwalk) {
const t_size cwalk = orderArray[_cwalk];
//const TColumnRuntime & col = m_columns[cwalk];
const t_uint32 width = GetSubItemWidth(cwalk);
if ((t_uint32)pt.x < cbase + width) {
t_uint32 estWidth = GetOptimalSubItemWidthSimple(item, cwalk);
CRect rc = itemRectAbs; rc.left = cbase; rc.right = cbase + estWidth;
if (estWidth > width || (IsRectPartiallyObscuredAbs(rc) && rc.PtInRect(pt))) {
pfc::string_formatter label, ccTemp;
if (GetSubItemText(item, cwalk, label)) {
PaintUtils::TextOutColors_StripCodes(ccTemp, label);
outFont = GetFont(); outRC = rc; outText = ccTemp;
return true;
}
}
break;
}
cbase += width;
}
}
} else if (GroupHeaderFromPointAbs(pt, group)) {
CRect rc;
if (GetGroupHeaderRectAbs(group, rc) && rc.PtInRect(pt)) {
const t_uint32 estWidth = GetOptimalGroupHeaderWidth(group);
CRect rcText = rc; rcText.right = rcText.left + estWidth;
if (estWidth > (t_uint32)rc.Width() || (IsRectPartiallyObscuredAbs(rcText) && rcText.PtInRect(pt))) {
pfc::string_formatter label;
if (GetGroupHeaderText(group, label)) {
outFont = GetGroupHeaderFont(); outRC = rc; outText = label;
return true;
}
}
}
}
return false;
}
LRESULT CListControlTruncationTooltipImpl::OnHover(UINT,WPARAM wp,LPARAM lp,BOOL&) {
if (!m_tooltipRect.IsRectEmpty()) {
return 0;
}
if (wp & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON)) return 0;
const CPoint viewOffset = GetViewOffset();
CPoint pt ( lp ); pt += viewOffset;
CFontHandle font;
CRect rc;
pfc::string8 text;
if ( this->GetTooltipData(pt, text, rc, font) ) {
this->m_tooltipFont = font;
// Gets stuck if the text is very long!
if (text.length() < 4096) {
TooltipActivateAbs(text, rc);
}
}
return 0;
}
void CListControlTruncationTooltipImpl::TooltipActivateAbs(const char * label, const CRect & rect) {
CRect temp(rect);
temp.OffsetRect( - GetViewOffset() );
ClientToScreen(temp);
TooltipActivate(label,temp);
}
void CListControlTruncationTooltipImpl::TooltipActivate(const char * label, const CRect & rect) {
if (rect.IsRectEmpty()) return;
if (m_tooltip.m_hWnd == NULL) {
try {
InitTooltip();
} catch(std::exception const & e) {
(void) e;
// console::complain("Tooltip initialization failure", e);
return;
}
}
m_tooltipText.convert( EscapeTooltipText( label ) );
m_tooltipRect = rect;
TooltipUpdateFont();
m_tooltip.TrackPosition(rect.left,rect.top);
m_tooltip.TrackActivate(&m_toolinfo,TRUE);
}
void CListControlTruncationTooltipImpl::TooltipUpdateFont() {
if (m_tooltip.m_hWnd != NULL) {
if (m_tooltipFont) {
m_tooltip.SetFont(m_tooltipFont);
}
}
}
void CListControlTruncationTooltipImpl::InitTooltip() {
m_tooltipRect = CRect(0,0,0,0);
WIN32_OP( m_tooltip.Create(NULL,NULL,NULL,WS_POPUP,WS_EX_TRANSPARENT) );
m_toolinfo.cbSize = sizeof(m_toolinfo);
m_toolinfo.uFlags = TTF_TRACK | TTF_IDISHWND | TTF_ABSOLUTE | TTF_TRANSPARENT;
m_toolinfo.hwnd = *this;
m_toolinfo.uId = 0;
m_toolinfo.lpszText = LPSTR_TEXTCALLBACK;
m_toolinfo.hinst = GetThisModuleHandle();
WIN32_OP_D( m_tooltip.AddTool(&m_toolinfo) );
}

View File

@@ -0,0 +1,51 @@
#pragma once
class CListControlTruncationTooltipImpl : public CListControlHeaderImpl {
private:
typedef CListControlHeaderImpl TParent;
public:
CListControlTruncationTooltipImpl();
BEGIN_MSG_MAP_EX(CListControlTruncationTooltipImpl)
MESSAGE_HANDLER(WM_MOUSEHOVER,OnHover);
MESSAGE_HANDLER(WM_MOUSEMOVE,OnMouseMovePassThru);
MESSAGE_HANDLER(WM_TIMER,OnTimer);
MESSAGE_HANDLER(WM_DESTROY,OnDestroyPassThru);
CHAIN_MSG_MAP(TParent)
NOTIFY_CODE_HANDLER(TTN_GETDISPINFO,OnTTGetDispInfo);
NOTIFY_CODE_HANDLER(TTN_POP,OnTTPop);
NOTIFY_CODE_HANDLER(TTN_SHOW,OnTTShow);
END_MSG_MAP()
void OnViewOriginChange(CPoint p_delta) {TParent::OnViewOriginChange(p_delta);TooltipRemove();}
void TooltipRemove();
protected:
virtual bool GetTooltipData( CPoint ptAbs, pfc::string_base & text, CRect & rc, CFontHandle & font) const;
private:
enum {
KTooltipTimer = 0x51dbee9e,
KTooltipTimerDelay = 50,
};
LRESULT OnHover(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnMouseMovePassThru(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnTimer(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnTTGetDispInfo(int,LPNMHDR,BOOL&);
LRESULT OnTTShow(int,LPNMHDR,BOOL&);
LRESULT OnTTPop(int,LPNMHDR,BOOL&);
LRESULT OnDestroyPassThru(UINT,WPARAM,LPARAM,BOOL&);
void InitTooltip();
void TooltipActivateAbs(const char * label, const CRect & rect);
void TooltipActivate(const char * label, const CRect & rect);
void TooltipRemoveCheck(LPARAM pos);
void TooltipRemoveCheck();
void TooltipUpdateFont();
void OnSetFont(bool) {TooltipUpdateFont();}
bool IsRectFullyVisibleAbs(CRect const & r);
bool IsRectPartiallyObscuredAbs(CRect const & r) const;
CRect m_tooltipRect;
CToolTipCtrl m_tooltip;
TOOLINFO m_toolinfo;
pfc::stringcvt::string_os_from_utf8 m_tooltipText;
CFontHandle m_tooltipFont;
};

View File

@@ -0,0 +1,9 @@
#pragma once
class CListControlUserOptions {
public:
CListControlUserOptions() { instance = this; }
virtual bool useSmoothScroll() = 0;
static CListControlUserOptions * instance;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,265 @@
#pragma once
#include "CListControl.h"
//! Implementation of focus/selection handling. Leaves maintaining focus/selection info to the derived class. \n
//! Most classes should derive from CListControlWithSelectionImpl instead.
class CListControlWithSelectionBase : public CListControl {
public:
typedef CListControl TParent;
CListControlWithSelectionBase() : m_selectDragMode(), m_prepareDragDropMode(), m_prepareDragDropModeRightClick(), m_ownDDActive(), m_noEnsureVisible(), m_drawThemeText(), m_typeFindTS() {}
BEGIN_MSG_MAP_EX(CListControlWithSelectionBase)
MSG_WM_CREATE(OnCreatePassThru);
MSG_WM_DESTROY(OnDestroyPassThru);
CHAIN_MSG_MAP(TParent)
MESSAGE_HANDLER(WM_LBUTTONDBLCLK,OnLButtonDblClk)
MESSAGE_HANDLER(WM_LBUTTONDOWN,OnButtonDown)
MESSAGE_HANDLER(WM_RBUTTONDOWN,OnButtonDown)
MESSAGE_HANDLER(WM_RBUTTONDBLCLK,OnButtonDown)
MESSAGE_HANDLER(WM_RBUTTONUP,OnRButtonUp)
MESSAGE_HANDLER(WM_MOUSEMOVE,OnMouseMove)
MESSAGE_HANDLER(WM_LBUTTONUP,OnLButtonUp)
MESSAGE_HANDLER(WM_KEYDOWN,OnKeyDown);
MESSAGE_HANDLER(WM_SYSKEYDOWN,OnKeyDown);
MESSAGE_HANDLER(WM_SETFOCUS,OnFocus);
MESSAGE_HANDLER(WM_KILLFOCUS,OnFocus);
MESSAGE_HANDLER(WM_TIMER,OnTimer);
MESSAGE_HANDLER(WM_CAPTURECHANGED,OnCaptureChanged);
MESSAGE_HANDLER(WM_GETDLGCODE,OnGetDlgCode);
MSG_WM_CHAR(OnChar)
END_MSG_MAP()
virtual void SetFocusItem(t_size index) = 0;
virtual t_size GetFocusItem() const = 0;
virtual void SetGroupFocus(int group) = 0;
virtual void SetGroupFocusByItem(t_size item) = 0;
virtual int GetGroupFocus() const = 0;
virtual bool IsItemSelected(t_size index) const = 0;
virtual void SetSelection(pfc::bit_array const & affected,pfc::bit_array const & status) = 0;
void SelectSingle(size_t which);
virtual bool SelectAll();
void SelectNone();
virtual void RequestMoveSelection(int delta);
bool MoveSelectionProbe(int delta);
virtual void RequestReorder( size_t const * order, size_t count ) = 0;
virtual void RequestRemoveSelection() = 0;
virtual void ExecuteDefaultAction(t_size index) = 0;
virtual void ExecuteDefaultActionGroup(t_size base, t_size count) {}
virtual bool ExecuteCanvasDefaultAction(CPoint pt) { return false; }
virtual t_size GetSelectionStart() const = 0;
virtual void SetSelectionStart(t_size val) = 0;
//! Full hook for drag-drop loop
virtual void RunDragDrop(const CPoint & p_origin,bool p_isRightClick);
//! Should RunDragDrop() be called at all?
virtual bool IsDragDropSupported() {return QueryDragDropTypes() != 0;}
//! Notification, mandatory to call by SetFocusItem() implementation. \n
//! If overridden by subclass, must call parent.
virtual void OnFocusChanged(size_t newFocus) {}
virtual void OnFocusChangedGroup(int inGroup) {}
//! Notification, mandatory to call by SetSelection() implementation. \n
//! If overridden by subclass, must call parent.
virtual void OnSelectionChanged(pfc::bit_array const & affected, pfc::bit_array const & status) {}
enum {
dragDrop_reorder = 1 << 0,
dragDrop_external = 1 << 1,
};
virtual uint32_t QueryDragDropTypes() const { return 0; }
virtual DWORD DragDropAccept(IDataObject * obj, bool & showDropMark) { return DROPEFFECT_NONE; }
virtual pfc::com_ptr_t<IDataObject> MakeDataObject();
virtual void OnDrop(IDataObject * obj, CPoint pt ) {}
virtual DWORD DragDropSourceEffects() { return DROPEFFECT_MOVE | DROPEFFECT_COPY;}
virtual void DragDropSourceSucceeded( DWORD effect ) {}
bool GroupFocusActive() const {return GetGroupFocus() >= 0;}
void RenderOverlay(const CRect & p_updaterect,CDCHandle p_dc);
bool IsItemFocused(t_size index) const {return GetFocusItem() == index;}
bool IsGroupHeaderFocused(int p_id) const {return GetGroupFocus() == p_id;}
void ToggleSelection(pfc::bit_array const & mask);
size_t GetSelectedCount(pfc::bit_array const & mask,size_t max = SIZE_MAX) const;
size_t GetSelectedCount() const {return GetSelectedCount(pfc::bit_array_true());}
size_t GetSingleSel() const;
size_t GetFirstSelected() const;
size_t GetLastSelected() const;
//! Execute default action per focus or selection depending on what's focused/selected
virtual void ExecuteDefaultActionByFocus();
void FocusToUpdateRgn(HRGN rgn);
//! Self-contained minimal drag and drop implementation for reordering list items only; dummy IDataObject presented. \n
//! Call from your override of RunDragDrop(), if p_isRightClick is false / left button clicked, never with right button clicked. \n
//! On success, use MakeDropReorderPermutation() to fetch the permutation to apply to your content.
bool RunReorderDragDrop(CPoint ptOrigin, CPoint & ptDrop);
bool MakeDropReorderPermutation(pfc::array_t<t_size> & out, CPoint ptDrop) const;
size_t GetPasteTarget( const CPoint * ptPaste = nullptr ) const;
CPoint GetContextMenuPoint(LPARAM lp);
CPoint GetContextMenuPoint(CPoint ptGot);
protected:
void ToggleDDScroll(bool p_state);
void AbortSelectDragMode() {AbortSelectDragMode(false);}
void RenderDropMarkerByOffset(int offset,CDCHandle p_dc);
void RenderDropMarker(CDCHandle dc, t_size item, bool bInside);
bool RenderDropMarkerClipped(CDCHandle dc, const CRect & update, t_size item, bool bInside);
CRect DropMarkerRect(int offset) const;
int DropMarkerOffset(t_size marker) const;
void AddDropMarkToUpdateRgn(HRGN p_rgn, t_size p_index, bool bInside = false) const;
CRect DropMarkerUpdateRect(t_size index,bool bInside) const;
bool GetFocusRect(CRect & p_rect);
bool GetFocusRectAbs(CRect & p_rect);
bool IsOwnDDActive() const {return m_ownDDActive;}
SIZE DropMarkerMargin() const;
void MakeDropMarkerPen(CPen & out) const;
virtual void EnsureVisibleRectAbs(const CRect & p_rect);
virtual size_t EvalTypeFind();
virtual bool AllowRangeSelect() const { return true; }
CRect GetWholeSelectionRectAbs() const;
size_t GetDropMark( ) const { return m_dropMark; }
bool IsDropMarkInside( ) const { return m_dropMarkInside; }
void SetDropMark( size_t idx, bool bInside );
void ClearDropMark() { SetDropMark(pfc_infinite, false); }
private:
int OnCreatePassThru(LPCREATESTRUCT lpCreateStruct);
void OnDestroyPassThru();
struct TDDScrollControl {
bool m_timerActive = false;
enum {KTimerID = 0x35bb25af,KTimerPeriod = 25};
};
enum {
KSelectionTimerID = 0xad8abd04,
KSelectionTimerPeriod = 50,
};
LRESULT OnFocus(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnKeyDown(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnLButtonDblClk(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnButtonDown(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnRButtonUp(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnMouseMove(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnLButtonUp(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnTimer(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnCaptureChanged(UINT,WPARAM,LPARAM,BOOL&);
LRESULT OnGetDlgCode(UINT,WPARAM,LPARAM,BOOL&);
void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
void RunTypeFind();
void OnKeyDown_HomeEndHelper(bool isEnd, int p_keys);
void OnKeyDown_SetIndexHelper(int p_index, int p_keys);
void OnKeyDown_SetIndexDeltaHelper(int p_delta, int p_keys);
void OnKeyDown_SetIndexDeltaLineHelper(int p_delta, int p_keys);
void OnKeyDown_SetIndexDeltaPageHelper(int p_delta, int p_keys);
void SelectGroupHelper(int p_group,int p_keys);
void HandleDragSel(const CPoint & p_pt);
void AbortSelectDragMode(bool p_lostCapture);
void InitSelectDragMode(const CPoint & p_pt,bool p_rightClick = false);
void ToggleRangeSelection(pfc::bit_array const & mask);
void ToggleGroupSelection(int p_group);
void HandleDDScroll();
void PrepareDragDrop(const CPoint & p_point,bool p_isRightClick);
void AbortPrepareDragDropMode(bool p_lostCapture = false);
bool TypeFindCheck(DWORD ts = GetTickCount()) const;
protected:
// Spacebar handler
void ToggleSelectedItems();
void RenderItem(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc);
void RenderGroupHeader(int p_group,const CRect & p_headerRect,const CRect & p_updateRect,CDCHandle p_dc);
void RenderSubItemText(t_size item, t_size subItem,const CRect & subItemRect,const CRect & updateRect,CDCHandle dc, bool allowColors);
private:
bool m_selectDragMode;
CPoint m_selectDragOriginAbs, m_selectDragCurrentAbs;
bool m_selectDragChanged, m_selectDragMoved;
TDDScrollControl m_ddScroll;
bool m_prepareDragDropMode, m_prepareDragDropModeRightClick;
bool m_noEnsureVisible;
CPoint m_prepareDragDropOrigin;
bool m_ownDDActive;
bool m_drawThemeText;
pfc::string8 m_typeFind; DWORD m_typeFindTS;
size_t m_dropMark = SIZE_MAX; bool m_dropMarkInside = false;
};
//! CListControlWithSelectionImpl implements virtual methods of CListControlWithSelectionBase,
//! maintaining focus/selection info for you.
class CListControlWithSelectionImpl : public CListControlWithSelectionBase {
public:
enum { selectionSupportNone = 0, selectionSupportSingle, selectionSupportMulti };
unsigned m_selectionSupport = selectionSupportMulti;
void SetSelectionModeNone() { m_selectionSupport = selectionSupportNone; }
void SetSelectionModeSingle() { m_selectionSupport = selectionSupportSingle; }
void SetSelectionModeMulti() { m_selectionSupport = selectionSupportMulti; }
CListControlWithSelectionImpl() {}
void SetFocusItem(t_size index);
t_size GetFocusItem() const {return m_groupFocus ? SIZE_MAX : m_focus;}
void SetGroupFocus(int group);
void SetGroupFocusByItem(t_size item);
int GetGroupFocus() const;
bool IsItemSelected(t_size index) const {return index < m_selection.get_size() ? m_selection[index] : false;}
void SetSelection(pfc::bit_array const & affected,pfc::bit_array const & status);
virtual bool CanSelectItem( size_t index ) const { return true; }
t_size GetSelectionStart() const {return m_selectionStart;}
void SetSelectionStart(t_size val) {m_selectionStart = val;}
void SelHandleReorder(const t_size * order, t_size count);
void SelHandleRemoval(const pfc::bit_array & mask, t_size oldCount);
void SelHandleInsertion(t_size base, t_size count, bool select);
void SelHandleReset();
void ReloadData() override;
size_t _DebugGetItemCountSel() const { return m_selection.get_size(); }
virtual void OnItemsReordered( const size_t* order, size_t count ) override;
virtual void OnItemsRemoved( pfc::bit_array const & mask, size_t oldCount ) override;
virtual void OnItemsInserted( size_t at, size_t count, bool bSelect ) override;
pfc::bit_array_bittable GetSelectionMask(); // returns an standalone object holding a copy of the state
bool SelectAll() override;
protected:
const bool * GetSelectionArray() {RefreshSelectionSize();return m_selection.get_ptr();}
pfc::bit_array_table GetSelectionMaskRef(); // returns a *temporary* object referencing internal data structures
bool AllowRangeSelect() const override { return m_selectionSupport == selectionSupportMulti; }
private:
void SetSelectionImpl(pfc::bit_array const & affected,pfc::bit_array const & status);
void RefreshSelectionSize();
void RefreshSelectionSize(t_size size);
pfc::array_t<bool> m_selection;
size_t m_focus = SIZE_MAX, m_selectionStart = SIZE_MAX;
bool m_groupFocus = false;
};

View File

@@ -0,0 +1,62 @@
#pragma once
#include "InPlaceEditTable.h"
#include "GDIUtils.h" // MakeTempBrush()
//! Implements inplace-edit functionality on top of TParent class. Must derive from CListControlHeaderImpl.
template<typename T_CListControl_EditImpl_Parent> class CListControl_EditImpl : public T_CListControl_EditImpl_Parent, protected InPlaceEdit::CTableEditHelperV2 {
public:
BEGIN_MSG_MAP_EX(CListControl_EditImpl)
CHAIN_MSG_MAP(T_CListControl_EditImpl_Parent)
MESSAGE_HANDLER(WM_CTLCOLOREDIT,OnCtlColor);
MESSAGE_HANDLER(WM_CTLCOLORSTATIC,OnCtlColor);
END_MSG_MAP()
// IMPLEMENT ME
// virtual void TableEdit_SetField(t_size item, t_size subItem, const char * value) = 0;
protected:
RECT TableEdit_GetItemRect(t_size item, t_size subItem) const override {
return this->GetSubItemRect(item,subItem);
}
void TableEdit_GetField(t_size item, t_size subItem, pfc::string_base & out, t_size & lineCount) override {
lineCount = 1;
if (!this->GetSubItemText(item,subItem,out)) {
PFC_ASSERT(!"Should not get here."); out = "";
}
}
HWND TableEdit_GetParentWnd() const override {return this->m_hWnd;}
t_size TableEdit_GetItemCount() const override {return this->GetItemCount();}
t_size TableEdit_GetColumnCount() const override {return this->GetColumnCount();}
void TableEdit_SetItemFocus(t_size item, t_size subItem) override {
this->TooltipRemove();
this->SetFocusItem(item); this->SetSelection(pfc::bit_array_true(), pfc::bit_array_one(item));
auto rcView = this->GetVisibleRectAbs();
auto rcEdit = this->GetSubItemRectAbs(item,subItem);
CRect rcTest;
if (!rcTest.IntersectRect(rcView, rcEdit)) {
// Only scroll to subitem if entirely invisible
this->EnsureVisibleRectAbs( rcEdit );
}
}
void TableEdit_GetColumnOrder(t_size * out, t_size count) const override {
PFC_ASSERT( count == this->GetColumnCount() );
if ( this->IsHeaderEnabled() ) {
pfc::array_t<int> temp; temp.set_size(count);
WIN32_OP_D( this->GetHeaderCtrl().GetOrderArray(temp.get_size(), temp.get_ptr()) );
for(t_size walk = 0; walk < count; ++walk) out[walk] = (t_size) temp[walk];
} else {
for(t_size walk = 0; walk < count; ++walk) out[walk] = walk;
}
}
void TableEdit_OnColorsChanged() {}
private:
LRESULT OnCtlColor(UINT,WPARAM wp,LPARAM lp,BOOL&) {
CDCHandle dc((HDC)wp);
const COLORREF bkgnd = this->GetSysColorHook(CListControlImpl::colorBackground);
dc.SetTextColor(this->GetSysColorHook(CListControlImpl::colorText));
dc.SetBkColor(bkgnd);
return (LRESULT) MakeTempBrush(dc, bkgnd);
}
};

34
libPPUI/CListViewCtrlEx.h Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
class CListViewCtrlEx : public CListViewCtrl {
public:
CListViewCtrlEx( HWND wnd = NULL ) : CListViewCtrl(wnd) {}
CListViewCtrlEx const & operator=( HWND wnd ) { m_hWnd = wnd; return *this; }
unsigned InsertColumnEx(unsigned index, const wchar_t * name, unsigned widthDLU);
unsigned AddColumnEx( const wchar_t * name, unsigned widthDLU );
void FixContextMenuPoint( CPoint & pt );
unsigned GetColunnCount();
unsigned InsertString( unsigned index, const wchar_t * str );
unsigned InsertString8( unsigned index, const char * str );
unsigned AddString( const wchar_t * str );
unsigned AddString8(const char * str);
void SetItemText(unsigned item, unsigned subItem, const wchar_t * str );
void SetItemText8(unsigned item, unsigned subItem, const char * str );
void AutoSizeColumn( int iCol ) { SetColumnWidth(iCol, LVSCW_AUTOSIZE) ;}
int AddGroup(int iGroupID, const wchar_t * header);
};
// BOOL HandleLVKeyDownMod()
#define LVN_KEYDOWN_MOD_HANDLER(id, key, mod, func) \
if (uMsg == WM_NOTIFY && LVN_KEYDOWN == ((LPNMHDR)lParam)->code && id == ((LPNMHDR)lParam)->idFrom && ((LPNMLVKEYDOWN)lParam)->wVKey == (key) && GetHotkeyModifierFlags() == (mod)) \
{ \
SetMsgHandled(TRUE); \
lResult = func()?1:0; \
if(IsMsgHandled()) \
return TRUE; \
}
// BOOL HandleLVCopy()
#define LVN_COPY_HANDLER(id, func) LVN_KEYDOWN_MOD_HANDLER(id, 'C', MOD_CONTROL, func)

View File

@@ -0,0 +1,40 @@
#include "stdafx.h"
#include "CMiddleDragImpl.h"
double CMiddleDragCommon::myPow(double p_val, double p_exp) {
if (p_val < 0) {
return -pow(-p_val, p_exp);
} else {
return pow(p_val, p_exp);
}
}
double CMiddleDragCommon::ProcessMiddleDragDeltaInternal(double p_delta) {
p_delta *= (double)KTimerPeriod / 25; /*originally calculated for 25ms timer interval*/
return myPow(p_delta * 0.05, 2.0);
}
double CMiddleDragCommon::radiusHelper(double p_x, double p_y) {
return sqrt(p_x * p_x + p_y * p_y);
}
int CMiddleDragCommon::mySGN(LONG v) {
if (v > 0) return 1;
else if (v < 0) return -1;
else return 0;
}
int32_t CMiddleDragCommon::Round(double val, double & acc) {
val += acc;
int32_t ret = (int32_t)floor(val + 0.5);
acc = val - ret;
return ret;
}
LONG CMiddleDragCommon::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;
}

280
libPPUI/CMiddleDragImpl.h Normal file
View File

@@ -0,0 +1,280 @@
#pragma once
#include "CIconOverlayWindow.h"
#include <utility>
#include "ppresources.h"
#include "win32_utility.h"
class CMiddleDragCommon {
public:
enum {
KTimerID = 0x389675f8,
KTimerPeriod = 15,
};
static double myPow(double p_val, double p_exp);
static double ProcessMiddleDragDeltaInternal(double p_delta);
static double radiusHelper(double p_x, double p_y);
static int mySGN(LONG v);
static int32_t Round(double val, double & acc);
static LONG LineToPixelsHelper(LONG & p_overflow, LONG p_pixels, LONG p_dpi, LONG p_lineWidth);
};
template<typename TBase>
class CMiddleDragImpl : public TBase, protected CMiddleDragCommon {
private:
typedef CMiddleDragImpl<TBase> TSelf;
public:
template<typename ... arg_t> CMiddleDragImpl( arg_t && ... arg ) : TBase(std::forward<arg_t>(arg) ... ) {}
BEGIN_MSG_MAP_EX(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 = false, m_dragged = false;
CPoint m_base;
CIconOverlayWindow m_overlay;
double m_accX = 0, m_accY = 0;
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();
this->SetFocus();
CPoint pt(p_lp);
WIN32_OP_D( this->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( this->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( this->m_hWnd != NULL );
if (m_overlay.Create(*this) == NULL) {PFC_ASSERT(!"Should not get here!"); return;}
HANDLE temp;
WIN32_OP_D( (temp = LoadImage(GetThisModuleHandle(),MAKEINTRESOURCE(CPPUIResources::get_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;
this->SetCapture();
this->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;
this->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(GetCursorPos()) - m_base));
return 0;
default:
bHandled = FALSE;
return 0;
}
}
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((deltaTotal*deltaTotal) / (1.0 + (ratio*ratio)));
xVal = yVal * ratio;
}
xVal = mySGN(p_delta.x) * fabs(xVal);
yVal = mySGN(p_delta.y) * fabs(yVal);
return CPoint(Round(xVal / dpiMulX, m_accX), Round(yVal / dpiMulY, m_accY));
}
};
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_EX(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)(p_delta<0?-p_delta: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;
}
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 = CMiddleDragCommon::LineToPixelsHelper(p_overflow.x,p_pixels.x,dpi.cx,m_lineWidth.cx);
pt.y = CMiddleDragCommon::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> >(msgMapCast(this)) {}
private:
template<typename t> static CMessageMap * msgMapCast(t* arg) { return arg; }
};
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_EX(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,101 @@
#pragma once
#include "win32_op.h"
#include "wtl-pp.h"
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_EX(CDialogWithTooltip)
MSG_WM_DESTROY(OnDestroy)
END_MSG_MAP()
void ShowTip(UINT id, const TCHAR * label) {
m_tip.Show(label, this->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, this->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(); this->SetMsgHandled(FALSE); }
CPopupTooltipMessage m_tip;
};

91
libPPUI/CPowerRequest.cpp Normal file
View File

@@ -0,0 +1,91 @@
#include "stdafx.h"
#include "CPowerRequest.h"
#ifdef CPowerRequestAPI_Avail
// win32 API declaration duplicate - not always defined on some of the Windows versions we target
namespace winapi_substitute {
typedef struct _REASON_CONTEXT {
ULONG Version;
DWORD Flags;
union {
struct {
HMODULE LocalizedReasonModule;
ULONG LocalizedReasonId;
ULONG ReasonStringCount;
LPWSTR *ReasonStrings;
} Detailed;
LPWSTR SimpleReasonString;
} Reason;
} REASON_CONTEXT, *PREASON_CONTEXT;
//
// Power Request APIs
//
typedef REASON_CONTEXT POWER_REQUEST_CONTEXT, *PPOWER_REQUEST_CONTEXT, *LPPOWER_REQUEST_CONTEXT;
}
HANDLE CPowerRequestAPI::PowerCreateRequestNamed( const wchar_t * str ) {
winapi_substitute::REASON_CONTEXT ctx = {POWER_REQUEST_CONTEXT_VERSION, POWER_REQUEST_CONTEXT_SIMPLE_STRING};
ctx.Reason.SimpleReasonString = const_cast<wchar_t*>(str);
return this->PowerCreateRequest(&ctx);
}
CPowerRequest::CPowerRequest(const wchar_t * Reason) : m_Request(INVALID_HANDLE_VALUE), m_bSystem(), m_bDisplay() {
if (m_API.IsValid()) {
winapi_substitute::REASON_CONTEXT ctx = {POWER_REQUEST_CONTEXT_VERSION, POWER_REQUEST_CONTEXT_SIMPLE_STRING};
ctx.Reason.SimpleReasonString = const_cast<wchar_t*>(Reason);
m_Request = m_API.PowerCreateRequest(&ctx);
}
}
void CPowerRequest::SetSystem(bool bSystem) {
if (bSystem == m_bSystem) return;
m_bSystem = bSystem;
if (m_Request != INVALID_HANDLE_VALUE) {
m_API.ToggleSystem( m_Request, bSystem );
} else {
_UpdateTES();
}
}
void CPowerRequest::SetExecution(bool bExecution) {
if (bExecution == m_bSystem) return;
m_bSystem = bExecution;
if (m_Request != INVALID_HANDLE_VALUE) {
m_API.ToggleExecution( m_Request, bExecution );
} else {
_UpdateTES();
}
}
void CPowerRequest::SetDisplay(bool bDisplay) {
if (bDisplay == m_bDisplay) return;
m_bDisplay = bDisplay;
if (m_Request != INVALID_HANDLE_VALUE) {
m_API.ToggleDisplay(m_Request, bDisplay);
} else {
_UpdateTES();
}
}
CPowerRequest::~CPowerRequest() {
if (m_Request != INVALID_HANDLE_VALUE) {
CloseHandle(m_Request);
} else {
if (m_bDisplay || m_bSystem) SetThreadExecutionState(ES_CONTINUOUS);
}
}
void CPowerRequest::_UpdateTES() {
SetThreadExecutionState(ES_CONTINUOUS | (m_bSystem ? ES_SYSTEM_REQUIRED : 0 ) | (m_bDisplay ? ES_DISPLAY_REQUIRED : 0) );
}
#endif // _WIN32

115
libPPUI/CPowerRequest.h Normal file
View File

@@ -0,0 +1,115 @@
#pragma once
#ifdef _WIN32
#ifdef WINAPI_FAMILY_PARTITION
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
#define CPowerRequestAPI_Avail
#endif
#else // no WINAPI_FAMILY_PARTITION, desktop SDK
#define CPowerRequestAPI_Avail
#endif
#endif // _WIN32
#ifdef CPowerRequestAPI_Avail
typedef HANDLE (WINAPI * pPowerCreateRequest_t) (
__in void* Context
);
typedef BOOL (WINAPI * pPowerSetRequest_t) (
__in HANDLE PowerRequest,
__in POWER_REQUEST_TYPE RequestType
);
typedef BOOL (WINAPI * pPowerClearRequest_t) (
__in HANDLE PowerRequest,
__in POWER_REQUEST_TYPE RequestType
);
class CPowerRequestAPI {
public:
CPowerRequestAPI() : PowerCreateRequest(), PowerSetRequest(), PowerClearRequest() {
Bind();
}
bool Bind() {
HMODULE kernel32 = GetModuleHandle(_T("kernel32.dll"));
return Bind(PowerCreateRequest, kernel32, "PowerCreateRequest")
&& Bind(PowerSetRequest, kernel32, "PowerSetRequest")
&& Bind(PowerClearRequest, kernel32, "PowerClearRequest") ;
}
bool IsValid() {return PowerCreateRequest != NULL;}
void ToggleSystem(HANDLE hRequest, bool bSystem) {
Toggle(hRequest, bSystem, PowerRequestSystemRequired);
}
void ToggleExecution(HANDLE hRequest, bool bSystem) {
const POWER_REQUEST_TYPE _PowerRequestExecutionRequired = (POWER_REQUEST_TYPE)3;
const POWER_REQUEST_TYPE RequestType = IsWin8() ? _PowerRequestExecutionRequired : PowerRequestSystemRequired;
Toggle(hRequest, bSystem, RequestType);
}
void ToggleDisplay(HANDLE hRequest, bool bDisplay) {
Toggle(hRequest, bDisplay, PowerRequestDisplayRequired);
}
void Toggle(HANDLE hRequest, bool bToggle, POWER_REQUEST_TYPE what) {
if (bToggle) {
PowerSetRequest(hRequest, what);
} else {
PowerClearRequest(hRequest, what);
}
}
HANDLE PowerCreateRequestNamed( const wchar_t * str );
static bool IsWin8() {
auto ver = myGetOSVersion();
return ver >= 0x602;
}
static WORD myGetOSVersion() {
const DWORD ver = GetVersion();
return (WORD)HIBYTE(LOWORD(ver)) | ((WORD)LOBYTE(LOWORD(ver)) << 8);
}
pPowerCreateRequest_t PowerCreateRequest;
pPowerSetRequest_t PowerSetRequest;
pPowerClearRequest_t PowerClearRequest;
private:
template<typename func_t> static bool Bind(func_t & f, HMODULE dll, const char * name) {
f = reinterpret_cast<func_t>(GetProcAddress(dll, name));
return f != NULL;
}
};
class CPowerRequest {
public:
CPowerRequest(const wchar_t * Reason);
void SetSystem(bool bSystem);
void SetExecution(bool bExecution);
void SetDisplay(bool bDisplay);
~CPowerRequest();
private:
void _UpdateTES();
HANDLE m_Request;
bool m_bSystem, m_bDisplay;
CPowerRequestAPI m_API;
CPowerRequest(const CPowerRequest&);
void operator=(const CPowerRequest&);
};
#else
class CPowerRequest {
public:
CPowerRequest(const wchar_t * Reason) {}
void SetSystem(bool bSystem) {}
void SetExecution(bool bExecution) {}
void SetDisplay(bool bDisplay) {}
private:
CPowerRequest(const CPowerRequest&);
void operator=(const CPowerRequest&);
};
#endif // CPowerRequestAPI_Avail

54
libPPUI/CPropVariant.h Normal file
View File

@@ -0,0 +1,54 @@
#pragma once
class CPropVariant : public PROPVARIANT {
public:
CPropVariant() {init();}
~CPropVariant() {clear();}
CPropVariant( const CPropVariant & other ) {
init();
PropVariantCopy( this, &other );
}
const CPropVariant& operator=( const CPropVariant & other ) {
clear();
PropVariantCopy(this, &other);
return *this;
}
bool toInt64(int64_t & out) const {
switch( vt ) {
case VT_I1: out = (int64_t) cVal; return true;
case VT_I2: out = (int64_t) iVal; return true;
case VT_I4: out = (int64_t) lVal; return true;
case VT_I8: out = (int64_t) hVal.QuadPart; return true;
case VT_INT: out = (int64_t) intVal; return true;
default: return false;
}
}
bool toUint64(uint64_t & out) const {
switch( vt ) {
case VT_UI1: out = (uint64_t) bVal; return true;
case VT_UI2: out = (uint64_t) uiVal; return true;
case VT_UI4: out = (uint64_t) ulVal; return true;
case VT_UI8: out = (uint64_t) uhVal.QuadPart; return true;
case VT_UINT: out = (uint64_t) uintVal; return true;
default: return false;
}
}
bool toString( pfc::string_base & out ) const {
switch( vt ) {
case VT_LPSTR:
out = pfc::stringcvt::string_utf8_from_ansi( pszVal ); return true;
case VT_LPWSTR:
out = pfc::stringcvt::string_utf8_from_wide( pwszVal ); return true;
default: return false;
}
}
private:
void clear() {
PropVariantClear( this );
}
void init() {
PROPVARIANT * pv = this;
PropVariantInit( pv );
}
};

View File

@@ -0,0 +1,11 @@
#pragma once
#include "win32_op.h"
template<typename TClass>
class CWindowCreateAndDelete : public TClass {
public:
template<typename ... arg_t> CWindowCreateAndDelete(HWND parent, arg_t && ... arg) : TClass(std::forward<arg_t>(arg) ...) { WIN32_OP(this->Create(parent) != NULL); }
private:
void OnFinalMessage(HWND wnd) { PFC_ASSERT_NO_EXCEPTION(TClass::OnFinalMessage(wnd)); PFC_ASSERT_NO_EXCEPTION(delete this); }
};

50
libPPUI/Controls.cpp Normal file
View File

@@ -0,0 +1,50 @@
#include "stdafx.h"
#include <vsstyle.h>
#include "Controls.h"
#include "PaintUtils.h"
void CStaticSeparator::OnPaint(CDCHandle) {
PaintUtils::PaintSeparatorControl(*this);
}
void CSeparator::OnPaint(CDCHandle dc) {
PaintUtils::PaintSeparatorControl(*this);
}
CStaticMainInstruction::CStaticMainInstruction() {
SetThemePart(TEXT_MAININSTRUCTION);
}
void CStaticThemed::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 = (int) 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));
}
}

101
libPPUI/Controls.h Normal file
View File

@@ -0,0 +1,101 @@
#pragma once
#pragma comment(lib, "uxtheme.lib")
#include "wtl-pp.h"
#include "win32_op.h"
// Separator-in-dialog tool: subclass a static control on init
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);
};
// CWindowRegistered with font & text functionality, for creating custom text label classes
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) this->Invalidate();
}
int OnSetText(LPCTSTR lpstrText) {
this->Invalidate();this->SetMsgHandled(FALSE); return 0;
}
CFontHandle m_font;
};
// Static control subclass with override for theme part used for rendering
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);
int m_id;
CTheme m_theme;
bool m_fallback;
};
class CStaticMainInstruction : public CStaticThemed {
public:
CStaticMainInstruction();
};
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);
};

261
libPPUI/GDIUtils.h Normal file
View File

@@ -0,0 +1,261 @@
#pragma once
#include "win32_op.h"
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) {
this->SubclassWindow(wnd); this->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;
};
static CSize GetBitmapSize( HBITMAP bmp ) {
CBitmapHandle h ( bmp );
BITMAP bm = {};
WIN32_OP_D( h.GetBitmap(bm) );
return CSize(bm.bmWidth, bm.bmHeight);
}

BIN
libPPUI/IDI_SCROLL.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

3
libPPUI/IDI_SCROLL.txt Normal file
View File

@@ -0,0 +1,3 @@
Icon needed by CMiddleDragImpl and things that use it - such as CListControl.
Include IDI_SCROLL.ico in your resources, with identifier set to IDI_SCROLL.
To get libPPUI to recognize your resource, include your resource.h before libPPUI headers in at least one of your source files.

View File

@@ -0,0 +1,187 @@
#include "stdafx.h"
#include "IDataObjectUtils.h"
HRESULT IDataObjectUtils::DataBlockToSTGMEDIUM(const void * blockPtr, t_size blockSize, STGMEDIUM * medium, DWORD tymed, bool bHere) throw() {
try {
if (bHere) {
switch(tymed) {
case TYMED_ISTREAM:
{
if (medium->pstm == NULL) return E_INVALIDARG;
ULONG written = 0;
HRESULT state;
state = medium->pstm->Write(blockPtr, pfc::downcast_guarded<ULONG>(blockSize),&written);
if (FAILED(state)) return state;
if (written != blockSize) return STG_E_MEDIUMFULL;
return S_OK;
}
default:
return DV_E_TYMED;
}
} else {
if (tymed & TYMED_HGLOBAL) {
HGLOBAL hMem = HGlobalFromMemblock(blockPtr, blockSize);
if (hMem == NULL) return E_OUTOFMEMORY;
medium->tymed = TYMED_HGLOBAL;
medium->hGlobal = hMem;
medium->pUnkForRelease = NULL;
return S_OK;
}
if (tymed & TYMED_ISTREAM) {
HRESULT state;
HGLOBAL hMem = HGlobalFromMemblock(blockPtr, blockSize);
if (hMem == NULL) return E_OUTOFMEMORY;
medium->tymed = TYMED_ISTREAM;
pfc::com_ptr_t<IStream> stream;
if (FAILED( state = CreateStreamOnHGlobal(hMem,TRUE,stream.receive_ptr()) ) ) {
GlobalFree(hMem);
return state;
}
{
LARGE_INTEGER wtf = {};
if (FAILED( state = stream->Seek(wtf,STREAM_SEEK_END,NULL) ) ) {
return state;
}
}
medium->pstm = stream.detach();
medium->pUnkForRelease = NULL;
return S_OK;
}
return DV_E_TYMED;
}
} catch(pfc::exception_not_implemented) {
return E_NOTIMPL;
} catch(std::bad_alloc) {
return E_OUTOFMEMORY;
} catch(...) {
return E_UNEXPECTED;
}
}
HGLOBAL IDataObjectUtils::HGlobalFromMemblock(const void * ptr,t_size size) {
HGLOBAL handle = GlobalAlloc(GMEM_MOVEABLE,size);
if (handle != NULL) {
void * destptr = GlobalLock(handle);
if (destptr == NULL) {
GlobalFree(handle);
handle = NULL;
} else {
memcpy(destptr,ptr,size);
GlobalUnlock(handle);
}
}
return handle;
}
HRESULT IDataObjectUtils::ExtractDataObjectContent(pfc::com_ptr_t<IDataObject> obj, UINT format, DWORD aspect, LONG index, pfc::array_t<t_uint8> & out) {
FORMATETC fmt = {};
fmt.cfFormat = format; fmt.dwAspect = aspect; fmt.lindex = index;
fmt.tymed = TYMED_HGLOBAL /* | TYMED_ISTREAM*/;
STGMEDIUM med = {};
HRESULT state;
if (FAILED( state = obj->GetData(&fmt,&med) ) ) return state;
ReleaseStgMediumScope relScope(&med);
return STGMEDIUMToDataBlock(med, out);
}
HRESULT IDataObjectUtils::STGMEDIUMToDataBlock(const STGMEDIUM & med, pfc::array_t<t_uint8> & out) {
switch(med.tymed) {
case TYMED_HGLOBAL:
{
CGlobalLockScope lock(med.hGlobal);
out.set_data_fromptr( (const t_uint8*) lock.GetPtr(), lock.GetSize() );
}
return S_OK;
case TYMED_ISTREAM:
{
HRESULT state;
IStream * stream = med.pstm;
LARGE_INTEGER offset = {};
STATSTG stats = {};
if (FAILED( state = stream->Stat(&stats,STATFLAG_NONAME ) ) ) return state;
t_size toRead = pfc::downcast_guarded<t_size>(stats.cbSize.QuadPart);
out.set_size(toRead);
if (FAILED( state = stream->Seek(offset,STREAM_SEEK_SET,NULL) ) ) return state;
ULONG cbRead = 0;
if (FAILED( state = stream->Read(out.get_ptr(), pfc::downcast_guarded<ULONG>(toRead), &cbRead) ) ) return state;
if (cbRead != toRead) return E_UNEXPECTED;
}
return S_OK;
default:
return DV_E_TYMED;
}
}
HRESULT IDataObjectUtils::ExtractDataObjectContent(pfc::com_ptr_t<IDataObject> obj, UINT format, pfc::array_t<t_uint8> & out) {
return ExtractDataObjectContent(obj, format, DVASPECT_CONTENT, -1, out);
}
HRESULT IDataObjectUtils::ExtractDataObjectContentTest(pfc::com_ptr_t<IDataObject> obj, UINT format, DWORD aspect, LONG index) {
FORMATETC fmt = {};
fmt.cfFormat = format; fmt.dwAspect = aspect; fmt.lindex = index;
for(t_uint32 walk = 0; walk < 32; ++walk) {
const DWORD tymed = 1 << walk;
if ((ExtractDataObjectContent_SupportedTymeds & tymed) != 0) {
fmt.tymed = tymed;
HRESULT state = obj->QueryGetData(&fmt);
if (SUCCEEDED(state)) {
if (state == S_OK) return S_OK;
} else {
if (state != DV_E_TYMED) return state;
}
}
}
return E_FAIL;
}
HRESULT IDataObjectUtils::ExtractDataObjectContentTest(pfc::com_ptr_t<IDataObject> obj, UINT format) {
return ExtractDataObjectContentTest(obj,format,DVASPECT_CONTENT,-1);
}
HRESULT IDataObjectUtils::ExtractDataObjectString(pfc::com_ptr_t<IDataObject> obj, pfc::string_base & out) {
pfc::array_t<t_uint8> data;
HRESULT state;
state = ExtractDataObjectContent(obj,CF_UNICODETEXT,data);
if (SUCCEEDED(state)) {
out = pfc::stringcvt::string_utf8_from_os_ex( (const wchar_t*) data.get_ptr(), data.get_size() / sizeof(wchar_t) );
return S_OK;
}
state = ExtractDataObjectContent(obj,CF_TEXT,data);
if (SUCCEEDED(state)) {
out = pfc::stringcvt::string_utf8_from_os_ex( (const char*) data.get_ptr(), data.get_size() / sizeof(char) );
return S_OK;
}
return E_FAIL;
}
HRESULT IDataObjectUtils::SetDataObjectContent(pfc::com_ptr_t<IDataObject> obj, UINT format, DWORD aspect, LONG index, const void * data, t_size dataSize) {
STGMEDIUM med = {};
FORMATETC fmt = {};
fmt.cfFormat = format; fmt.dwAspect = aspect; fmt.lindex = index; fmt.tymed = TYMED_HGLOBAL;
HRESULT state;
if (FAILED(state = DataBlockToSTGMEDIUM(data,dataSize,&med,TYMED_HGLOBAL,false))) return state;
return obj->SetData(&fmt,&med,TRUE);
}
HRESULT IDataObjectUtils::SetDataObjectString(pfc::com_ptr_t<IDataObject> obj, const char * str) {
pfc::stringcvt::string_wide_from_utf8 s(str);
return SetDataObjectContent(obj,CF_UNICODETEXT,DVASPECT_CONTENT,-1,s.get_ptr(), (s.length()+1) * sizeof(s[0]));
}
HRESULT IDataObjectUtils::ExtractDataObjectDWORD(pfc::com_ptr_t<IDataObject> obj, UINT format, DWORD & val) {
HRESULT state;
pfc::array_t<t_uint8> buffer;
if (FAILED( state = ExtractDataObjectContent(obj, format, DVASPECT_CONTENT, -1, buffer) ) ) return state;
if (buffer.get_size() < sizeof(val)) return E_UNEXPECTED;
val = *(DWORD*) buffer.get_ptr();
return S_OK;
}
HRESULT IDataObjectUtils::SetDataObjectDWORD(pfc::com_ptr_t<IDataObject> obj, UINT format, DWORD val) {
return SetDataObjectContent(obj,format,DVASPECT_CONTENT,-1,&val,sizeof(val));
}
HRESULT IDataObjectUtils::PasteSucceeded(pfc::com_ptr_t<IDataObject> obj, DWORD effect) {
return SetDataObjectDWORD(obj, RegisterClipboardFormat(CFSTR_PASTESUCCEEDED), effect);
}

228
libPPUI/IDataObjectUtils.h Normal file
View File

@@ -0,0 +1,228 @@
#pragma once
#include <shlobj.h>
#include <ShlDisp.h> // IAsyncOperation
#include "pp-COM-macros.h"
namespace IDataObjectUtils {
class ReleaseStgMediumScope {
public:
ReleaseStgMediumScope(STGMEDIUM * medium) : m_medium(medium) {}
~ReleaseStgMediumScope() {if (m_medium != NULL) ReleaseStgMedium(m_medium);}
private:
STGMEDIUM * m_medium;
PFC_CLASS_NOT_COPYABLE_EX(ReleaseStgMediumScope)
};
static const DWORD DataBlockToSTGMEDIUM_SupportedTymeds = TYMED_ISTREAM | TYMED_HGLOBAL;
static const DWORD ExtractDataObjectContent_SupportedTymeds = TYMED_ISTREAM | TYMED_HGLOBAL;
HRESULT DataBlockToSTGMEDIUM(const void * blockPtr, t_size blockSize, STGMEDIUM * medium, DWORD tymed, bool bHere) throw();
HGLOBAL HGlobalFromMemblock(const void * ptr,t_size size);
HRESULT ExtractDataObjectContent(pfc::com_ptr_t<IDataObject> obj, UINT format, DWORD aspect, LONG index, pfc::array_t<t_uint8> & out);
HRESULT ExtractDataObjectContent(pfc::com_ptr_t<IDataObject> obj, UINT format, pfc::array_t<t_uint8> & out);
HRESULT ExtractDataObjectContentTest(pfc::com_ptr_t<IDataObject> obj, UINT format, DWORD aspect, LONG index);
HRESULT ExtractDataObjectContentTest(pfc::com_ptr_t<IDataObject> obj, UINT format);
HRESULT ExtractDataObjectString(pfc::com_ptr_t<IDataObject> obj, pfc::string_base & out);
HRESULT SetDataObjectString(pfc::com_ptr_t<IDataObject> obj, const char * str);
HRESULT SetDataObjectContent(pfc::com_ptr_t<IDataObject> obj, UINT format, DWORD aspect, LONG index, const void * data, t_size dataSize);
HRESULT STGMEDIUMToDataBlock(const STGMEDIUM & med, pfc::array_t<t_uint8> & out);
HRESULT ExtractDataObjectDWORD(pfc::com_ptr_t<IDataObject> obj, UINT format, DWORD & val);
HRESULT SetDataObjectDWORD(pfc::com_ptr_t<IDataObject> obj, UINT format, DWORD val);
HRESULT PasteSucceeded(pfc::com_ptr_t<IDataObject> obj, DWORD effect);
class comparator_FORMATETC {
public:
static int compare(const FORMATETC & v1, const FORMATETC & v2) {
int val;
val = pfc::compare_t(v1.cfFormat,v2.cfFormat); if (val != 0) return val;
val = pfc::compare_t(v1.dwAspect,v2.dwAspect); if (val != 0) return val;
val = pfc::compare_t(v1.lindex, v2.lindex ); if (val != 0) return val;
return 0;
}
};
class CDataObjectBase : public IDataObject {
public:
COM_QI_SIMPLE(IDataObject)
HRESULT STDMETHODCALLTYPE GetData(FORMATETC * formatetc, STGMEDIUM * medium) override {
return GetData_internal(formatetc,medium,false);
}
HRESULT STDMETHODCALLTYPE GetDataHere(FORMATETC * formatetc, STGMEDIUM * medium) override {
return GetData_internal(formatetc,medium,true);
}
HRESULT STDMETHODCALLTYPE QueryGetData(FORMATETC * formatetc) override {
if (formatetc == NULL) return E_INVALIDARG;
if ((DataBlockToSTGMEDIUM_SupportedTymeds & formatetc->tymed) == 0) return DV_E_TYMED;
try {
return RenderDataTest(formatetc->cfFormat,formatetc->dwAspect,formatetc->lindex);
} PP_COM_CATCH;
}
HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc(FORMATETC * in, FORMATETC * out) override {
//check this again
if (in == NULL || out == NULL)
return E_INVALIDARG;
*out = *in;
return DATA_S_SAMEFORMATETC;
}
HRESULT STDMETHODCALLTYPE EnumFormatEtc(DWORD dwDirection,IEnumFORMATETC ** ppenumFormatetc) override {
if (dwDirection == DATADIR_GET) {
if (ppenumFormatetc == NULL) return E_INVALIDARG;
return CreateIEnumFORMATETC(ppenumFormatetc);
} else if (dwDirection == DATADIR_SET) {
return E_NOTIMPL;
} else {
return E_INVALIDARG;
}
}
HRESULT STDMETHODCALLTYPE SetData(FORMATETC * pFormatetc, STGMEDIUM * pmedium, BOOL fRelease) override {
try {
ReleaseStgMediumScope relScope(fRelease ? pmedium : NULL);
if (pFormatetc == NULL || pmedium == NULL) return E_INVALIDARG;
/*TCHAR buf[256];
if (GetClipboardFormatName(pFormatetc->cfFormat,buf,PFC_TABSIZE(buf)) > 0) {
buf[PFC_TABSIZE(buf)-1] = 0;
OutputDebugString(TEXT("SetData: ")); OutputDebugString(buf); OutputDebugString(TEXT("\n"));
} else {
OutputDebugString(TEXT("SetData: unknown clipboard format.\n"));
}*/
pfc::array_t<t_uint8> temp;
HRESULT state = STGMEDIUMToDataBlock(*pmedium,temp);
if (FAILED(state)) return state;
m_entries.set(*pFormatetc,temp);
return S_OK;
} PP_COM_CATCH;
}
HRESULT STDMETHODCALLTYPE DAdvise(FORMATETC * pFormatetc, DWORD advf, IAdviseSink * pAdvSink, DWORD * pdwConnection) override {return OLE_E_ADVISENOTSUPPORTED;}
HRESULT STDMETHODCALLTYPE DUnadvise(DWORD dwConnection) override {return OLE_E_ADVISENOTSUPPORTED;}
HRESULT STDMETHODCALLTYPE EnumDAdvise(IEnumSTATDATA ** ppenumAdvise) override {return OLE_E_ADVISENOTSUPPORTED;}
protected:
typedef pfc::array_t<uint8_t> data_t;
virtual HRESULT RenderData(UINT format,DWORD aspect,LONG dataIndex, data_t & out) const {
FORMATETC fmt = {};
fmt.cfFormat = format; fmt.dwAspect = aspect; fmt.lindex = dataIndex;
const pfc::array_t<t_uint8> * entry = m_entries.query_ptr(fmt);
if (entry != NULL) {
out = * entry;
return S_OK;
}
return DV_E_FORMATETC;
}
virtual HRESULT RenderDataTest(UINT format,DWORD aspect,LONG dataIndex) const {
FORMATETC fmt = {};
fmt.cfFormat = format; fmt.dwAspect = aspect; fmt.lindex = dataIndex;
if (m_entries.have_item(fmt)) return S_OK;
return DV_E_FORMATETC;
}
typedef pfc::list_base_t<FORMATETC> TFormatList;
static void AddFormat(TFormatList & out,UINT code) {
FORMATETC fmt = {};
fmt.dwAspect = DVASPECT_CONTENT;
fmt.lindex = -1;
fmt.cfFormat = code;
for(t_size medWalk = 0; medWalk < 32; ++medWalk) {
const DWORD med = 1 << medWalk;
if ((DataBlockToSTGMEDIUM_SupportedTymeds & med) != 0) {
fmt.tymed = med;
out.add_item(fmt);
}
}
}
virtual void EnumFormats(TFormatList & out) const {
pfc::avltree_t<UINT> formats;
for(auto walk = m_entries.cfirst(); walk.is_valid(); ++walk) {
formats.add_item( walk->m_key.cfFormat );
}
for(auto walk = formats.cfirst(); walk.is_valid(); ++walk) {
AddFormat(out, *walk);
}
}
HRESULT CreateIEnumFORMATETC(IEnumFORMATETC ** outptr) const throw() {
try {
pfc::list_t<FORMATETC> out;
EnumFormats(out);
return SHCreateStdEnumFmtEtc((UINT)out.get_count(), out.get_ptr(), outptr);
} PP_COM_CATCH;
}
private:
HRESULT GetData_internal(FORMATETC * formatetc, STGMEDIUM * medium,bool bHere) {
if (formatetc == NULL || medium == NULL) return E_INVALIDARG;
try {
data_t out;
HRESULT hr = RenderData(formatetc->cfFormat,formatetc->dwAspect,formatetc->lindex,out);
if (FAILED(hr)) return hr;
return DataBlockToSTGMEDIUM(out.get_ptr(),out.get_size(),medium,formatetc->tymed,bHere);
} PP_COM_CATCH;
}
typedef pfc::map_t<FORMATETC, pfc::array_t<t_uint8>, comparator_FORMATETC> t_entries;
t_entries m_entries;
};
#ifdef __IDataObjectAsyncCapability_INTERFACE_DEFINED__
typedef IDataObjectAsyncCapability IDataObjectAsyncCapability_t;
#else
typedef IAsyncOperation IDataObjectAsyncCapability_t;
#endif
class CAsyncDataObjectBase : public CDataObjectBase, public IDataObjectAsyncCapability_t {
BOOL m_inOperation = FALSE;
BOOL m_asyncMode = TRUE;
protected:
COM_QI_BEGIN()
COM_QI_CHAIN(CDataObjectBase)
COM_QI_ENTRY(IDataObjectAsyncCapability_t)
COM_QI_END()
public:
HRESULT STDMETHODCALLTYPE SetAsyncMode(BOOL fDoOpAsync) override {
m_asyncMode = fDoOpAsync;
return S_OK;
}
HRESULT STDMETHODCALLTYPE GetAsyncMode(BOOL *pfIsOpAsync) override {
if ( pfIsOpAsync == nullptr ) return E_INVALIDARG;
*pfIsOpAsync = m_asyncMode;
return S_OK;
}
HRESULT STDMETHODCALLTYPE StartOperation(IBindCtx *pbcReserved) override {
m_inOperation = TRUE;
return S_OK;
}
HRESULT STDMETHODCALLTYPE InOperation(BOOL *pfInAsyncOp) override {
if ( pfInAsyncOp == nullptr ) return E_INVALIDARG;
*pfInAsyncOp = m_inOperation;
return S_OK;
}
HRESULT STDMETHODCALLTYPE EndOperation(HRESULT hResult,IBindCtx *pbcReserved,DWORD dwEffects) override {
m_inOperation = FALSE;
return S_OK;
}
};
}

349
libPPUI/InPlaceCombo.cpp Normal file
View File

@@ -0,0 +1,349 @@
#include "stdafx.h"
#include "InPlaceEdit.h"
#include "wtl-pp.h"
#include "win32_op.h"
#include "AutoComplete.h"
#include "CWindowCreateAndDelete.h"
#include "win32_utility.h"
#include "listview_helper.h" // ListView_GetColumnCount
#include "clipboard.h"
#include <forward_list>
#ifndef WM_MOUSEHWHEEL
#define WM_MOUSEHWHEEL 0x20E
#endif
using namespace InPlaceEdit;
namespace {
enum {
MSG_COMPLETION = WM_USER,
MSG_DISABLE_EDITING
};
// Rationale: more than one HWND on the list is extremely uncommon, hence forward_list
static std::forward_list<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.begin(); walk != g_editboxes.end(); ++walk) {
GAbortEditing(*walk, code);
}
}
static bool IsSamePopup(CWindow wnd1, CWindow wnd2) {
return pfc::findOwningPopup(wnd1) == pfc::findOwningPopup(wnd2);
}
static void MouseEventTest(HWND target, CPoint pt, bool isWheel) {
for (auto walk = g_editboxes.begin(); walk != g_editboxes.end(); ++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);
}
static void on_editbox_creation(HWND p_editbox) {
// PFC_ASSERT(core_api::is_main_thread());
g_editboxes.push_front(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(p_editbox);
if (g_editboxes.empty()) {
UnhookHelper(g_hook); /*UnhookHelper(g_keyHook);*/
}
}
class CInPlaceComboBox : public CContainedWindowSimpleT<CComboBox> {
public:
BEGIN_MSG_MAP_EX(CInPlaceComboBox)
//MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
MSG_WM_GETDLGCODE(OnGetDlgCode)
// MSG_WM_KILLFOCUS(OnKillFocus)
MSG_WM_KEYDOWN(OnKeyDown)
END_MSG_MAP()
void OnCreation() {
on_editbox_creation(m_hWnd);
}
private:
void OnDestroy() {
m_selfDestruct = true;
on_editbox_destruction(m_hWnd);
SetMsgHandled(FALSE);
}
int OnCreate(LPCREATESTRUCT lpCreateStruct) {
OnCreation();
SetMsgHandled(FALSE);
return 0;
}
UINT OnGetDlgCode(LPMSG lpMsg) {
if (lpMsg == NULL) {
SetMsgHandled(FALSE); return 0;
} else {
switch (lpMsg->message) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
switch (lpMsg->wParam) {
case VK_TAB:
case VK_ESCAPE:
case VK_RETURN:
return DLGC_WANTMESSAGE;
default:
SetMsgHandled(FALSE); return 0;
}
default:
SetMsgHandled(FALSE); return 0;
}
}
}
void OnKillFocus(CWindow wndFocus) {
if ( wndFocus != NULL ) ForwardCompletion(KEditLostFocus);
SetMsgHandled(FALSE);
}
void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
m_suppressChar = nFlags & 0xFF;
switch (nChar) {
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);
}
}
bool m_selfDestruct = false;
UINT m_suppressChar = 0;
};
class InPlaceComboContainer : public CWindowImpl<InPlaceComboContainer> {
public:
DECLARE_WND_CLASS_EX(_T("{18D85006-0CDB-49AB-A563-6A42014309A3}"), 0, -1);
const pfc::string_list_const * m_initData;
const unsigned m_iDefault;
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
style |= CBS_DROPDOWNLIST;
CComboBox edit;
WIN32_OP(edit.Create(*this, rcClient, NULL, style, 0, ID_MYEDIT) != NULL);
edit.SetFont(parent.GetFont());
m_edit.SubclassWindow(edit);
m_edit.OnCreation();
#if 0 // doesn't quite work
if (m_flags & (KFlagAlignCenter | KFlagAlignRight)) {
COMBOBOXINFO info = {sizeof(info)};
if (m_edit.GetComboBoxInfo(&info)) {
CEdit edit2 = info.hwndList;
if (edit2) {
if (m_flags & KFlagAlignCenter) edit2.ModifyStyle(0, ES_CENTER);
else if (m_flags & KFlagAlignRight) edit2.ModifyStyle(0, ES_RIGHT);
}
}
}
#endif
if (m_initData != nullptr) {
const size_t count = m_initData->get_count();
for (size_t walk = 0; walk < count; ++walk) {
m_edit.AddString(pfc::stringcvt::string_os_from_utf8(m_initData->get_item(walk)));
}
if (m_iDefault < count) m_edit.SetCurSel(m_iDefault);
}
} catch (...) {
PostMessage(MSG_COMPLETION, InPlaceEdit::KEditAborted, 0);
return m_hWnd;
}
ShowWindow(SW_SHOW);
m_edit.SetFocus();
m_initialized = true;
m_edit.ShowDropDown();
PFC_ASSERT(m_hWnd != NULL);
return m_hWnd;
}
InPlaceComboContainer(const RECT & p_rect, unsigned p_flags, pfc::string_list_const * initData, unsigned iDefault, comboReply_t p_notify) : m_notify(p_notify), m_initData(initData), m_iDefault(iDefault), m_initRect(p_rect), m_flags(p_flags) { }
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_ID_HANDLER_EX(ID_MYEDIT, OnComboMsg)
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();
}
LRESULT OnMsgCompletion(UINT, WPARAM wParam, LPARAM lParam) {
PFC_ASSERT(m_initialized);
if ((wParam & KEditMaskReason) != KEditLostFocus) {
GetParent().SetFocus();
}
OnCompletion((unsigned) wParam );
if (!m_selfDestruct) {
m_selfDestruct = true;
DestroyWindow();
}
return 0;
}
void OnComboMsg(UINT code, int, CWindow source) {
if (m_initialized && (code == CBN_SELENDOK || code == CBN_SELENDCANCEL) ) {
PostMessage(MSG_COMPLETION, InPlaceEdit::KEditLostFocus, 0);
}
}
private:
void OnCompletion(unsigned p_status) {
if (!m_completed) {
m_completed = true;
if (m_notify) m_notify( p_status, m_edit.GetCurSel() );
}
}
const comboReply_t m_notify;
bool m_completed = false;
bool m_initialized = false;
bool m_selfDestruct = false;
const CRect m_initRect;
CInPlaceComboBox m_edit;
const unsigned m_flags;
};
}
static void fail(comboReply_t p_notify) {
p_notify(KEditAborted, UINT_MAX);
}
HWND InPlaceEdit::StartCombo(HWND p_parentwnd, const RECT & p_rect, unsigned p_flags, pfc::string_list_const & data, unsigned iDefault, comboReply_t p_notify) {
try {
PFC_ASSERT((CWindow(p_parentwnd).GetWindowLong(GWL_STYLE) & WS_CLIPCHILDREN) != 0);
return (new CWindowCreateAndDelete<InPlaceComboContainer>(p_parentwnd, p_rect, p_flags, &data, iDefault, p_notify))->GetEditBox();
} catch (...) {
fail(p_notify);
return NULL;
}
}

520
libPPUI/InPlaceEdit.cpp Normal file
View File

@@ -0,0 +1,520 @@
#include "stdafx.h"
#include "InPlaceEdit.h"
#include "wtl-pp.h"
#include "win32_op.h"
#include "AutoComplete.h"
#include "CWindowCreateAndDelete.h"
#include "win32_utility.h"
#include "listview_helper.h" // ListView_GetColumnCount
#include "clipboard.h"
#include <forward_list>
#ifndef WM_MOUSEHWHEEL
#define WM_MOUSEHWHEEL 0x20E
#endif
using namespace InPlaceEdit;
namespace {
enum {
MSG_COMPLETION = WM_USER,
MSG_DISABLE_EDITING
};
// Rationale: more than one HWND on the list is extremely uncommon, hence forward_list
static std::forward_list<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.begin(); walk != g_editboxes.end(); ++walk) {
GAbortEditing(*walk, code);
}
}
static bool IsSamePopup(CWindow wnd1, CWindow wnd2) {
return pfc::findOwningPopup(wnd1) == pfc::findOwningPopup(wnd2);
}
static void MouseEventTest(HWND target, CPoint pt, bool isWheel) {
for (auto walk = g_editboxes.begin(); walk != g_editboxes.end(); ++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);
}
static void on_editbox_creation(HWND p_editbox) {
// PFC_ASSERT(core_api::is_main_thread());
g_editboxes.push_front(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(p_editbox);
if (g_editboxes.empty()) {
UnhookHelper(g_hook); /*UnhookHelper(g_keyHook);*/
}
}
class CInPlaceEditBox : public CContainedWindowSimpleT<CEdit> {
public:
CInPlaceEditBox(uint32_t flags) : m_flags(flags) {}
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)
MSG_WM_PASTE(OnPaste)
END_MSG_MAP()
void OnCreation() {
on_editbox_creation(m_hWnd);
}
private:
void OnDestroy() {
m_selfDestruct = true;
on_editbox_destruction(m_hWnd);
SetMsgHandled(FALSE);
}
int OnCreate(LPCREATESTRUCT lpCreateStruct) {
OnCreation();
SetMsgHandled(FALSE);
return 0;
}
UINT OnGetDlgCode(LPMSG lpMsg) {
if (lpMsg == NULL) {
SetMsgHandled(FALSE); return 0;
} else {
switch (lpMsg->message) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
switch (lpMsg->wParam) {
case VK_TAB:
case VK_ESCAPE:
case VK_RETURN:
return DLGC_WANTMESSAGE;
default:
SetMsgHandled(FALSE); return 0;
}
default:
SetMsgHandled(FALSE); return 0;
}
}
}
void OnKillFocus(CWindow wndFocus) {
if ( wndFocus != NULL ) ForwardCompletion(KEditLostFocus);
SetMsgHandled(FALSE);
}
bool testPaste(const char* str) {
if (m_flags & InPlaceEdit::KFlagNumberSigned) {
if (pfc::string_is_numeric(str)) return true;
if (str[0] == '-' && pfc::string_is_numeric(str + 1) && GetWindowTextLength() == 0) return true;
return false;
}
if (m_flags & InPlaceEdit::KFlagNumber) {
return pfc::string_is_numeric(str);
}
return true;
}
void OnPaste() {
if (m_flags & (InPlaceEdit::KFlagNumber | InPlaceEdit::KFlagNumberSigned)) {
pfc::string8 temp;
ClipboardHelper::OpenScope scope; scope.Open(m_hWnd);
if (ClipboardHelper::GetString(temp)) {
if (!testPaste(temp)) return;
}
}
// Let edit box handle it
SetMsgHandled(FALSE);
}
bool testChar(UINT nChar) {
// Allow various non text characters
if (nChar < ' ') return true;
if (m_flags & InPlaceEdit::KFlagNumberSigned) {
if (pfc::char_is_numeric(nChar)) return true;
if (nChar == '-') {
return GetWindowTextLength() == 0;
}
return false;
}
if (m_flags & InPlaceEdit::KFlagNumber) {
return pfc::char_is_numeric(nChar);
}
return true;
}
void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) {
if (m_suppressChar != 0) {
UINT code = nFlags & 0xFF;
if (code == m_suppressChar) return;
}
if (!testChar(nChar)) {
MessageBeep(0);
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);
}
}
const uint32_t m_flags;
bool m_selfDestruct = false;
UINT m_suppressChar = 0;
};
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;
// ES_NUMBER is buggy in many ways (repaint glitches after balloon popup) and does not allow signed numbers.
// We implement number handling by filtering WM_CHAR instead.
// if (m_flags & KFlagNumber) style |= ES_NUMBER;
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();
pfc::setWindowText(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, reply_t 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),
m_edit(p_flags)
{
}
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();
}
if ( m_changed && m_edit != NULL ) {
*m_content = pfc::getWindowText(m_edit);
}
OnCompletion((unsigned)wParam);
if (!m_selfDestruct) {
m_selfDestruct = true;
DestroyWindow();
}
return 0;
}
void OnEditChange(UINT, int, CWindow source) {
if (m_initialized && !m_disable_editing) {
*m_content = pfc::getWindowText(source);
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) m_notify(code);
}
}
const pfc::rcptr_t<pfc::string_base> m_content;
const reply_t 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(reply_t p_notify) {
p_notify(KEditAborted);
// 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, reply_t 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, reply_t 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, reply_t p_notify, IUnknown * ACData, DWORD ACOpts) {
try {
PFC_ASSERT((CWindow(p_parentwnd).GetWindowLong(GWL_STYLE) & WS_CLIPCHILDREN) != 0);
return (new CWindowCreateAndDelete<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, reply_t 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);
}
}

44
libPPUI/InPlaceEdit.h Normal file
View File

@@ -0,0 +1,44 @@
#pragma once
#include <functional>
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,
KFlagNumber = 1 << 4,
KFlagNumberSigned = 1 << 5,
KFlagCombo = 1 << 8, // FOR INTERNAL USE
};
typedef std::function< void (unsigned) > reply_t;
typedef std::function< void(unsigned, unsigned) > comboReply_t; // status, index (UINT_MAX if n/a)
HWND Start(HWND p_parentwnd, const RECT & p_rect, bool p_multiline, pfc::rcptr_t<pfc::string_base> p_content, reply_t p_notify);
HWND StartEx(HWND p_parentwnd, const RECT & p_rect, unsigned p_flags, pfc::rcptr_t<pfc::string_base> p_content, reply_t p_notify, IUnknown * ACData = NULL, DWORD ACOpts = 0);
HWND StartCombo(HWND p_parentwnd, const RECT & p_rect, unsigned p_flags, pfc::string_list_const & data, unsigned iDefault, comboReply_t p_notify);
void Start_FromListView(HWND p_listview, unsigned p_item, unsigned p_subitem, unsigned p_linecount, pfc::rcptr_t<pfc::string_base> p_content, reply_t 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, reply_t 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);
}

View File

@@ -0,0 +1,221 @@
#include "stdafx.h"
#include "InPlaceEditTable.h"
#include "win32_op.h"
#include "win32_utility.h"
#include "AutoComplete.h"
#include "listview_helper.h"
namespace InPlaceEdit {
bool CTableEditHelperV2::tableEdit_cancel_task() {
bool rv = false;
if (m_taskKill) {
*m_taskKill = true; m_taskKill = nullptr;
rv = true;
}
return rv;
}
reply_t CTableEditHelperV2::tableEdit_create_task() {
tableEdit_cancel_task();
auto ks = std::make_shared<bool>(false);
m_taskKill = ks;
return [ks,this](unsigned code) {
if ( ! * ks ) {
this->tableEdit_on_task_completion( code );
}
};
}
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 & p_item, t_size & p_subItem, t_uint32 whathappened) {
size_t guardItem = SIZE_MAX, guardSubItem = SIZE_MAX; // infinite loop guard
size_t item = p_item, subItem = p_subItem;
for ( ;; ) {
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);
if ( guardItem == SIZE_MAX ) {
guardItem = item; guardSubItem = subItem;
} else {
// infinite loop guard
if ( item == guardItem && subItem == guardSubItem ) return false;
}
if (TableEdit_CanAdvanceHere(item, subItem, whathappened)) break;
}
p_item = item;
p_subItem = subItem;
return true;
}
void CTableEditHelperV2::TableEdit_Abort(bool forwardContent) {
if (tableEdit_cancel_task()) {
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();
m_editDataCombo.reset();
::SetFocus(TableEdit_GetParentWnd());
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_editFlags = TableEdit_GetEditFlags(m_editItem, m_editSubItem);
m_editData.release();
m_editDataCombo.reset();
if (m_editFlags & InPlaceEdit::KFlagCombo) {
auto combo = TableEdit_GetCombo(m_editItem, m_editSubItem);
RECT rc = TableEdit_GetItemRect(m_editItem, m_editSubItem);
auto data = std::make_shared< combo_t >(combo);
m_editDataCombo = data;
auto task = tableEdit_create_task();
auto comboTask = [data, task](unsigned status, unsigned sel) {
data->iDefault = sel;
task(status);
};
InPlaceEdit::StartCombo(TableEdit_GetParentWnd(), rc, m_editFlags, combo.strings, combo.iDefault, comboTask );
return;
}
m_editData.new_t();
t_size lineCount = 1;
TableEdit_GetField(m_editItem, m_editSubItem, *m_editData, lineCount);
RECT rc = TableEdit_GetItemRect(m_editItem, m_editSubItem);
if (lineCount > 1) {
rc.bottom = (LONG)( rc.top + (rc.bottom - rc.top) * lineCount );
m_editFlags |= KFlagMultiLine;
}
auto ac = this->TableEdit_GetAutoCompleteEx(m_editItem, m_editSubItem );
InPlaceEdit::StartEx(TableEdit_GetParentWnd(), rc, m_editFlags, m_editData, tableEdit_create_task(), ac.data.get_ptr(), ac.options);
}
CTableEditHelperV2::combo_t CTableEditHelperV2::TableEdit_GetCombo(size_t item, size_t sub) {
return combo_t();
}
CTableEditHelperV2::autoComplete_t CTableEditHelperV2::TableEdit_GetAutoCompleteEx( size_t item, size_t sub ) {
autoComplete_t ret;
if ( this->TableEdit_GetAutoComplete( item, sub, ret.data ) ) ret.options = ret.optsDefault;
return ret;
}
void CTableEditHelperV2::tableEdit_on_task_completion(unsigned status) {
tableEdit_cancel_task();
if (m_editData.is_valid()) {
if (status & InPlaceEdit::KEditFlagContentChanged) {
TableEdit_SetField(m_editItem, m_editSubItem, *m_editData);
}
m_editData.release();
}
if (m_editDataCombo != nullptr) {
unsigned idx = m_editDataCombo->iDefault;
if ( idx < m_editDataCombo->strings.get_count()) {
const char * text = m_editDataCombo->strings.get_item(idx);
TableEdit_SetField(m_editItem, m_editSubItem, text);
}
m_editDataCombo.reset();
}
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(), (int)item, (int)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(), (int)item, (int)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(), (int)item, (int)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(), (int) item));
}
t_size CTableEditHelperV2_ListView::TableEdit_GetColumnCount() const {
return (t_size)ListView_GetColumnCount(TableEdit_GetParentWnd());
}
}

View File

@@ -0,0 +1,87 @@
#pragma once
#include <memory>
#include "InPlaceEdit.h"
namespace InPlaceEdit {
class NOVTABLE CTableEditHelperV2 {
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 bool TableEdit_CanAdvanceHere( size_t item, size_t subItem, uint32_t whatHappened ) const { return true; }
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; }
struct autoComplete_t {
pfc::com_ptr_t<IUnknown> data;
enum { // ACO_* equivalents
optsNone = 0,
optsDefault = 1,
optsAutoSuggest = 1,
optsAutoAppend = 3,
};
DWORD options = optsNone;
};
virtual autoComplete_t TableEdit_GetAutoCompleteEx( size_t item, size_t sub );
struct combo_t {
unsigned iDefault = 0;
pfc::string_list_impl strings;
};
virtual combo_t TableEdit_GetCombo(size_t item, size_t sub);
void TableEdit_Start(t_size item, t_size subItem);
void TableEdit_Abort(bool forwardContent);
bool TableEdit_IsActive() const { return !!m_taskKill; }
protected:
~CTableEditHelperV2() { tableEdit_cancel_task(); }
CTableEditHelperV2() {}
private:
void tableEdit_on_task_completion(unsigned p_status);
reply_t tableEdit_create_task();
bool tableEdit_cancel_task();
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;
std::shared_ptr< combo_t > m_editDataCombo;
std::shared_ptr<bool> m_taskKill;
CTableEditHelperV2( const CTableEditHelperV2 & ) = delete;
void operator=( const CTableEditHelperV2 & ) = delete;
};
class NOVTABLE CTableEditHelperV2_ListView : public CTableEditHelperV2 {
public:
RECT TableEdit_GetItemRect(t_size item, t_size subItem) const override;
void TableEdit_GetField(t_size item, t_size subItem, pfc::string_base & out, t_size & lineCount) override;
void TableEdit_SetField(t_size item, t_size subItem, const char * value) override;
t_size TableEdit_GetColumnCount() const override;
t_size TableEdit_GetItemCount() const override;
void TableEdit_SetItemFocus(t_size item, t_size subItem) override;
void TableEdit_GetColumnOrder(t_size * out, t_size count) const override;
};
}

502
libPPUI/PaintUtils.cpp Normal file
View File

@@ -0,0 +1,502 @@
#include "stdafx.h"
#include <vsstyle.h>
#include "PaintUtils.h"
#include "gdiplus_helpers.h"
// #include <helpers/win32_misc.h>
// #include <ATLHelpers/GDIUtils.h>
#include "GDIUtils.h"
#include "win32_op.h"
#include "wtl-pp.h"
namespace PaintUtils {
static t_uint16 extractChannel16(t_uint32 p_color,int p_which) throw() {
return (t_uint16)( ((p_color >> (p_which * 8)) & 0xFF) << 8 );
}
static t_uint8 extractbyte(t_uint32 p_val,t_size p_which) throw() {
return (t_uint8) ( (p_val >> (p_which * 8)) & 0xFF );
}
t_uint32 BlendColorEx(t_uint32 p_color1, t_uint32 p_color2, double mix) throw() {
PFC_ASSERT(mix >= 0 && mix <= 1);
t_uint32 ret = 0;
for(t_size walk = 0; walk < 3; ++walk) {
int val1 = extractbyte(p_color1,walk), val2 = extractbyte(p_color2,walk);
int val = val1 + pfc::rint32((val2 - val1) * mix);
ret |= (t_uint32)val << (walk * 8);
}
return ret;
}
t_uint32 BlendColor(t_uint32 p_color1, t_uint32 p_color2, int p_percentage) throw() {
PFC_ASSERT(p_percentage <= 100);
t_uint32 ret = 0;
for(t_size walk = 0; walk < 3; ++walk) {
int val1 = extractbyte(p_color1,walk), val2 = extractbyte(p_color2,walk);
int val = val1 + MulDiv(val2 - val1,p_percentage,100);
ret |= (t_uint32)val << (walk * 8);
}
return ret;
}
t_uint32 DriftColor(t_uint32 p_color,unsigned p_delta,bool p_direction) throw() {
t_uint32 ret = 0;
for(t_size walk = 0; walk < 3; ++walk) {
unsigned val = extractbyte(p_color,walk);
if (p_direction) val = 0xFF - val;
if (val < p_delta) val = p_delta;
val += p_delta;
if (val > 0xFF) val = 0xFF;
if (p_direction) val = 0xFF - val;
ret |= (t_uint32)val << (walk * 8);
}
return ret;
}
void FillVertexColor(TRIVERTEX & p_vertex,t_uint32 p_color,t_uint16 p_alpha) throw() {
p_vertex.Red = extractChannel16(p_color,0);
p_vertex.Green = extractChannel16(p_color,1);
p_vertex.Blue = extractChannel16(p_color,2);
p_vertex.Alpha = p_alpha;
}
void FillRectSimple(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_color) throw() {
p_dc.FillSolidRect(p_rect, p_color);
}
void GradientFillRect(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_color1, t_uint32 p_color2, bool p_horizontal) throw() {
TRIVERTEX verticies[2];
GRADIENT_RECT element = {0,1};
FillVertexColor(verticies[0],p_color1);
FillVertexColor(verticies[1],p_color2);
verticies[0].x = p_rect.left; verticies[0].y = p_rect.top;
verticies[1].x = p_rect.right; verticies[1].y = p_rect.bottom;
WIN32_OP_D( p_dc.GradientFill(verticies,tabsize(verticies),&element,1,p_horizontal ? GRADIENT_FILL_RECT_H : GRADIENT_FILL_RECT_V) );
}
void GradientSplitRect(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_bkColor, t_uint32 p_gradientColor,int p_splitPercent) throw() {
const long split = p_rect.top + MulDiv(p_rect.Height(),p_splitPercent,100);
CRect rcTemp;
rcTemp = p_rect;
rcTemp.bottom = split;
GradientFillRect(p_dc,rcTemp,p_bkColor,p_gradientColor,false);
rcTemp = p_rect;
rcTemp.top = split;
GradientFillRect(p_dc,rcTemp,p_gradientColor,p_bkColor,false);
}
void GradientBar(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_exterior, t_uint32 p_interior, int p_percentage) throw() {
const int gradientPix = MulDiv(p_rect.Height(),p_percentage,100);
CRect rcTemp;
rcTemp = p_rect;
rcTemp.bottom = rcTemp.top + gradientPix;
GradientFillRect(p_dc,rcTemp,p_exterior,p_interior,false);
rcTemp = p_rect;
rcTemp.top += gradientPix; rcTemp.bottom -= gradientPix;
FillRectSimple(p_dc,rcTemp,p_interior);
rcTemp = p_rect;
rcTemp.top = rcTemp.bottom - gradientPix;
GradientFillRect(p_dc,rcTemp,p_interior,p_exterior,false);
}
void RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,t_size p_item,t_uint32 p_color) throw() {
const DWORD bkColor_base = p_color;
const DWORD bkColor = DriftColor(bkColor_base,3, (p_item&1) != 0);
//GradientSplitRect(p_dc,p_itemRect,bkColor,BlendColor(bkColor,textColor,7),80);
GradientBar(p_dc,p_itemRect,bkColor_base,bkColor,10);
}
double Luminance(t_uint32 color) throw() {
double r = extractbyte(color,0), g = extractbyte(color,1), b = extractbyte(color,2);
return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255.0;
//return (r * 0.3 + g * 0.59 + b * 0.11) / 255.0;
}
t_uint32 DetermineTextColor(t_uint32 bk) throw() {
double l = Luminance(bk);
if ( l > 0.6 ) {
return 0; // black
} else {
return 0xFFFFFF; // white
}
}
void AddRectToRgn(HRGN p_rgn,CRect const & p_rect) throw() {
CRgn temp;
WIN32_OP_D( temp.CreateRectRgnIndirect(p_rect) != NULL );
CRgnHandle(p_rgn).CombineRgn(temp,RGN_OR);
}
void FocusRect2(CDCHandle dc, CRect const & rect, COLORREF bkColor) throw() {
COLORREF txColor = DetermineTextColor( bkColor );
COLORREF useColor = BlendColor(bkColor, txColor, 50);
CDCBrush brush(dc, useColor);
WIN32_OP_D( dc.FrameRect(rect,brush) );
}
void FocusRect(CDCHandle dc, CRect const & rect) throw() {
CDCBrush brush(dc, 0x7F7F7F);
WIN32_OP_D( dc.FrameRect(rect,brush) );
//dc.DrawFocusRect(rect);
}
namespace TrackBar {
void DrawThumb(HTHEME theme,HDC dc,int state,const RECT * rcThumb, const RECT * rcUpdate) {
if (theme == NULL) {
RECT blah = *rcThumb;
int flags = DFCS_BUTTONPUSH;
switch(state) {
case TUS_NORMAL:
break;
case TUS_DISABLED:
flags |= DFCS_INACTIVE;
break;
case TUS_PRESSED:
flags |= DFCS_PUSHED;
break;
}
DrawFrameControl(dc,&blah,DFC_BUTTON,flags);
} else {
DrawThemeBackground(theme,dc,TKP_THUMB,state,rcThumb,rcUpdate);
}
}
void DrawTrack(HTHEME theme,HDC dc,const RECT * rcTrack, const RECT * rcUpdate) {
if (theme == NULL) {
RECT blah = *rcTrack;
DrawFrameControl(dc,&blah,DFC_BUTTON,DFCS_BUTTONPUSH|DFCS_PUSHED);
} else {
DrawThemeBackground(theme,dc,TKP_TRACK,TKS_NORMAL,rcTrack,rcUpdate);
}
}
void DrawTrackVolume(HTHEME theme,HDC p_dc,const RECT * rcTrack, const RECT * rcUpdate) {
CMemoryDC dc(p_dc,CRect(rcUpdate));
CRect rc(*rcTrack);
CRect update(*rcUpdate);
WIN32_OP_D( dc.BitBlt(update.left,update.top,update.Width(),update.Height(),p_dc,update.left,update.top,SRCCOPY) );
/*CDCHandle dc(p_dc);
CPen pen; pen.CreatePen(PS_SOLID,1, GetSysColor(COLOR_GRAYTEXT));
SelectObjectScope scope(dc, pen);
dc.MoveTo(rc.left,rc.bottom);
dc.LineTo(rc.right,rc.bottom);
dc.LineTo(rc.right,rc.top);
dc.LineTo(rc.left,rc.bottom);*/
//DrawTrack(theme,dc,rcTrack,rcUpdate);
try {
Gdiplus::Point points[] = { Gdiplus::Point(rc.left, rc.bottom), Gdiplus::Point(rc.right, rc.bottom), Gdiplus::Point(rc.right, rc.top) } ;
GdiplusErrorHandler eh;
Gdiplus::Graphics graphics(dc);
eh << graphics.GetLastStatus();
Gdiplus::Color c;
c.SetFromCOLORREF( GetSysColor(COLOR_BTNHIGHLIGHT) );
Gdiplus::Pen penHL(c);
c.SetFromCOLORREF( GetSysColor(COLOR_BTNSHADOW) );
Gdiplus::Pen penSH(c);
eh << graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
//graphics.DrawPolygon(&pen,points,tabsize(points));
eh << graphics.DrawLine(&penSH, points[0], points[0] + Gdiplus::Point(0,-1));
eh << graphics.DrawLine(&penHL, points[0], points[1]);
eh << graphics.DrawLine(&penHL, points[1], points[2]);
eh << graphics.DrawLine(&penSH, points[2], points[0] + Gdiplus::Point(0,-1));
} catch(std::exception const & e) {
(void) e;
// console::print(e.what());
}
}
}
void DrawSmoothedLine(HDC dc, CPoint pt1, CPoint pt2, COLORREF col, double width) {
try {
Gdiplus::Point points[] = { Gdiplus::Point(pt1.x,pt1.y), Gdiplus::Point(pt2.x,pt2.y) };
GdiplusErrorHandler eh;
Gdiplus::Graphics graphics(dc);
eh << graphics.GetLastStatus();
Gdiplus::Color c;
c.SetFromCOLORREF( col );
Gdiplus::Pen pen(c, (Gdiplus::REAL)( width ));
eh << graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
//graphics.DrawPolygon(&pen,points,tabsize(points));
eh << graphics.DrawLine(&pen, points[0], points[1]);
} catch(std::exception const & e) {
(void) e;
// console::print(e.what());
}
}
static int get_text_width(HDC dc,const TCHAR * src,int len) {
if (len<=0) return 0;
else {
SIZE goatse;
GetTextExtentPoint32(dc,src,len,&goatse);
return goatse.cx;
}
}
static t_uint32 TextOutColors_TranslateColor(const t_uint32 colors[3], int offset) {
const double v = (double)offset / 3.0;
if (v <= -1) return colors[0];
else if (v < 0) return BlendColorEx(colors[0], colors[1], v + 1);
else if (v == 0) return colors[1];
else if (v < 1) return BlendColorEx(colors[1], colors[2], v);
else return colors[2];
}
void TextOutColors_StripCodesAppend(pfc::string_formatter & out, const char * in) {
t_size done = 0, walk = 0;
for(;;) {
if (in[walk] == 0) {
if (walk > done) out.add_string_nc(in + done, walk - done);
return;
}
if ((unsigned)in[walk] < 32) {
if (walk > done) {out.add_string_nc(in + done, walk - done);}
done = walk + 1;
}
++walk;
}
}
void TextOutColors_StripCodes(pfc::string_formatter & out, const char * in) {
out.reset(); TextOutColors_StripCodesAppend(out, in);
}
static bool IsControlChar(TCHAR c) {
return (unsigned)c < 32;
}
static int MatchTruncat(HDC dc, int & pixels, const TCHAR * text, int textLen) {
int min = 0, max = textLen;
int minWidth = 0;
while(min + 1 < max) {
const int probe = (min + max) / 2;
CSize size;
WIN32_OP( GetTextExtentPoint32(dc, text, probe, &size) );
if (size.cx <= pixels) {min = probe; minWidth = size.cx;}
else max = probe;
}
pixels = minWidth;
return min;
}
static int TruncatHeadroom(HDC dc) {
CSize size;
WIN32_OP( GetTextExtentPoint32(dc, _T("\x2026"), 1, &size) );
return size.cx;
}
static void ExtTextOut_Truncat(HDC dc, int x, int y, CRect const & clip, const TCHAR * text, int textLen) {
int width = pfc::max_t<int>(0, clip.right - x - TruncatHeadroom(dc));
int truncat = MatchTruncat(dc, width, text, textLen);
WIN32_OP( ExtTextOut(dc, x, y, ETO_CLIPPED, &clip, text, truncat, NULL) );
WIN32_OP( ExtTextOut(dc, x + width, y, ETO_CLIPPED, &clip, _T("\x2026"), 1, NULL) );
}
bool TextContainsCodes(const TCHAR * src) {
for(;;) {
if (*src == 0) return false;
if ((unsigned)*src < 32) return true;
++src;
}
}
void TextOutColorsEx(HDC dc,const TCHAR * src,const CRect & target,DWORD flags,const t_uint32 colors[3]) {
if (!TextContainsCodes(src)) {
SetTextColorScope cs(dc, colors[1]);
CRect rc(target);
CDCHandle(dc).DrawText(src,(int)_tcslen(src),rc,DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | flags);
} else {
const CSize textSize = PaintUtils::TextOutColors_CalcSize(dc, src);
CPoint origin = target.TopLeft();
origin.y = (target.top + target.bottom - textSize.cy) / 2;
switch(flags & (DT_LEFT | DT_RIGHT | DT_CENTER)) {
case DT_LEFT:
break;
case DT_RIGHT:
if (textSize.cx < target.Width()) origin.x = target.right - textSize.cx;
break;
case DT_CENTER:
if (textSize.cx < target.Width()) origin.x = (target.right + target.left - textSize.cx) / 2;
break;
}
TextOutColors(dc, src, (int)_tcslen(src), origin, target, colors);
}
}
void TextOutColors(HDC dc,const TCHAR * src,int len,CPoint offset,const CRect & clip,const t_uint32 colors[3], int tabWidthTotal, int tabWidthDiv) {
SetTextAlign(dc,TA_LEFT);
SetBkMode(dc,TRANSPARENT);
int walk = 0;
int position = offset.x;
int colorOffset = 0;
int tabs = 0;
int positionTabDelta = 0;
for(;;) {
int base = walk;
while(walk < len && !IsControlChar(src[walk])) ++walk;
if (walk>base) {
SetTextColor(dc,TextOutColors_TranslateColor(colors, colorOffset));
int width = get_text_width(dc,src+base,walk-base);
if (position + width > clip.right) {
ExtTextOut_Truncat(dc, position, offset.y, clip, src + base, walk - base);
return;
}
WIN32_OP( ExtTextOut(dc,position,offset.y,ETO_CLIPPED,&clip,src+base,walk-base,0) );
position += width;
}
if (walk>=len) break;
while(walk < len && IsControlChar(src[walk])) {
if (src[walk] == TextOutColors_Dim) --colorOffset;
else if (src[walk] == TextOutColors_Highlight) ++colorOffset;
else if (src[walk] == '\t') {
int newDelta = MulDiv(++tabs, tabWidthTotal, tabWidthDiv);
position += newDelta - positionTabDelta;
positionTabDelta = newDelta;
}
walk++;
}
}
}
CSize TextOutColors_CalcSize(HDC dc, const TCHAR * src) {
CSize acc(0,0);
for(int walk = 0;;) {
const int done = walk;
while(!IsControlChar(src[walk])) ++walk;
if (walk > done) {
CSize temp;
WIN32_OP( GetTextExtentPoint32(dc,src + done, walk - done, &temp) );
acc.cx += temp.cx; pfc::max_acc(acc.cy, temp.cy);
}
if (src[walk] == 0) return acc;
while(src[walk] != 0 && IsControlChar(src[walk])) ++walk;
}
}
t_uint32 TextOutColors_CalcWidth(HDC dc, const TCHAR * src) {
t_uint32 acc = 0;
for(int walk = 0;;) {
const int done = walk;
while(!IsControlChar(src[walk])) ++walk;
acc += get_text_width(dc, src + done, walk - done);
if (src[walk] == 0) return acc;
while(src[walk] != 0 && IsControlChar(src[walk])) ++walk;
}
}
static const unsigned Marker_Dim = '<', Marker_Highlight = '>';
pfc::string TextOutColors_ImportScript(pfc::string script) {
pfc::string_formatter temp; TextOutColors_ImportScript(temp, script.ptr()); return temp.get_ptr();
}
void TextOutColors_ImportScript(pfc::string_base & out, const char * in) {
out.reset();
for(;;) {
t_size delta; t_uint32 c;
delta = pfc::utf8_decode_char(in, c);
if (delta == 0) break;
switch(c) {
case '>':
c = PaintUtils::TextOutColors_Highlight;
break;
case '<':
c = PaintUtils::TextOutColors_Dim;
break;
}
out.add_char(c);
in += delta;
}
}
t_uint32 DrawText_TranslateHeaderAlignment(t_uint32 val) {
switch(val & HDF_JUSTIFYMASK) {
case HDF_LEFT:
default:
return DT_LEFT;
case HDF_RIGHT:
return DT_RIGHT;
case HDF_CENTER:
return DT_CENTER;
}
}
void RenderButton(HWND wnd_, HDC dc_, CRect rcUpdate, bool bPressed) {
CDCHandle dc(dc_); CWindow wnd(wnd_);
CTheme theme; theme.OpenThemeData(wnd, L"BUTTON");
RelayEraseBkgnd(wnd, wnd.GetParent(), dc);
const int part = BP_PUSHBUTTON;
enum {
stNormal = PBS_NORMAL,
stHot = PBS_HOT,
stDisabled = PBS_DISABLED,
stPressed = PBS_PRESSED,
};
int state = 0;
if (!wnd.IsWindowEnabled()) state = stDisabled;
else if (bPressed) state = stPressed;
else state = stNormal;
CRect rcClient; WIN32_OP_D( wnd.GetClientRect(rcClient) );
if (theme != NULL && IsThemePartDefined(theme, part, 0)) {
DrawThemeBackground(theme, dc, part, state, rcClient, &rcUpdate);
} else {
int stateEx = DFCS_BUTTONPUSH;
switch(state) {
case stPressed: stateEx |= DFCS_PUSHED; break;
case stDisabled: stateEx |= DFCS_INACTIVE; break;
}
DrawFrameControl(dc, rcClient, DFC_BUTTON, stateEx);
}
}
void PaintSeparatorControl(HWND wnd_) {
CWindow wnd(wnd_);
CPaintDC dc(wnd);
TCHAR buffer[512] = {};
wnd.GetWindowText(buffer, _countof(buffer));
const int txLen = (int) 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));
}
}
}
}

53
libPPUI/PaintUtils.h Normal file
View File

@@ -0,0 +1,53 @@
#pragma once
#include <Uxtheme.h>
namespace PaintUtils {
t_uint32 BlendColor(t_uint32 p_color1, t_uint32 p_color2, int p_percentage = 50) throw();
t_uint32 BlendColorEx(t_uint32 p_color1, t_uint32 p_color2, double mix = 0.5) throw();
t_uint32 DriftColor(t_uint32 p_color,unsigned p_delta,bool p_direction) throw();
void FillVertexColor(TRIVERTEX & p_vertex,t_uint32 p_color,t_uint16 p_alpha = 0) throw();
void FillRectSimple(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_color) throw();
void GradientFillRect(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_color1, t_uint32 p_color2, bool p_horizontal) throw();
void GradientSplitRect(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_bkColor, t_uint32 p_gradientColor,int p_splitPercent) throw();
void GradientBar(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_exterior, t_uint32 p_interior, int p_percentage) throw();
void RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,t_size p_item,t_uint32 p_color) throw();
t_uint32 DetermineTextColor(t_uint32 background) throw();
double Luminance(t_uint32 color) throw();
void AddRectToRgn(HRGN rgn, CRect const & rect) throw();
void FocusRect(CDCHandle dc, CRect const & rect) throw();
void FocusRect2(CDCHandle dc, CRect const & rect, COLORREF bkColor) throw();
namespace TrackBar {
void DrawThumb(HTHEME theme,HDC dc,int state,const RECT * rcThumb, const RECT * rcUpdate);
void DrawTrack(HTHEME theme,HDC dc,const RECT * rcTrack, const RECT * rcUpdate);
void DrawTrackVolume(HTHEME theme,HDC dc,const RECT * rcTrack, const RECT * rcUpdate);
};
void DrawSmoothedLine(HDC dc, CPoint p1, CPoint p2, COLORREF col, double width);
enum {
TextOutColors_Dim = 21,
TextOutColors_Highlight = 22,
};
void TextOutColors(HDC dc,const TCHAR * src,int len,CPoint offset,const CRect & clip,const t_uint32 colors[3], int tabWidthTotal = 0, int tabWidthDiv = 1);
void TextOutColorsEx(HDC dc,const TCHAR * src,const CRect & target,DWORD flags,const t_uint32 colors[3]);
void TextOutColors_StripCodesAppend(pfc::string_formatter & out, const char * in);
void TextOutColors_StripCodes(pfc::string_formatter & out, const char * in);
t_uint32 TextOutColors_CalcWidth(HDC dc, const TCHAR * src);
CSize TextOutColors_CalcSize(HDC dc, const TCHAR * src);
pfc::string TextOutColors_ImportScript(pfc::string script);
void TextOutColors_ImportScript(pfc::string_base & out, const char * in);
bool TextContainsCodes(const TCHAR * src);
t_uint32 DrawText_TranslateHeaderAlignment(t_uint32 val);
void RenderButton(HWND wnd_, HDC dc_, CRect rcUpdate, bool bPressed);
void PaintSeparatorControl(HWND wnd_);
}

195
libPPUI/SmartStrStr.cpp Normal file
View File

@@ -0,0 +1,195 @@
#include "StdAfx.h"
#include "SmartStrStr.h"
SmartStrStr::SmartStrStr() {
for (uint32_t walk = 128; walk < 0x10000; ++walk) {
uint32_t c = Transform(walk);
if (c != walk) {
substituions[walk].insert(c);
}
}
for (uint32_t walk = 32; walk < 0x10000; ++walk) {
auto lo = ToLower(walk);
if (lo != walk) {
auto & s = substituions[walk]; s.insert(lo);
auto iter = substituions.find(lo);
if (iter != substituions.end()) {
s.insert(iter->second.begin(), iter->second.end());
}
}
}
InitTwoCharMappings();
}
const wchar_t * SmartStrStr::matchHereW(const wchar_t * pString, const wchar_t * pUserString) const {
auto walkData = pString;
auto walkUser = pUserString;
for (;; ) {
if (*walkUser == 0) return walkData;
uint32_t cData, cUser;
size_t dData = pfc::utf16_decode_char(walkData, &cData);
size_t dUser = pfc::utf16_decode_char(walkUser, &cUser);
if (dData == 0 || dUser == 0) return nullptr;
if (cData != cUser) {
bool gotMulti = false;
{
auto multi = twoCharMappings.find(cData);
if (multi != twoCharMappings.end()) {
const char * cDataSubst = multi->second;
PFC_ASSERT(strlen(cDataSubst) == 2);
if (matchOneChar(cUser, (uint32_t)cDataSubst[0])) {
auto walkUser2 = walkUser + dUser;
uint32_t cUser2;
auto dUser2 = pfc::utf16_decode_char(walkUser2, &cUser2);
if (matchOneChar(cUser2, (uint32_t)cDataSubst[1])) {
gotMulti = true;
dUser += dUser2;
}
}
}
}
if (!gotMulti) {
if (!matchOneChar(cUser, cData)) return nullptr;
}
}
walkData += dData;
walkUser += dUser;
}
}
bool SmartStrStr::equals(const char * pString, const char * pUserString) const {
auto p = matchHere(pString, pUserString);
if ( p == nullptr ) return false;
return *p == 0;
}
const char * SmartStrStr::matchHere(const char * pString, const char * pUserString) const {
const char * walkData = pString;
const char * walkUser = pUserString;
for (;; ) {
if (*walkUser == 0) return walkData;
uint32_t cData, cUser;
size_t dData = pfc::utf8_decode_char(walkData, cData);
size_t dUser = pfc::utf8_decode_char(walkUser, cUser);
if (dData == 0 || dUser == 0) return nullptr;
if (cData != cUser) {
bool gotMulti = false;
{
auto multi = twoCharMappings.find(cData);
if (multi != twoCharMappings.end()) {
const char * cDataSubst = multi->second;
PFC_ASSERT(strlen(cDataSubst) == 2);
if (matchOneChar(cUser, (uint32_t)cDataSubst[0])) {
auto walkUser2 = walkUser + dUser;
uint32_t cUser2;
auto dUser2 = pfc::utf8_decode_char(walkUser2, cUser2);
if (matchOneChar(cUser2, (uint32_t)cDataSubst[1])) {
gotMulti = true;
dUser += dUser2;
}
}
}
}
if (!gotMulti) {
if (!matchOneChar(cUser, cData)) return nullptr;
}
}
walkData += dData;
walkUser += dUser;
}
}
const char * SmartStrStr::strStrEnd(const char * pString, const char * pSubString, size_t * outFoundAt) const {
size_t walk = 0;
for (;; ) {
if (pString[walk] == 0) return nullptr;
auto end = matchHere(pString+walk, pSubString);
if (end != nullptr) {
if ( outFoundAt != nullptr ) * outFoundAt = walk;
return end;
}
size_t delta = pfc::utf8_char_len( pString + walk );
if ( delta == 0 ) return nullptr;
walk += delta;
}
}
const wchar_t * SmartStrStr::strStrEndW(const wchar_t * pString, const wchar_t * pSubString, size_t * outFoundAt) const {
size_t walk = 0;
for (;; ) {
if (pString[walk] == 0) return nullptr;
auto end = matchHereW(pString + walk, pSubString);
if (end != nullptr) {
if (outFoundAt != nullptr) * outFoundAt = walk;
return end;
}
uint32_t dontcare;
size_t delta = pfc::utf16_decode_char(pString + walk, & dontcare);
if (delta == 0) return nullptr;
walk += delta;
}
}
bool SmartStrStr::matchOneChar(uint32_t cInput, uint32_t cData) const {
if (cInput == cData) return true;
auto iter = substituions.find(cData);
if (iter == substituions.end()) return false;
auto & s = iter->second;
return s.find(cInput) != s.end();
}
uint32_t SmartStrStr::Transform(uint32_t c) {
wchar_t wide[2] = {}; char out[4] = {};
pfc::utf16_encode_char(c, wide);
BOOL fail = FALSE;
if (WideCharToMultiByte(pfc::stringcvt::codepage_ascii, 0, wide, 2, out, 4, "?", &fail) > 0) {
if (!fail) {
if (out[0] > 0 && out[1] == 0) {
c = out[0];
}
}
}
return c;
}
uint32_t SmartStrStr::ToLower(uint32_t c) {
return pfc::charLower(c);
}
void SmartStrStr::ImportTwoCharMappings(const wchar_t * list, const char * replacement) {
PFC_ASSERT(strlen(replacement) == 2);
for (const wchar_t* ptr = list; ; ) {
wchar_t c = *ptr++;
if (c == 0) break;
twoCharMappings[(uint32_t)c] = replacement;
}
}
void SmartStrStr::InitTwoCharMappings() {
ImportTwoCharMappings(L"ÆǢǼ", "AE");
ImportTwoCharMappings(L"æǣǽ", "ae");
ImportTwoCharMappings(L"Œ", "OE");
ImportTwoCharMappings(L"œɶ", "oe");
ImportTwoCharMappings(L"DŽDZ", "DZ");
ImportTwoCharMappings(L"dždzʣʥ", "dz");
ImportTwoCharMappings(L"ß", "ss");
ImportTwoCharMappings(L"LJ", "LJ");
ImportTwoCharMappings(L"Lj", "Lj");
ImportTwoCharMappings(L"lj", "lj");
ImportTwoCharMappings(L"NJ", "NJ");
ImportTwoCharMappings(L"Nj", "Nj");
ImportTwoCharMappings(L"nj", "nj");
ImportTwoCharMappings(L"IJ", "IJ");
ImportTwoCharMappings(L"ij", "ij");
}

42
libPPUI/SmartStrStr.h Normal file
View File

@@ -0,0 +1,42 @@
#pragma once
#include <set>
#include <map>
#include <string>
//! Implementation of string matching for search purposes, such as media library search or typefind in list views. \n
//! Inspired by Unicode asymetic search, but not strictly implementing the Unicode asymetric search specifications. \n
//! Bootstraps its character mapping data from various Win32 API methods, requires no externally provided character mapping data. \n
//! Windows-only code. \n
//! \n
//! Keeping a global instance of it is recommended, due to one time init overhead. \n
//! Thread safety: safe to call concurrently once constructed.
class SmartStrStr {
public:
SmartStrStr();
//! Returns ptr to the end of the string if positive (for continuing search), nullptr if negative.
const char * strStrEnd(const char * pString, const char * pSubString, size_t * outFoundAt = nullptr) const;
const wchar_t * strStrEndW(const wchar_t * pString, const wchar_t * pSubString, size_t * outFoundAt = nullptr) const;
//! Returns ptr to the end of the string if positive (for continuing search), nullptr if negative.
const char * matchHere(const char * pString, const char * pUserString) const;
const wchar_t * matchHereW( const wchar_t * pString, const wchar_t * pUserString) const;
//! String-equals tool, compares strings rather than searching for occurance
bool equals( const char * pString, const char * pUserString) const;
//! One-char match. Doesn't use twoCharMappings, use only if you have to operate on char by char basis rather than call the other methods.
bool matchOneChar(uint32_t cInput, uint32_t cData) const;
private:
static uint32_t Transform(uint32_t c);
static uint32_t ToLower(uint32_t c);
void ImportTwoCharMappings(const wchar_t * list, const char * replacement);
void InitTwoCharMappings();
std::map<uint32_t, std::set<uint32_t> > substituions;
std::map<uint32_t, const char* > twoCharMappings;
};

397
libPPUI/TreeMultiSel.h Normal file
View File

@@ -0,0 +1,397 @@
#pragma once
// ================================================================================
// CTreeMultiSel
// Implementation of multi-selection in a tree view ctrl
// Instantiate with dialog ID of your treeview,
// plug into your dialog's message map.
// Doesn't work correctly with explorer-themed tree controls (glitches happen).
// ================================================================================
#include <set>
#include <vector>
class CTreeMultiSel : public CMessageMap {
public:
typedef std::set<HTREEITEM> selection_t;
typedef std::vector<HTREEITEM> selectionOrdered_t;
CTreeMultiSel(unsigned ID) : m_ID(ID) {}
BEGIN_MSG_MAP_EX(CTreeMultiSel)
NOTIFY_HANDLER_EX(m_ID, TVN_ITEMEXPANDED, OnItemExpanded)
NOTIFY_HANDLER_EX(m_ID, NM_CLICK, OnClick)
NOTIFY_HANDLER_EX(m_ID, TVN_DELETEITEM, OnItemDeleted)
NOTIFY_HANDLER_EX(m_ID, TVN_SELCHANGING, OnSelChanging)
NOTIFY_HANDLER_EX(m_ID, TVN_SELCHANGED, OnSelChangedFilter)
NOTIFY_HANDLER_EX(m_ID, NM_SETFOCUS, OnFocus)
NOTIFY_HANDLER_EX(m_ID, NM_KILLFOCUS, OnFocus)
END_MSG_MAP()
const unsigned m_ID;
// Retrieves selected items - on order of appearance in the view
selectionOrdered_t GetSelectionOrdered(CTreeViewCtrl tree) const {
HTREEITEM first = tree.GetRootItem();
selectionOrdered_t ret; ret.reserve( m_selection.size() );
for(HTREEITEM walk = first; walk != NULL; walk = tree.GetNextVisibleItem(walk)) {
if (m_selection.count(walk) > 0) ret.push_back( walk );
}
return ret;
}
//! Undefined order! Use only when order of selected items is not relevant.
selection_t GetSelection() const { return m_selection; }
selection_t const & GetSelectionRef() const { return m_selection; }
bool IsItemSelected(HTREEITEM item) const {return m_selection.count(item) > 0;}
size_t GetSelCount() const {return m_selection.size();}
//! Retrieves a single-selection item. Null if nothing or more than one item is selected.
HTREEITEM GetSingleSel() const {
if (m_selection.size() != 1) return NULL;
return *m_selection.begin();
}
void OnContextMenu_FixSelection(CTreeViewCtrl tree, CPoint pt) {
if (pt != CPoint(-1, -1)) {
WIN32_OP_D(tree.ScreenToClient(&pt));
UINT flags = 0;
const HTREEITEM item = tree.HitTest(pt, &flags);
if (item != NULL && (flags & TVHT_ONITEM) != 0) {
if (!IsItemSelected(item)) {
SelectSingleItem(tree, item);
}
CallSelectItem(tree, item);
}
}
}
void OnLButtonDown(CTreeViewCtrl tree, WPARAM wp, LPARAM lp) {
if (!IsKeyPressed(VK_CONTROL)) {
UINT flags = 0;
HTREEITEM item = tree.HitTest(CPoint(lp), &flags);
if (item != NULL && (flags & TVHT_ONITEM) != 0) {
if (!IsItemSelected(item)) tree.SelectItem(item);
}
}
}
static bool IsNavKey(UINT vk) {
switch(vk) {
case VK_UP:
case VK_DOWN:
case VK_RIGHT:
case VK_LEFT:
case VK_PRIOR:
case VK_NEXT:
case VK_HOME:
case VK_END:
return true;
default:
return false;
}
}
BOOL OnChar(CTreeViewCtrl tree, WPARAM code) {
switch(code) {
case ' ':
if (IsKeyPressed(VK_CONTROL) || !IsTypingInProgress()) {
HTREEITEM item = tree.GetSelectedItem();
if (item != NULL) SelectToggleItem(tree, item);
return TRUE;
}
break;
}
m_lastTypingTime = GetTickCount(); m_lastTypingTimeValid = true;
return FALSE;
}
BOOL OnKeyDown(CTreeViewCtrl tree, UINT vKey) {
if (IsNavKey(vKey)) m_lastTypingTimeValid = false;
switch(vKey) {
case VK_UP:
if (IsKeyPressed(VK_CONTROL)) {
HTREEITEM item = tree.GetSelectedItem();
if (item != NULL) {
HTREEITEM prev = tree.GetPrevVisibleItem(item);
if (prev != NULL) {
CallSelectItem(tree, prev);
if (IsKeyPressed(VK_SHIFT)) {
if (m_selStart == NULL) m_selStart = item;
SelectItemRange(tree, prev);
}
}
}
return TRUE;
}
break;
case VK_DOWN:
if (IsKeyPressed(VK_CONTROL)) {
HTREEITEM item = tree.GetSelectedItem();
if (item != NULL) {
HTREEITEM next = tree.GetNextVisibleItem(item);
if (next != NULL) {
CallSelectItem(tree, next);
if (IsKeyPressed(VK_SHIFT)) {
if (m_selStart == NULL) m_selStart = item;
SelectItemRange(tree, next);
}
}
}
return TRUE;
}
break;
/*case VK_LEFT:
if (IsKeyPressed(VK_CONTROL)) {
tree.SendMessage(WM_HSCROLL, SB_LINEUP, 0);
}
break;
case VK_RIGHT:
if (IsKeyPressed(VK_CONTROL)) {
tree.SendMessage(WM_HSCROLL, SB_LINEDOWN, 0);
}
break;*/
}
return FALSE;
}
private:
LRESULT OnFocus(LPNMHDR hdr) {
if ( m_selection.size() > 100 ) {
CTreeViewCtrl tree(hdr->hwndFrom);
tree.RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
} else if (m_selection.size() > 0) {
CTreeViewCtrl tree(hdr->hwndFrom);
CRgn rgn; rgn.CreateRectRgn(0,0,0,0);
for(auto walk = m_selection.begin(); walk != m_selection.end(); ++walk) {
CRect rc;
if (tree.GetItemRect(*walk, rc, TRUE)) {
CRgn temp; temp.CreateRectRgnIndirect(rc);
rgn.CombineRgn(temp, RGN_OR);
}
}
tree.RedrawWindow(NULL, rgn, RDW_INVALIDATE | RDW_ERASE);
}
SetMsgHandled(FALSE);
return 0;
}
void CallSelectItem(CTreeViewCtrl tree, HTREEITEM item) {
const bool was = m_ownSelChange; m_ownSelChange = true;
tree.SelectItem(item);
m_ownSelChange = was;
}
LRESULT OnSelChangedFilter(LPNMHDR) {
if (m_ownSelChangeNotify) SetMsgHandled(FALSE);
return 0;
}
LRESULT OnItemDeleted(LPNMHDR pnmh) {
const HTREEITEM item = reinterpret_cast<NMTREEVIEW*>(pnmh)->itemOld.hItem;
m_selection.erase( item );
if (m_selStart == item) m_selStart = NULL;
SetMsgHandled(FALSE);
return 0;
}
LRESULT OnItemExpanded(LPNMHDR pnmh) {
NMTREEVIEW * info = reinterpret_cast<NMTREEVIEW *>(pnmh);
CTreeViewCtrl tree ( pnmh->hwndFrom );
if ((info->itemNew.state & TVIS_EXPANDED) == 0) {
if (DeselectChildren( tree, info->itemNew.hItem )) {
SendOnSelChanged(tree);
}
}
SetMsgHandled(FALSE);
return 0;
}
BOOL HandleClick(CTreeViewCtrl tree, CPoint pt) {
UINT htFlags = 0;
HTREEITEM item = tree.HitTest(pt, &htFlags);
if (item != NULL && (htFlags & TVHT_ONITEM) != 0) {
if (IsKeyPressed(VK_CONTROL)) {
SelectToggleItem(tree, item);
return TRUE;
} else if (item == tree.GetSelectedItem() && !IsItemSelected(item)) {
SelectToggleItem(tree, item);
return TRUE;
} else {
//tree.SelectItem(item);
return FALSE;
}
} else {
return FALSE;
}
}
LRESULT OnClick(LPNMHDR pnmh) {
CPoint pt(GetMessagePos());
CTreeViewCtrl tree ( pnmh->hwndFrom );
WIN32_OP_D ( tree.ScreenToClient( &pt ) );
return HandleClick(tree, pt) ? 1 : 0;
}
LRESULT OnSelChanging(LPNMHDR pnmh) {
if (!m_ownSelChange) {
//console::formatter() << "OnSelChanging";
NMTREEVIEW * info = reinterpret_cast<NMTREEVIEW *>(pnmh);
CTreeViewCtrl tree ( pnmh->hwndFrom );
const HTREEITEM item = info->itemNew.hItem;
if (IsTypingInProgress()) {
SelectSingleItem(tree, item);
} else if (IsKeyPressed(VK_SHIFT)) {
SelectItemRange(tree, item);
} else if (IsKeyPressed(VK_CONTROL)) {
SelectToggleItem(tree, item);
} else {
SelectSingleItem(tree, item);
}
}
return 0;
}
void SelectItemRange(CTreeViewCtrl tree, HTREEITEM item) {
if (m_selStart == NULL || m_selStart == item) {
SelectSingleItem(tree, item);
return;
}
selection_t newSel = GrabRange(tree, m_selStart, item );
ApplySelection(tree, newSel);
}
static selection_t GrabRange(CTreeViewCtrl tree, HTREEITEM item1, HTREEITEM item2) {
selection_t range1, range2;
HTREEITEM walk1 = item1, walk2 = item2;
for(;;) {
if (walk1 != NULL) {
range1.insert( walk1 );
if (walk1 == item2) {
return range1;
}
walk1 = tree.GetNextVisibleItem(walk1);
}
if (walk2 != NULL) {
range2.insert( walk2 );
if (walk2 == item1) {
return range2;
}
walk2 = tree.GetNextVisibleItem(walk2);
}
if (walk1 == NULL && walk2 == NULL) {
// should not get here
return selection_t();
}
}
}
void SelectToggleItem(CTreeViewCtrl tree, HTREEITEM item) {
m_selStart = item;
if ( IsItemSelected( item ) ) {
m_selection.erase( item );
} else {
m_selection.insert( item );
}
UpdateItem(tree, item);
}
public:
void SelectSingleItem(CTreeViewCtrl tree, HTREEITEM item) {
m_selStart = item;
if (m_selection.size() == 1 && *m_selection.begin() == item) return;
DeselectAll(tree); SelectItem(tree, item);
}
void ApplySelection(CTreeViewCtrl tree, selection_t const & newSel) {
CRgn updateRgn;
bool changed = false;
if (newSel.size() != m_selection.size() && newSel.size() + m_selection.size() > 100) {
// don't bother with regions
changed = true;
} else {
WIN32_OP_D(updateRgn.CreateRectRgn(0, 0, 0, 0) != NULL);
for (auto walk = m_selection.begin(); walk != m_selection.end(); ++walk) {
if (newSel.count(*walk) == 0) {
changed = true;
CRect rc;
if (tree.GetItemRect(*walk, rc, TRUE)) {
CRgn temp; WIN32_OP_D(temp.CreateRectRgnIndirect(rc));
WIN32_OP_D(updateRgn.CombineRgn(temp, RGN_OR) != ERROR);
}
}
}
for (auto walk = newSel.begin(); walk != newSel.end(); ++walk) {
if (m_selection.count(*walk) == 0) {
changed = true;
CRect rc;
if (tree.GetItemRect(*walk, rc, TRUE)) {
CRgn temp; WIN32_OP_D(temp.CreateRectRgnIndirect(rc));
WIN32_OP_D(updateRgn.CombineRgn(temp, RGN_OR) != ERROR);
}
}
}
}
if (changed) {
m_selection = newSel;
tree.RedrawWindow(NULL, updateRgn);
SendOnSelChanged(tree);
}
}
void DeselectItem(CTreeViewCtrl tree, HTREEITEM item) {
if (IsItemSelected(item)) {
m_selection.erase(item); UpdateItem(tree, item);
}
}
void SelectItem(CTreeViewCtrl tree, HTREEITEM item) {
if (!IsItemSelected(item)) {
m_selection.insert(item); UpdateItem(tree, item);
}
}
void DeselectAll(CTreeViewCtrl tree) {
if (m_selection.size() == 0) return;
CRgn updateRgn;
if (m_selection.size() <= 100) {
WIN32_OP_D(updateRgn.CreateRectRgn(0, 0, 0, 0) != NULL);
for (auto walk = m_selection.begin(); walk != m_selection.end(); ++walk) {
CRect rc;
if (tree.GetItemRect(*walk, rc, TRUE)) {
CRgn temp; WIN32_OP_D(temp.CreateRectRgnIndirect(rc));
WIN32_OP_D(updateRgn.CombineRgn(temp, RGN_OR) != ERROR);
}
}
}
m_selection.clear();
tree.RedrawWindow(NULL, updateRgn);
}
private:
void UpdateItem(CTreeViewCtrl tree, HTREEITEM item) {
CRect rc;
if (tree.GetItemRect(item, rc, TRUE) ) {
tree.RedrawWindow(rc);
}
SendOnSelChanged(tree);
}
void SendOnSelChanged(CTreeViewCtrl tree) {
NMHDR hdr = {};
hdr.code = TVN_SELCHANGED;
hdr.hwndFrom = tree;
hdr.idFrom = m_ID;
const bool was = m_ownSelChangeNotify; m_ownSelChangeNotify = true;
tree.GetParent().SendMessage(WM_NOTIFY, m_ID, (LPARAM) &hdr );
m_ownSelChangeNotify = was;
}
bool DeselectChildren( CTreeViewCtrl tree, HTREEITEM item ) {
bool state = false;
for(HTREEITEM walk = tree.GetChildItem( item ); walk != NULL; walk = tree.GetNextSiblingItem( walk ) ) {
if (m_selection.erase(walk) > 0) state = true;
if (m_selStart == walk) m_selStart = NULL;
if (tree.GetItemState( walk, TVIS_EXPANDED ) ) {
if (DeselectChildren( tree, walk )) state = true;
}
}
return state;
}
bool IsTypingInProgress() const {
return m_lastTypingTimeValid && (GetTickCount() - m_lastTypingTime < 500);
}
selection_t m_selection;
HTREEITEM m_selStart = NULL;
bool m_ownSelChangeNotify = false, m_ownSelChange = false;
DWORD m_lastTypingTime = 0; bool m_lastTypingTimeValid = false;
};

53
libPPUI/TypeFind.cpp Normal file
View File

@@ -0,0 +1,53 @@
#include "stdafx.h"
#include "TypeFind.h"
#include "SmartStrStr.h"
static size_t _ItemCount(HWND wnd) {
return ListView_GetItemCount(wnd);
}
static const wchar_t * _ItemText(wchar_t * buffer, int bufSize, HWND wnd, size_t index, int subItem) {
NMLVDISPINFO info = {};
info.hdr.code = LVN_GETDISPINFO;
info.hdr.idFrom = GetDlgCtrlID(wnd);
info.hdr.hwndFrom = wnd;
info.item.iItem = (int) index;
info.item.iSubItem = subItem;
info.item.mask = LVIF_TEXT;
info.item.pszText = buffer;
info.item.cchTextMax = bufSize;
::SendMessage(::GetParent(wnd), WM_NOTIFY, info.hdr.idFrom, reinterpret_cast<LPARAM>(&info));
if (info.item.pszText == NULL) return L"";
if ( bufSize > 0 && info.item.pszText == buffer ) buffer[ bufSize - 1 ] = 0;
return info.item.pszText;
}
LRESULT TypeFind::Handler(NMHDR* hdr, int subItemFrom, int subItemCnt) {
NMLVFINDITEM * info = reinterpret_cast<NMLVFINDITEM*>(hdr);
const HWND wnd = hdr->hwndFrom;
if (info->lvfi.flags & LVFI_NEARESTXY) return -1;
const size_t count = _ItemCount(wnd);
if (count == 0) return -1;
const size_t base = (size_t)info->iStart % count;
static SmartStrStr tool;
enum {
bufSize = 1024,
};
wchar_t textBuf[bufSize];
for (size_t walk = 0; walk < count; ++walk) {
const size_t index = (walk + base) % count;
for (int subItem = subItemFrom; subItem < subItemFrom + subItemCnt; ++subItem) {
if (tool.matchHereW(_ItemText(textBuf, bufSize, wnd, index, subItem), info->lvfi.psz)) return (LRESULT)index;
}
}
for (size_t walk = 0; walk < count; ++walk) {
const size_t index = (walk + base) % count;
for (int subItem = subItemFrom; subItem < subItemFrom + subItemCnt; ++subItem) {
if (tool.strStrEndW(_ItemText(textBuf, bufSize, wnd, index, subItem), info->lvfi.psz)) return (LRESULT)index;
}
}
return -1;
}

6
libPPUI/TypeFind.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
class TypeFind {
public:
static LRESULT Handler(NMHDR* hdr, int subItemFrom = 0, int subItemCnt = 1);
};

51
libPPUI/clipboard.cpp Normal file
View File

@@ -0,0 +1,51 @@
#include "stdafx.h"
#include "clipboard.h"
#include "win32_op.h"
#ifdef UNICODE
#define CF_TCHAR CF_UNICODETEXT
#else
#define CF_TCHAR CF_TEXT
#endif
namespace ClipboardHelper {
void OpenScope::Open(HWND p_owner) {
Close();
WIN32_OP(OpenClipboard(p_owner));
m_open = true;
}
void OpenScope::Close() {
if (m_open) {
m_open = false;
CloseClipboard();
}
}
void SetRaw(UINT format,const void * data, t_size size) {
HANDLE buffer = GlobalAlloc(GMEM_DDESHARE,size);
if (buffer == NULL) throw std::bad_alloc();
try {
CGlobalLockScope lock(buffer);
PFC_ASSERT(lock.GetSize() == size);
memcpy(lock.GetPtr(),data,size);
} catch(...) {
GlobalFree(buffer); throw;
}
WIN32_OP(SetClipboardData(format,buffer) != NULL);
}
void SetString(const char * in) {
pfc::stringcvt::string_os_from_utf8 temp(in);
SetRaw(CF_TCHAR,temp.get_ptr(),(temp.length() + 1) * sizeof(TCHAR));
}
bool GetString(pfc::string_base & out) {
pfc::array_t<t_uint8> temp;
if (!GetRaw(CF_TCHAR,temp)) return false;
out = pfc::stringcvt::string_utf8_from_os(reinterpret_cast<const TCHAR*>(temp.get_ptr()),temp.get_size() / sizeof(TCHAR));
return true;
}
bool IsTextAvailable() {
return IsClipboardFormatAvailable(CF_TCHAR) == TRUE;
}
}

33
libPPUI/clipboard.h Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
namespace ClipboardHelper {
class OpenScope {
public:
OpenScope() : m_open(false) {}
~OpenScope() {Close();}
void Open(HWND p_owner);
void Close();
private:
bool m_open;
PFC_CLASS_NOT_COPYABLE_EX(OpenScope)
};
void SetRaw(UINT format,const void * buffer, t_size size);
void SetString(const char * in);
bool GetString(pfc::string_base & out);
template<typename TArray>
bool GetRaw(UINT format,TArray & out) {
pfc::assert_byte_type<typename TArray::t_item>();
HANDLE data = GetClipboardData(format);
if (data == NULL) return false;
CGlobalLockScope lock(data);
out.set_size( lock.GetSize() );
memcpy(out.get_ptr(), lock.GetPtr(), lock.GetSize() );
return true;
}
bool IsTextAvailable();
};

View File

@@ -0,0 +1,65 @@
#include "stdafx.h"
#include "commandline_parser.h"
commandline_parser::commandline_parser() {
init(pfc::stringcvt::string_utf8_from_os(GetCommandLine()));
}
void commandline_parser::init(const char * cmd)
{
pfc::string8_fastalloc temp;
pfc::chain_list_v2_t<pfc::string8> out;
while(*cmd)
{
temp.reset();
while(*cmd && *cmd!=' ')
{
if (*cmd=='\"')
{
cmd++;
while(*cmd && *cmd!='\"') temp.add_byte(*(cmd++));
if (*cmd == '\"') cmd++;
}
else temp.add_byte(*(cmd++));
}
out.insert_last(temp);
while(*cmd==' ') cmd++;
}
pfc::list_to_array(m_data,out);
}
size_t commandline_parser::find_param(const char * ptr) const {
for(size_t n=1;n<m_data.get_size();n++)
{
const char * cmd = m_data[n];
if (cmd[0]=='/') {
if (!strcmp(cmd+1,ptr)) return n;
}
}
return SIZE_MAX;
}
bool commandline_parser::check_param(const char * ptr) const
{
return find_param(ptr) != SIZE_MAX;
}
void commandline_parser::build_string(pfc::string_base & out)
{
out.reset();
unsigned n;
for(n=0;n<m_data.get_size();n++)
{
const char * cmd = m_data[n];
if (!out.is_empty()) out += " ";
if (strchr(cmd,' '))
{
out += "\"";
out += cmd;
out += "\"";
}
else
{
out += cmd;
}
}
}

View File

@@ -0,0 +1,18 @@
#pragma once
class commandline_parser
{
public:
commandline_parser(const char * p_cmd) { init(p_cmd); }
commandline_parser();
inline size_t get_count() const {return m_data.get_size();}
inline const char * get_item(size_t n) const {return m_data[n];}
inline const char * operator[](size_t n) const {return m_data[n];}
bool check_param(const char * p_ptr) const;
void build_string(pfc::string_base & p_out);
size_t find_param(const char * ptr) const;
private:
void init(const char * cmd);
pfc::array_t<pfc::string8> m_data;
};

View File

@@ -0,0 +1,46 @@
#pragma once
#include "gdiplus_helpers.h"
// Presumes prior #include of webp/decode.h
static bool IsImageWebP(const void * ptr, size_t bytes) {
if (bytes < 12) return false;
return memcmp(ptr, "RIFF", 4) == 0 && memcmp((const char*)ptr + 8, "WEBP", 4) == 0;
}
// WebP-aware GdiplusImageFromMem
static Gdiplus::Image * GdiplusImageFromMem2(const void * ptr, size_t bytes) {
GdiplusErrorHandler EH;
using namespace Gdiplus;
if (IsImageWebP(ptr, bytes)) {
WebPBitstreamFeatures bs;
if (WebPGetFeatures((const uint8_t*)ptr, bytes, &bs) != VP8_STATUS_OK) {
throw std::runtime_error("WebP decoding failure");
}
const Gdiplus::PixelFormat pf = bs.has_alpha ? PixelFormat32bppARGB : PixelFormat32bppRGB;
const int pfBytes = 4; // Gdiplus RGB is 4 bytes
int w = 0, h = 0;
// ALWAYS decode BGRA, Gdiplus will disregard alpha if was not originally present
uint8_t * decodedData = WebPDecodeBGRA((const uint8_t*)ptr, bytes, &w, &h);
pfc::onLeaving scope([decodedData] {WebPFree(decodedData); });
if (decodedData == nullptr || w <= 0 || h <= 0) throw std::runtime_error("WebP decoding failure");
pfc::ptrholder_t<Bitmap> ret = new Gdiplus::Bitmap(w, h, pf);
EH << ret->GetLastStatus();
Rect rc(0, 0, w, h);
Gdiplus::BitmapData bitmapData;
EH << ret->LockBits(&rc, 0, pf, &bitmapData);
uint8_t * target = (uint8_t*)bitmapData.Scan0;
const uint8_t * source = decodedData;
for (int y = 0; y < h; ++y) {
memcpy(target, source, w * pfBytes);
target += bitmapData.Stride;
source += pfBytes * w;
}
EH << ret->UnlockBits(&bitmapData);
return ret.detach();
}
return GdiplusImageFromMem(ptr, bytes);
}

233
libPPUI/gdiplus_helpers.h Normal file
View File

@@ -0,0 +1,233 @@
#pragma once
#include <GdiPlus.h>
#include "win32_op.h"
#include "win32_utility.h"
class GdiplusErrorHandler {
public:
void operator<<(Gdiplus::Status p_code) {
if (p_code != Gdiplus::Ok) {
throw pfc::exception(PFC_string_formatter() << "Gdiplus error (" << (unsigned) p_code << ")");
}
}
};
class GdiplusScope {
public:
GdiplusScope() {
Gdiplus::GdiplusStartupInput input;
Gdiplus::GdiplusStartupOutput output;
GdiplusErrorHandler() << Gdiplus::GdiplusStartup(&m_token,&input,&output);
}
~GdiplusScope() {
Gdiplus::GdiplusShutdown(m_token);
}
static void Once() {
static bool done = _Once();
}
private:
static bool _Once() {
ULONG_PTR token = 0;
Gdiplus::GdiplusStartupInput input;
Gdiplus::GdiplusStartupOutput output;
GdiplusErrorHandler() << Gdiplus::GdiplusStartup(&token, &input, &output);
return true;
}
GdiplusScope( const GdiplusScope & ) = delete;
void operator=( const GdiplusScope & ) = delete;
ULONG_PTR m_token = 0;
};
static HBITMAP GdiplusLoadBitmap(UINT id, const TCHAR * resType, CSize size) {
using namespace Gdiplus;
try {
auto stream = WinLoadResourceAsStream(GetThisModuleHandle(), MAKEINTRESOURCE(id), resType );
GdiplusErrorHandler EH;
pfc::ptrholder_t<Image> source = new Image(stream);
EH << source->GetLastStatus();
pfc::ptrholder_t<Bitmap> resized = new Bitmap(size.cx, size.cy, PixelFormat32bppARGB);
EH << resized->GetLastStatus();
{
pfc::ptrholder_t<Graphics> target = new Graphics(resized.get_ptr());
EH << target->GetLastStatus();
EH << target->SetInterpolationMode(InterpolationModeHighQuality);
EH << target->Clear(Color(0,0,0,0));
EH << target->DrawImage(source.get_ptr(), Rect(0,0,size.cx,size.cy));
}
HBITMAP bmp = NULL;
EH << resized->GetHBITMAP(Gdiplus::Color::White, & bmp );
return bmp;
} catch(...) {
PFC_ASSERT( !"Should not get here");
return NULL;
}
}
static Gdiplus::Image * GdiplusImageFromMem(const void * ptr, size_t bytes) {
using namespace Gdiplus;
GdiplusErrorHandler EH;
pfc::ptrholder_t<Image> source;
{
pfc::com_ptr_t<IStream> stream;
stream.attach( SHCreateMemStream((const BYTE*)ptr, pfc::downcast_guarded<UINT>(bytes)) );
if (stream.is_empty()) throw std::bad_alloc();
source = new Image(stream.get_ptr());
}
EH << source->GetLastStatus();
return source.detach();
}
static Gdiplus::Bitmap * GdiplusResizeImage( Gdiplus::Image * source, CSize size ) {
using namespace Gdiplus;
GdiplusErrorHandler EH;
pfc::ptrholder_t<Bitmap> resized = new Bitmap(size.cx, size.cy, PixelFormat32bppARGB);
EH << resized->GetLastStatus();
pfc::ptrholder_t<Graphics> target = new Graphics(resized.get_ptr());
EH << target->GetLastStatus();
EH << target->SetInterpolationMode(InterpolationModeHighQuality);
EH << target->Clear(Color(0, 0, 0, 0));
EH << target->DrawImage(source, Rect(0, 0, size.cx, size.cy));
return resized.detach();
}
static HICON GdiplusLoadIconFromMem( const void * ptr, size_t bytes, CSize size ) {
using namespace Gdiplus;
try {
GdiplusErrorHandler EH;
pfc::ptrholder_t<Image> source = GdiplusImageFromMem(ptr, bytes);
pfc::ptrholder_t<Bitmap> resized = GdiplusResizeImage( source.get_ptr(), size );
HICON icon = NULL;
EH << resized->GetHICON(&icon);
return icon;
} catch (...) {
PFC_ASSERT(!"Should not get here");
return NULL;
}
}
static HICON GdiplusLoadIcon(UINT id, const TCHAR * resType, CSize size) {
using namespace Gdiplus;
try {
auto stream = WinLoadResourceAsStream(GetThisModuleHandle(), MAKEINTRESOURCE(id), resType);
GdiplusErrorHandler EH;
pfc::ptrholder_t<Image> source = new Image(stream);
EH << source->GetLastStatus();
pfc::ptrholder_t<Bitmap> resized = new Bitmap(size.cx, size.cy, PixelFormat32bppARGB);
EH << resized->GetLastStatus();
{
pfc::ptrholder_t<Graphics> target = new Graphics(resized.get_ptr());
EH << target->GetLastStatus();
EH << target->SetInterpolationMode(InterpolationModeHighQuality);
EH << target->Clear(Color(0,0,0,0));
EH << target->DrawImage(source.get_ptr(), Rect(0,0,size.cx,size.cy));
}
HICON icon = NULL;
EH << resized->GetHICON(&icon);
return icon;
} catch(...) {
PFC_ASSERT( !"Should not get here");
return NULL;
}
}
static HICON GdiplusLoadPNGIcon(UINT id, CSize size) {return GdiplusLoadIcon(id, _T("PNG"), size);}
static HICON LoadPNGIcon(UINT id, CSize size) {
GdiplusScope scope;
return GdiplusLoadPNGIcon(id, size);
}
static void GdiplusLoadButtonPNG(CIcon & icon, HWND btn_, UINT image) {
CButton btn(btn_);
if (icon == NULL) {
CRect client; WIN32_OP_D( btn.GetClientRect(client) );
CSize size = client.Size();
int v = MulDiv(pfc::min_t<int>(size.cx, size.cy), 3, 4);
if (v < 8) v = 8;
icon = GdiplusLoadPNGIcon(image, CSize(v,v));
}
btn.SetIcon(icon);
}
static Gdiplus::Bitmap * GdiplusLoadResource(UINT id, const TCHAR * resType) {
using namespace Gdiplus;
GdiplusErrorHandler EH;
auto stream = WinLoadResourceAsStream(GetThisModuleHandle(), MAKEINTRESOURCE(id), resType);
pfc::ptrholder_t<Bitmap> img = new Bitmap(stream);
EH << img->GetLastStatus();
return img.detach();
}
static Gdiplus::Bitmap * GdiplusLoadResourceAsSize(UINT id, const TCHAR * resType, CSize size) {
using namespace Gdiplus;
GdiplusErrorHandler EH;
pfc::ptrholder_t<Bitmap> source = GdiplusLoadResource(id, resType);
pfc::ptrholder_t<Bitmap> resized = new Bitmap(size.cx, size.cy, PixelFormat32bppARGB);
EH << resized->GetLastStatus();
{
pfc::ptrholder_t<Graphics> target = new Graphics(resized.get_ptr());
EH << target->GetLastStatus();
EH << target->SetInterpolationMode(InterpolationModeHighQuality);
EH << target->DrawImage(source.get_ptr(), Rect(0, 0, size.cx, size.cy));
}
return resized.detach();
}
static void GdiplusDimImage(Gdiplus::Bitmap * bmp) {
using namespace Gdiplus;
GdiplusErrorHandler EH;
Gdiplus::Rect r(0, 0, bmp->GetWidth(), bmp->GetHeight());
BitmapData data = {};
EH << bmp->LockBits(&r, ImageLockModeRead | ImageLockModeWrite, PixelFormat32bppARGB, &data);
BYTE * scan = (BYTE*)data.Scan0;
for (UINT y = 0; y < data.Height; ++y) {
BYTE * px = scan;
for (UINT x = 0; x < data.Width; ++x) {
//px[0] = _dimPixel(px[0]);
//px[1] = _dimPixel(px[1]);
//px[2] = _dimPixel(px[2]);
px[3] /= 3;
px += 4;
}
scan += data.Stride;
}
EH << bmp->UnlockBits(&data);
}
static void GdiplusInvertImage(Gdiplus::Bitmap * bmp) {
using namespace Gdiplus;
GdiplusErrorHandler EH;
Gdiplus::Rect r(0, 0, bmp->GetWidth(), bmp->GetHeight());
BitmapData data = {};
EH << bmp->LockBits(&r, ImageLockModeRead | ImageLockModeWrite, PixelFormat32bppARGB, &data);
BYTE * scan = (BYTE*)data.Scan0;
for (UINT y = 0; y < data.Height; ++y) {
BYTE * px = scan;
for (UINT x = 0; x < data.Width; ++x) {
unsigned v = (unsigned)px[0] + (unsigned)px[1] + (unsigned)px[2];
v /= 3;
px[0] = px[1] = px[2] = 255 - v;
px += 4;
}
scan += data.Stride;
}
EH << bmp->UnlockBits(&data);
}
#pragma comment(lib, "gdiplus.lib")

264
libPPUI/gesture.h Normal file
View File

@@ -0,0 +1,264 @@
#pragma once
#ifndef WM_GESTURE
#define WM_GESTURE 0x0119
#define WM_GESTURENOTIFY 0x011A
DECLARE_HANDLE(HGESTUREINFO);
/*
* Gesture flags - GESTUREINFO.dwFlags
*/
#define GF_BEGIN 0x00000001
#define GF_INERTIA 0x00000002
#define GF_END 0x00000004
/*
* Gesture IDs
*/
#define GID_BEGIN 1
#define GID_END 2
#define GID_ZOOM 3
#define GID_PAN 4
#define GID_ROTATE 5
#define GID_TWOFINGERTAP 6
#define GID_PRESSANDTAP 7
#define GID_ROLLOVER GID_PRESSANDTAP
/*
* Gesture information structure
* - Pass the HGESTUREINFO received in the WM_GESTURE message lParam into the
* GetGestureInfo function to retrieve this information.
* - If cbExtraArgs is non-zero, pass the HGESTUREINFO received in the WM_GESTURE
* message lParam into the GetGestureExtraArgs function to retrieve extended
* argument information.
*/
typedef struct tagGESTUREINFO {
UINT cbSize; // size, in bytes, of this structure (including variable length Args field)
DWORD dwFlags; // see GF_* flags
DWORD dwID; // gesture ID, see GID_* defines
HWND hwndTarget; // handle to window targeted by this gesture
POINTS ptsLocation; // current location of this gesture
DWORD dwInstanceID; // internally used
DWORD dwSequenceID; // internally used
ULONGLONG ullArguments; // arguments for gestures whose arguments fit in 8 BYTES
UINT cbExtraArgs; // size, in bytes, of extra arguments, if any, that accompany this gesture
} GESTUREINFO, *PGESTUREINFO;
typedef GESTUREINFO const * PCGESTUREINFO;
/*
* Gesture notification structure
* - The WM_GESTURENOTIFY message lParam contains a pointer to this structure.
* - The WM_GESTURENOTIFY message notifies a window that gesture recognition is
* in progress and a gesture will be generated if one is recognized under the
* current gesture settings.
*/
typedef struct tagGESTURENOTIFYSTRUCT {
UINT cbSize; // size, in bytes, of this structure
DWORD dwFlags; // unused
HWND hwndTarget; // handle to window targeted by the gesture
POINTS ptsLocation; // starting location
DWORD dwInstanceID; // internally used
} GESTURENOTIFYSTRUCT, *PGESTURENOTIFYSTRUCT;
/*
* Gesture argument helpers
* - Angle should be a double in the range of -2pi to +2pi
* - Argument should be an unsigned 16-bit value
*/
#define GID_ROTATE_ANGLE_TO_ARGUMENT(_arg_) ((USHORT)((((_arg_) + 2.0 * 3.14159265) / (4.0 * 3.14159265)) * 65535.0))
#define GID_ROTATE_ANGLE_FROM_ARGUMENT(_arg_) ((((double)(_arg_) / 65535.0) * 4.0 * 3.14159265) - 2.0 * 3.14159265)
/*
* Gesture information retrieval
* - HGESTUREINFO is received by a window in the lParam of a WM_GESTURE message.
*/
WINUSERAPI
BOOL
WINAPI
GetGestureInfo(
__in HGESTUREINFO hGestureInfo,
__out PGESTUREINFO pGestureInfo);
/*
* Gesture extra arguments retrieval
* - HGESTUREINFO is received by a window in the lParam of a WM_GESTURE message.
* - Size, in bytes, of the extra argument data is available in the cbExtraArgs
* field of the GESTUREINFO structure retrieved using the GetGestureInfo function.
*/
WINUSERAPI
BOOL
WINAPI
GetGestureExtraArgs(
__in HGESTUREINFO hGestureInfo,
__in UINT cbExtraArgs,
__out_bcount(cbExtraArgs) PBYTE pExtraArgs);
/*
* Gesture information handle management
* - If an application processes the WM_GESTURE message, then once it is done
* with the associated HGESTUREINFO, the application is responsible for
* closing the handle using this function. Failure to do so may result in
* process memory leaks.
* - If the message is instead passed to DefWindowProc, or is forwarded using
* one of the PostMessage or SendMessage class of API functions, the handle
* is transfered with the message and need not be closed by the application.
*/
WINUSERAPI
BOOL
WINAPI
CloseGestureInfoHandle(
__in HGESTUREINFO hGestureInfo);
/*
* Gesture configuration structure
* - Used in SetGestureConfig and GetGestureConfig
* - Note that any setting not included in either GESTURECONFIG.dwWant or
* GESTURECONFIG.dwBlock will use the parent window's preferences or
* system defaults.
*/
typedef struct tagGESTURECONFIG {
DWORD dwID; // gesture ID
DWORD dwWant; // settings related to gesture ID that are to be turned on
DWORD dwBlock; // settings related to gesture ID that are to be turned off
} GESTURECONFIG, *PGESTURECONFIG;
/*
* Gesture configuration flags - GESTURECONFIG.dwWant or GESTURECONFIG.dwBlock
*/
/*
* Common gesture configuration flags - set GESTURECONFIG.dwID to zero
*/
#define GC_ALLGESTURES 0x00000001
/*
* Zoom gesture configuration flags - set GESTURECONFIG.dwID to GID_ZOOM
*/
#define GC_ZOOM 0x00000001
/*
* Pan gesture configuration flags - set GESTURECONFIG.dwID to GID_PAN
*/
#define GC_PAN 0x00000001
#define GC_PAN_WITH_SINGLE_FINGER_VERTICALLY 0x00000002
#define GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY 0x00000004
#define GC_PAN_WITH_GUTTER 0x00000008
#define GC_PAN_WITH_INERTIA 0x00000010
/*
* Rotate gesture configuration flags - set GESTURECONFIG.dwID to GID_ROTATE
*/
#define GC_ROTATE 0x00000001
/*
* Two finger tap gesture configuration flags - set GESTURECONFIG.dwID to GID_TWOFINGERTAP
*/
#define GC_TWOFINGERTAP 0x00000001
/*
* PressAndTap gesture configuration flags - set GESTURECONFIG.dwID to GID_PRESSANDTAP
*/
#define GC_PRESSANDTAP 0x00000001
#define GC_ROLLOVER GC_PRESSANDTAP
#define GESTURECONFIGMAXCOUNT 256 // Maximum number of gestures that can be included
// in a single call to SetGestureConfig / GetGestureConfig
WINUSERAPI
BOOL
WINAPI
SetGestureConfig(
__in HWND hwnd, // window for which configuration is specified
__in DWORD dwReserved, // reserved, must be 0
__in UINT cIDs, // count of GESTURECONFIG structures
__in_ecount(cIDs) PGESTURECONFIG pGestureConfig, // array of GESTURECONFIG structures, dwIDs will be processed in the
// order specified and repeated occurances will overwrite previous ones
__in UINT cbSize); // sizeof(GESTURECONFIG)
#define GCF_INCLUDE_ANCESTORS 0x00000001 // If specified, GetGestureConfig returns consolidated configuration
// for the specified window and it's parent window chain
WINUSERAPI
BOOL
WINAPI
GetGestureConfig(
__in HWND hwnd, // window for which configuration is required
__in DWORD dwReserved, // reserved, must be 0
__in DWORD dwFlags, // see GCF_* flags
__in PUINT pcIDs, // *pcIDs contains the size, in number of GESTURECONFIG structures,
// of the buffer pointed to by pGestureConfig
__inout_ecount(*pcIDs) PGESTURECONFIG pGestureConfig,
// pointer to buffer to receive the returned array of GESTURECONFIG structures
__in UINT cbSize); // sizeof(GESTURECONFIG)
typedef BOOL
(WINAPI *pGetGestureInfo_t)(
__in HGESTUREINFO hGestureInfo,
__out PGESTUREINFO pGestureInfo);
typedef BOOL
(WINAPI * pCloseGestureInfoHandle_t)(
__in HGESTUREINFO hGestureInfo);
typedef BOOL
(WINAPI * pSetGestureConfig_t) (
__in HWND hwnd, // window for which configuration is specified
__in DWORD dwReserved, // reserved, must be 0
__in UINT cIDs, // count of GESTURECONFIG structures
__in_ecount(cIDs) PGESTURECONFIG pGestureConfig, // array of GESTURECONFIG structures, dwIDs will be processed in the
// order specified and repeated occurances will overwrite previous ones
__in UINT cbSize); // sizeof(GESTURECONFIG)
class CGestureAPI {
public:
CGestureAPI() {
HMODULE dll = GetModuleHandle(_T("user32.dll"));
Bind(GetGestureInfo, dll, "GetGestureInfo");
Bind(CloseGestureInfoHandle, dll, "CloseGestureInfoHandle");
Bind(SetGestureConfig, dll, "SetGestureConfig");
}
bool IsAvailable() {
return this->GetGestureInfo != NULL;
}
pGetGestureInfo_t GetGestureInfo;
pCloseGestureInfoHandle_t CloseGestureInfoHandle;
pSetGestureConfig_t SetGestureConfig;
private:
template<typename func_t> static void Bind(func_t & f, HMODULE dll, const char * name) {
f = reinterpret_cast<func_t>(GetProcAddress(dll, name));
}
};
#else
class CGestureAPI {
public:
inline static bool IsAvailable() { return true; }
inline static BOOL GetGestureInfo(HGESTUREINFO hGestureInfo, PGESTUREINFO pGestureInfo) {
return ::GetGestureInfo(hGestureInfo, pGestureInfo);
}
inline static BOOL CloseGestureInfoHandle(HGESTUREINFO hGestureInfo) {
return ::CloseGestureInfoHandle(hGestureInfo);
}
inline static BOOL SetGestureConfig(HWND hwnd, DWORD dwReserved, UINT cIDs, PGESTURECONFIG pGestureConfig, UINT cbSize) {
return ::SetGestureConfig(hwnd, dwReserved, cIDs, pGestureConfig, cbSize);
}
};
#endif

View File

@@ -0,0 +1,17 @@
Copyright (C) 2002-2021 Peter Pawlowski
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

View File

@@ -0,0 +1,3 @@
libPPUI
A library of user interface classes used by foobar2000 codebase; freely available for anyone to use in their programming projects.

260
libPPUI/libPPUI.vcxproj Normal file
View File

@@ -0,0 +1,260 @@
<?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>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{7729EB82-4069-4414-964B-AD399091A03F}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>libPPUI</RootNamespace>
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<AdditionalIncludeDirectories>..</AdditionalIncludeDirectories>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<TreatSpecificWarningsAsErrors>4715</TreatSpecificWarningsAsErrors>
<BufferSecurityCheck>false</BufferSecurityCheck>
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<BufferSecurityCheck>false</BufferSecurityCheck>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<AdditionalIncludeDirectories>..</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MinSpace</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<AdditionalIncludeDirectories>..</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<TreatSpecificWarningsAsErrors>4715</TreatSpecificWarningsAsErrors>
<BufferSecurityCheck>false</BufferSecurityCheck>
<FloatingPointModel>Fast</FloatingPointModel>
<OmitFramePointers>true</OmitFramePointers>
<AdditionalOptions>/d2notypeopt %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<BufferSecurityCheck>false</BufferSecurityCheck>
<FloatingPointModel>Fast</FloatingPointModel>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<AdditionalIncludeDirectories>..</AdditionalIncludeDirectories>
<AdditionalOptions>/d2notypeopt %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="AutoComplete.h" />
<ClInclude Include="CButtonLite.h" />
<ClInclude Include="CDialogResizeHelperCompat.h" />
<ClInclude Include="CEditWithButtons.h" />
<ClInclude Include="CEnumString.h" />
<ClInclude Include="CFlashWindow.h" />
<ClInclude Include="CHeaderCtrlEx.h" />
<ClInclude Include="CIconOverlayWindow.h" />
<ClInclude Include="clipboard.h" />
<ClInclude Include="CListAccessible.h" />
<ClInclude Include="CListControl-Cell.h" />
<ClInclude Include="CListControl-Cells-Compat.h" />
<ClInclude Include="CListControl-Cells.h" />
<ClInclude Include="CListControl.h" />
<ClInclude Include="CListControlComplete.h" />
<ClInclude Include="CListControlHeaderImpl.h" />
<ClInclude Include="CListControlOwnerData.h" />
<ClInclude Include="CListControlSimple.h" />
<ClInclude Include="CListControlTruncationTooltipImpl.h" />
<ClInclude Include="CListControlUserOptions.h" />
<ClInclude Include="CListControlWithSelection.h" />
<ClInclude Include="CListControl_EditImpl.h" />
<ClInclude Include="CListViewCtrlEx.h" />
<ClInclude Include="CMiddleDragImpl.h" />
<ClInclude Include="commandline_parser.h" />
<ClInclude Include="Controls.h" />
<ClInclude Include="CPopupTooltipMessage.h" />
<ClInclude Include="CPowerRequest.h" />
<ClInclude Include="CPropVariant.h" />
<ClInclude Include="CWindowCreateAndDelete.h" />
<ClInclude Include="CDialogResizeHelper.h" />
<ClInclude Include="gdiplus-helpers-webp.h" />
<ClInclude Include="gdiplus_helpers.h" />
<ClInclude Include="GDIUtils.h" />
<ClInclude Include="gesture.h" />
<ClInclude Include="IDataObjectUtils.h" />
<ClInclude Include="InPlaceEdit.h" />
<ClInclude Include="InPlaceEditTable.h" />
<ClInclude Include="link-CommonControls6.h" />
<ClInclude Include="listview_helper.h" />
<ClInclude Include="PaintUtils.h" />
<ClInclude Include="pp-COM-macros.h" />
<ClInclude Include="ppresources.h" />
<ClInclude Include="SmartStrStr.h" />
<ClInclude Include="stdafx.h" />
<ClInclude Include="targetver.h" />
<ClInclude Include="TreeMultiSel.h" />
<ClInclude Include="TypeFind.h" />
<ClInclude Include="win32_op.h" />
<ClInclude Include="win32_utility.h" />
<ClInclude Include="wtl-pp.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="AutoComplete.cpp" />
<ClCompile Include="CEditWithButtons.cpp" />
<ClCompile Include="clipboard.cpp" />
<ClCompile Include="CListAccessible.cpp" />
<ClCompile Include="CListControl-Cells.cpp" />
<ClCompile Include="CListControl.cpp" />
<ClCompile Include="CListControlHeaderImpl.cpp" />
<ClCompile Include="CListControlTruncationTooltipImpl.cpp" />
<ClCompile Include="CListControlWithSelection.cpp" />
<ClCompile Include="CMiddleDragImpl.cpp" />
<ClCompile Include="commandline_parser.cpp" />
<ClCompile Include="Controls.cpp" />
<ClCompile Include="CDialogResizeHelper.cpp" />
<ClCompile Include="CPowerRequest.cpp" />
<ClCompile Include="IDataObjectUtils.cpp" />
<ClCompile Include="InPlaceCombo.cpp" />
<ClCompile Include="InPlaceEdit.cpp" />
<ClCompile Include="InPlaceEditTable.cpp" />
<ClCompile Include="listview_helper.cpp" />
<ClCompile Include="PaintUtils.cpp" />
<ClCompile Include="SmartStrStr.cpp" />
<ClCompile Include="stdafx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="TypeFind.cpp" />
<ClCompile Include="win32_op.cpp" />
<ClCompile Include="win32_utility.cpp" />
</ItemGroup>
<ItemGroup>
<Text Include="IDI_SCROLL.txt" />
<Text Include="libPPUI-license.txt" />
<Text Include="libPPUI-readme.txt" />
</ItemGroup>
<ItemGroup>
<Image Include="IDI_SCROLL.ico" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,260 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<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="Resources">
<UniqueIdentifier>{b5bb0d58-0b1e-442d-a84d-09288c375185}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="stdafx.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="targetver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CEditWithButtons.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CFlashWindow.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="CButtonLite.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ppresources.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="win32_op.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="win32_utility.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="wtl-pp.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CListAccessible.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CListControl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CListControl_EditImpl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CListControlWithSelection.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gesture.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="PaintUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="pp-COM-macros.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gdiplus_helpers.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="GDIUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="IDataObjectUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SmartStrStr.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CListControlUserOptions.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="InPlaceEdit.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="InPlaceEditTable.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="AutoComplete.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CWindowCreateAndDelete.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CEnumString.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="listview_helper.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Controls.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CDialogResizeHelper.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CDialogResizeHelperCompat.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="clipboard.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CPropVariant.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CPowerRequest.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="TypeFind.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CPopupTooltipMessage.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CListViewCtrlEx.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CHeaderCtrlEx.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="link-CommonControls6.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CListControlComplete.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CListControlSimple.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CListControlOwnerData.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="TreeMultiSel.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CListControlHeaderImpl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CListControlTruncationTooltipImpl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CListControl-Cells.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CListControl-Cells-Compat.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CListControl-Cell.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gdiplus-helpers-webp.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="commandline_parser.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CEditWithButtons.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CMiddleDragImpl.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="win32_op.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="win32_utility.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CListAccessible.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CListControl.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CListControlHeaderImpl.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CListControlTruncationTooltipImpl.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CListControlWithSelection.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="PaintUtils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="IDataObjectUtils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SmartStrStr.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="InPlaceEdit.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="InPlaceEditTable.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="AutoComplete.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="listview_helper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Controls.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CDialogResizeHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="clipboard.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CPowerRequest.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="TypeFind.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CListControl-Cells.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="commandline_parser.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="InPlaceCombo.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Text Include="libPPUI-license.txt" />
<Text Include="libPPUI-readme.txt" />
<Text Include="IDI_SCROLL.txt">
<Filter>Resources</Filter>
</Text>
</ItemGroup>
<ItemGroup>
<Image Include="IDI_SCROLL.ico">
<Filter>Resources</Filter>
</Image>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,3 @@
#pragma once
#pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' processorArchitecture='*'\"")

284
libPPUI/listview_helper.cpp Normal file
View File

@@ -0,0 +1,284 @@
#include "stdafx.h"
#include "win32_utility.h"
#include "win32_op.h"
#include "listview_helper.h"
#include "CListViewCtrlEx.h"
#include "CHeaderCtrlEx.h"
namespace listview_helper {
unsigned insert_item(HWND p_listview,unsigned p_index,const char * p_name,LPARAM p_param)
{
if (p_index == ~0) p_index = ListView_GetItemCount(p_listview);
LVITEM item = {};
pfc::stringcvt::string_os_from_utf8 os_string_temp(p_name);
item.mask = LVIF_TEXT | LVIF_PARAM;
item.iItem = p_index;
item.lParam = p_param;
item.pszText = const_cast<TCHAR*>(os_string_temp.get_ptr());
LRESULT ret = SendMessage(p_listview,LVM_INSERTITEM,0,(LPARAM)&item);
if (ret < 0) return ~0;
else return (unsigned) ret;
}
unsigned insert_item2(HWND p_listview, unsigned p_index, const char * col0, const char * col1, LPARAM p_param) {
unsigned i = insert_item( p_listview, p_index, col0, p_param );
if (i != ~0) {
set_item_text( p_listview, i, 1, col1 );
}
return i;
}
unsigned insert_item3(HWND p_listview, unsigned p_index, const char * col0, const char * col1, const char * col2, LPARAM p_param) {
unsigned i = insert_item( p_listview, p_index, col0, p_param );
if (i != ~0) {
set_item_text( p_listview, i, 1, col1 );
set_item_text( p_listview, i, 2, col2 );
}
return i;
}
unsigned insert_column(HWND p_listview,unsigned p_index,const char * p_name,unsigned p_width_dlu)
{
pfc::stringcvt::string_os_from_utf8 os_string_temp(p_name);
RECT rect = {0,0,(LONG)p_width_dlu,0};
MapDialogRect(GetParent(p_listview),&rect);
LVCOLUMN data = {};
data.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_FMT;
data.fmt = LVCFMT_LEFT;
data.cx = rect.right;
data.pszText = const_cast<TCHAR*>(os_string_temp.get_ptr());
LRESULT ret = SendMessage(p_listview,LVM_INSERTCOLUMN,p_index,(LPARAM)&data);
if (ret < 0) return UINT_MAX;
else return (unsigned) ret;
}
void get_item_text(HWND p_listview,unsigned p_index,unsigned p_column,pfc::string_base & p_out) {
enum {buffer_length = 1024*64};
pfc::array_t<TCHAR> buffer; buffer.set_size(buffer_length);
ListView_GetItemText(p_listview,p_index,p_column,buffer.get_ptr(),buffer_length);
p_out = pfc::stringcvt::string_utf8_from_os(buffer.get_ptr(),buffer_length);
}
bool set_item_text(HWND p_listview,unsigned p_index,unsigned p_column,const char * p_name)
{
LVITEM item = {};
pfc::stringcvt::string_os_from_utf8 os_string_temp(p_name);
item.mask = LVIF_TEXT;
item.iItem = p_index;
item.iSubItem = p_column;
item.pszText = const_cast<TCHAR*>(os_string_temp.get_ptr());
return SendMessage(p_listview,LVM_SETITEM,0,(LPARAM)&item) ? true : false;
}
bool is_item_selected(HWND p_listview,unsigned p_index)
{
LVITEM item = {};
item.mask = LVIF_STATE;
item.iItem = p_index;
item.stateMask = LVIS_SELECTED;
if (!SendMessage(p_listview,LVM_GETITEM,0,(LPARAM)&item)) return false;
return (item.state & LVIS_SELECTED) ? true : false;
}
void set_item_selection(HWND p_listview,unsigned p_index,bool p_state)
{
PFC_ASSERT( ::IsWindow(p_listview) );
LVITEM item = {};
item.stateMask = LVIS_SELECTED;
item.state = p_state ? LVIS_SELECTED : 0;
WIN32_OP_D( SendMessage(p_listview,LVM_SETITEMSTATE,(WPARAM)p_index,(LPARAM)&item) );
}
bool select_single_item(HWND p_listview,unsigned p_index)
{
LRESULT temp = SendMessage(p_listview,LVM_GETITEMCOUNT,0,0);
if (temp < 0) return false;
ListView_SetSelectionMark(p_listview,p_index);
unsigned n; const unsigned m = pfc::downcast_guarded<unsigned>(temp);
for(n=0;n<m;n++) {
enum {mask = LVIS_FOCUSED | LVIS_SELECTED};
ListView_SetItemState(p_listview,n,n == p_index ? mask : 0, mask);
}
return ensure_visible(p_listview,p_index);
}
bool ensure_visible(HWND p_listview,unsigned p_index)
{
return SendMessage(p_listview,LVM_ENSUREVISIBLE,p_index,FALSE) ? true : false;
}
}
void ListView_GetContextMenuPoint(HWND p_list,LPARAM p_coords,POINT & p_point,int & p_selection) {
POINT pt = {(short)LOWORD(p_coords),(short)HIWORD(p_coords)};
ListView_GetContextMenuPoint(p_list, pt, p_point, p_selection);
}
void ListView_GetContextMenuPoint(HWND p_list,POINT p_coords,POINT & p_point,int & p_selection) {
if (p_coords.x == -1 && p_coords.y == -1) {
int firstsel = ListView_GetFirstSelection(p_list);
if (firstsel >= 0) {
ListView_EnsureVisible(p_list, firstsel, FALSE);
RECT rect;
WIN32_OP_D( ListView_GetItemRect(p_list,firstsel,&rect,LVIR_BOUNDS) );
p_point.x = (rect.left + rect.right) / 2;
p_point.y = (rect.top + rect.bottom) / 2;
WIN32_OP_D( ClientToScreen(p_list,&p_point) );
} else {
RECT rect;
WIN32_OP_D(GetClientRect(p_list,&rect));
p_point.x = (rect.left + rect.right) / 2;
p_point.y = (rect.top + rect.bottom) / 2;
WIN32_OP_D(ClientToScreen(p_list,&p_point));
}
p_selection = firstsel;
} else {
POINT pt = p_coords; // {(short)LOWORD(p_coords),(short)HIWORD(p_coords)};
p_point = pt;
POINT client = pt;
WIN32_OP_D( ScreenToClient(p_list,&client) );
LVHITTESTINFO info = {};
info.pt = client;
p_selection = ListView_HitTest(p_list,&info);
}
}
int ListView_GetColumnCount(HWND listView) {
HWND header = ListView_GetHeader(listView);
PFC_ASSERT(header != NULL);
return Header_GetItemCount(header);
}
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;
}
}
}
}
unsigned CListViewCtrlEx::GetColunnCount() {
return (unsigned) ListView_GetColumnCount( *this );
}
unsigned CListViewCtrlEx::AddColumnEx(const wchar_t * name, unsigned widthDLU) {
return InsertColumnEx( GetColunnCount(), name, widthDLU );
}
unsigned CListViewCtrlEx::InsertColumnEx(unsigned index, const wchar_t * name, unsigned widthDLU) {
RECT rect = { 0,0,(LONG)widthDLU,0 };
MapDialogRect(GetParent(), &rect);
LVCOLUMN data = {};
data.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_FMT;
data.fmt = LVCFMT_LEFT;
data.cx = rect.right;
data.pszText = const_cast<wchar_t*>(name);
auto ret = this->InsertColumn(index, & data );
if (ret < 0) return UINT_MAX;
else return (unsigned)ret;
}
void CListViewCtrlEx::FixContextMenuPoint(CPoint & pt) {
ListView_FixContextMenuPoint(*this, pt);
}
unsigned CListViewCtrlEx::InsertString(unsigned index, const wchar_t * str) {
LVITEM item = {};
item.mask = LVIF_TEXT;
item.iItem = index;
item.pszText = const_cast<wchar_t*>(str);
auto ret = InsertItem(&item);
if ( ret < 0 ) return UINT_MAX;
else return (unsigned) ret;
}
unsigned CListViewCtrlEx::InsertString8(unsigned index, const char * str) {
return InsertString(index, pfc::stringcvt::string_os_from_utf8( str ) );
}
unsigned CListViewCtrlEx::AddString(const wchar_t * str) {
return InsertString(GetItemCount(), str);
}
unsigned CListViewCtrlEx::AddString8(const char * str) {
return AddString(pfc::stringcvt::string_os_from_utf8( str ) );
}
void CListViewCtrlEx::SetItemText(unsigned iItem, unsigned iSubItem, const wchar_t * str) {
LVITEM item = {};
item.mask = LVIF_TEXT;
item.iItem = iItem;
item.iSubItem = iSubItem;
item.pszText = const_cast<wchar_t*>(str);
SetItem(&item);
}
void CListViewCtrlEx::SetItemText8(unsigned item, unsigned subItem, const char * str) {
SetItemText( item, subItem, pfc::stringcvt::string_os_from_utf8( str ) );
}
DWORD CHeaderCtrlEx::GetItemFormat(int iItem) {
HDITEM item = {};
item.mask = HDI_FORMAT;
if (!this->GetItem(iItem, &item)) return 0;
return item.fmt;
}
void CHeaderCtrlEx::SetItemFormat(int iItem, DWORD flags) {
HDITEM item = {};
item.mask = HDI_FORMAT;
item.fmt = flags;
SetItem(iItem, &item);
}
void CHeaderCtrlEx::SetItemSort(int iItem, int direction) {
DWORD fmtWas = GetItemFormat(iItem);
DWORD fmt = fmtWas & ~(HDF_SORTDOWN | HDF_SORTUP);
if (direction > 0) fmt |= HDF_SORTDOWN;
else if (direction < 0) fmt |= HDF_SORTUP;
if (fmt != fmtWas) SetItemFormat(iItem, fmt);
}
void CHeaderCtrlEx::SetSingleItemSort(int iItem, int direction) {
const int total = GetItemCount();
for (int walk = 0; walk < total; ++walk) {
SetItemSort(walk, walk == iItem ? direction : 0);
}
}
void CHeaderCtrlEx::ClearSort() {
SetSingleItemSort(-1,0);
}
int CListViewCtrlEx::AddGroup(int iGroupID, const wchar_t * header) {
LVGROUP g = { sizeof(g) };
g.mask = LVGF_HEADER | LVGF_GROUPID;
g.pszHeader = const_cast<wchar_t*>( header );
g.iGroupId = iGroupID;
return __super::AddGroup(&g);
}

49
libPPUI/listview_helper.h Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
namespace listview_helper
{
unsigned insert_item(HWND p_listview,unsigned p_index,const char * p_name,LPARAM p_param);//returns index of new item on success, infinite on failure
unsigned insert_column(HWND p_listview,unsigned p_index,const char * p_name,unsigned p_width_dlu);//returns index of new item on success, infinite on failure
bool set_item_text(HWND p_listview,unsigned p_index,unsigned p_column,const char * p_name);
bool is_item_selected(HWND p_listview,unsigned p_index);
void set_item_selection(HWND p_listview,unsigned p_index,bool p_state);
bool select_single_item(HWND p_listview,unsigned p_index);
bool ensure_visible(HWND p_listview,unsigned p_index);
void get_item_text(HWND p_listview,unsigned p_index,unsigned p_column,pfc::string_base & p_out);
unsigned insert_item2(HWND p_listview, unsigned p_index, const char * col0, const char * col1, LPARAM p_param = 0);
unsigned insert_item3(HWND p_listview, unsigned p_index, const char * col0, const char * col1, const char * col2, LPARAM p_param = 0);
};
static int ListView_GetFirstSelection(HWND p_listview) {
return ListView_GetNextItem(p_listview,-1,LVNI_SELECTED);
}
static int ListView_GetSingleSelection(HWND p_listview) {
if (ListView_GetSelectedCount(p_listview) != 1) return -1;
return ListView_GetFirstSelection(p_listview);
}
static int ListView_GetFocusItem(HWND p_listview) {
return ListView_GetNextItem(p_listview,-1,LVNI_FOCUSED);
}
static bool ListView_IsItemSelected(HWND p_listview,int p_index) {
return ListView_GetItemState(p_listview,p_index,LVIS_SELECTED) != 0;
}
void ListView_GetContextMenuPoint(HWND p_list,LPARAM p_coords,POINT & p_point,int & p_selection);
void ListView_GetContextMenuPoint(HWND p_list,POINT p_coords,POINT & p_point,int & p_selection);
int ListView_GetColumnCount(HWND listView);
void ListView_FixContextMenuPoint(CListViewCtrl list, CPoint & coords);

13
libPPUI/pp-COM-macros.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#define COM_QI_BEGIN() HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid,void ** ppvObject) { if (ppvObject == NULL) return E_INVALIDARG;
#define COM_QI_ENTRY(IWhat) { if (iid == __uuidof(IWhat)) {IWhat * temp = this; temp->AddRef(); * ppvObject = temp; return S_OK;} }
#define COM_QI_ENTRY_(IWhat, IID) { if (iid == IID) {IWhat * temp = this; temp->AddRef(); * ppvObject = temp; return S_OK;} }
#define COM_QI_END() * ppvObject = NULL; return E_NOINTERFACE; }
#define COM_QI_CHAIN(Parent) { HRESULT status = Parent::QueryInterface(iid, ppvObject); if (SUCCEEDED(status)) return status; }
#define COM_QI_SIMPLE(IWhat) COM_QI_BEGIN() COM_QI_ENTRY(IUnknown) COM_QI_ENTRY(IWhat) COM_QI_END()
#define PP_COM_CATCH catch(exception_com const & e) {return e.get_code();} catch(std::bad_alloc) {return E_OUTOFMEMORY;} catch(pfc::exception_invalid_params) {return E_INVALIDARG;} catch(...) {return E_UNEXPECTED;}

10
libPPUI/ppresources.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
class CPPUIResources {
public:
static UINT get_IDI_SCROLL()
#ifdef IDI_SCROLL
{ return IDI_SCROLL; }
#endif
;
};

1
libPPUI/stdafx.cpp Normal file
View File

@@ -0,0 +1 @@
#include "stdafx.h"

21
libPPUI/stdafx.h Normal file
View File

@@ -0,0 +1,21 @@
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#pragma once
#include "targetver.h"
#define _SECURE_ATL 1
#include <atlbase.h>
#include <atltypes.h>
#include <atlstr.h>
#include <atlapp.h>
#include <atlctrls.h>
#include <atlwin.h>
#include <atlcom.h>
#include <atlcrack.h>
#include <pfc/pfc.h>

6
libPPUI/targetver.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0601
#include <SDKDDKVer.h>
#endif

36
libPPUI/win32_op.cpp Normal file
View File

@@ -0,0 +1,36 @@
#include "stdafx.h"
#include "win32_op.h"
#include <assert.h>
PFC_NORETURN PFC_NOINLINE void WIN32_OP_FAIL() {
const DWORD code = GetLastError();
PFC_ASSERT(code != NO_ERROR);
throw exception_win32(code);
}
PFC_NORETURN PFC_NOINLINE void WIN32_OP_FAIL_CRITICAL(const char * what) {
#if PFC_DEBUG
const DWORD code = GetLastError();
PFC_ASSERT(code != NO_ERROR);
#endif
pfc::crash();
#if 0
pfc::string_formatter msg; msg << what << " failure #" << (uint32_t)code;
TRACK_CODE(msg.get_ptr(), uBugCheck());
#endif
}
#if PFC_DEBUG
void WIN32_OP_D_FAIL(const wchar_t * _Message, const wchar_t *_File, unsigned _Line) {
const DWORD code = GetLastError();
pfc::array_t<wchar_t> msgFormatted; msgFormatted.set_size(pfc::strlen_t(_Message) + 64);
wsprintfW(msgFormatted.get_ptr(), L"%s (code: %u)", _Message, code);
if (IsDebuggerPresent()) {
OutputDebugString(TEXT("WIN32_OP_D() failure:\n"));
OutputDebugString(msgFormatted.get_ptr());
OutputDebugString(TEXT("\n"));
pfc::crash();
}
_wassert(msgFormatted.get_ptr(), _File, _Line);
}
#endif

36
libPPUI/win32_op.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
PFC_NORETURN PFC_NOINLINE void WIN32_OP_FAIL();
PFC_NORETURN PFC_NOINLINE void WIN32_OP_FAIL_CRITICAL(const char * what);
#ifdef _DEBUG
void WIN32_OP_D_FAIL(const wchar_t * _Message, const wchar_t *_File, unsigned _Line);
#endif
//Throws an exception when (OP) evaluates to false/zero.
#define WIN32_OP(OP) \
{ \
SetLastError(NO_ERROR); \
if (!(OP)) WIN32_OP_FAIL(); \
}
// Kills the application with appropriate debug info when (OP) evaluates to false/zero.
#define WIN32_OP_CRITICAL(WHAT, OP) \
{ \
SetLastError(NO_ERROR); \
if (!(OP)) WIN32_OP_FAIL_CRITICAL(WHAT); \
}
//WIN32_OP_D() acts like an assert specialized for win32 operations in debug build, ignores the return value / error codes in release build.
//Use WIN32_OP_D() instead of WIN32_OP() on operations that are extremely unlikely to fail, so failure condition checks are performed in the debug build only, to avoid bloating release code with pointless error checks.
#ifdef _DEBUG
#define WIN32_OP_D(OP) \
{ \
SetLastError(NO_ERROR); \
if (!(OP)) WIN32_OP_D_FAIL(PFC_WIDESTRING(#OP), PFC_WIDESTRING(__FILE__), __LINE__); \
}
#else
#define WIN32_OP_D(OP) (void)( (OP), 0);
#endif

218
libPPUI/win32_utility.cpp Normal file
View File

@@ -0,0 +1,218 @@
#include "stdafx.h"
#include "win32_utility.h"
#include "win32_op.h"
unsigned QueryScreenDPI(HWND wnd) {
HDC dc = GetDC(wnd);
unsigned ret = GetDeviceCaps(dc, LOGPIXELSY);
ReleaseDC(wnd, dc);
return ret;
}
unsigned QueryScreenDPI_X(HWND wnd) {
HDC dc = GetDC(wnd);
unsigned ret = GetDeviceCaps(dc, LOGPIXELSX);
ReleaseDC(wnd, dc);
return ret;
}
unsigned QueryScreenDPI_Y(HWND wnd) {
HDC dc = GetDC(wnd);
unsigned ret = GetDeviceCaps(dc, LOGPIXELSY);
ReleaseDC(wnd, dc);
return ret;
}
SIZE QueryScreenDPIEx(HWND wnd) {
HDC dc = GetDC(wnd);
SIZE ret = { GetDeviceCaps(dc,LOGPIXELSX), GetDeviceCaps(dc,LOGPIXELSY) };
ReleaseDC(wnd, dc);
return ret;
}
void HeaderControl_SetSortIndicator(HWND header_, int column, bool isUp) {
CHeaderCtrl header(header_);
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);
}
}
}
}
HINSTANCE GetThisModuleHandle() {
return (HINSTANCE)_AtlBaseModule.m_hInst;
}
WinResourceRef_t WinLoadResource(HMODULE hMod, const TCHAR * name, const TCHAR * type, WORD wLang) {
SetLastError(0);
HRSRC res = wLang ? FindResourceEx(hMod, type, name, wLang) : FindResource(hMod, name, type);
if ( res == NULL ) WIN32_OP_FAIL();
SetLastError(0);
HGLOBAL hglob = LoadResource(hMod, res);
if ( hglob == NULL ) WIN32_OP_FAIL();
SetLastError(0);
void * ptr = LockResource(hglob);
if ( ptr == nullptr ) WIN32_OP_FAIL();
WinResourceRef_t ref;
ref.ptr = ptr;
ref.bytes = SizeofResource(hMod, res);
return ref;
}
CComPtr<IStream> WinLoadResourceAsStream(HMODULE hMod, const TCHAR * name, const TCHAR * type, WORD wLang) {
auto res = WinLoadResource(hMod, name, type, wLang );
auto str = SHCreateMemStream( (const BYTE*) res.ptr, (UINT) res.bytes );
if ( str == nullptr ) throw std::bad_alloc();
CComPtr<IStream> ret;
ret.Attach( str );
return ret;
}
UINT GetFontHeight(HFONT font)
{
UINT ret;
HDC dc = CreateCompatibleDC(0);
SelectObject(dc, font);
ret = GetTextHeight(dc);
DeleteDC(dc);
return ret;
}
UINT GetTextHeight(HDC dc)
{
TEXTMETRIC tm;
POINT pt[2];
GetTextMetrics(dc, &tm);
pt[0].x = 0;
pt[0].y = tm.tmHeight;
pt[1].x = 0;
pt[1].y = 0;
LPtoDP(dc, pt, 2);
int ret = pt[0].y - pt[1].y;
return ret > 1 ? (unsigned)ret : 1;
}
LRESULT RelayEraseBkgnd(HWND p_from, HWND p_to, HDC p_dc) {
LRESULT status;
POINT pt = { 0, 0 }, pt_old = { 0,0 };
MapWindowPoints(p_from, p_to, &pt, 1);
OffsetWindowOrgEx(p_dc, pt.x, pt.y, &pt_old);
status = SendMessage(p_to, WM_ERASEBKGND, (WPARAM)p_dc, 0);
SetWindowOrgEx(p_dc, pt_old.x, pt_old.y, 0);
return status;
}
pfc::string8 EscapeTooltipText(const char * src)
{
pfc::string8 out;
while (*src)
{
if (*src == '&')
{
out.add_string("&&&");
src++;
while (*src == '&')
{
out.add_string("&&");
src++;
}
} else out.add_byte(*(src++));
}
return out;
}
bool IsMenuNonEmpty(HMENU menu) {
unsigned n, m = GetMenuItemCount(menu);
for (n = 0; n < m; n++) {
if (GetSubMenu(menu, n)) return true;
if (!(GetMenuState(menu, n, MF_BYPOSITION)&MF_SEPARATOR)) return true;
}
return false;
}
void SetDefaultMenuItem(HMENU p_menu, unsigned p_id) {
MENUITEMINFO info = { sizeof(info) };
info.fMask = MIIM_STATE;
GetMenuItemInfo(p_menu, p_id, FALSE, &info);
info.fState |= MFS_DEFAULT;
SetMenuItemInfo(p_menu, p_id, FALSE, &info);
}
static bool running_under_wine(void) {
HMODULE module = GetModuleHandle(_T("ntdll.dll"));
if (!module) return false;
return GetProcAddress(module, "wine_server_call") != NULL;
}
static bool FetchWineInfoAppend(pfc::string_base & out) {
typedef const char *(__cdecl *t_wine_get_build_id)(void);
typedef void(__cdecl *t_wine_get_host_version)(const char **sysname, const char **release);
const HMODULE ntdll = GetModuleHandle(_T("ntdll.dll"));
if (ntdll == NULL) return false;
t_wine_get_build_id wine_get_build_id;
t_wine_get_host_version wine_get_host_version;
wine_get_build_id = (t_wine_get_build_id)GetProcAddress(ntdll, "wine_get_build_id");
wine_get_host_version = (t_wine_get_host_version)GetProcAddress(ntdll, "wine_get_host_version");
if (wine_get_build_id == NULL || wine_get_host_version == NULL) {
if (GetProcAddress(ntdll, "wine_server_call") != NULL) {
out << "wine (unknown version)";
return true;
}
return false;
}
const char * sysname = NULL; const char * release = NULL;
wine_get_host_version(&sysname, &release);
out << wine_get_build_id() << ", on: " << sysname << " / " << release;
return true;
}
static void GetOSVersionStringAppend(pfc::string_base & out) {
if (FetchWineInfoAppend(out)) return;
OSVERSIONINFO ver = {}; ver.dwOSVersionInfoSize = sizeof(ver);
WIN32_OP(GetVersionEx(&ver));
SYSTEM_INFO info = {};
GetNativeSystemInfo(&info);
out << "Windows " << (int)ver.dwMajorVersion << "." << (int)ver.dwMinorVersion << "." << (int)ver.dwBuildNumber;
if (ver.szCSDVersion[0] != 0) out << " " << pfc::stringcvt::string_utf8_from_os(ver.szCSDVersion, PFC_TABSIZE(ver.szCSDVersion));
switch (info.wProcessorArchitecture) {
case PROCESSOR_ARCHITECTURE_AMD64:
out << " x64"; break;
case PROCESSOR_ARCHITECTURE_IA64:
out << " IA64"; break;
case PROCESSOR_ARCHITECTURE_INTEL:
out << " x86"; break;
}
}
void GetOSVersionString(pfc::string_base & out) {
out.reset(); GetOSVersionStringAppend(out);
}
WORD GetOSVersionCode() {
OSVERSIONINFO ver = {sizeof(ver)};
WIN32_OP_D(GetVersionEx(&ver));
DWORD ret = ver.dwMinorVersion;
ret += ver.dwMajorVersion << 8;
return (WORD)ret;
}
POINT GetCursorPos() {
POINT pt;
WIN32_OP_D( GetCursorPos(&pt) );
return pt;
}

48
libPPUI/win32_utility.h Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
#include <atlcomcli.h> // CComPtr
unsigned QueryScreenDPI(HWND wnd = NULL);
unsigned QueryScreenDPI_X(HWND wnd = NULL);
unsigned QueryScreenDPI_Y(HWND wnd = NULL);
SIZE QueryScreenDPIEx(HWND wnd = NULL);
void HeaderControl_SetSortIndicator(HWND header, int column, bool isUp);
POINT GetCursorPos();
HINSTANCE GetThisModuleHandle();
struct WinResourceRef_t {
const void * ptr;
size_t bytes;
} ;
WinResourceRef_t WinLoadResource(HMODULE hMod, const TCHAR * name, const TCHAR * type, WORD wLang = 0);
CComPtr<IStream> WinLoadResourceAsStream(HMODULE hMod, const TCHAR * name, const TCHAR * type, WORD wLang = 0);
UINT GetFontHeight(HFONT font);
UINT GetTextHeight(HDC dc);
LRESULT RelayEraseBkgnd(HWND p_from, HWND p_to, HDC p_dc);
pfc::string8 EscapeTooltipText(const char * text);
class CloseHandleScope {
public:
CloseHandleScope(HANDLE handle) throw() : m_handle(handle) {}
~CloseHandleScope() throw() { CloseHandle(m_handle); }
HANDLE Detach() throw() { return pfc::replace_t(m_handle, INVALID_HANDLE_VALUE); }
HANDLE Get() const throw() { return m_handle; }
void Close() throw() { CloseHandle(Detach()); }
PFC_CLASS_NOT_COPYABLE_EX(CloseHandleScope)
private:
HANDLE m_handle;
};
bool IsMenuNonEmpty(HMENU menu);
void SetDefaultMenuItem(HMENU p_menu, unsigned p_id);
void GetOSVersionString(pfc::string_base & out);
WORD GetOSVersionCode();

474
libPPUI/wtl-pp.h Normal file
View File

@@ -0,0 +1,474 @@
#pragma once
// Various WTL extensions that are not fb2k specific and can be reused in other WTL based software
#include <Uxtheme.h>
#include <functional>
#define ATLASSERT_SUCCESS(X) {auto RetVal = (X); ATLASSERT( RetVal ); }
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 = true, NoEscSteal = false, NoEnterSteal = false;
std::function<void ()> onEnterKey;
std::function<void ()> onEscKey;
CEditPPHooks(CMessageMap * hookMM = nullptr, int hookMMID = 0) : CContainedWindowT<CEdit>(this, 0), m_hookMM(hookMM), m_hookMMID(hookMMID) {}
BEGIN_MSG_MAP_EX(CEditPPHooks)
MSG_WM_KEYDOWN(OnKeyDown)
MSG_WM_CHAR(OnChar)
MSG_WM_GETDLGCODE(OnEditGetDlgCode)
if ( m_hookMM != nullptr ) {
CHAIN_MSG_MAP_ALT_MEMBER( ( * m_hookMM ), m_hookMMID );
}
END_MSG_MAP()
static void DeleteLastWord( CEdit wnd ) {
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) {
if (nChar == m_suppressChar) return;
}
if (m_suppressScanCode != 0) {
UINT code = nFlags & 0xFF;
if (code == m_suppressScanCode) return;
}
SetMsgHandled(FALSE);
}
void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
m_suppressChar = 0;
m_suppressScanCode = 0;
if (HandleCtrlA) {
if (nChar == 'A') {
if (GetHotkeyModifierFlags() == MOD_CONTROL) {
m_suppressScanCode = nFlags & 0xFF;
this->SetSelAll(); return;
}
}
if ( nChar == VK_BACK ) {
if (GetHotkeyModifierFlags() == MOD_CONTROL) {
m_suppressScanCode = nFlags & 0xFF;
DeleteLastWord( *this ) ; return;
}
}
if ( nChar == VK_RETURN && onEnterKey ) {
m_suppressChar = nChar;
onEnterKey(); return;
}
if ( nChar == VK_ESCAPE && onEscKey ) {
m_suppressChar = nChar;
onEscKey(); 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:
if (onEscKey) {
return DLGC_WANTMESSAGE;
}
SetMsgHandled(!!NoEscSteal);
return 0;
case VK_RETURN:
if (onEnterKey) {
return DLGC_WANTMESSAGE;
}
SetMsgHandled(!!NoEnterSteal);
return 0;
default:
SetMsgHandled(FALSE); return 0;
}
default:
SetMsgHandled(FALSE); return 0;
}
}
}
UINT m_suppressChar = 0, m_suppressScanCode = 0;
CMessageMap * const m_hookMM;
const int m_hookMMID;
};
class CEditNoEscSteal : public 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&) = delete;
void operator=(const CSRWlock&) = delete;
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&) = delete;
void operator=(const CSRWorCS&) = delete;
CSRWlock srw;
CRITICAL_SECTION cs;
};
#else
typedef CSRWlock CSRWorCS;
#endif
template<typename TBase> class CContainedWindowSimpleT : public CContainedWindowT<TBase>, public CMessageMap {
public:
CContainedWindowSimpleT() : CContainedWindowT<TBase>(this) {}
BEGIN_MSG_MAP(CContainedWindowSimpleT)
END_MSG_MAP()
};