/*
* CExtWS.h
*
* author j.wolfe <vachaun22@gmail.com>
* copyright (c) 2012 all rights reserved
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* Portions of code handling the CMap objects and CList objects based on
* code from Paolo Massina's child anchoring classes which were adapted into
* the Prof-UIS suite as a CExtWA template.
*
* Portions of code positioning windows based on code found in the
* CWinampWnd class found on the CodeProject site.
*
*/
#ifndef _CEXTWS_H_
#define _CEXTWS_H_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
// Messages that get passed between parent and child windows
#define WM_PARENT_MOVING WM_USER+0x100
#define WM_DOCK_STATUS WM_USER+0x101
#define WM_REMOVE_SNAPPER WM_USER+0x102
#define WM_SET_PARENTHWND WM_USER+0x103
#define WM_PARENT_MOVED WM_USER+0x104
// Message notification can be handle outside of the class
#define WM_NOTIFY_DOCK WM_USER+0x105
#define SNAP_OFFSET 10
#define AERO_OFFSET 5
#define DOCK_NONE 0
#define DOCK_TOP 1
#define DOCK_LEFT 2
#define DOCK_RIGHT 4
#define DOCK_BOTTOM 8
typedef HRESULT (WINAPI *DwmIsCompositionEnabledFunc)(_Out_ BOOL *pfEnabled);
typedef HRESULT (WINAPI *DwmGetWindowAttributeFunc)(
HWND hwnd,
DWORD dwAttribute,
_Out_ PVOID pvAttribute,
DWORD cbAttribute
);
#ifndef _DWMAPI_H_
// Window attributes
enum DWMWINDOWATTRIBUTE
{
DWMWA_NCRENDERING_ENABLED = 1, // [get] Is non-client rendering enabled/disabled
DWMWA_NCRENDERING_POLICY, // [set] Non-client rendering policy
DWMWA_TRANSITIONS_FORCEDISABLED, // [set] Potentially enable/forcibly disable transitions
DWMWA_ALLOW_NCPAINT, // [set] Allow contents rendered in the non-client area to be visible on the DWM-drawn frame.
DWMWA_CAPTION_BUTTON_BOUNDS, // [get] Bounds of the caption button area in window-relative space.
DWMWA_NONCLIENT_RTL_LAYOUT, // [set] Is non-client content RTL mirrored
DWMWA_FORCE_ICONIC_REPRESENTATION, // [set] Force this window to display iconic thumbnails.
DWMWA_FLIP3D_POLICY, // [set] Designates how Flip3D will treat the window.
DWMWA_EXTENDED_FRAME_BOUNDS, // [get] Gets the extended frame bounds rectangle in screen space
DWMWA_HAS_ICONIC_BITMAP, // [set] Indicates an available bitmap when there is no better thumbnail representation.
DWMWA_DISALLOW_PEEK, // [set] Don't invoke Peek on the window.
DWMWA_EXCLUDED_FROM_PEEK, // [set] LivePreview exclusion information
DWMWA_LAST
};
#endif
/*
* CExtPS
*
* The parent window that other windows will snap to
*/
template < class CExtPSBase >
class CExtPS : public CExtPSBase
{
protected:
struct SDI_t
{
BOOL m_bIsDocked;
UINT m_nDockSide;
int m_nSnapOffset;
SDI_t(BOOL bIsDocked = FALSE,
UINT nDockSide = DOCK_NONE,
int nSnapOffset = SNAP_OFFSET)
: m_bIsDocked(bIsDocked)
, m_nDockSide(nDockSide)
, m_nSnapOffset(nSnapOffset)
{ }
SDI_t(const SDI_t &other)
: m_bIsDocked(other.m_bIsDocked)
, m_nDockSide(other.m_nDockSide)
, m_nSnapOffset(other.m_nSnapOffset)
{ }
SDI_t & operator= (const SDI_t &other)
{
m_bIsDocked = other.m_bIsDocked;
m_nDockSide = other.m_nDockSide;
m_nSnapOffset = other.m_nSnapOffset;
return *this;
}
}; // struct SDI_t
// map of snapping windows
typedef CMap < HWND, HWND, SDI_t, SDI_t > CMapSDI;
CMapSDI m_mapSDI;
public:
CExtPS()
{ }
CExtPS(UINT nIDTemplate)
: CExtPSBase(nIDTemplate)
{ }
CExtPS(UINT nIDTemplate, CWnd * pParentWnd)
: CExtPSBase(nIDTemplate, pParentWnd)
{ }
CExtPS(LPCTSTR lpszTemplateName, CWnd * pParentWnd)
: CExtPSBase(lpszTemplateName, pParentWnd)
{ }
CExtPS(UINT nIDTemplate, UINT nIDCaption/* = 0*/)
: CExtPSBase(nIDTemplate, nIDCaption)
{ }
CExtPS(LPCTSTR lpszTemplateName, UINT nIDCaption = 0)
: CExtPSBase(lpszTemplateName, nIDCaption)
{ }
CExtPS(UINT nIDCaption, CWnd * pParentWnd, UINT iSelectPage)
: CExtPSBase(nIDCaption, pParentWnd, iSelectPage)
{ }
CExtPS(LPCTSTR lpszCaption, CWnd * pParentWnd, UINT iSelectPage)
: CExtPSBase(lpszCaption, pParentWnd, iSelectPage)
{ }
virtual ~CExtPS()
{ RemoveAllSnappers(); }
bool RemoveSnapper(HWND hwnd)
{
if (hwnd == NULL)
return false;
return m_mapSDI.RemoveKey(hwnd) ? true : false;
}
void RemoveAllSnappers()
{
m_mapSDI.RemoveAll();
}
bool AddSnapper(CWnd *pWindow)
{
HWND hwnd = pWindow->GetSafeHwnd();
if ((hwnd == NULL) || !::IsWindow(hwnd))
return false;
return AddSnapper(hwnd);
}
virtual bool AddSnapper(HWND hwnd)
{
if ((hwnd == NULL) || !::IsWindow(hwnd))
return false;
m_mapSDI.SetAt(hwnd, SDI_t());
::SendMessage(hwnd, WM_SET_PARENTHWND, (WPARAM)GetSafeHwnd(), 0L);
return true;
}
protected:
virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
if ((message == WM_MOVE) ||
(message == WM_SIZE)) /* WM_MOVING|WM_SIZING not sent when not
* showing window contents while dragging */
{
LRESULT lResult = CExtPSBase::WindowProc(message, wParam, lParam);
CRect rcWindow;
GetWindowRect(&rcWindow);
MoveSnappers(wParam, (LPARAM)&rcWindow);
NotifyUnsnapped();
return lResult;
} // WM_MOVE || WM_SIZE
if ((message == WM_MOVING) ||
(message == WM_SIZING))
{
LRESULT lResult = CExtPSBase::WindowProc(message, wParam, lParam);
MoveSnappers(wParam, lParam);
return lResult;
} // WM_MOVING || WM_SIZING
if (message == WM_DOCK_STATUS)
{
HWND _hwnd = (HWND)wParam;
BOOL _bIsDocked = (BOOL)LOWORD(lParam);
UINT _nDockSide = (UINT)HIWORD(lParam);
UpdateDockStatus(_hwnd, _bIsDocked, _nDockSide);
return 0;
} // WM_DOCK_STATUS
if (message == WM_REMOVE_SNAPPER)
{
HWND _hwnd = (HWND)wParam;
RemoveSnapper(_hwnd);
return 0;
} // WM_REMOVE_SNAPPER
CWnd * pWndThis = this;
HWND hWndOwn = m_hWnd;
LRESULT lResult = CExtPSBase::WindowProc(message, wParam, lParam);
if (message == WM_DESTROY ||
message == WM_NCDESTROY)
{
if (hWndOwn != NULL &&
::IsWindow(hWndOwn) &&
CWnd::FromHandlePermanent(hWndOwn) == pWndThis)
{
RemoveAllSnappers();
}
}
return lResult;
}
private:
void NotifyUnsnapped()
{
INT nCount = INT(m_mapSDI.GetCount());
if (nCount <= 0)
return;
CList < HWND, HWND > listInvalidHWNDs;
POSITION pos = m_mapSDI.GetStartPosition();
for( ; pos != NULL; )
{
HWND _hwnd;
SDI_t _sdi;
m_mapSDI.GetNextAssoc(pos, _hwnd, _sdi);
if (!::IsWindow(_hwnd))
{
listInvalidHWNDs.AddTail(_hwnd);
continue;
}
if (!_sdi.m_bIsDocked)
::PostMessage(_hwnd, WM_PARENT_MOVED, 0L, 0L);
}
if (listInvalidHWNDs.GetCount() > 0)
{
pos = listInvalidHWNDs.GetHeadPosition();
for( ; pos != NULL; )
{
HWND _hwndInvalid = listInvalidHWNDs.GetNext(pos);
m_mapSDI.RemoveKey(_hwndInvalid);
}
}
}
void MoveSnappers(WPARAM wParam, LPARAM lParam)
{
INT nCount = INT(m_mapSDI.GetCount());
if (nCount <= 0)
return;
CList < HWND, HWND > listInvalidHWNDs;
POSITION pos = m_mapSDI.GetStartPosition();
for( ; pos != NULL; )
{
HWND _hwnd;
SDI_t _sdi;
m_mapSDI.GetNextAssoc(pos, _hwnd, _sdi);
if (!::IsWindow(_hwnd))
{
listInvalidHWNDs.AddTail(_hwnd);
continue;
}
if (_sdi.m_bIsDocked)
::PostMessage(_hwnd, WM_PARENT_MOVING, (WPARAM)_sdi.m_nDockSide, lParam);
}
if (listInvalidHWNDs.GetCount() > 0)
{
pos = listInvalidHWNDs.GetHeadPosition();
for( ; pos != NULL; )
{
HWND _hwndInvalid = listInvalidHWNDs.GetNext(pos);
m_mapSDI.RemoveKey(_hwndInvalid);
}
}
}
void UpdateDockStatus(HWND _hwnd, BOOL _bIsDocked, UINT _nDockSide)
{
if (!::IsWindow(_hwnd))
return;
CMapSDI::CPair *pCurVal;
pCurVal = m_mapSDI.PLookup(_hwnd);
pCurVal->value.m_bIsDocked = _bIsDocked;
pCurVal->value.m_nDockSide = _nDockSide;
}
}; // class CExtPS : public CExtPSBase
/*
* CExtCS
*
* The child window that will snap to other windows
*/
template < class CExtCSBase >
class CExtCS : public CExtCSBase
{
protected:
HWND m_hwndParent;
BOOL m_bIsDocked;
int m_nSnapOffset;
int m_nAeroOffset;
UINT m_nDockSide;
BOOL m_bAeroInUse;
public:
CExtCS()
{ Initialize(); }
CExtCS(UINT nIDTemplate, CWnd * pParentWnd)
: CExtCSBase(nIDTemplate, pParentWnd)
{ Initialize(); }
CExtCS(LPCTSTR lpszTemplateName, CWnd * pParentWnd)
: CExtCSBase(lpszTemplateName, pParentWnd)
{ Initialize(); }
CExtCS(UINT nIDTemplate, UINT nIDCaption = 0)
: CExtCSBase(nIDTemplate, nIDCaption)
{ Initialize(); }
CExtCS(LPCTSTR lpszTemplateName, UINT nIDCaption = 0)
: CExtCSBase(lpszTemplateName, nIDCaption)
{ Initialize(); }
CExtCS(UINT nIDCaption, CWnd * pParentWnd, UINT iSelectPage)
: CExtCSBase(nIDCaption, pParentWnd, iSelectPage)
{ Initialize(); }
CExtCS(LPCTSTR lpszCaption, CWnd * pParentWnd, UINT iSelectPage)
: CExtCSBase(lpszCaption, pParentWnd, iSelectPage)
{ Initialize(); }
virtual ~CExtCS()
{ }
virtual void OnOK()
{
RemoveSnapper();
CExtCSBase::OnOK();
}
virtual void OnCancel()
{
RemoveSnapper();
CExtCSBase::OnCancel();
}
void SetSnapOffset(int nSnapOffset = SNAP_OFFSET)
{ m_nSnapOffset = nSnapOffset; }
void DockNow(UINT nDockSide = DOCK_RIGHT)
{
m_bIsDocked = TRUE;
m_nDockSide = nDockSide;
if ((m_hwndParent != NULL) &&
::IsWindow(m_hwndParent))
{
CRect rcParent;
::GetWindowRect(m_hwndParent, &rcParent);
UpdateWindowPos(m_nDockSide, &rcParent, TRUE);
::PostMessage(m_hwndParent, WM_DOCK_STATUS, (WPARAM)GetSafeHwnd(), MAKELPARAM((WORD)m_bIsDocked, (WORD)m_nDockSide));
}
}
protected:
virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_MOVE)
{
LRESULT lResult = CExtCSBase::WindowProc(message, wParam, lParam);
Dock(TRUE);
return lResult;
} // WM_MOVE
if (message == WM_EXITSIZEMOVE)
{
if (m_bIsDocked)
{
CRect rcParent;
::GetWindowRect(m_hwndParent, &rcParent);
UpdateWindowPos(m_nDockSide, &rcParent, TRUE);
}
return CExtCSBase::WindowProc(message, wParam, lParam);
} // WM_LBUTTONUP
if (message == WM_PARENT_MOVED)
{
Dock(FALSE);
return 0L;
} // WM_PARENT_MOVED
if (message == WM_PARENT_MOVING)
{
UINT _nDockSide = UINT(wParam);
CRect rcParent;
::GetWindowRect(m_hwndParent, &rcParent);
UpdateWindowPos(_nDockSide, &rcParent, FALSE);
return 0L;
} // WM_PARENT_MOVING
if (message == WM_SET_PARENTHWND)
{
HWND _hwnd = HWND(wParam);
if (_hwnd != NULL &&
::IsWindow(_hwnd))
{
m_hwndParent = _hwnd;
}
} // WM_SET_PARENTHWND
if (message == WM_SETTINGCHANGE)
{
TRACE("WM_SETTINGCHANGE\n");
UpdateOffsets();
} // WM_SETTINGCHANGE
LRESULT lResult = CExtCSBase::WindowProc(message, wParam, lParam);
if (message == WM_DESTROY ||
message == WM_NCDESTROY)
{
RemoveSnapper();
}
return lResult;
}
private:
void Initialize()
{
m_hwndParent = NULL;
m_bIsDocked = FALSE;
m_nDockSide = DOCK_NONE;
UpdateOffsets();
}
void UpdateOffsets()
{
m_nAeroOffset = AERO_OFFSET;
m_bAeroInUse = IsAeroEnabled();
m_nAeroOffset = (m_bAeroInUse) ? GetAeroOffset() : AERO_OFFSET;
m_nSnapOffset = (m_bAeroInUse) ? (SNAP_OFFSET + m_nAeroOffset) : SNAP_OFFSET;
}
void RemoveSnapper()
{
CWnd * pWndThis = this;
HWND hWndOwn = m_hWnd;
if (hWndOwn != NULL &&
::IsWindow(hWndOwn) &&
CWnd::FromHandlePermanent(hWndOwn) == pWndThis)
{
::PostMessageA(m_hwndParent, WM_REMOVE_SNAPPER, (WPARAM)hWndOwn, 0L);
}
}
void UpdateWindowPos(UINT _nDockSide, LPRECT lprcParent, BOOL bActivate)
{
CRect rcWindow;
GetWindowRect(&rcWindow);
UINT nFlags = (bActivate) ? SWP_SHOWWINDOW : SWP_SHOWWINDOW | SWP_NOACTIVATE;
#if (_MFC_PLATFORM_TOOLSET < 100)
if (m_bAeroInUse)
{
if (_nDockSide == DOCK_LEFT)
::OffsetRect(lprcParent, (m_nAeroOffset * (-1)), m_nAeroOffset);
else if (_nDockSide == DOCK_RIGHT || _nDockSide == DOCK_BOTTOM)
::OffsetRect(lprcParent, m_nAeroOffset, m_nAeroOffset);
else if (_nDockSide == DOCK_TOP)
::OffsetRect(lprcParent, m_nAeroOffset, (m_nAeroOffset * (-1)));
}
#endif
if (_nDockSide == DOCK_LEFT)
{
SetWindowPos(NULL, (lprcParent->left - rcWindow.Width()), lprcParent->top, rcWindow.Width(), rcWindow.Height(), nFlags);
} // DOCK_LEFT
else if (_nDockSide == DOCK_RIGHT)
{
SetWindowPos(NULL, lprcParent->right, lprcParent->top, rcWindow.Width(), rcWindow.Height(), nFlags);
} // DOCK_RIGHT
else if (_nDockSide == DOCK_TOP)
{
SetWindowPos(NULL, lprcParent->left, (lprcParent->top - rcWindow.Height()), rcWindow.Width(), rcWindow.Height(), nFlags);
} // DOCK_TOP
else if (_nDockSide == DOCK_BOTTOM)
{
SetWindowPos(NULL, lprcParent->left, lprcParent->bottom, rcWindow.Width(), rcWindow.Height(), nFlags);
} // DOCK_BOTTOM
}
void Dock(BOOL bActivate)
{
CRect rcParent, rcWindow;
if (m_hwndParent == NULL &&
!::IsWindow(m_hwndParent))
return;
::GetWindowRect(m_hwndParent, &rcParent);
GetWindowRect(&rcWindow);
// Dock to right side of window
if ((rcWindow.left <= (rcParent.right + m_nSnapOffset)) &&
(rcWindow.left >= (rcParent.right - m_nSnapOffset)))
{
if (!m_bIsDocked)
{
m_bIsDocked = TRUE;
m_nDockSide = DOCK_RIGHT;
::PostMessage(m_hwndParent, WM_DOCK_STATUS, (WPARAM)GetSafeHwnd(), MAKELPARAM((WORD)m_bIsDocked, (WORD)DOCK_RIGHT));
}
} // Dock to right side of window
// Dock to left side of window
else if ((rcWindow.right <= (rcParent.left + m_nSnapOffset)) &&
(rcWindow.right >= (rcParent.left - m_nSnapOffset)))
{
if (!m_bIsDocked)
{
m_bIsDocked = TRUE;
m_nDockSide = DOCK_LEFT;
::PostMessage(m_hwndParent, WM_DOCK_STATUS, (WPARAM)GetSafeHwnd(), MAKELPARAM((WORD)m_bIsDocked, (WORD)DOCK_LEFT));
}
} // Dock to left side of window
// Dock to top of window
else if ((rcWindow.bottom <= (rcParent.top + m_nSnapOffset)) &&
(rcWindow.bottom >= (rcParent.top - m_nSnapOffset)))
{
if (!m_bIsDocked)
{
m_bIsDocked = TRUE;
m_nDockSide = DOCK_TOP;
::PostMessage(m_hwndParent, WM_DOCK_STATUS, (WPARAM)GetSafeHwnd(), MAKELPARAM((WORD)m_bIsDocked, (WORD)DOCK_TOP));
}
} // Dock to top side of window
// Dock to bottom side of window
else if ((rcWindow.top <= (rcParent.bottom + m_nSnapOffset)) &&
(rcWindow.top >= (rcParent.bottom - m_nSnapOffset)))
{
if (!m_bIsDocked)
{
m_bIsDocked = TRUE;
m_nDockSide = DOCK_BOTTOM;
::PostMessage(m_hwndParent, WM_DOCK_STATUS, (WPARAM)GetSafeHwnd(), MAKELPARAM((WORD)m_bIsDocked, (WORD)DOCK_BOTTOM));
}
} // Dock to bottom side of window
// Window not docked
else
{
if (m_bIsDocked)
{
m_bIsDocked = FALSE;
m_nDockSide = DOCK_NONE;
::PostMessage(m_hwndParent, WM_DOCK_STATUS, (WPARAM)GetSafeHwnd(),MAKELPARAM((WORD)m_bIsDocked, (WORD)DOCK_NONE));
}
}
// Post a notification message to self to be used if needed
PostMessage(WM_NOTIFY_DOCK, (WPARAM)m_bIsDocked, (LPARAM)m_nDockSide);
}
BOOL IsAeroEnabled()
{
HRESULT aResult = S_OK;
BOOL isEnabled = FALSE;
// Load the dll and keep the handle to it
// Must load dynamically because this dll exists only in vista and newer
HMODULE dwmapiDllHandle = LoadLibrary(L"dwmapi.dll");
if (dwmapiDllHandle != NULL) // not on XP, so do aero calculations
{
DwmIsCompositionEnabledFunc DwmIsCompositionEnabled;
DwmIsCompositionEnabled = (DwmIsCompositionEnabledFunc)
::GetProcAddress(dwmapiDllHandle, "DwmIsCompositionEnabled");
if (DwmIsCompositionEnabled != NULL)
{
aResult = DwmIsCompositionEnabled(&isEnabled);
}
if (!SUCCEEDED(aResult))
isEnabled = FALSE;
}
FreeLibrary(dwmapiDllHandle);
return isEnabled;
}
int GetAeroOffset()
{
const int DIVISOR = -15;
int retVal = AERO_OFFSET;
HKEY m_hKey, m_base = HKEY_CURRENT_USER;
CString m_value, m_key = _T("PaddedBorderWidth"), m_path = _T("Control Panel\\Desktop\\WindowMetrics");
if (RegOpenKeyEx(m_base, m_path, 0, KEY_EXECUTE, &m_hKey) == ERROR_SUCCESS)
{
int size = 0;
DWORD type;
RegQueryValueEx(m_hKey, m_key, NULL, &type, NULL, (LPDWORD)&size);
TCHAR* pStr = new TCHAR[size];
if (RegQueryValueEx(m_hKey, m_key, NULL, &type, (BYTE*)pStr, (LPDWORD)&size) == ERROR_SUCCESS)
{
m_value = CString(pStr);
delete [] pStr;
ASSERT(type == REG_SZ);
RegCloseKey(m_hKey);
int retVal = _ttoi(m_value);
return (retVal / DIVISOR);
}
else
{
delete [] pStr;
}
}
RegCloseKey(m_hKey);
return retVal;
}
}; // class CExtCS : public CExtCSBase
#endif // _CEXTWS_H_