/////////////////////////////////////////////////////////////////////////////
// ATL Window Snapping Framework
//
// Written by Eugene Polonsky (partnerinflight@hotmail.com)
// Copyright (c) 2002 Eugene Polonsky
// Thanks to Bjarne Viksoe for CMouseHover class, and a template of
// this message. :)
//
// This code may be used in compiled form in any way you desire. This
// file may be redistributed unmodified by any means PROVIDING it is
// not sold for profit without the authors written consent, and
// providing that this notice and the authors name is included.
//
// This file is provided "as is" with no expressed or implied warranty.
// The author accepts no liability if it causes any damage to you or your
// computer whatsoever. It's free, so don't hassle me about it.
//
// Beware of bugs. Really beware of bugs. As this code is... well...
// rather complex, there is a VERY good chance that there are bugs
// lurking in here. I will be continually working on improving/updating
// this project, and I'm hoping someone out there will do so as well.
//
#pragma once
#include <math.h>
#ifndef __cplusplus
# error ATL requires C++ compilation (use a .cpp suffix)
#endif
#ifndef __ATLAPP_H__
#error wtlSnapWnd.h requires atlapp.h to be included first
#endif
// need the STL list....
#ifndef _LIST_
# include <list>
#endif
// also need algorithms for std::find
#ifndef _ALGORITHM_
# include <algorithm>
#endif
// fixed. atltypes for VC7, atlmisc for VC6, and everyone's happy.
#if (_ATL_VER < 0x0700)
# include <atlmisc.h>
#else
# include <atltypes.h>
#endif
#pragma warning(disable: 4290) // disable the stupid warning about throw being ignored in some functions.
// notes on this whole thing:
// the framework implements the State pattern in dealing with the changing states of autohidden vs floating vs pinned windows.
// That is, we have a very simple Window class that leaves all the event handling to a particular State class. There are
// two states -- autohidden and floating, and, of course, they do event handling quite differently.
// the framework class is not meant to function as a standalone window, rather it's meant to play a Role in an existing
// window (i.e. you derive from the CSnapFramework just like you would derive from CUpdateUI. You must also do a
// CHAIN_MSG_MAP(CSnapFramework<yourclass>) as the first message macro in your window's message map. This will ensure that
// the framework gets access to its messages first, and doesn't interfere with your message processing.
// notice that you MUST use this framework on a child window -- i.e. you can't use it on a top level frame. This actually
// makes sense if you think about it -- you wouldn't want the framework to mess with a frame's toolbars/menus,
// and this way you can implement the framework in a context of an arbitrarily nested child window. In terms of the
// project I created this for, it's the perfect behavior.
////////////////////////////////////////////////BEGIN MOUSE HOVER CLASS//////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// Mouse Hover helper
// This code is borrowed from the wonderful atldgix.h class by Bjarne Viksoe.
// I'm enclosing his copyright message right here, just in case.
/////////////////////////////////////////////////////////////////////////////
// Additional GDI/USER wrappers
//
// Written by Bjarke Viksoe (bjarke@viksoe.dk)
// Copyright (c) 2001-2002 Bjarke Viksoe.
// Thanks to Daniel Bowen for COffscreenDrawRect.
//
// This code may be used in compiled form in any way you desire. This
// file may be redistributed unmodified by any means PROVIDING it is
// not sold for profit without the authors written consent, and
// providing that this notice and the authors name is included.
//
// This file is provided "as is" with no expressed or implied warranty.
// The author accepts no liability if it causes any damage to you or your
// computer whatsoever. It's free, so don't hassle me about it.
//
// Beware of bugs.
//
// Um, I'm not sure about the "unmodified by any means" bit in there,
// but I hope Bjarne won't mind if I just rip one of the classes from that file....
#ifndef NOTRACKMOUSEEVENT
#ifndef WM_MOUSEENTER
#define WM_MOUSEENTER WM_USER+253
#endif // WM_MOUSEENTER
// define TME_NONCLIENT if we need to... if you defined winver as 5.0, it'll already be defined.
#ifndef TME_NONCLIENT
#define TME_NONCLIENT 0x00000010
#endif
// To use it, derive from it and chain it in the message map...
// Make sure to set bHandled to FALSE when handling WM_MOUSEMOVE or
// the WM_MOUSELEAVE message!!
template< class T, bool tbLeave, bool tbHover, bool tbNCLeave >
class CMouseHover
{
public:
// Internal states
bool m_fMouseOver;
CMouseHover() :
m_fMouseOver(false)
{
}
BEGIN_MSG_MAP(CMouseHover)
MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
MESSAGE_HANDLER(WM_MOUSELEAVE, OnMouseLeave)
END_MSG_MAP()
LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
T* pT = static_cast<T*>(this);
if( !m_fMouseOver ) {
m_fMouseOver = true;
pT->SendMessage(WM_MOUSEENTER, wParam, lParam);
pT->Invalidate();
pT->UpdateWindow();
_StartTrackMouseLeave(pT->m_hWnd);
}
bHandled = FALSE;
return 0;
}
LRESULT OnMouseLeave(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
T* pT = static_cast<T*>(this);
if( m_fMouseOver ) {
m_fMouseOver = false;
pT->Invalidate();
pT->UpdateWindow();
}
bHandled = FALSE;
return 0;
}
BOOL _StartTrackMouseLeave(HWND hWnd)
{
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.dwFlags = 0;
if(tbLeave) tme.dwFlags |= TME_LEAVE;
if(tbHover) tme.dwFlags |= TME_HOVER;
if(tbNCLeave) tme.dwFlags |= TME_NONCLIENT;
tme.hwndTrack = hWnd;
tme.dwHoverTime = 400;
return _TrackMouseEvent(&tme);
}
};
#endif // NOTRACKMOUSEEVENT
///////////////////////////////////////////////////END MOUSE HOVER CLASS//////////////////////////////////////
// some general defines. These change the geometry of the snap bars, geometry of text/whatever drawing
// that sort of stuff. Feel free to mess with it.
#define SNAPBAR_SIZE 24
#define SNAP_DISTANCE 10
#define SNAPBAR_ICON_INTERNAL_OFFSET 3
#define SNAPBAR_ICON_SEPARATION ( 4 + SNAPBAR_ICON_INTERNAL_OFFSET)
#define SNAPBAR_ICON_OFFSET ( 2 + SNAPBAR_ICON_INTERNAL_OFFSET)
#define SNAPPED_WINDOW_CAPTION_SIZE 16
#define SNAPPED_WINDOW_CAPTION_BUTTON_OFFSET_X 4
#define SNAPPED_WINDOW_CAPTION_BUTTON_OFFSET_Y 2
#define SNAPPED_WINDOW_CAPTION_BUTTON_SIZE_XY 12
#define SNAPPED_WINDOW_CAPTION_BUTTON_SEPARATION 4
#define SNAPPED_WINDOW_GRIPPER_SIZE 4
#define UWM_SNAP_WINDOW_DESTROYED WM_APP + 1024
#define TRACKING_TIMER_ID WM_APP
#define SNAPPED_WINDOW_GAP_SIZE 40 // offsets from top/bottom for MINMAXINFO stuff
// a couple utility macros
#define IS_FLOATING(a) ((a) == eFloating)
#define IS_SNAPPED(a) ((a) != eFloating)
#define IS_SNAPPED_HORZ(a) (((a) == eDockedTop) || ((a) == eDockedBottom))
#define IS_SNAPPED_VERT(a) (((a) == eDockedLeft) || ((a) == eDockedRight))
template<class T>
class CSnapFramework
{
private:
typedef CSnapFramework<T> thisClass;
public: // helper definitions
enum eSide { eTop=0, eRight, eBottom, eLeft }; // enumeration for the sides active array
enum eWinState { eNull = -1, eDockedTop=0, eDockedRight, eDockedBottom, eDockedLeft, eFloating }; // enumeration for the state lists
protected:
// we need to subclass the parent window of the framework. We'll walk the parent list to get the topmost parent when
// we do the subclassing. The upshot is that we need that to get the WM_ACTIVATEAPP messages to do correct
// floater hiding/showing.
class CParentWindow : public CWindowImpl<CParentWindow>
{
protected:
// base state class. This dude is an abstract base class out of which we'll define the actual states.
public:
CParentWindow(CSnapFramework<T>* pParent) : m_pParent(pParent)
{
}
BEGIN_MSG_MAP(CParentWindow)
MESSAGE_HANDLER(WM_ACTIVATEAPP, OnActivateApp)
END_MSG_MAP()
protected:
LRESULT OnActivateApp(UINT, WPARAM wParam, LPARAM, BOOL& bHandled)
{
bHandled = FALSE; // let other people handle this too...
// tell the parent to do the appropriate action based on this info....
this->m_pParent->NotifyActivationEvent(wParam == TRUE);
return 0;
}
CSnapFramework<T>* m_pParent;
};
friend class CParentWindow;
// this is the actual window class that'll show people's client classes
class CSnapBarWindow;
class CSnapWindow : public CWindowImpl<CSnapWindow>
{
protected:
// base state class. This dude is an abstract base class out of which we'll define the actual states.
class CSnapWindowStateBase
{
friend class CSnapWindow;
public:
virtual LRESULT OnCreate(CSnapWindow* parent, BOOL& bHandled) = 0;
virtual LRESULT OnEraseBkgnd(CSnapWindow* parent, BOOL& bHandled) =0;
virtual void OnSize(UINT ui, CPoint pt, CSnapWindow* parent, BOOL& bHandled) = 0;
virtual void OnNCLeftButtonDown(UINT ui, CPoint pt, CSnapWindow* parent, BOOL& bHandled)=0;
virtual void OnNCLeftButtonUp(UINT ui, CPoint pt, CSnapWindow* parent, BOOL& bHandled)=0;
virtual void OnMouseMove(UINT uiFlags, CPoint pt, CSnapWindow* parent, BOOL& bHandled) =0;
virtual bool Create(HWND hWndClient, IN eWinState state, bool bHasCloseCheckBox, CSize szSize,
CSnapWindow* pParent)=0;
virtual void OnPaint(CDCHandle dc, CSnapWindow* pParent) =0;
virtual void HandleFrameworkResize(IN CSnapWindow* pParent) =0;
virtual void SwitchState(CSnapWindow* pParent, eWinState eNewState, eWinState eOldState) =0;
virtual LRESULT GetMinMaxInfo(CSnapWindow* pParent, MINMAXINFO* pInfo, BOOL& bHandled) =0;
virtual int GetRequiredRealEstate(CSnapWindow* pParent) =0;
};
// this is the Floating state derivation.
class CSnapWindowStateFloating : public CSnapWindowStateBase
{
public:
// we don't handle on create for this one.
virtual LRESULT OnCreate(CSnapWindow* parent, BOOL& bHandled)
{
bHandled = FALSE;
return 0;
}
// eliminate flicker
virtual LRESULT OnEraseBkgnd(CSnapWindow* parent, BOOL& bHandled)
{
// just a standard anti-flicker return.
return 0;
}
virtual void OnSize(UINT, CPoint, CSnapWindow* parent, BOOL& bHandled)
{
CRect rc;
if(::IsWindow(parent->m_hWnd) == FALSE)
return; // nothing to do here...
// first let's take care of moving our main window
// the sizeset flag is used as a way to notify the window that we want to recompute its rect,
// and resize it.
if(!parent->m_bSizeSet)
{
// let's get the window rect we want for this window....
CRect rcWin = this->GetWindowRect(parent, parent->m_szSize);
// if it's an empty rect (as, for instance, on creation), we do nothing
parent->m_bSizeSet = !rcWin.IsRectEmpty();
if(parent->m_bSizeSet)
{
// once we call MoveWindow OnSize will get called again, and it gets rather nasty....
// this is just a flag to stop that sort of stuff from taking place.
parent->m_bStopOnSizeRecursion = true;
parent->MoveWindow(rcWin);
// bring our window back to the top
parent->SetWindowPos(HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
parent->m_bStopOnSizeRecursion = false;
}
}
// take care of the client window. Resize it to fit the newly sized parent.
CRect rcClient, rcWin;
parent->GetClientRect(rcClient);
parent->GetWindowRect(rcWin);
if(parent->m_bSizeSet)
parent->m_szSize.SetSize(rcWin.Width(), rcWin.Height());
::MoveWindow(parent->m_hwndClient, rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(), TRUE);
}
virtual void OnNCLeftButtonDown(UINT ui, CPoint pt, CSnapWindow* parent, BOOL& bHandled)
{
if(ui == HTCAPTION) // is the user attempting to drag the window?
{
if(::DragDetect(parent->m_hWnd, pt) == 0)
{
bHandled = FALSE;
return; // nope. User didn't drag.
}
// if the user's dragging, let's let them drag.... We'll take them to our custom message loop
// to do it.
parent->m_pParent->HandleDragging(pt, parent);
}
else
bHandled = FALSE;
}
virtual void OnNCLeftButtonUp(UINT ui, CPoint pt, CSnapWindow* parent, BOOL& bHandled)
{
// we don't care about this message here.
bHandled = FALSE;
}
virtual void OnMouseMove(UINT, CPoint pt, CSnapWindow* parent, BOOL& bHandled)
{
// we don't care about this one either. Dragging will be happening in a separate message loop.
bHandled = FALSE;
}
virtual void OnPaint(CDCHandle dc, CSnapWindow* pParent)
{
// we don't do anything when the win's floating.
}
virtual bool Create(HWND hWndClient, IN eWinState state, bool bHasCloseCheckBox,
CSize szSize, CSnapWindow* pParent)
{
CRect rc;
DWORD dwStyle = WS_POPUP|WS_BORDER|WS_CLIPSIBLINGS|WS_OVERLAPPED|WS_THICKFRAME|WS_DLGFRAME|WS_VISIBLE;
DWORD dwExStyle = WS_EX_TOOLWINDOW;
dwStyle |= bHasCloseCheckBox ? WS_SYSMENU : 0;
rc = this->GetWindowRect(pParent, szSize);
pParent->m_bSizeSet = !rc.IsRectEmpty();
HWND hWnd = pParent->Create(NULL, rc, pParent->m_cWindowText, dwStyle, dwExStyle);
if(::IsWindow(hWnd) != FALSE)
{
// let's take care of the client.
::SetParent(hWndClient, hWnd);
CRect rc;
pParent->GetClientRect(rc);
::MoveWindow(hWndClient, rc.left, rc.top, rc.Width(), rc.Height(), TRUE);
// let's create the font we'll be using for the captions, too.
pParent->m_fntCaption.CreatePointFont(80, "Tahoma");
return true;
}
return false;
}
virtual void HandleFrameworkResize(IN CSnapWindow* pParent)
{
BOOL bHandled;
if(!pParent->m_bSizeSet)
this->OnSize(0, CPoint(0,0), pParent, bHandled);
}
CRect GetWindowRect(CSnapWindow* pParent, CSize szDesiredSize)
{
CRect rc, rcWin, rcClient;
// now let's figure out what our rect will be
// ok first we need to figure out client rect of the framework
pParent->m_pParent->m_pT->GetWindowRect(rcWin);
pParent->m_pParent->m_pT->GetClientRect(rcClient);
if(rcClient.IsRectEmpty())
return rcClient; // return an emtpy rect
rc.top = rcWin.top;
rc.left = rcWin.left;
rc.right = rc.left + szDesiredSize.cx;
rc.bottom = rc.top + szDesiredSize.cy;
return rc;
}
virtual void SwitchState(CSnapWindow* pParent, eWinState eNewState, eWinState eOldState)
{
BOOL b;
if((eOldState != eFloating) && (eOldState != eNull))
{
CRect rc;
CPoint pt;
// let's hide....
pParent->ShowWindow(SW_HIDE);
::GetCursorPos(&pt);
rc.SetRect(pt.x, pt.y, pt.x+pParent->m_szSize.cx, pt.y+pParent->m_szSize.cy);
pParent->m_bStopOnSizeRecursion = true;
// means we're switching from WS_CHILD to WS_POPUP and all that that entails
pParent->ModifyStyle(WS_CHILD, WS_POPUP|WS_BORDER|WS_OVERLAPPED|WS_DLGFRAME|WS_THICKFRAME,
SWP_NOSIZE|SWP_NOMOVE);
pParent->SetParent(NULL);
pParent->ModifyStyleEx(0, WS_EX_TOOLWINDOW, SWP_NOSIZE|SWP_NOMOVE);
pParent->MoveWindow(rc);
// we need to show ourselves....
pParent->ShowWindow(SW_SHOW);
pParent->Invalidate();
pParent->m_bStopOnSizeRecursion = false;
pParent->m_bSizeSet = true; // make sure we DON'T try to get the rect...
}
this->OnSize(0, 0, pParent, b); // we need the client moved...
}
virtual int GetRequiredRealEstate(CSnapWindow* pParent) { return 0; } // floaters don't have it.
// we don't mess with this here...
virtual LRESULT GetMinMaxInfo(CSnapWindow* pParent, MINMAXINFO* pInfo, BOOL& bHandled)
{ bHandled = FALSE; return 1; /* I think...*/ }
};
// this is the snapping state derivation
class CSnapWindowStateAuto : public CSnapWindowStateBase
{
public:
virtual LRESULT OnCreate(CSnapWindow* parent, BOOL& bHandled)
{
// we also don't handle creation
bHandled = FALSE;
return 0;
}
virtual LRESULT OnEraseBkgnd(CSnapWindow* parent, BOOL& bHandled)
{
// same deal. standard anti-flicker
return 0;
}
virtual void OnSize(UINT ui, CPoint pt, CSnapWindow* parent, BOOL& bHandled)
{
CRect rcParentClient, rcClient;
if(::IsWindow(parent->m_hWnd) == FALSE) // haven't yet created it
return;
parent->m_pParent->m_pT->GetClientRect(rcParentClient);
// first let's take care of moving our main window
// since I can't seem to be able to get reliable MouseUp notification when
// resizing, I decided to screw the whole mouse thing. So here's how we
// decide what the user is doing. (the user could be resizing the parent window, in which case
// we'd want to actually move this window according to the stored percentage, or the user could
// be resizing this window, in which case we dont' move this window again, but update the percentage.)
// Anyway, the solution's pretty simple. We store the last known ClientRect of the parent. If, in OnSize,
// we find that the current ClientRect doesn't match the stored one, that means the user must've resized
// the parent, and we should resize this window. If the clientRects match, that means the user must be
// resizing us, and we should update the percentage.
if((rcParentClient != parent->m_rcLastParentClientRect) || (!parent->m_bSizeSet)) // or we've NEVER resized...
this->MoveWindow(parent);
// store the clientrect
parent->m_rcLastParentClientRect = rcParentClient;
if(parent->m_bSizeSet)
{
CRect rcWin;
parent->GetWindowRect(rcWin);
// need to modify rcWin -- before we increased the size by 3... now we gotta put it back
if(IS_SNAPPED_HORZ(parent->m_eState))
rcWin.bottom -= 3;
else
rcWin.right -= 3;
parent->GetClientRect(rcClient);
parent->m_szSize.SetSize(
IS_SNAPPED_VERT(parent->m_eState) ? rcWin.Width() : parent->m_szSize.cx,
IS_SNAPPED_HORZ(parent->m_eState) ? rcWin.Height(): parent->m_szSize.cy); // recompute size.
// now execute the actual sizing part
this->DoOnSize(parent, rcClient);
}
bHandled = FALSE;
}
// this function executes common code from CSnapWindowStateAuto and CSnapWindowStatePinned OnSize functions
// basically we're resizing the client, and all the UI elements to fit the new rect
void DoOnSize(IN CSnapWindow* parent, IN const CRect& rcClient)
{
::MoveWindow(parent->m_hwndClient, rcClient.left,
rcClient.top+SNAPPED_WINDOW_CAPTION_SIZE, rcClient.Width(),
rcClient.Height()-SNAPPED_WINDOW_CAPTION_SIZE, TRUE);
// also, we need to calculate the close button, pushpin, and opacity rects
// let's go from right to left, so first we do the close button
parent->m_rcCloseButtonRect.top = rcClient.top + SNAPPED_WINDOW_CAPTION_BUTTON_OFFSET_Y;
parent->m_rcCloseButtonRect.right = rcClient.right - SNAPPED_WINDOW_CAPTION_BUTTON_OFFSET_X;
parent->m_rcCloseButtonRect.bottom = parent->m_rcCloseButtonRect.top + SNAPPED_WINDOW_CAPTION_BUTTON_SIZE_XY;
parent->m_rcCloseButtonRect.left = parent->m_rcCloseButtonRect.right - SNAPPED_WINDOW_CAPTION_BUTTON_SIZE_XY;
// now let's do the pushpin.
parent->m_rcPushPinRect.top = parent->m_rcCloseButtonRect.top;
parent->m_rcPushPinRect.bottom = parent->m_rcCloseButtonRect.bottom;
parent->m_rcPushPinRect.right = parent->m_rcCloseButtonRect.left - SNAPPED_WINDOW_CAPTION_BUTTON_OFFSET_X;
parent->m_rcPushPinRect.left = parent->m_rcPushPinRect.right - SNAPPED_WINDOW_CAPTION_BUTTON_SIZE_XY;
// finally let's do the slider
parent->m_rcOpacitySliderRect.top = parent->m_rcCloseButtonRect.top;
parent->m_rcOpacitySliderRect.bottom = parent->m_rcCloseButtonRect.bottom;
parent->m_rcOpacitySliderRect.right = parent->m_rcPushPinRect.left - SNAPPED_WINDOW_CAPTION_BUTTON_OFFSET_X;
parent->m_rcOpacitySliderRect.left = parent->m_rcOpacitySliderRect.right - SNAPPED_WINDOW_CAPTION_BUTTON_SIZE_XY*3;
}
virtual void OnNCLeftButtonDown(UINT ui, CPoint pt, CSnapWindow* parent, BOOL& bHandled)
{
if(parent->m_rcPushPinRect.PtInRect(pt)) // if we're on the pushpin button...
{
::SetCapture(parent->m_hWnd); // just capture the mouse and see what the user does...
parent->m_bPinButtonPushed = true;
parent->Invalidate(); // redraw though, to reflect state
}
else if(parent->m_rcCloseButtonRect.PtInRect(pt) && parent->m_bHasCloseButton) // if we're in the close button
{ // and the window has the close button enabled
::SetCapture(parent->m_hWnd);
parent->m_bCloseButtonPushed = true;
parent->Invalidate();
}
else
{
bHandled = FALSE; // otherwise i haven't the faintest what the user's doing. Silly user.
parent->m_bLeftButtonDown = true;
}
}
virtual void OnNCLeftButtonUp(UINT ui, CPoint pt, CSnapWindow* parent, BOOL& bHandled)
{
if(parent->m_bPinButtonPushed && parent->m_rcPushPinRect.PtInRect(pt))
{ // if the user mouseupped on the pushpin button...
::ReleaseCapture();
parent->m_bPinButtonPushed = false;
parent->Invalidate();
// assign the new state....
parent->m_pState = !parent->m_bWindowPinned ? &parent->m_cPinnedState : &parent->m_cAutoState;
// and switch to that state
parent->m_pState->SwitchState(parent, parent->m_eState, parent->m_eState);
}
else if(parent->m_bCloseButtonPushed && parent->m_rcCloseButtonRect.PtInRect(pt))
{ // the user mouseupped on the close button.
::ReleaseCapture();
parent->m_bCloseButtonPushed = false;
parent->Invalidate();
// take action
parent->DestroyWindow();
}
else
{ // the user mouseupped elsewhere. not much to do.
bHandled = FALSE;
parent->m_bLeftButtonDown = false;
if(parent->m_bCloseButtonPushed || parent->m_bPinButtonPushed)
{ // if the user previously pushed on of the buttons (and then moved the mouse away)
::ReleaseCapture();
parent->m_bCloseButtonPushed = parent->m_bPinButtonPushed = false;
parent->Invalidate();
}
}
}
virtual void OnMouseMove(UINT uiFlags, CPoint pt, CSnapWindow* parent, BOOL& bHandled)
{
// a quick sanity check...
parent->m_bLeftButtonDown = (uiFlags & MK_LBUTTON) != 0;
if(!parent->m_bLeftButtonDown && (parent->m_bPinButtonPushed || parent->m_bCloseButtonPushed))
{ // if one of the buttons is pushed...
// it shouldn't be.
parent->m_bPinButtonPushed = parent->m_bCloseButtonPushed = false;
parent->Invalidate();
}
if(parent->m_bLeftButtonDown && !(parent->m_bPinButtonPushed || parent->m_bCloseButtonPushed)) // aah. the user must be trying to drag....
{
parent->ClientToScreen(&pt);
parent->m_bLeftButtonDown = false; // just do it in case the mouse goes our of scope....
parent->m_pSnapParent->StartDragging(pt, parent);
}
bHandled = FALSE;
}
virtual void OnPaint(CDCHandle dc, CSnapWindow* pParent)
{
// we need to paint the window bar and all that crap
UINT uiCloseState = DFCS_CAPTIONCLOSE | (pParent->m_bHasCloseButton ? 0 : DFCS_INACTIVE) |
(pParent->m_bCloseButtonPushed ? DFCS_PUSHED : 0);
CRect rcClient;
HFONT hfOld = dc.SelectFont(pParent->m_fntCaption);
pParent->GetClientRect(rcClient);
rcClient.top += SNAPPED_WINDOW_CAPTION_SIZE;
dc.ExcludeClipRect(rcClient);
rcClient.top = 0;
rcClient.bottom = SNAPPED_WINDOW_CAPTION_SIZE;
dc.FillSolidRect(rcClient, ::GetSysColor(COLOR_BTNFACE));
dc.TextOut(rcClient.left + SNAPBAR_ICON_INTERNAL_OFFSET, rcClient.top + SNAPBAR_ICON_INTERNAL_OFFSET,
pParent->m_cWindowText);
dc.SelectFont(hfOld);
// now let's draw the buttons
dc.DrawFrameControl(&pParent->m_rcCloseButtonRect, DFC_CAPTION, uiCloseState);
this->DrawPushpinButton((CDCHandle)dc, pParent->m_bWindowPinned,
pParent->m_rcPushPinRect, pParent->m_bPinButtonPushed);
// finally we need to invalidate the snap bar -- it needs to redraw itself with the right icon
pParent->m_pSnapParent->Invalidate();
}
// This routine is stolen DIRECTLY from Jens Nilsson's article (http://www.codeproject.com/wtl/wtlsnap.asp). I added the
// code to do a vertical pin.
void DrawPushpinButton(CDCHandle dc, bool bVertical, const RECT &rc, bool bPushed)
{
if( ::IsRectEmpty(&rc) )
return;
RECT rcPushpin = rc;
dc.DrawFrameControl(&rcPushpin, DFC_BUTTON, DFCS_ADJUSTRECT|DFCS_BUTTONPUSH | (bPushed ? DFCS_PUSHED : 0));
POINT ptPin = { rcPushpin.left - 1, rcPushpin.top - 1 + (rcPushpin.bottom - rcPushpin.top) / 2};
if (bPushed)
{
ptPin.x += 1;
ptPin.y += 1;
}
POINT ptsPushpin[] = { {ptPin.x, ptPin.y},
{ptPin.x + 2, ptPin.y},
{ptPin.x + 2, ptPin.y - 3},
{ptPin.x + 4, ptPin.y - 1},
{ptPin.x + 5, ptPin.y - 2},
{ptPin.x + 6, ptPin.y - 2},
{ptPin.x + 6, ptPin.y + 2},
{ptPin.x + 5, ptPin.y + 2},
{ptPin.x + 4, ptPin.y + 1},
{ptPin.x + 2, ptPin.y + 3},
{ptPin.x + 2, ptPin.y} };
POINT ptDownPin = { rcPushpin.left -1 + (rcPushpin.right - rcPushpin.left) / 2, rcPushpin.bottom -1 };
if(bPushed)
{
ptDownPin.x += 1;
ptDownPin.y += 1;
}
POINT ptsPushPinDown[] = {
{ ptDownPin.x, ptDownPin.y },
{ ptDownPin.x, ptDownPin.y - 2 },
{ ptDownPin.x - 3, ptDownPin.y - 2 },
{ ptDownPin.x - 3, ptDownPin.y - 3 },
{ ptDownPin.x - 1, ptDownPin.y - 6 },
{ ptDownPin.x - 2, ptDownPin.y - 7 },
{ ptDownPin.x - 2, ptDownPin.y - 8 },
{ ptDownPin.x + 2, ptDownPin.y - 8 },
{ ptDownPin.x + 2, ptDownPin.y - 7 },
{ ptDownPin.x + 1, ptDownPin.y - 6 },
{ ptDownPin.x + 3, ptDownPin.y - 3 },
{ ptDownPin.x + 3, ptDownPin.y - 2 },
{ ptDownPin.x, ptDownPin.y - 2 }
};
if(bPushed)
{
ptDownPin.x += 1;
ptDownPin.y += 1;
}
dc.Polyline(bVertical ? &ptsPushPinDown[0] : &ptsPushpin[0], bVertical ? 13 : 11);
}
virtual bool Create(HWND hWndClient, IN eWinState state,
bool bHasCloseCheckBox, CSize szSize, CSnapWindow* pParent)
{
CRect rc = this->GetWindowRect(pParent, state);
DWORD dwStyle = WS_CHILD|WS_CLIPSIBLINGS|WS_CLIPCHILDREN | WS_THICKFRAME;
DWORD dwExStyle = 0;//WS_EX_WINDOWEDGE;
dwStyle |= bHasCloseCheckBox ? WS_SYSMENU : 0;
pParent->m_bSizeSet = !rc.IsRectEmpty();
HWND hWnd = pParent->Create(pParent->m_pParent->m_pT->m_hWnd, rc, pParent->m_cWindowText, dwStyle, dwExStyle);
if(::IsWindow(hWnd) != FALSE) // ok this is a little bit nuts.
// MSDN says that ::IsWindow returns a NONZERO value as the return. In VC7 that means
// it returns TRUE. In VC6 it returns a WILDLY nonzero value... hell knows why. Oh well.
{
::SetParent(hWndClient, hWnd);
CRect rcTemp;
pParent->GetClientRect(rcTemp);
::MoveWindow(hWndClient, rcTemp.left, rcTemp.top, rcTemp.Width(), rcTemp.Height(), TRUE);
// let's create the font we'll be using for the captions, too.
pParent->m_fntCaption.CreatePointFont(80, "Tahoma");
return true;
}
return false;
}
virtual void HandleFrameworkResize(IN CSnapWindow* pParent)
{
BOOL bHandled;
pParent->m_bSizeSet = false; // we need to force a move, not a recalculation....
this->OnSize(0, CPoint(0,0), pParent, bHandled);
}
virtual CRect GetWindowRect(IN CSnapWindow* pParent, IN eWinState state)
{
CRect rc, rcClient;
pParent->m_pParent->m_pT->GetClientRect(rcClient);
if(rcClient.IsRectEmpty())
return rcClient; // nothing to do
// let's figure out where they want this window
rc.SetRect(state == eDockedRight ? rcClient.right - pParent->m_szSize.cx : rcClient.left,
state == eDockedBottom ? rcClient.bottom - pParent->m_szSize.cy : rcClient.top,
state == eDockedLeft ? rcClient.left + pParent->m_szSize.cx : rcClient.right,
state == eDockedTop ? rcClient.top + pParent->m_szSize.cy : rcClient.bottom);
return rc;
}
virtual void SwitchState(CSnapWindow* pParent, eWinState eNewState, eWinState eOldState)
{
BOOL b;
// first take care of the transition from floating
if((eOldState == eFloating))
{
// switching FROM Floating TO snapped.
pParent->SetParent(pParent->m_pParent->m_pT->m_hWnd);
pParent->ModifyStyleEx(WS_EX_TOOLWINDOW, 0, 0);
pParent->ModifyStyle(WS_POPUP|WS_BORDER|WS_OVERLAPPED|WS_DLGFRAME, WS_CHILD, 0);
pParent->m_bSizeSet = false; // unfortunately the ModifyStyle calls will cause an OnSize which will
// reset that boolean.
pParent->MoveWindow(this->GetWindowRect(pParent, eNewState), TRUE);
}
// now take care of the transition to pinned, if we need to.
if(::IsWindow(pParent->m_hWnd) && pParent->m_bWindowPinned) // dont' bother if we're setting state on creation...
{ // we must be switching from pinned...
pParent->ShowWindow(SW_HIDE); // hide the window TODO: maybe change this?
pParent->ModifyStyle(0, WS_THICKFRAME, 0);
pParent->SetParent(pParent->m_pParent->m_pT->m_hWnd);
pParent->m_pParent->TriggerNCResize();
pParent->m_bWindowPinned = false;
pParent->m_bSizeSet = false;
OnSize(0, 0, pParent, b);
pParent->ShowWindow(SW_SHOW);
// notify the snapbar about this change
pParent->m_pSnapParent->WindowPinnedStateChange(pParent, false);
}
else if(::IsWindow(pParent->m_hWnd))// just a simple side switch
{
pParent->m_bSizeSet = false;
OnSize(0,0, pParent, b);
if(pParent->GetStyle() & WS_VISIBLE) // if we're visible, we need to start tracking....
pParent->m_pSnapParent->StartTrackingWindow(pParent);
}
}
void MoveWindow(CSnapWindow* parent)
{
CRect rcWin = this->GetWindowRect(parent, parent->m_eState);
parent->m_bSizeSet = !rcWin.IsRectEmpty();
if(parent->m_bSizeSet)
{
// we need to stretch out our rect to cover up the thick bars in all places except middle of the
// screen.
bool bHorz = (parent->m_eState == eDockedTop) || (parent->m_eState == eDockedBottom);
rcWin.InflateRect((bHorz || (parent->m_eState == eDockedLeft)) ? 3 : 0,
(bHorz && (parent->m_eState != eDockedTop)) ? 0 : 3,
(bHorz || (parent->m_eState == eDockedRight))? 3 : 0,
(bHorz && (parent->m_eState != eDockedBottom)) ? 0 : 3);
parent->m_bStopOnSizeRecursion = true;
parent->MoveWindow(rcWin);
parent->SetWindowPos(HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
parent->m_bStopOnSizeRecursion = false;
}
}
virtual int GetRequiredRealEstate(CSnapWindow* pParent) { return 0; } // autos don't have it either
virtual LRESULT GetMinMaxInfo(CSnapWindow* pParent, MINMAXINFO* pInfo, BOOL& bHandled)
{
int iTemp[4];
pInfo->ptMinTrackSize.x = pInfo->ptMinTrackSize.y = SNAPPED_WINDOW_GAP_SIZE;
iTemp[eLeft] = pParent->m_pParent->m_arrSnapBarWindows[eLeft].GetStartOffsetForPinnedWindow(pParent);
iTemp[eRight] = pParent->m_pParent->m_arrSnapBarWindows[eRight].GetStartOffsetForPinnedWindow(pParent);
iTemp[eTop] = pParent->m_pParent->m_arrSnapBarWindows[eTop].GetStartOffsetForPinnedWindow(pParent);
iTemp[eBottom] = pParent->m_pParent->m_arrSnapBarWindows[eBottom].GetStartOffsetForPinnedWindow(pParent);
// the max size is the window client area, minus the width of the snapbars/pinned windows, minus the
// gap size we've set
if(IS_SNAPPED_VERT(pParent->m_eState))
pInfo->ptMaxTrackSize.x = pParent->m_pParent->m_arrSnapBarRects[eRight].right -
pParent->m_pParent->m_arrSnapBarRects[eLeft].left -
(iTemp[eLeft]+iTemp[eRight]+SNAPPED_WINDOW_GAP_SIZE);
if(IS_SNAPPED_HORZ(pParent->m_eState))
pInfo->ptMaxTrackSize.y = pParent->m_pParent->m_arrSnapBarRects[eBottom].bottom -
pParent->m_pParent->m_arrSnapBarRects[eTop].top -
(iTemp[eTop]+iTemp[eBottom]+SNAPPED_WINDOW_GAP_SIZE);
return 0;
}
};
// this is the pinned state derivation. Notice it's derived from CSnapWindowStateAuto, not the base.
// that's because the pinned window has a lot of the same characteristics as the snapping window.
class CSnapWindowStatePinned : public CSnapWindowStateAuto
{
public:
virtual void OnSize(UINT ui, CPoint pt, CSnapWindow* parent, BOOL& bHandled)
{
// let's take care of the global window moving stuff first.
CRect rcParentClient, rcClient;
if(::IsWindow(parent->m_hWnd) == FALSE) // haven't yet created it
return;
if(parent->m_bWindowBeingSized == true)
return; // this means we do NOT want internal OnSize to handle this... we must be resizing.
// we need a kind of roundabout way of figuring out the correct coordinates
// problem is, we want to translate the client window's coordinates into its parent's
// coordinate frame. (I talk more about this in the CSnapFramework's OnSize function.)
parent->m_pParent->m_pT->GetWindowRect(rcParentClient);
LPRECT lpRC = (LPRECT)rcParentClient;
::MapWindowPoints(NULL, parent->m_pParent->m_pT->GetParent(), (LPPOINT)lpRC, sizeof(RECT)/sizeof(POINT));
// ok now we can continue...
if((rcParentClient != parent->m_rcLastParentClientRect) || (!parent->m_bSizeSet)) // or we've NEVER resized...
this->MoveWindow(parent);
parent->m_rcLastParentClientRect = rcParentClient;
// now let's deal with our clientrect
parent->GetClientRect(rcClient);
if(parent->m_bSizeSet)
{
CRect rcWin;
parent->GetWindowRect(rcWin);
parent->m_szSize.SetSize(
IS_SNAPPED_VERT(parent->m_eState) ? rcWin.Width() : parent->m_szSize.cx,
IS_SNAPPED_HORZ(parent->m_eState) ? rcWin.Height() : parent->m_szSize.cy); // recompute size.
}
// we need to adjust the client rect to make some room for the resizing bars...
this->AdjustClientRect(rcClient, parent->m_eState);
// ok let the baseclass handle all the silly drawing stuff
this->DoOnSize(parent, rcClient);
}
// the difference between this GetWindowRect and the parent's one is that in this one we need to
// adjust the client rect to discount the size of THIS window
virtual CRect GetWindowRect(IN CSnapWindow* pParent, IN eWinState state,
IN bool bCareAboutPinnedWindows = false)
{
CRect rc, rcClient;
int iTemp[4];
CSize sz = this->GetClientSize(pParent, iTemp);
// we can also use the snapbar rects to base our calculations on, especially since they're already in the
// correct window's coordinate frame
// notice that we're using only eTop and eBottom snapbars for calculations because eRight and eLeft
// are already offset to make room for top/bottom ones. (and can mess up calculations)
switch(state)
{
case eDockedLeft:
rc.left = pParent->m_pParent->m_arrSnapBarRects[eLeft].left + iTemp[eLeft];
rc.top = pParent->m_pParent->m_arrSnapBarRects[eTop].top + iTemp[eTop];
rc.right = rc.left + pParent->m_szSize.cx;
rc.bottom = pParent->m_pParent->m_arrSnapBarRects[eBottom].bottom - iTemp[eBottom];
break;
case eDockedBottom:
rc.left = pParent->m_pParent->m_arrSnapBarRects[eBottom].left +
(pParent->m_pParent->m_arrSidesActive[eLeft] ? SNAPBAR_SIZE : 0);
rc.right = pParent->m_pParent->m_arrSnapBarRects[eBottom].right -
(pParent->m_pParent->m_arrSidesActive[eRight] ? SNAPBAR_SIZE : 0);
rc.bottom = pParent->m_pParent->m_arrSnapBarRects[eBottom].bottom - iTemp[eBottom];
rc.top = rc.bottom - pParent->m_szSize.cy;
break;
case eDockedRight:
rc.right = pParent->m_pParent->m_arrSnapBarRects[eRight].right - iTemp[eRight];
rc.top = pParent->m_pParent->m_arrSnapBarRects[eTop].top + iTemp[eTop];
rc.left = rc.right - pParent->m_szSize.cx;
rc.bottom = pParent->m_pParent->m_arrSnapBarRects[eBottom].bottom - iTemp[eBottom];
break;
case eDockedTop:
rc.left = pParent->m_pParent->m_arrSnapBarRects[eTop].left +
(pParent->m_pParent->m_arrSidesActive[eLeft] ? SNAPBAR_SIZE : 0);
rc.right = pParent->m_pParent->m_arrSnapBarRects[eTop].right -
(pParent->m_pParent->m_arrSidesActive[eRight] ? SNAPBAR_SIZE : 0);
rc.top = pParent->m_pParent->m_arrSnapBarRects[eTop].top + iTemp[eTop];
rc.bottom = rc.top + pParent->m_szSize.cy;
break;
default:
ATLASSERT(false);
break;
}
return rc;
}
// that itemp thing is a bit of a hack -- basically one of the functions that uses this also wants to
// know the actual offsets used calculating size... so we'll copy them into this variable...
CSize GetClientSize(CSnapWindow* pParent, OUT int* iOffsets = NULL)
{
int iTemp[4];
CSize szNCSize, szResult;
// need to get all the offsets. This is basically taking into account all the other pinned windows
// the given snapbar has.
iTemp[eLeft] = pParent->m_pParent->m_arrSnapBarWindows[eLeft].GetStartOffsetForPinnedWindow(pParent);
iTemp[eRight] = pParent->m_pParent->m_arrSnapBarWindows[eRight].GetStartOffsetForPinnedWindow(pParent);
iTemp[eTop] = pParent->m_pParent->m_arrSnapBarWindows[eTop].GetStartOffsetForPinnedWindow(pParent);
iTemp[eBottom] = pParent->m_pParent->m_arrSnapBarWindows[eBottom].GetStartOffsetForPinnedWindow(pParent);
szNCSize.SetSize(iTemp[eLeft] + iTemp[eRight], iTemp[eTop] + iTemp[eBottom]);
szResult.SetSize(pParent->m_pParent->m_arrSnapBarRects[eRight].right -
pParent->m_pParent->m_arrSnapBarRects[eLeft].left - szNCSize.cx,
pParent->m_pParent->m_arrSnapBarRects[eBottom].bottom -
pParent->m_pParent->m_arrSnapBarRects[eTop].top - szNCSize.cy);
if(iOffsets)
memcpy(iOffsets, iTemp, sizeof(iTemp));
return szResult;
}
virtual void OnMouseMove(UINT uiFlags, CPoint pt, CSnapWindow* parent, BOOL& bHandled)
{
CRect rcGripper = this->GetGripperRect(parent);
CSize szDragInflation(::GetSystemMetrics(SM_CXDRAG), ::GetSystemMetrics(SM_CYDRAG));
// inflate the gripper a bit so the user has some leeway...
rcGripper.InflateRect(szDragInflation.cx,szDragInflation.cy,szDragInflation.cx,szDragInflation.cy);
bHandled = FALSE;
// a quick sanity check, just in case we missed a mouse up event somehow.
if(((uiFlags & MK_LBUTTON) == 0) && (parent->m_bLeftButtonDown || parent->m_bWindowBeingSized))
{
parent->m_bLeftButtonDown = parent->m_bWindowBeingSized = parent->m_bStopOnSizeRecursion = false;
if(parent->m_bPinButtonPushed || parent->m_bCloseButtonPushed) // if one of the buttons are pushed...
{
// they shouldn't be.
parent->m_bPinButtonPushed = parent->m_bCloseButtonPushed = false;
parent->Invalidate();
}
}
// don't mess with ANY of this if the user pushed a button...
if(parent->m_bLeftButtonDown && !(parent->m_bPinButtonPushed || parent->m_bCloseButtonPushed))
{
if(rcGripper.PtInRect(pt))
{
// ok... they're trying to resize...
if(::DragDetect(parent->m_hWnd, pt) == 0) // make sure they'll actually move....
return;
::SetCapture(parent->m_hWnd); // ok they're moving.
parent->m_bWindowBeingSized = true;
// the idea here is to completely disable windows sizing here... we want to do sizing ourselves.
// so this flag will do that for us.
parent->m_bStopOnSizeRecursion = true;
::SetCursor(LoadCursor(NULL, IS_SNAPPED_HORZ(parent->m_eState) ? IDC_SIZENS : IDC_SIZEWE));
this->HandleInternalResize(parent, pt);
bHandled = TRUE;
}
else if(parent->m_bWindowBeingSized) // if we're already sizing....
{
::SetCursor(LoadCursor(NULL, IS_SNAPPED_HORZ(parent->m_eState) ? IDC_SIZENS : IDC_SIZEWE));
this->HandleInternalResize(parent, pt);
bHandled = TRUE;
}
else
{
parent->ClientToScreen(&pt);
parent->m_bLeftButtonDown = false; // just do it in case the mouse goes our of scope....
parent->m_pSnapParent->StartDragging(pt, parent);
}
}
else if(rcGripper.PtInRect(pt))
::SetCursor(LoadCursor(NULL, IS_SNAPPED_HORZ(parent->m_eState) ? IDC_SIZENS : IDC_SIZEWE));
}
virtual void SwitchState(CSnapWindow* pParent, eWinState eNewState, eWinState eOldState)
{
BOOL bSwitchFromFloating = eOldState == eFloating; // are we switching from floating?
pParent->ShowWindow(SW_HIDE);
// remove the resizing borders... we'll have to do them ourselves.
// the reason for this is that when we have a normal, autohidden window, we can just hide the three
// unneeded borders (you wouldn't want to resize the window INTO the snapbars, or AWAY from them, right?)
// by expanding the window rect so the snapbars obscure them. This tactic doesn't work here, and we
// have to actually remove all the borders, and then manually add one. The reason it doesn't work is that
// since a pinned window has the same parent as the snapbars, it can actually obscure the snapbars, showing
// its borders.
pParent->ModifyStyle(WS_THICKFRAME | (bSwitchFromFloating ? WS_POPUP|WS_BORDER|WS_OVERLAPPED|WS_DLGFRAME : 0),
bSwitchFromFloating ? WS_CHILD : 0, 0);
if(bSwitchFromFloating)
pParent->ModifyStyleEx(WS_EX_TOOLWINDOW, 0, 0);
pParent->SetParent(pParent->m_pParent->m_pT->GetParent());
pParent->m_bSizeSet = false;
pParent->m_bWindowPinned = true;
OnSize(0, 0, pParent, bSwitchFromFloating); // using it as a dummy bool... by this point we don't care...
// need to make the parent window ncsize, so the client's area can be updated.
pParent->m_pParent->TriggerNCResize();
pParent->ShowWindow(SW_SHOW);
// and notify the snapbar that a pinned window's status has changed.
pParent->m_pSnapParent->WindowPinnedStateChange(pParent, true);
}
void MoveWindow(CSnapWindow* parent)
{
// same idea as in the auto, except the GetWindowRect function's different...
CRect rcWin = this->GetWindowRect(parent, parent->m_eState, true);
parent->m_bSizeSet = !rcWin.IsRectEmpty();
if(parent->m_bSizeSet)
{
parent->m_bStopOnSizeRecursion = true;
parent->MoveWindow(rcWin);
parent->SetWindowPos(HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
parent->m_bStopOnSizeRecursion = false;
}
}
// figure out how much NC area this window's taking up. We only need one dimension, which depends
// on what side the window's docked at.
virtual int GetRequiredRealEstate(CSnapWindow* pParent)
{
CRect rc;
pParent->GetClientRect(rc);
switch(pParent->m_eState)
{
case eDockedTop:
case eDockedBottom:
return rc.Height();
break;
case eDockedLeft:
case eDockedRight:
return rc.Width();
break;
default:
ATLASSERT(false);
return 0;
break;
}
}
// adjusts the client rect to leave space for the gripper.
void AdjustClientRect(IN OUT CRect& rcClient, eWinState eState)
{
switch(eState)
{
case eDockedTop:
rcClient.bottom -= SNAPPED_WINDOW_GRIPPER_SIZE;
break;
case eDockedLeft:
rcClient.right -= SNAPPED_WINDOW_GRIPPER_SIZE;
break;
case eDockedBottom:
rcClient.top += SNAPPED_WINDOW_GRIPPER_SIZE;
break;
case eDockedRight:
rcClient.left += SNAPPED_WINDOW_GRIPPER_SIZE;
break;
default:
ATLASSERT(false);
break;
}
}
virtual void OnPaint(CDCHandle dc, CSnapWindow* pParent)
{
int iOldTop;
// we need to paint the window bar and all that crap
UINT uiCloseState = DFCS_CAPTIONCLOSE | (pParent->m_bHasCloseButton ? 0 : DFCS_INACTIVE) |
(pParent->m_bCloseButtonPushed ? DFCS_PUSHED : 0);
CRect rcClient;
HFONT hfOld = dc.SelectFont(pParent->m_fntCaption);
pParent->GetClientRect(rcClient);
// adjust the client rect for the gripper
this->AdjustClientRect(rcClient, pParent->m_eState);
// remember the old top, since we're about to modify it to calculate the excluderect.
iOldTop = rcClient.top;
rcClient.top += SNAPPED_WINDOW_CAPTION_SIZE;
dc.ExcludeClipRect(rcClient);
rcClient.top = iOldTop;
rcClient.bottom = rcClient.top + SNAPPED_WINDOW_CAPTION_SIZE;
dc.FillSolidRect(rcClient, ::GetSysColor(COLOR_BTNFACE));
dc.TextOut(rcClient.left + SNAPBAR_ICON_INTERNAL_OFFSET,
rcClient.top + SNAPBAR_ICON_INTERNAL_OFFSET, pParent->m_cWindowText);
dc.SelectFont(hfOld);
// now let's draw the buttons
dc.DrawFrameControl(&pParent->m_rcCloseButtonRect, DFC_CAPTION, uiCloseState);
this->DrawPushpinButton((CDCHandle)dc, pParent->m_bWindowPinned,
pParent->m_rcPushPinRect, pParent->m_bPinButtonPushed);
// now we need to paint the gripper
CRect rcGripper = this->GetGripperRect(pParent);
dc.FillSolidRect(rcGripper, ::GetSysColor(COLOR_BTNFACE));
// finally we need to invalidate the snap bar -- it needs to redraw itself with the right icon
pParent->m_pSnapParent->Invalidate();
}
// figure out the actual rect of the gripper
CRect GetGripperRect(CSnapWindow* pParent)
{
CRect rcGripper;
pParent->GetClientRect(rcGripper);
switch(pParent->m_eState)
{
case eDockedTop:
rcGripper.top = rcGripper.bottom - SNAPPED_WINDOW_GRIPPER_SIZE;
break;
case eDockedBottom:
rcGripper.bottom = rcGripper.top + SNAPPED_WINDOW_GRIPPER_SIZE;
break;
case eDockedLeft:
rcGripper.left = rcGripper.right - SNAPPED_WINDOW_GRIPPER_SIZE;
break;
case eDockedRight:
rcGripper.right = rcGripper.left + SNAPPED_WINDOW_GRIPPER_SIZE;
break;
default:
ATLASSERT(false);
break;
}
return rcGripper;
}
virtual void HandleFrameworkResize(IN CSnapWindow* pParent)
{
BOOL bHandled;
pParent->m_bSizeSet = false; // we need to force a move, not a recalculation....
this->OnSize(0, CPoint(0,0), pParent, bHandled);
}
// the window's being resized.
void HandleInternalResize(CSnapWindow* parent, CPoint pt)
{
CRect rc, rcOld;
CSize sz = GetClientSize(parent);
parent->GetClientRect(rc);
rcOld.CopyRect(rc);
//convert it to screen...
::MapWindowPoints(parent->m_hWnd, NULL, (LPPOINT)((LPRECT)rc), sizeof(RECT)/sizeof(POINT));
// convert the point to screen too
parent->ClientToScreen(&pt);
switch(parent->m_eState)
{
case eDockedLeft:
rc.right = pt.x;
break;
case eDockedRight:
rc.left = pt.x;
break;
case eDockedTop:
rc.bottom = pt.y;
break;
case eDockedBottom:
rc.top = pt.y;
break;
}
parent->m_szSize.SetSize(
IS_SNAPPED_VERT(parent->m_eState) ?
__min(__max(rc.Width(),SNAPPED_WINDOW_GAP_SIZE), sz.cx-SNAPPED_WINDOW_GAP_SIZE) :
parent->m_szSize.cx,
IS_SNAPPED_HORZ(parent->m_eState) ?
__min(__max(rc.Height(), SNAPPED_WINDOW_GAP_SIZE), sz.cy - SNAPPED_WINDOW_GAP_SIZE) :
parent->m_szSize.cy);
// we have to do this again b/c we just gated the percentage...
rc = this->GetWindowRect(parent, parent->m_eState, true);
parent->m_bSizeSet = false;
parent->MoveWindow(rc);
// now let's trigger an NCSize
parent->m_pParent->TriggerNCResize();
// now let's resize the base elements of the window
parent->GetClientRect(rc);
// we need to adjust the client rect to make some room for the resizing bars...
this->AdjustClientRect(rc, parent->m_eState);
// ok let the baseclass handle all the silly drawing stuff
this->DoOnSize(parent, rc);
parent->SendMessage(WM_PAINT);
parent->m_pParent->m_pT->SendMessage(WM_PAINT); // repaint the parent too.
// let's intelligently repaint the opposite side windows
parent->m_pParent->m_arrSnapBarWindows[eRight].ForceRepaintOfChildWindows();
parent->m_pParent->m_arrSnapBarWindows[eLeft].ForceRepaintOfChildWindows();
parent->m_pParent->m_arrSnapBarWindows[eTop].ForceRepaintOfChildWindows();
parent->m_pParent->m_arrSnapBarWindows[eBottom].ForceRepaintOfChildWindows();
}
virtual void OnNCLeftButtonUp(UINT ui, CPoint pt, CSnapWindow* parent, BOOL& bHandled)
{
parent->m_bStopOnSizeRecursion = false; // just in case...
if(parent->m_bWindowBeingSized)
{
::ReleaseCapture();
parent->m_bWindowBeingSized = false;
parent->m_bLeftButtonDown = false;
}
else
CSnapWindowStateAuto::OnNCLeftButtonUp(ui, pt, parent, bHandled);
}
virtual void OnNCLeftButtonDown(UINT ui, CPoint pt, CSnapWindow* parent, BOOL& bHandled)
{
CRect rcGripper = this->GetGripperRect(parent);
if(rcGripper.PtInRect(pt))
{
parent->m_bLeftButtonDown = true;
::SetCursor(LoadCursor(NULL, IS_SNAPPED_HORZ(parent->m_eState) ? IDC_SIZENS : IDC_SIZEWE));
}
else
CSnapWindowStateAuto::OnNCLeftButtonDown(ui, pt, parent, bHandled);
}
};
// let's give these two classes full access to their parent.
friend class CSnapWindowStateFloating;
friend class CSnapWindowStateAuto;
friend class CSnapWindowStatePinned;
public:
CSnapWindow(eWinState eState, HWND hwndClient,
CSnapBarWindow* pSnapParent, CSnapFramework<T>* pParent) : m_bSizeSet(false),
m_pSnapParent(pSnapParent), m_bLeftButtonDown(false), m_pState(NULL), m_pParent(pParent), m_hwndClient(hwndClient),
m_bStopOnSizeRecursion(false), m_bCloseButtonPushed(false), m_bHasCloseButton(true), m_bPinButtonPushed(false),
m_bWindowPinned(false), m_bWindowBeingSized(false), m_eState(eNull)
{
// set the designated state.
this->SetState(eState);
}
void SetState(eWinState eState)
{
eWinState eOldState = m_eState;
// set the required state, and then do a stateswitch
m_pState = (eState == eFloating) ? (CSnapWindowStateBase*)&this->m_cFloatState :
(m_bWindowPinned ? (CSnapWindowStateBase*)&this->m_cPinnedState : (CSnapWindowStateBase*)&this->m_cAutoState);
this->m_eState = eState;
m_pState->SwitchState(this, eState, eOldState);
}
// creates a window based on the current state.
bool CreateSnapWindow(IN HWND hWndClient, IN CSize szSize, IN bool bHasCloseCheckBox)
{
m_hwndClient = hWndClient;
m_bHasCloseButton = bHasCloseCheckBox;
::GetWindowText(hWndClient, m_cWindowText, 1024);
this->m_szSize = szSize;
return this->m_pState->Create(hWndClient, m_eState, bHasCloseCheckBox, szSize, this); // let the state handle this...
}
void FrameworkResized()
{
this->m_pState->HandleFrameworkResize(this);
}
// paints the individual icon
// note that the icon can go either on the snapbar (using the stored rect), or can be used for dragging
// (in which case bUseStoredRect will be false, and we'll use a 0,0 as start point.)
void PaintSnapBarIcon(IN CDCHandle dc, IN COLORREF clrCaptionBack, IN COLORREF clrCaptionDown,
IN CBrushHandle brushCaptionOutline,
IN bool bHorz, IN bool bUseStoredRect)
{
CRect rc;
int iMaxChars;
CSize sz;
char* cWindowText = m_cWindowText; // yes, just a simple pointer assignment so far...
CPoint pt;
// is the mouse inside this window? it is if we're visible (otherwise we wouldn't be....), or if
// we're pinned.
bool bInside = ((this->GetStyle() & WS_VISIBLE) || this->m_bWindowPinned) && bUseStoredRect;
// this is used when we're calculating a rect from scratch, i.e. when we're dragging.
// if the user, for instance, wants this icon drawn vertically, and we normally draw it horizontally,
// then we'll set this, and invert our stored rect's width and height in calculating the new rect.
bool bNeedToInvert = (bHorz && (this->m_rcSnapBarCoords.Height() > this->m_rcSnapBarCoords.Width())) ||
(!bHorz && (this->m_rcSnapBarCoords.Width() > this->m_rcSnapBarCoords.Height()));
if(!bUseStoredRect)
rc.SetRect(0, 0, bNeedToInvert ? this->m_rcSnapBarCoords.Height() : this->m_rcSnapBarCoords.Width(),
bNeedToInvert ? this->m_rcSnapBarCoords.Width() : this->m_rcSnapBarCoords.Height());
else
rc.CopyRect(this->m_rcSnapBarCoords);
dc.FillSolidRect(rc, bInside ? clrCaptionDown : clrCaptionBack);
dc.FrameRect(rc, brushCaptionOutline);
// we may need to modify the string a bit to wrap it correctly. This is what END_ELLIPSIS in DrawText would
// do for us, but unfortunately we don't have it in TextOut, and DrawText doesn't draw vertical text.
dc.DPtoLP(&rc);
::GetTextExtentExPoint(dc, m_cWindowText, strlen(m_cWindowText),
bHorz ? rc.Width()-SNAPBAR_ICON_INTERNAL_OFFSET*2 : rc.Height() - SNAPBAR_ICON_INTERNAL_OFFSET*2,
&iMaxChars, NULL, &sz);
iMaxChars = __max(iMaxChars, 0);
if((size_t)iMaxChars < strlen(m_cWindowText)) // gate the new text if we have to.
{
cWindowText = new char[__max(iMaxChars + 1, 1)];
memcpy(cWindowText, m_cWindowText, iMaxChars);
cWindowText[iMaxChars] = 0;
if(iMaxChars - 3 > 1)
{
cWindowText[iMaxChars-3] = 0;
strcat(cWindowText, "...");
}
else
cWindowText[0] = 0;
}
dc.LPtoDP(rc);
dc.TextOut(bHorz ? rc.left + SNAPBAR_ICON_INTERNAL_OFFSET : rc.right - SNAPBAR_ICON_INTERNAL_OFFSET,
rc.top+SNAPBAR_ICON_INTERNAL_OFFSET, cWindowText);
if(cWindowText != this->m_cWindowText) // simple pointer comparison
delete [] cWindowText;
}
const CRect& GetSnapBarIconRect() const { return m_rcSnapBarCoords; }
eWinState GetWinState() { return m_eState; }
void ComputeSnapBarRect(IN CDCHandle dc, IN bool bHorz, IN OUT CPoint& pt)
{
// ok this is fairly easy... just get the window text, find out how many pixels it'll take... that's it
CSize sz;
dc.GetTextExtent(m_cWindowText, strlen(m_cWindowText), &sz);
// now let's calculate our rect
this->m_rcSnapBarCoords.SetRect(pt.x, pt.y,
pt.x + (bHorz ? sz.cx : sz.cy), pt.y + (bHorz ? sz.cy : sz.cx));
// now pad the rectangle w/ the offsets
this->m_rcSnapBarCoords.InflateRect(SNAPBAR_ICON_INTERNAL_OFFSET,
SNAPBAR_ICON_INTERNAL_OFFSET,
SNAPBAR_ICON_INTERNAL_OFFSET,
SNAPBAR_ICON_INTERNAL_OFFSET);
pt.x += bHorz ? this->m_rcSnapBarCoords.Width() : 0;
pt.y += bHorz ? 0 : this->m_rcSnapBarCoords.Height();
}
// if the snapbar decided it can't fit all the windows, it'll start squeezing the rects.
void SqueezeSnapBarRect(IN int iSqueezeAmount, IN bool bHorz, IN OUT CPoint& pt)
{
// first offset the rect to the new point
this->m_rcSnapBarCoords.OffsetRect(bHorz ? pt.x - m_rcSnapBarCoords.left : 0,
bHorz ? 0 : pt.y - m_rcSnapBarCoords.top);
// now squeeze it
this->m_rcSnapBarCoords.right -= bHorz ? iSqueezeAmount : 0;
this->m_rcSnapBarCoords.bottom -= bHorz ? 0 : iSqueezeAmount;
if(bHorz)
pt.x = this->m_rcSnapBarCoords.right;
else
pt.y = this->m_rcSnapBarCoords.bottom;
}
void SetSnapBarParent(CSnapBarWindow* pParent) { this->m_pSnapParent = pParent; }
int GetRequiredRealEstate() { return this->m_pState->GetRequiredRealEstate(this); }
bool IsPinned() { return m_bWindowPinned; }
BEGIN_MSG_MAP(CSnapWindow)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_NCLBUTTONDOWN, OnNCLeftButtonDown)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnNCLeftButtonDown)
MESSAGE_HANDLER(WM_NCLBUTTONUP, OnNCLeftButtonUp)
MESSAGE_HANDLER(WM_LBUTTONUP, OnNCLeftButtonUp)
MESSAGE_HANDLER(WM_SIZE, OnSize)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
MESSAGE_HANDLER(WM_NCMOUSEMOVE, OnMouseMove)
MESSAGE_HANDLER(WM_GETMINMAXINFO, OnGetMinMaxInfo)
END_MSG_MAP()
protected:
// all these basically pass the message off to the current state to handle it. This is basically
// the crux of the state design pattern.
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
return this->m_pState->OnCreate(const_cast<CSnapWindow*>(this), bHandled);
}
LRESULT OnEraseBkgnd(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
return this->m_pState->OnEraseBkgnd(this, bHandled);
}
LRESULT OnSize(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if(!this->m_bStopOnSizeRecursion)
this->m_pState->OnSize(wParam, CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)), this, bHandled);
return 0;
}
LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
CPaintDC dc(m_hWnd);
this->m_pState->OnPaint((CDCHandle)dc, this);
return 0;
}
LRESULT OnNCLeftButtonDown(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
this->m_pState->OnNCLeftButtonDown(wParam, CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)), this, bHandled);
return 0;
}
LRESULT OnNCLeftButtonUp(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
this->m_pState->OnNCLeftButtonUp(wParam, CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)), this, bHandled);
return 0;
}
LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
::DestroyWindow(m_hwndClient);
::PostMessage(this->m_pParent->m_pT->m_hWnd, UWM_SNAP_WINDOW_DESTROYED, NULL, (LPARAM)this);
return 0;
}
LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
this->m_pState->OnMouseMove(wParam, CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)), this, bHandled);
return 0;
}
LRESULT OnGetMinMaxInfo(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
this->m_pState->GetMinMaxInfo(this, (MINMAXINFO*)lParam, bHandled);
return 0;
}
// lotsa lotsa variables.... :)
CSnapWindowStateBase* m_pState; // current state
HWND m_hwndClient; // the client window.
// notice I could've made these static... but these classes are pretty small (no member vars at all), and I figured
// why clutter people up with basically global variables?
CSnapWindowStateAuto m_cAutoState; // an instance of the auto state.
CSnapWindowStateFloating m_cFloatState; // an instance of the float state
CSnapWindowStatePinned m_cPinnedState; // an instance of the pinned state
CSnapFramework<T>* m_pParent; // the framework
CSize m_szSize; // size of the window. depending on state, one of the dimensions
// might be ignored.
bool m_bSizeSet; // is this window sized correctly?
eWinState m_eState; // current window state
CRect m_rcSnapBarCoords; // coordinates of this window on the snap bar (if used)
CFont m_fntCaption; // the font used for the caption
char m_cWindowText[1024]; // just stores the window text so we don't need
// to retrieve it all the time
CSnapBarWindow* m_pSnapParent; // the snapbar parent, if any
bool m_bLeftButtonDown; // flag if left button's down
bool m_bStopOnSizeRecursion; // flag to stop OnSize recursion
CRect m_rcLastParentClientRect; // last client rect. (see explanation in CSnapWindowStateAuto::OnSize)
CRect m_rcCloseButtonRect; // location of the close button
CRect m_rcPushPinRect; // location of the pushpin button
CRect m_rcOpacitySliderRect; // NOT IMPLEMENTED: location of the opacity slider rect
bool m_bHasCloseButton; // does this window have a close button?
bool m_bCloseButtonPushed; // is the button pushed?
bool m_bPinButtonPushed; // is the pin button pushed?
bool m_bWindowPinned; // is the window pinned?
bool m_bWindowBeingSized; // is it being resized?
};
typedef std::list<CSnapWindow*> tSnapWindowList;
// this is the horizontal/vertical window that'll draw all the buttons
class CSnapBarWindow : public CWindowImpl<CSnapBarWindow>, public CMouseHover<CSnapBarWindow, true, true, false>
{
typedef CMouseHover<CSnapBarWindow, true, true, false> tSnapBarHover;
public:
CSnapBarWindow() : m_itCurWin(this->m_lstSnappedWindows.end()), m_bLButtonDown(false),
m_bScheduleRepaint(false)
{
}
~CSnapBarWindow()
{
tSnapWindowList::iterator i;
// kill all our windows if any are left.
for(i=this->m_lstSnappedWindows.begin(); i!=this->m_lstSnappedWindows.end(); i++)
{
if(::IsWindow((*i)->m_hWnd))
(*i)->DestroyWindow();
delete (*i);
}
}
// this really should be in the constructor, but since I have NO idea how to initialize
// an array of classes when there's no default constructor (hey, C++ buffs, is it possible?),
// I'm forced to use this rather kludgy way. Oh well, at least this class isn't exposed to the outside.
void SetOptions(CSnapFramework<T>* pParent, eSide side)
{
this->m_pParent = pParent;
this->m_bHorz = (side == eTop) || (side == eBottom);
this->m_eSide = side;
this->SetColors();
}
DECLARE_WND_CLASS(NULL)
BEGIN_MSG_MAP(CSnapBarWindow)
CHAIN_MSG_MAP(tSnapBarHover)
MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
MESSAGE_HANDLER(WM_SIZE, OnSize)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingsChange)
MESSAGE_HANDLER(WM_MOUSEHOVER, OnMouseHover)
MESSAGE_HANDLER(WM_MOUSELEAVE, OnMouseLeave)
END_MSG_MAP()
void AddWindowToBar(IN CSnapWindow* pWin)
{
BOOL bHandled;
// stick this window into our list
this->m_lstSnappedWindows.push_back(pWin);
// tell the window that we're its parent now
pWin->SetSnapBarParent(this);
// schedule ourselves for a repaint (whenever the framework feels like checking it...)
this->m_bScheduleRepaint = true;
// let's trigger a forced resize too -- we need to recalculate the rects of the icons
this->OnSize(0, 0, 0, bHandled);
}
bool RemoveWindowFromBar(IN CSnapWindow* pWin, bool bDestroyWindow)
{
// let's find that window in our list
tSnapWindowList::iterator i = std::find(this->m_lstSnappedWindows.begin(),
this->m_lstSnappedWindows.end(), pWin);
BOOL bHandled;
if(i == this->m_lstSnappedWindows.end())
return false;
// if we want the window destroyed, too....
if(bDestroyWindow)
{
// ok now let's destroy this window
pWin->DestroyWindow();
delete pWin;
}
else // otherwise just reset its snapbar parent
pWin->SetSnapBarParent(NULL);
if(i == this->m_itCurWin) // oh crap, we were tracking this window....
{
// tell the parent to quit tracking
this->m_pParent->ForceEndTracking();
this->m_itCurWin = this->m_lstSnappedWindows.end();
}
this->m_lstSnappedWindows.erase(i);
this->m_bScheduleRepaint = true;
// if this window was pinned and not destroyed, we need to trigger an NCSize...
if(pWin->IsPinned() && !bDestroyWindow)
{
this->m_pParent->TriggerNCResize();
this->m_pParent->m_pT->Invalidate();
}
// let's trigger a forced resize too -- we need to recalculate the rects of the icons
this->OnSize(0, 0, 0, bHandled);
return true;
}
// this gets called by the framework when it thinks that the window should be hidden
void CursorLeftTrackingWindow()
{
ATLASSERT(this->m_itCurWin != this->m_lstSnappedWindows.end());
// just a sanity check. I've seen this happen for some strange reason... let's make sure we don't crash
// if it does...
if(this->m_itCurWin == this->m_lstSnappedWindows.end())
{
this->m_pParent->ForceEndTracking(); // let's tell the parent window to stop tracking this....
return;
}
// we need to check if the cursor is in a valid location, actually.
// let's just get the rect of the client window, and see if the cursor's in it.
CPoint pt;
CRect rc;
::GetCursorPos(&pt);
(*this->m_itCurWin)->ScreenToClient(&pt);
(*this->m_itCurWin)->GetClientRect(rc);
if(rc.PtInRect(pt))
return; // don't stop the timer
else
(*this->m_itCurWin)->ClientToScreen(&pt); // translate back for the next test.
// um.... let's check if the cursor isn't actually IN the exact right square on the snapbar...
this->ScreenToClient(&pt);
if((*this->m_itCurWin)->GetSnapBarIconRect().PtInRect(pt))
{
this->m_pParent->ForceEndTracking(); // let's tell the parent window to stop tracking this....
return; // nothing to do... we'll restart the tracker when the mouse leaves the bar again
}
// ok. Now we can hide the window and stop tracking.
(*this->m_itCurWin)->ShowWindow(SW_HIDE);
this->m_itCurWin = this->m_lstSnappedWindows.end();
this->m_pParent->ForceEndTracking(); // let's tell the parent window to stop tracking this....
// invalidate ourselves, too... to redraw the paint icon
// invalidate ourselves, since we'll need to redraw the rect
this->Invalidate();
}
void StartDragging(CPoint ptStartPoint, CSnapWindow* pWin)
{
// let the framework handle this...
if(::DragDetect(this->m_hWnd, ptStartPoint) == 0)
return; // nope. User didn't drag.
this->m_pParent->HandleDragging(ptStartPoint, pWin);
}
int GetNumSnappedWindows() { return this->m_lstSnappedWindows.size(); }
bool CheckRepaint()
{
if(this->m_bScheduleRepaint)
{
m_bScheduleRepaint = false;
return true;
}
return false;
}
// this returns the width or height (depending on the side the snapbar's on) of the snapbar, plus
// all of its pinned windows.
int GetRequiredArea()
{
int iResult = this->m_pParent->m_arrSidesActive[this->m_eSide] ? SNAPBAR_SIZE : 0;
tSnapWindowList::iterator i;
for(i=this->m_lstSnappedWindows.begin(); i!=this->m_lstSnappedWindows.end(); i++)
iResult += (*i)->GetRequiredRealEstate();
return iResult;
}
//tell all our windows that framework resized.
void NotifyFrameworkResized()
{
for(tSnapWindowList::iterator i=this->m_lstSnappedWindows.begin(); i!=this->m_lstSnappedWindows.end(); i++)
(*i)->FrameworkResized();
}
// handle pinned state change
void WindowPinnedStateChange(CSnapWindow* pWin, bool bPinned)
{
if(bPinned)
{
if((this->m_itCurWin != this->m_lstSnappedWindows.end()) && (*this->m_itCurWin) == pWin)
this->m_itCurWin = this->m_lstSnappedWindows.end();
this->m_pParent->ForceEndTracking();
}
else
{
if(this->m_itCurWin == this->m_lstSnappedWindows.end())
{
this->m_itCurWin = std::find(this->m_lstSnappedWindows.begin(), this->m_lstSnappedWindows.end(), pWin);
ATLASSERT(this->m_itCurWin != this->m_lstSnappedWindows.end());
this->m_pParent->StartTrackingWindow(this->m_eSide);
}
}
}
// this returns only the area BETWEEN the snapbar (including the snapbar) and the window passed in
int GetStartOffsetForPinnedWindow(CSnapWindow* pWin)
{
tSnapWindowList::iterator i;
int iResult = this->m_pParent->m_arrSidesActive[this->m_eSide] ? SNAPBAR_SIZE : 0;
for(i=this->m_lstSnappedWindows.begin(); (i!=this->m_lstSnappedWindows.end()) && ((*i)!=pWin); i++)
iResult += (*i)->GetRequiredRealEstate();
return iResult;
}
void ForceRepaintOfChildWindows()
{
tSnapWindowList::iterator i;
for(i=this->m_lstSnappedWindows.begin(); i!=this->m_lstSnappedWindows.end(); i++)
(*i)->SendMessage(WM_PAINT);
}
void StartTrackingWindow(CSnapWindow* pWin) // gets called by the window when it thinks it should be tracked.
{
tSnapWindowList::iterator i = std::find(this->m_lstSnappedWindows.begin(), this->m_lstSnappedWindows.end(), pWin);
ATLASSERT( i != this->m_lstSnappedWindows.end());
if(i == this->m_lstSnappedWindows.end()) // crash proof
return; // don't do anything.
ATLASSERT(this->m_itCurWin == this->m_lstSnappedWindows.end()); // shouldn't be one already being tracked.
this->m_itCurWin = i;
this->m_pParent->StartTrackingWindow(this->m_eSide);
}
protected:
// just sets all the colors we'll be using.
void SetColors()
{
this->m_rgbBackground = ::GetSysColor(COLOR_BTNFACE);
this->m_rgbCaptionOutline = ::GetSysColor(COLOR_BTNSHADOW);
this->m_rgbCaptionBackground = ::GetSysColor(COLOR_INACTIVEBORDER);
this->m_rgbCaptionPressedBackground = ::GetSysColor(COLOR_BTNSHADOW);
}
// reset our colors.
LRESULT OnSettingsChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
this->SetColors();
this->Invalidate();
return 0;
}
LRESULT OnEraseBkgnd(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{ // standard anti-flicker.
return 0;
}
LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
this->m_bLButtonDown = true;
return 0;
}
LRESULT OnLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
this->m_bLButtonDown = false;
return 0;
}
LRESULT OnMouseLeave(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{ // if the mouse left the snapbar, and we're supposed to be tracking something, tell the framework
if(this->m_itCurWin != this->m_lstSnappedWindows.end()) // to start tracking
this->m_pParent->StartTrackingWindow(this->m_eSide);
return 0;
}
LRESULT OnMouseHover(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
tSnapWindowList::iterator i;
CPoint pt(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
// see if the user hovered over a window... if so, let's show it.
for(i=this->m_lstSnappedWindows.begin(); i!=this->m_lstSnappedWindows.end(); i++)
{
if(((*i)->GetSnapBarIconRect().PtInRect(pt)) && // are we inside a particular rect?
(!(*i)->IsPinned()) && // is this rect representing a pinned window? Don't mess with it then
(i!=this->m_itCurWin)) // is this rect representing a currently tracking window? Why bother then?
{
// ok we found our window. let's make it visible.
// first let's check if we already have a visible window...
if(this->m_itCurWin != this->m_lstSnappedWindows.end())
this->CursorLeftTrackingWindow();
// let's see if this window's already selected....
if(i != this->m_itCurWin)
{
(*i)->ShowWindow(SW_SHOW);
(*i)->SetWindowPos(HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
this->m_itCurWin = i;
}
m_fMouseOver = false;
return 0;
}
}
m_fMouseOver = false;
return 0;
}
LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
CPoint pt(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
if(this->m_bLButtonDown) // oh oh.... we're drrrrrrragging!
{
tSnapWindowList::iterator i;
for(i = this->m_lstSnappedWindows.begin(); i!=this->m_lstSnappedWindows.end(); i++)
{
if((*i)->GetSnapBarIconRect().PtInRect(pt))
{
this->ClientToScreen(&pt);
this->StartDragging(pt, (*i));
return 0;
}
}
// didn't find anything... oh well.
this->m_bLButtonDown = false;
}
else if(this->m_pParent->m_bTrackingWindow) // if we're tracking...
this->m_pParent->SnapBarMouseMove(pt, this->m_eSide);
return 0;
}
LRESULT OnSize(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
CRect rc;
this->GetClientRect(rc);
if(rc.IsRectEmpty())
return 0; // nothing to do....
// let's tell all our windows that there's been a resize
tSnapWindowList::iterator i;
CDCHandle dc = this->GetDC();
HFONT hfOld = dc.SelectFont(this->m_bHorz ? this->m_pParent->m_fntHorizontal : this->m_pParent->m_fntVertical);
// now for the hard part. We need to compute all the snapbar rects....
// well, not really that hard, since every window computes its own rect
CPoint ptStart;
ptStart.SetPoint(rc.left+SNAPBAR_ICON_OFFSET, rc.top+SNAPBAR_ICON_OFFSET);
for(i = this->m_lstSnappedWindows.begin(); i!=this->m_lstSnappedWindows.end(); i++)
{
(*i)->ComputeSnapBarRect(dc, m_bHorz, ptStart);
ptStart.Offset(m_bHorz ? SNAPBAR_ICON_SEPARATION : 0, m_bHorz ? 0 : SNAPBAR_ICON_SEPARATION);
}
// let's see if we've been too enthusiastic with allocation....
if((m_bHorz && (ptStart.x > rc.right)) || (!m_bHorz && (ptStart.y > rc.bottom)))
{
// we must do a bit of squeezing...
double dblSqueezeAmount =
(double)((m_bHorz ? ptStart.x : ptStart.y) - (m_bHorz ? rc.right : rc.bottom)) /
(double)this->m_lstSnappedWindows.size();
// now let's tell everyone to squeeze... together we stand, and all that...
ptStart.SetPoint(rc.left+SNAPBAR_ICON_OFFSET, rc.top+SNAPBAR_ICON_OFFSET);
for(i = this->m_lstSnappedWindows.begin(); i!=this->m_lstSnappedWindows.end(); i++)
{
(*i)->SqueezeSnapBarRect((int)dblSqueezeAmount, m_bHorz, ptStart);
ptStart.Offset(m_bHorz ? SNAPBAR_ICON_SEPARATION : 0, m_bHorz ? 0 : SNAPBAR_ICON_SEPARATION);
}
}
// that's all there's to it
dc.SelectFont(hfOld);
::ReleaseDC(m_hWnd, dc);
return 0;
}
LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
tSnapWindowList::iterator i;
CPaintDC dc(m_hWnd);
CBrush brushOutline;
brushOutline.CreateSolidBrush(this->m_rgbCaptionOutline);
HFONT hfOld = dc.SelectFont(this->m_bHorz ? this->m_pParent->m_fntHorizontal : this->m_pParent->m_fntVertical);
CRect rc;
this->GetClientRect(rc);
if(rc.IsRectEmpty())
return 0; // don't do anything!
dc.FillSolidRect(rc, this->m_rgbBackground);
dc.SetBkMode(TRANSPARENT);
// tell all the snap windows to paint their rects.
for(i = this->m_lstSnappedWindows.begin(); i!=this->m_lstSnappedWindows.end(); i++)
(*i)->PaintSnapBarIcon((CDCHandle)dc, this->m_rgbCaptionBackground, this->m_rgbCaptionPressedBackground,
(CBrushHandle)brushOutline, this->m_bHorz, true);
dc.SelectFont(hfOld);
return 0;
}
CSnapFramework<T>* m_pParent; // parent class
bool m_bHorz; // are we drawing horizontally or vertically?
tSnapWindowList m_lstSnappedWindows;
COLORREF m_rgbBackground;
COLORREF m_rgbCaptionOutline;
COLORREF m_rgbCaptionBackground;
COLORREF m_rgbCaptionPressedBackground;
tSnapWindowList::iterator m_itCurWin;
eSide m_eSide;
bool m_bLButtonDown;
bool m_bScheduleRepaint;
};
friend class CSnapWindow; // let this guy have access to our protected stuff.
friend class CSnapWindow::CSnapWindowStateFloating;
friend class CSnapWindow::CSnapWindowStateAuto;
friend class CSnapWindow::CSnapWindowStatePinned;
friend class CSnapBarWindow;
// MAIN CLASS STUFF
public:
BEGIN_MSG_MAP(thisClass)
MESSAGE_HANDLER(WM_SIZE, OnSize)
MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
MESSAGE_HANDLER(WM_LBUTTONDBLCLK, OnLButtonDblClk)
MESSAGE_HANDLER(WM_NCCALCSIZE, OnNCCalcSize)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
MESSAGE_HANDLER(WM_TIMER, OnTimer)
MESSAGE_HANDLER (WM_SETTINGCHANGE, OnSettingsChange)
MESSAGE_HANDLER(UWM_SNAP_WINDOW_DESTROYED, OnSnapWindowDestroyed)
END_MSG_MAP()
CSnapFramework() : m_bTrackingWindow(false), m_bTrackingTimerStarted(false), m_bFloaterActivationState(true),
m_cParent(this)
{
::ZeroMemory(this->m_arrSidesActive, sizeof(this->m_arrSidesActive));
// see notes on this function on why I had to do this rather silly thing.
this->m_arrSnapBarWindows[eLeft].SetOptions(this, eLeft);
this->m_arrSnapBarWindows[eTop].SetOptions(this, eTop);
this->m_arrSnapBarWindows[eRight].SetOptions(this, eRight);
this->m_arrSnapBarWindows[eBottom].SetOptions(this, eBottom);
// finally get us to the CWindow somehow....
m_pT = static_cast<T*>(this);
}
~CSnapFramework()
{
}
// ok this dude attaches a new window with the specified options to the framework.
// Arguments:
// hWnd: duh
// state: how you want the window attached (see enum above)
// szSize : the dimensions of the window you want
// notice that if the window is docked top or bottom, the cx will be ignored,
// likewise, if the window is docked left or right, the cy will be ignored.
// bHasCloseCheckBox: can this window be closed by the user?
void AttachWindow(IN HWND hWnd, IN eWinState state, IN CSize szSize, IN bool bHasCloseCheckBox)
{
// ok first thing we need to do is figure out what you people want:
switch(state)
{
case eFloating:
this->CreateFloatingWindow(hWnd, szSize, bHasCloseCheckBox);
break;
case eDockedTop:
case eDockedRight:
case eDockedBottom:
case eDockedLeft:
this->CreateSnappedWindow(hWnd, szSize, state, bHasCloseCheckBox);
break;
default:
ATLASSERT(false);
break;
}
}
// notice I do NOT give a way to detach the window from the framework. In my project I don't need it. If you guys
// need it just put in a request, it's quite easy to do.
protected:
// Activation/Deactivation-related functions
void NotifyActivationEvent(IN bool bActivated)
{
// this will show/hide the floaters based on whether we've activated or deactivated
if(this->m_bFloaterActivationState != bActivated) // i.e. if an event has occurred....
{
this->m_bFloaterActivationState = bActivated;
tSnapWindowList::iterator i;
// so just iterate through all the floaters, and perform the appropriate action on them
for(i=this->m_lstFloatingWindows.begin(); i!=this->m_lstFloatingWindows.end(); i++)
(*i)->ShowWindow(bActivated ? SW_SHOW : SW_HIDE);
}
}
// dragging-related functions
void HandleDragging(IN CPoint ptStartPoint, IN CSnapWindow* pWin)
{
CBitmap bmp, bmpMask;
CPoint ptH, ptV;
CImageList lstNormal, lstRotated;
CSize sz;
CPoint ptOffset(0);
if(this->m_bTrackingWindow) // if we're tracking we need to stop before we do anything....
this->ForceEndTracking();
sz = CSize(pWin->GetSnapBarIconRect().Width(), pWin->GetSnapBarIconRect().Height());
if((sz.cx == 0) && (sz.cy == 0)) // ah crap, we haven't computed snapbar rects yet. darnit!
{
CDCHandle dc = this->m_pT->GetDC();
CPoint pt(0);
pWin->ComputeSnapBarRect(dc, true, pt);
sz = CSize(pWin->GetSnapBarIconRect().Width(), pWin->GetSnapBarIconRect().Height());
ATLASSERT((sz.cx > 0) && (sz.cy > 0));
::ReleaseDC(this->m_pT->m_hWnd, dc);
}
lstNormal.Create(__max(sz.cx, sz.cy), __min(sz.cy, sz.cx), ILC_COLOR24|ILC_MASK, 1, 1);
lstRotated.Create(__min(sz.cx, sz.cy), __max(sz.cy, sz.cx), ILC_COLOR24|ILC_MASK, 1, 1);
if(!this->CreateDragBitmaps(true, CSize(__max(sz.cx, sz.cy), __min(sz.cx, sz.cy)), pWin, bmp, bmpMask))
return;
lstNormal.Add(bmp, bmpMask);
bmp.DeleteObject();
bmpMask.DeleteObject();
if(!this->CreateDragBitmaps(false, CSize(__min(sz.cx, sz.cy), __max(sz.cx, sz.cy)), pWin, bmp, bmpMask))
return;
lstRotated.Add(bmp, bmpMask);
ptH = CPoint((int)((double)__max(pWin->GetSnapBarIconRect().Width(), pWin->GetSnapBarIconRect().Height())/2.0f),
(int)((double)__min(pWin->GetSnapBarIconRect().Height(), pWin->GetSnapBarIconRect().Width())/2.0f));
ptV = CPoint((int)((double)__min(pWin->GetSnapBarIconRect().Height(), pWin->GetSnapBarIconRect().Width())/2.0f),
(int)((double)__max(pWin->GetSnapBarIconRect().Width(), pWin->GetSnapBarIconRect().Height())/2.0f));
if(IS_SNAPPED(pWin->GetWinState())) // don't do this if we're a floating window....
{
lstNormal.BeginDrag(0,ptH);
lstNormal.DragEnter(NULL, ptStartPoint);
::SetCapture(this->m_pT->m_hWnd);
}
else
{
// let's figure out what our floating offset is
CRect rc;
pWin->GetWindowRect(rc);
ptOffset.SetPoint(ptStartPoint.x - rc.left, ptStartPoint.y - rc.top);
::SetCapture(pWin->m_hWnd);
}
this->DoDragging(pWin, lstNormal, lstRotated, ptH, ptV, ptOffset);
// let's destroy our image lists....
lstNormal.RemoveAll();
lstNormal.Destroy();
lstRotated.RemoveAll();
lstRotated.Destroy();
}
void DoDragging(CSnapWindow* pWin, CImageList& lstNormal, CImageList& lstRotated,
CPoint ptH, CPoint ptV, CPoint pointFloatOffset)
{
bool bDone = false, bHorz = true, bActivatedSnapBars = false, bSnapBarArray[4], bCurrent = true;
eWinState state = eFloating, eCurState = eFloating;
MSG msg;
CPoint pt;
::GetCursorPos(&pt); // just in case.
// copy our current snap bar array into this one...
memcpy(bSnapBarArray, this->m_arrSidesActive, sizeof(this->m_arrSidesActive));
while(!bDone)
{
if(!::GetMessage(&msg, NULL, 0, 0))
{
::PostQuitMessage(msg.wParam);
break;
}
switch(msg.message)
{
case WM_MOUSEMOVE:
if(IS_SNAPPED(pWin->GetWinState()))
this->HandleSnappedWindowMouseMove(lstNormal, lstRotated, bHorz, bCurrent, msg.lParam,
bActivatedSnapBars, bSnapBarArray, state, pt, ptV, ptH);
else
{
CPoint ptMouse(msg.lParam);
::MapWindowPoints(pWin->m_hWnd, this->m_pT->m_hWnd, &ptMouse, 1);
this->HandleFloatingWindowMouseMove(lstNormal, lstRotated, eCurState, state,
bActivatedSnapBars, bSnapBarArray, ptV, ptH, ptMouse, pWin);
// now actually reposition the window to reflect the changes
if(IS_FLOATING(eCurState)) // don't bother if we're snapped in.
{
ptMouse -= pointFloatOffset;
::SetWindowPos(pWin->m_hWnd, NULL, ptMouse.x, ptMouse.y, 0, 0, SWP_NOZORDER|SWP_NOSIZE);
}
}
break;
case WM_LBUTTONUP:
bDone = true;
if(IS_SNAPPED(eCurState) || IS_SNAPPED(pWin->GetWinState()))
{
bCurrent ? lstNormal.DragLeave(NULL) : lstRotated.DragLeave(NULL);
bCurrent ? lstNormal.EndDrag() : lstRotated.EndDrag();
}
::ReleaseCapture();
// ok now let's figure out what to do with this guy....
this->HandleEndDrag(state, pWin, pt);
break;
default:
::DispatchMessage(&msg);
break;
}
}
this->CheckSnapBars();
}
void HandleSnappedWindowMouseMove(CImageList& lstNormal, CImageList& lstVertical, bool& bDesiredOrientation,
bool& bCurrentOrientation, CPoint point, bool& bActivatedSnapBars,
bool* bSnapBarArray, eWinState& state, CPoint& ptLastDragLocation,
CPoint& ptDragV, CPoint& ptDragH)
{
ptLastDragLocation = this->GetDragPoint(point, state);
bDesiredOrientation = IS_SNAPPED_HORZ(state);
if(IS_FLOATING(state) && !bDesiredOrientation) // if we're not snapped, and are still vertical
bDesiredOrientation = true; // we want to switch to horizontal.
// let's check if we need to redo the client area
//first we check if there are any snapbars we should DEactivate
if(bActivatedSnapBars)
{
bCurrentOrientation ? lstNormal.DragLeave(NULL) : lstVertical.DragLeave(NULL);
bActivatedSnapBars = this->CheckSnapBarDeactivation(bSnapBarArray, state);
bCurrentOrientation ? lstNormal.DragEnter(NULL, ptLastDragLocation) : lstVertical.DragEnter(NULL, ptLastDragLocation);
}
// now let's check if there are any we should activate...
if(IS_SNAPPED(state) && (this->m_arrSidesActive[state] == false))
{
// ok we have to temporarily "show" that snap bar
bCurrentOrientation ? lstNormal.DragLeave(NULL) : lstVertical.DragLeave(NULL);
this->ActivateSnapBar((eSide)state);
bActivatedSnapBars = true;
bCurrentOrientation ? lstNormal.DragEnter(NULL, ptLastDragLocation) : lstVertical.DragEnter(NULL, ptLastDragLocation);
}
if(bDesiredOrientation != bCurrentOrientation)
{
// terminate previous drag
bCurrentOrientation ? lstNormal.DragLeave(NULL) : lstVertical.DragLeave(NULL);
bCurrentOrientation ? lstNormal.EndDrag() : lstVertical.EndDrag();
// start new drag
bCurrentOrientation ? lstVertical.BeginDrag(0, ptDragV.x, ptDragV.y) : lstNormal.BeginDrag(0, ptDragH.x, ptDragH.y);
bCurrentOrientation ? lstVertical.DragEnter(NULL, ptLastDragLocation) : lstNormal.DragEnter(NULL, ptLastDragLocation);
bCurrentOrientation = bDesiredOrientation;
}
bCurrentOrientation ? lstNormal.DragMove(ptLastDragLocation) : lstVertical.DragMove(ptLastDragLocation);
}
void HandleFloatingWindowMouseMove( CImageList& lstNormal, CImageList& lstVertical, eWinState& eCurState,
eWinState& eDesiredState, bool& bActivatedSnapBars, bool* bSnapBarArray,
CPoint& ptDragV, CPoint& ptDragH, CPoint& ptMousePos, CSnapWindow* pWin)
{
// this is trying to figure out what actions we want to take at the end of the day.
// we'll have a bunch of if statements trying to figure out what to do, possibly affecting each other,
// and then at the end of the day we'll do what's needed.
bool bWantNormalOn = false, bWantNormalOff = false, bWantNormalEnded = false, bWantVerticalOn = false,
bWantVerticalOff = false, bWantVerticalEnded = false, bWantWindowShown = false, bWantWindowHidden = false,
bWantActivateSnapBar = false, bWantNormalStarted = false, bWantVerticalStarted = false, bWantDeactivationChecked = true;
CPoint pt = this->GetDragPoint(ptMousePos, eDesiredState);
// ok first let's check if we're floating, and we weren't before
if(IS_FLOATING(eDesiredState) && IS_SNAPPED(eCurState))
{
// ok we need to destroy our drag image
IS_SNAPPED_HORZ(eCurState) ? (bWantNormalOff = bWantNormalEnded = true) :
(bWantVerticalOff = bWantVerticalEnded = true);
bWantWindowShown = true;
}
// check if we need to activate any bars...
if(IS_SNAPPED(eDesiredState) && (this->m_arrSidesActive[eDesiredState] == false))
{
// ok we have to temporarily "show" that snap bar
IS_SNAPPED_HORZ(eCurState) ? (bWantNormalOff = true) : (bWantVerticalOff = true);
bWantActivateSnapBar = bActivatedSnapBars = true;
IS_SNAPPED_HORZ(eCurState) ? (bWantNormalOn = true) : (bWantVerticalOn = true);
}
// ok now let's check if we WERE floating, and are now snapped....
if(IS_FLOATING(eCurState) && IS_SNAPPED(eDesiredState))
{
// and we need to hide the window...
bWantWindowHidden = true;
// this means we must create a drag image...
IS_SNAPPED_HORZ(eDesiredState) ? (bWantNormalStarted = true) : (bWantVerticalStarted = true);
IS_SNAPPED_HORZ(eDesiredState) ? (bWantNormalOn = true) : (bWantVerticalOn = true);
}
if(IS_SNAPPED(eDesiredState) && IS_SNAPPED(eCurState) && (eDesiredState != eCurState))
{
IS_SNAPPED_HORZ(eCurState) ? (bWantNormalOff = bWantNormalEnded = true) :
(bWantVerticalOff = bWantVerticalEnded = true);
IS_SNAPPED_HORZ(eDesiredState) ? (bWantNormalOn = bWantNormalStarted = true) :
(bWantVerticalOn = bWantVerticalStarted = true);
}
bWantDeactivationChecked = bActivatedSnapBars;
// ok now let's do all our actions..........
// first let's do drag-ending routines
if(bWantNormalOff) lstNormal.DragLeave(NULL);
if(bWantNormalEnded) lstNormal.EndDrag();
if(bWantVerticalOff) lstVertical.DragLeave(NULL);
if(bWantVerticalEnded) lstVertical.EndDrag();
// now let's do window showing/hiding, etc
if(bWantWindowHidden)
{
pWin->ShowWindow(SW_HIDE);
this->m_arrSnapBarWindows[eDesiredState].SendMessage(WM_PAINT, 0, 0);
}
if(bWantActivateSnapBar) this->ActivateSnapBar((eSide)eDesiredState);
if(bWantDeactivationChecked) bActivatedSnapBars = this->CheckSnapBarDeactivation(bSnapBarArray, eDesiredState);
// finally let's do drag-starting routines
if(bWantNormalStarted) lstNormal.BeginDrag(0, ptDragH);
if(bWantVerticalStarted) lstVertical.BeginDrag(0, ptDragV);
if(bWantNormalOn) lstNormal.DragEnter(NULL, pt);
if(bWantVerticalOn) lstVertical.DragEnter(NULL, pt);
if(bWantWindowShown) pWin->ShowWindow(SW_SHOW);
if(IS_SNAPPED(eDesiredState)) // finally let's do the dragmove if we need to...
IS_SNAPPED_HORZ(eDesiredState) ? lstNormal.DragMove(pt) : lstVertical.DragMove(pt);
eCurState = eDesiredState;
ptMousePos = pt;
}
void HandleEndDrag(eWinState eDesiredState, CSnapWindow* pWin, CPoint ptScreenLast)
{
eWinState eOrigState = pWin->GetWinState();
this->TransferWindow(pWin, eOrigState, eDesiredState);
}
void CheckSnapBars()
{
bool bNeedResize = false;
int iNumWnds;
bool bRepaint[4];
int j;
memset(bRepaint, 0, sizeof(bRepaint));
for(j=0;j<4;j++)
{
iNumWnds = this->m_arrSnapBarWindows[j].GetNumSnappedWindows();
if((iNumWnds > 0) && (this->m_arrSidesActive[j] == false)) // we need to activate this one
{
this->m_arrSidesActive[j] = true;
bRepaint[j] = true;
bNeedResize = true;
}
else if((iNumWnds == 0) && (this->m_arrSidesActive[j] == true)) // need to deactivate this one
{
this->m_arrSidesActive[j] = false;
this->m_arrSnapBarWindows[j].ShowWindow(SW_HIDE);
bNeedResize = true;
}
}
if(bNeedResize)
{
m_pT->SetWindowPos(NULL, 0,0,0,0, SWP_FRAMECHANGED|SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOSIZE);
m_pT->SendMessage(WM_PAINT, NULL, NULL);
for(j=0;j<4;j++)
{
if(bRepaint[j])
{
this->m_arrSnapBarWindows[j].ShowWindow(SW_SHOW);
this->m_arrSnapBarWindows[j].SetWindowPos(HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
this->m_arrSnapBarWindows[j].SendMessage(WM_PAINT, 0, 0);
}
}
}
// finally let's check to see if we need to repaint any of the snapbars
for(j=0;j<4; j++)
if(this->m_arrSnapBarWindows[j].CheckRepaint())
this->m_arrSnapBarWindows[j].Invalidate();
//this->m_arrSnapBarWindows[j].SendMessage(WM_PAINT, 0, 0);
}
bool CheckSnapBarDeactivation(IN bool* arrTempBars, eWinState eCurrentState)
{
bool bResult = true;
bool bNeedResize = false;
for(int j=0;j<4;j++)
{
if((arrTempBars[j] != this->m_arrSidesActive[j]) && this->m_arrSidesActive[j])
{
// ok found them. let's see if it should remain activated
if(eCurrentState != (eWinState)j) // i.e. if we're no longer there...
{
this->m_arrSidesActive[j] = false;
this->m_arrSnapBarWindows[j].ShowWindow(SW_HIDE);
bResult &= true;
bNeedResize = true;
}
else // we must leave them...
bResult = false;
}
}
if(bNeedResize)
{
m_pT->SetWindowPos(NULL, 0,0,0,0, SWP_FRAMECHANGED|SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOSIZE);
m_pT->SendMessage(WM_PAINT, NULL, NULL);
}
return !bResult; // return TRUE if there are any bars left. return FALSE if we got them all.
}
CPoint GetDragPoint(IN CPoint ptClientMousePos, eWinState& state)
{
CPoint ptResult(ptClientMousePos);
::MapWindowPoints(this->m_pT->m_hWnd, this->m_pT->GetParent(), &ptResult, 1);
// this function basically checks if we should snap to any of the snap bars, and modifies the point accordingly
state = eFloating;
for(eSide side = eTop; side <= eLeft; ((short&)side)++)
{
if(this->m_arrSnapSideRects[side].PtInRect(ptResult))
{
// wee we found one. Ok we need to modify the point to snap to the real rect...
if((side == eTop) || (side == eBottom)) // i.e. we're horizontal
ptResult.y = this->m_arrSnapBarRects[side].top +
(int)((double)this->m_arrSnapBarRects[side].Height()/2.0f);
else
ptResult.x = this->m_arrSnapBarRects[side].left +
(int)((double)this->m_arrSnapBarRects[side].Width()/2.0f);
state = (eWinState)side;
break;
}
}
// map this guy into screen coordinates
::ClientToScreen(this->m_pT->GetParent(), &ptResult);
return ptResult;
}
bool CreateDragBitmaps(IN bool bHorz, IN CSize sz, IN CSnapWindow* pWin,
OUT CBitmap& bitmap, OUT CBitmap& maskBitmap)
{
CDCHandle dc(this->m_pT->GetDC()); // let's get the CDC we'll use
CDC memDC, maskDC;
CBitmapHandle hBitmap;
CBrush brsh;
brsh.CreateSolidBrush(RGB(96,96,96));
// let's create a memory device context
if(memDC.CreateCompatibleDC(dc) == FALSE)
goto CreateDragBitmaps_Failure;
// now create an offscreen bitmap, and select it into the memory device context we just created
if(bitmap.CreateCompatibleBitmap(dc, sz.cx, sz.cy) == FALSE)
goto CreateDragBitmaps_Failure;
hBitmap = memDC.SelectBitmap(bitmap);
// now let's tell the window to draw itself into this bitmap
memDC.SetTextColor(RGB(255,255,255));
memDC.SelectFont(bHorz ? this->m_fntHorizontal : this->m_fntVertical);
memDC.SetBkMode(TRANSPARENT);
// paint at 0....
pWin->PaintSnapBarIcon((CDCHandle)memDC, RGB(0,0,128), RGB(0,0,0), (CBrushHandle)brsh, bHorz, false);
// restore the old bitmap into the memdc
memDC.SelectBitmap(hBitmap);
// now let's create the mask DC
if(maskDC.CreateCompatibleDC(memDC) == FALSE)
goto CreateDragBitmaps_Failure;
if(maskBitmap.CreateCompatibleBitmap(memDC, sz.cx, sz.cy) == FALSE)
goto CreateDragBitmaps_Failure;
hBitmap = maskDC.SelectBitmap(maskBitmap);
maskDC.SetTextColor(RGB(255,255,255));
maskDC.SelectFont(bHorz ? this->m_fntHorizontal : this->m_fntVertical);
maskDC.SetBkMode(TRANSPARENT);
pWin->PaintSnapBarIcon((CDCHandle)maskDC, RGB(0,0,128), RGB(0,0,0), (CBrushHandle) brsh, bHorz, false);
maskDC.SelectBitmap(hBitmap);
::ReleaseDC(m_pT->m_hWnd, dc);
return true;
CreateDragBitmaps_Failure:
::ReleaseDC(m_pT->m_hWnd, dc);
return false;
}
// tracking-related functions
void ForceEndTracking()
{
if(this->m_bTrackingTimerStarted)
this->m_pT->KillTimer(TRACKING_TIMER_ID);
this->m_bTrackingTimerStarted = false;
this->m_bTrackingWindow = false;
}
void SnapBarMouseMove(CPoint pt, eSide side)
{
// what this means is user stuck their mouse onto a different snapbar... we ouggtha do the same thing
// we do in mousemove
ATLASSERT(this->m_bTrackingWindow);
if(!this->m_bTrackingTimerStarted)
{
this->m_pT->SetTimer(TRACKING_TIMER_ID, 400);
this->m_bTrackingTimerStarted = true;
// let's translate this point to our coordinates before we store it...
this->m_arrSnapBarWindows[side].ClientToScreen(&pt);
this->m_pT->ScreenToClient(&pt);
this->m_ptLastTrackPoint = pt;
}
}
void StartTrackingWindow(eSide eSideToTrack)
{
// let's check if we're already tracking, and if so cancel it...
if(this->m_bTrackingWindow)
this->ForceEndTracking();
this->m_bTrackingWindow = true;
this->m_eTrackingSide = eSideToTrack;
}
LRESULT OnSnapWindowDestroyed(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
// let's try to find it in our list first, then we'll pass it on to the snapbars
tSnapWindowList::iterator i;
CSnapWindow* pWnd = (CSnapWindow*)lParam;
for(i=this->m_lstFloatingWindows.begin(); i!=this->m_lstFloatingWindows.end(); i++)
{
if((*i) == pWnd)
{
this->m_lstFloatingWindows.erase(i); // can't use remove because then we won't know if it got removed
// and doing a find first would result in two iterations of the list, which is just silly.
delete pWnd;
return 0;
}
}
// ok I guess we didn't find it... let's pass it onto our bars...
this->m_arrSnapBarWindows[pWnd->GetWinState()].RemoveWindowFromBar(pWnd, false);
delete pWnd;
this->CheckSnapBars();
return 0;
}
void CreateFloatingWindow(IN HWND hWnd, IN CSize szSize, IN bool bHasCloseCheckBox) throw(DWORD)
{
// ok first we need to create a new snapwindow
CSnapWindow* pWin = new CSnapWindow(eFloating, hWnd, NULL, this);
if(!pWin->CreateSnapWindow(hWnd, szSize, bHasCloseCheckBox))
{
ATLASSERT(false);
throw(GetLastError());
}
this->m_lstFloatingWindows.push_back(pWin);
}
void CreateSnappedWindow(IN HWND hWnd, IN CSize szSize,
IN eWinState state, IN bool bHasCloseCheckBox) throw(DWORD)
{
CSnapWindow* pWin = new CSnapWindow(state, hWnd, &this->m_arrSnapBarWindows[state], this);
if(!pWin->CreateSnapWindow(hWnd, szSize, bHasCloseCheckBox))
{
ATLASSERT(false);
throw(GetLastError());
}
this->m_arrSnapBarWindows[state].AddWindowToBar(pWin);
this->CheckSnapBars();
}
// window transferring (transfer from one state to another)
void TransferWindow(CSnapWindow* pWin, eWinState eOrigState, eWinState eDesiredState)
{
// TODO put better error checking here..
if(eOrigState == eDesiredState)
return; // nothing to do
if(eOrigState == eFloating)
{
tSnapWindowList::iterator i = std::find(this->m_lstFloatingWindows.begin(),
this->m_lstFloatingWindows.end(), pWin);
if(i == this->m_lstFloatingWindows.end())
return; // strange....
// handle floating transitions completely differently...
if(eDesiredState == eNull)
{
pWin->DestroyWindow();
delete pWin;
}
else
{
this->m_arrSnapBarWindows[eDesiredState].AddWindowToBar(pWin);
pWin->SetState(eDesiredState); // we'll be no longer floating....
// let's add it to the snap bar
}
this->m_lstFloatingWindows.erase(i);
return;
}
if(eDesiredState == eNull) // ok they actually want to REMOVE the window....
this->m_arrSnapBarWindows[eOrigState].RemoveWindowFromBar(pWin, true); // TODO check return value here!
else
{
if(eDesiredState == eFloating) // they want to float the window
{
this->m_arrSnapBarWindows[eOrigState].RemoveWindowFromBar(pWin, false);
pWin->SetState(eDesiredState); // switch to floating.
this->m_lstFloatingWindows.push_back(pWin);
}
else
{
this->m_arrSnapBarWindows[eOrigState].RemoveWindowFromBar(pWin, false);
this->m_arrSnapBarWindows[eDesiredState].AddWindowToBar(pWin);
pWin->SetState(eDesiredState);
}
}
}
// MESSAGE HANDLERS
LRESULT OnTimer(UINT, WPARAM wParam, LPARAM, BOOL& bHandled)
{
if(wParam == TRACKING_TIMER_ID)
{
CPoint pt;
::GetCursorPos(&pt);
this->m_pT->ScreenToClient(&pt);
if(pt == this->m_ptLastTrackPoint)
{
// ok we're done... CursorLeftTrackingWindow will take care of destroying the
// timer if it wants to... there are some conditions under which it shouldn't.
this->m_arrSnapBarWindows[this->m_eTrackingSide].CursorLeftTrackingWindow(); // that's all the doctor ordered.
}
else
this->m_ptLastTrackPoint = pt;
return 0;
}
bHandled = FALSE;
return 0;
}
LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
bHandled = FALSE; // give the other guys a chance to handle this.
// let's get rid of the our windows
this->m_arrSnapBarWindows[0].DestroyWindow();
this->m_arrSnapBarWindows[1].DestroyWindow();
this->m_arrSnapBarWindows[2].DestroyWindow();
this->m_arrSnapBarWindows[3].DestroyWindow();
tSnapWindowList::iterator i;
for(i=this->m_lstFloatingWindows.begin(); i!=this->m_lstFloatingWindows.end(); i++)
{
(*i)->DestroyWindow();
delete (*i);
}
// finally unsubclass our parent
this->m_cParent.UnsubclassWindow();
return 0;
}
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
bHandled = FALSE; // let's get our facts straight here...
HWND hWnd, hOldWnd;
T* pT = static_cast<T*>(this);
// ok now we need to create our four snapbar windows so they're ready for us.
this->m_arrSnapBarWindows[eLeft].Create(pT->GetParent(), T::rcDefault, NULL, WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS);
this->m_arrSnapBarWindows[eRight].Create(pT->GetParent(), T::rcDefault, NULL, WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS);
this->m_arrSnapBarWindows[eTop].Create(pT->GetParent(), T::rcDefault, NULL, WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS);
this->m_arrSnapBarWindows[eBottom].Create(pT->GetParent(), T::rcDefault, NULL, WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS);
this->SetFonts();
// we need to walk the parent list to get the topmost parent
hWnd = this->m_pT->GetParent();
ATLASSERT(::IsWindow(hWnd) != FALSE);
while(hWnd)
{
hOldWnd = hWnd;
hWnd = ::GetParent(hWnd);
}
this->m_cParent.SubclassWindow(hOldWnd);
return 0;
}
LRESULT OnEraseBkgnd(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = FALSE;
return 0;
}
LRESULT OnSize(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
CRect rcWin;
// let's get the window rect of this window
m_pT->GetWindowRect(rcWin);
// now let's translate that rect to the client window's coordinate space
// we have to do that instead of simply getting client window's clientrect because
// the client rect might include stuff we don't want to obscure, like toolbars, etc.
LPRECT lpRC = (LPRECT)rcWin;
::MapWindowPoints(NULL, m_pT->GetParent(), (LPPOINT)lpRC, sizeof(RECT)/sizeof(POINT));
// now let's calculate all the snapbar window sizes
// yes, I know I could've just done SetRect, but then the code would've been
// less readable.
this->m_arrSnapBarRects[eTop].left = rcWin.left;
this->m_arrSnapBarRects[eTop].right = rcWin.right;
this->m_arrSnapBarRects[eTop].top = rcWin.top;
this->m_arrSnapBarRects[eTop].bottom = rcWin.top + SNAPBAR_SIZE;
this->m_arrSnapBarRects[eBottom].left = rcWin.left;
this->m_arrSnapBarRects[eBottom].right = rcWin.right;
this->m_arrSnapBarRects[eBottom].top = rcWin.bottom - SNAPBAR_SIZE;
this->m_arrSnapBarRects[eBottom].bottom = rcWin.bottom;
this->m_arrSnapBarRects[eLeft].left = rcWin.left;
this->m_arrSnapBarRects[eLeft].right = rcWin.left + SNAPBAR_SIZE;
this->m_arrSnapBarRects[eLeft].top = rcWin.top + (this->m_arrSidesActive[eTop] ? SNAPBAR_SIZE : 0);
this->m_arrSnapBarRects[eLeft].bottom = rcWin.bottom - (this->m_arrSidesActive[eBottom] ? SNAPBAR_SIZE : 0);
this->m_arrSnapBarRects[eRight].left = rcWin.right - SNAPBAR_SIZE;
this->m_arrSnapBarRects[eRight].right = rcWin.right;
this->m_arrSnapBarRects[eRight].top = rcWin.top + (this->m_arrSidesActive[eTop] ? SNAPBAR_SIZE : 0);
this->m_arrSnapBarRects[eRight].bottom = rcWin.bottom - (this->m_arrSidesActive[eBottom] ? SNAPBAR_SIZE : 0);
eSide side;
// let's create our snap side rects, too
for(side = eTop; side <= eLeft; ((short&)side)++)
{
this->m_arrSnapSideRects[side].CopyRect(this->m_arrSnapBarRects[side]);
this->m_arrSnapSideRects[side].InflateRect(SNAP_DISTANCE, SNAP_DISTANCE, SNAP_DISTANCE, SNAP_DISTANCE);
switch(side)
{
case eLeft:
case eRight:
if(this->m_arrSidesActive[eTop])
this->m_arrSnapSideRects[side].top += SNAP_DISTANCE*2; // deflate it if there's a bar there
if(this->m_arrSidesActive[eBottom])
this->m_arrSnapSideRects[side].bottom -= SNAP_DISTANCE*2; // same thing.
break;
default:
break;
}
}
// let's move all the snapbar windows
this->m_arrSnapBarWindows[eLeft].MoveWindow(this->m_arrSnapBarRects[eLeft].left,
this->m_arrSnapBarRects[eLeft].top, this->m_arrSnapBarRects[eLeft].Width(),
this->m_arrSnapBarRects[eLeft].Height());
this->m_arrSnapBarWindows[eTop].MoveWindow(this->m_arrSnapBarRects[eTop].left,
this->m_arrSnapBarRects[eTop].top, this->m_arrSnapBarRects[eTop].Width(),
this->m_arrSnapBarRects[eTop].Height());
this->m_arrSnapBarWindows[eRight].MoveWindow(this->m_arrSnapBarRects[eRight].left,
this->m_arrSnapBarRects[eRight].top, this->m_arrSnapBarRects[eRight].Width(),
this->m_arrSnapBarRects[eRight].Height());
this->m_arrSnapBarWindows[eBottom].MoveWindow(this->m_arrSnapBarRects[eBottom].left,
this->m_arrSnapBarRects[eBottom].top, this->m_arrSnapBarRects[eBottom].Width(),
this->m_arrSnapBarRects[eBottom].Height());
// now we have to explicitly tell all the bars that the framework got resized
// the reason for this awkwardness (as opposed to, say, doing the framework resize
// from within the snapbar's OnSize method, is that sometimes this onsize gets called
// without the size of the parent changing. (for instance, when a window gets pinned,
// the client size changes, the parent size doesn't.) Since OnSize doesn't even get called
// when a MoveWindow doesn't change window's size, we have to do this explicitly
for(side = eTop; side <= eLeft; ((short&)side)++)
this->m_arrSnapBarWindows[side].NotifyFrameworkResized();
// finally let's notify our floaters that we've resized
tSnapWindowList::iterator i;
for(i = this->m_lstFloatingWindows.begin(); i!=this->m_lstFloatingWindows.end(); i++)
(*i)->FrameworkResized();
bHandled = FALSE;
return 0;
}
LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = FALSE;
if(this->m_bTrackingWindow) // ok we were tracking something... the fact that we've gotten this message means
{ // the user moved the mouse outside the window... we should set a timer
// only start the timer if we're outside the window's client rect
CPoint pt(lParam);
CRect rc;
this->m_pT->GetClientRect(rc);
if(!this->m_bTrackingTimerStarted && rc.PtInRect(pt))
{
this->m_pT->SetTimer(TRACKING_TIMER_ID, 400);
this->m_bTrackingTimerStarted = true;
this->m_ptLastTrackPoint = CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
}
}
return 0;
}
LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = FALSE;
return 0;
}
LRESULT OnNCCalcSize(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
BOOL bValidRect = (BOOL)wParam;
bHandled = FALSE; // let the default handler do all the frame adjusting crap.
if(bValidRect)
{
NCCALCSIZE_PARAMS* calcSize = (NCCALCSIZE_PARAMS*)lParam;
if((calcSize->rgrc[0].left == calcSize->rgrc[0].right) || (calcSize->rgrc[0].top == calcSize->rgrc[0].bottom))
return 0; // don't do anything
calcSize->rgrc[0].left += this->m_arrSidesActive[eLeft] ?
this->m_arrSnapBarWindows[eLeft].GetRequiredArea()-2 : 0;
calcSize->rgrc[0].right -= this->m_arrSidesActive[eRight] ?
this->m_arrSnapBarWindows[eRight].GetRequiredArea()-2 : 0;
calcSize->rgrc[0].bottom -= this->m_arrSidesActive[eBottom] ?
this->m_arrSnapBarWindows[eBottom].GetRequiredArea()-2 : 0;
calcSize->rgrc[0].top += this->m_arrSidesActive[eTop] ?
this->m_arrSnapBarWindows[eTop].GetRequiredArea()-2 : 0;
}
return 0;
}
LRESULT OnLButtonDblClk(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = FALSE;
return 0;
}
LRESULT OnSettingsChange(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
this->SetFonts();
return 0;
}
// utility functions.
void SetFonts()
{
// Caption fonts
NONCLIENTMETRICS ncm = { 0 };
ncm.cbSize = sizeof(ncm);
::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0);
if( !m_fntHorizontal.IsNull() )
m_fntHorizontal.DeleteObject();
// Don't use bold font
ncm.lfSmCaptionFont.lfWeight = FW_NORMAL;
this->m_fntHorizontal.CreateFontIndirect(&ncm.lfSmCaptionFont);
ncm.lfSmCaptionFont.lfEscapement = ncm.lfSmCaptionFont.lfOrientation = 2700;
if(!m_fntVertical.IsNull())
m_fntVertical.DeleteObject();
m_fntVertical.CreateFontIndirect(&ncm.lfSmCaptionFont); //Small caption font
}
void ActivateSnapBar(eSide side)
{
this->m_arrSidesActive[side] = true;
this->TriggerNCResize();
this->m_arrSnapBarWindows[side].ShowWindow(SW_SHOW);
this->m_arrSnapBarWindows[side].SetWindowPos(HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
// make SURE the snapbar repaints itself. this is VERY important, especially in dragging.
this->m_arrSnapBarWindows[side].SendMessage(WM_PAINT, NULL, NULL);
m_pT->Invalidate();
}
void TriggerNCResize()
{ m_pT->SetWindowPos(NULL, 0,0,0,0, SWP_FRAMECHANGED|SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOSIZE); }
CRect m_arrSnapBarRects[4]; // these are the actual coordinates of the snap bars
CRect m_arrSnapSideRects[4]; // these are the same coordinates, except inflated by a certain amount to
// aid with snap-to operations.
bool m_arrSidesActive[4]; // array of active sides
CSnapBarWindow m_arrSnapBarWindows[4]; // array of snap bar windows (hidden)
tSnapWindowList m_lstFloatingWindows;
T* m_pT;
eSide m_eTrackingSide;
CPoint m_ptLastTrackPoint;
bool m_bTrackingTimerStarted;
bool m_bTrackingWindow;
CFont m_fntHorizontal;
CFont m_fntVertical;
bool m_bFloaterActivationState;
CParentWindow m_cParent;
};