1 // ResizableLayout.cpp: implementation of the CResizableLayout class.
3 /////////////////////////////////////////////////////////////////////////////
5 // Copyright (C) 2000-2002 by Paolo Messina
6 // (http://www.geocities.com/ppescher - ppescher@yahoo.com)
8 // The contents of this file are subject to the Artistic License (the "License").
9 // You may not use this file except in compliance with the License.
10 // You may obtain a copy of the License at:
11 // http://www.opensource.org/licenses/artistic-license.html
13 // If you find this code useful, credits would be nice!
15 /////////////////////////////////////////////////////////////////////////////
19 #include "ResizableLayout.h"
20 #include "ResizableMsgSupport.inl"
24 static char THIS_FILE
[]=__FILE__
;
28 //////////////////////////////////////////////////////////////////////
29 // Construction/Destruction
30 //////////////////////////////////////////////////////////////////////
32 // In August 2002 Platform SDK, some guy at MS thought it was time to
33 // add the missing symbol BS_TYPEMASK, but forgot its original meaning
34 // and so now he's telling us not to use that symbol because its
35 // value is likely to change in the future SDK releases, including all
36 // the BS_* style bits in the mask, not just the button's type as the
37 // symbol's name suggests. So now we're forced to use another symbol!
38 #define _BS_TYPEMASK 0x0000000FL
40 void CResizableLayout::AddAnchor(HWND hWnd
, CSize sizeTypeTL
, CSize sizeTypeBR
)
42 CWnd
* pParent
= GetResizableWnd();
44 // child window must be valid
45 ASSERT(::IsWindow(hWnd
));
46 // must be child of parent window
47 // ASSERT(::IsChild(pParent->GetSafeHwnd(), hWnd));
48 // top-left anchor must be valid
49 ASSERT(sizeTypeTL
!= NOANCHOR
);
51 // get control's window class
53 GetClassName(hWnd
, sClassName
.GetBufferSetLength(MAX_PATH
), MAX_PATH
);
54 sClassName
.ReleaseBuffer();
56 // get parent window's rect
58 GetTotalClientRect(&rectParent
);
59 // and child control's rect
61 ::GetWindowRect(hWnd
, &rectChild
);
62 ::MapWindowPoints(NULL
, pParent
->m_hWnd
, (LPPOINT
)&rectChild
, 2);
64 // adjust position, if client area has been scrolled
65 rectChild
.OffsetRect(-rectParent
.TopLeft());
67 // go calculate margins
68 CSize sizeMarginTL
, sizeMarginBR
;
70 if (sizeTypeBR
== NOANCHOR
)
71 sizeTypeBR
= sizeTypeTL
;
73 // calculate margin for the top-left corner
75 sizeMarginTL
.cx
= rectChild
.left
- rectParent
.Width() * sizeTypeTL
.cx
/ 100;
76 sizeMarginTL
.cy
= rectChild
.top
- rectParent
.Height() * sizeTypeTL
.cy
/ 100;
78 // calculate margin for the bottom-right corner
80 sizeMarginBR
.cx
= rectChild
.right
- rectParent
.Width() * sizeTypeBR
.cx
/ 100;
81 sizeMarginBR
.cy
= rectChild
.bottom
- rectParent
.Height() * sizeTypeBR
.cy
/ 100;
83 // prepare the structure
84 LayoutInfo
layout(hWnd
, sizeTypeTL
, sizeMarginTL
,
85 sizeTypeBR
, sizeMarginBR
, sClassName
);
87 // initialize resize properties (overridable)
88 InitResizeProperties(layout
);
90 // must not be already there!
91 // (this is probably due to a duplicate call to AddAnchor)
93 ASSERT(!m_mapLayout
.Lookup(hWnd
, pos
));
95 // add to the list and the map
96 pos
= m_listLayout
.AddTail(layout
);
97 m_mapLayout
.SetAt(hWnd
, pos
);
100 void CResizableLayout::AddAnchorCallback(UINT nCallbackID
)
102 // one callback control cannot rely upon another callback control's
103 // size and/or position (they're updated all together at the end)
104 // it can however use a non-callback control, which is updated before
108 layout
.nCallbackID
= nCallbackID
;
109 m_listLayoutCB
.AddTail(layout
);
112 BOOL
CResizableLayout::ArrangeLayoutCallback(CResizableLayout::LayoutInfo
& /*layout*/)
115 // must be overridden, if callback is used
117 return FALSE
; // no output data
120 void CResizableLayout::ArrangeLayout()
125 CRect rectParent
, rectChild
;
126 GetTotalClientRect(&rectParent
); // get parent window's rect
127 int count
= m_listLayout
.GetCount();
128 int countCB
= m_listLayoutCB
.GetCount();
130 // reposition child windows
131 HDWP hdwp
= ::BeginDeferWindowPos(count
+ countCB
);
133 POSITION pos
= m_listLayout
.GetHeadPosition();
137 layout
= m_listLayout
.GetNext(pos
);
139 // calculate new child's position, size and flags for SetWindowPos
140 CalcNewChildPosition(layout
, rectParent
, rectChild
, uFlags
);
142 // only if size or position changed
143 if ((uFlags
& (SWP_NOMOVE
|SWP_NOSIZE
)) != (SWP_NOMOVE
|SWP_NOSIZE
))
145 hdwp
= ::DeferWindowPos(hdwp
, layout
.hWnd
, NULL
, rectChild
.left
,
146 rectChild
.top
, rectChild
.Width(), rectChild
.Height(), uFlags
);
150 // for callback items you may use GetAnchorPosition to know the
151 // new position and size of a non-callback item after resizing
153 pos
= m_listLayoutCB
.GetHeadPosition();
157 layout
= m_listLayoutCB
.GetNext(pos
);
158 // request layout data
159 if (!ArrangeLayoutCallback(layout
))
162 // calculate new child's position, size and flags for SetWindowPos
163 CalcNewChildPosition(layout
, rectParent
, rectChild
, uFlags
);
165 // only if size or position changed
166 if ((uFlags
& (SWP_NOMOVE
|SWP_NOSIZE
)) != (SWP_NOMOVE
|SWP_NOSIZE
))
168 hdwp
= ::DeferWindowPos(hdwp
, layout
.hWnd
, NULL
, rectChild
.left
,
169 rectChild
.top
, rectChild
.Width(), rectChild
.Height(), uFlags
);
173 // finally move all the windows at once
174 ::EndDeferWindowPos(hdwp
);
177 void CResizableLayout::ClipChildWindow(const CResizableLayout::LayoutInfo
& layout
,
180 // obtain window position
182 ::GetWindowRect(layout
.hWnd
, &rect
);
183 ::MapWindowPoints(NULL
, GetResizableWnd()->m_hWnd
, (LPPOINT
)&rect
, 2);
185 // use window region if any
187 rgn
.CreateRectRgn(0,0,0,0);
188 switch (::GetWindowRgn(layout
.hWnd
, rgn
))
192 rgn
.OffsetRgn(rect
.TopLeft());
196 rgn
.SetRectRgn(&rect
);
199 // get the clipping property
200 BOOL bClipping
= layout
.properties
.bAskClipping
?
201 LikesClipping(layout
) : layout
.properties
.bCachedLikesClipping
;
203 // modify region accordingly
205 pRegion
->CombineRgn(pRegion
, &rgn
, RGN_DIFF
);
207 pRegion
->CombineRgn(pRegion
, &rgn
, RGN_OR
);
210 void CResizableLayout::GetClippingRegion(CRgn
* pRegion
)
212 CWnd
* pWnd
= GetResizableWnd();
214 // System's default clipping area is screen's size,
215 // not enough for max track size, for example:
216 // if screen is 1024 x 768 and resizing border is 4 pixels,
217 // maximized size is 1024+4*2=1032 x 768+4*2=776,
218 // but max track size is 4 pixels bigger 1036 x 780 (don't ask me why!)
219 // So, if you resize the window to maximum size, the last 4 pixels
220 // are clipped out by the default clipping region, that gets created
221 // as soon as you call clipping functions (my guess).
223 // reset clipping region to the whole client area
225 pWnd
->GetClientRect(&rect
);
226 pRegion
->CreateRectRgnIndirect(&rect
);
228 // clip only anchored controls
230 POSITION pos
= m_listLayout
.GetHeadPosition();
234 layout
= m_listLayout
.GetNext(pos
);
236 if (::IsWindowVisible(layout
.hWnd
))
237 ClipChildWindow(layout
, pRegion
);
239 pos
= m_listLayoutCB
.GetHeadPosition();
243 layout
= m_listLayoutCB
.GetNext(pos
);
245 if (!ArrangeLayoutCallback(layout
))
248 if (::IsWindowVisible(layout
.hWnd
))
249 ClipChildWindow(layout
, pRegion
);
252 // fix for RTL layouts (1 pixel of horz offset)
253 if (pWnd
->GetExStyle() & 0x00400000L
/*WS_EX_LAYOUTRTL*/)
254 pRegion
->OffsetRgn(-1,0);
257 void CResizableLayout::EraseBackground(CDC
* pDC
)
259 HWND hWnd
= GetResizableWnd()->GetSafeHwnd();
261 // retrieve the background brush
262 HBRUSH hBrush
= NULL
;
264 // is this a dialog box?
265 // (using class atom is quickier than using the class name)
266 ATOM atomWndClass
= (ATOM
)::GetClassLong(hWnd
, GCW_ATOM
);
267 if (atomWndClass
== (ATOM
)0x8002)
269 // send a message to the dialog box
270 hBrush
= (HBRUSH
)::SendMessage(hWnd
, WM_CTLCOLORDLG
,
271 (WPARAM
)pDC
->GetSafeHdc(), (LPARAM
)hWnd
);
275 // take the background brush from the window's class
276 hBrush
= (HBRUSH
)::GetClassLongPtr(hWnd
, GCL_HBRBACKGROUND
);
279 // fill the clipped background
281 GetClippingRegion(&rgn
);
283 ::FillRgn(pDC
->GetSafeHdc(), rgn
, hBrush
);
286 // support legacy code (will disappear in future versions)
287 void CResizableLayout::ClipChildren(CDC
* pDC
)
290 GetClippingRegion(&rgn
);
291 // the clipping region is in device units
292 rgn
.OffsetRgn(-pDC
->GetWindowOrg());
293 pDC
->SelectClipRgn(&rgn
);
296 void CResizableLayout::GetTotalClientRect(LPRECT lpRect
)
298 GetResizableWnd()->GetClientRect(lpRect
);
301 BOOL
CResizableLayout::NeedsRefresh(const CResizableLayout::LayoutInfo
& layout
,
302 const CRect
& rectOld
, const CRect
& rectNew
)
304 if (layout
.bMsgSupport
)
306 REFRESHPROPERTY refresh
;
307 refresh
.rcOld
= rectOld
;
308 refresh
.rcNew
= rectNew
;
309 if (Send_NeedsRefresh(layout
.hWnd
, &refresh
))
310 return refresh
.bNeedsRefresh
;
313 int nDiffWidth
= (rectNew
.Width() - rectOld
.Width());
314 int nDiffHeight
= (rectNew
.Height() - rectOld
.Height());
317 if (nDiffWidth
== 0 && nDiffHeight
== 0)
320 // optimistic, no need to refresh
321 BOOL bRefresh
= FALSE
;
323 // window classes that need refresh when resized
324 if (layout
.sWndClass
== WC_STATIC
)
326 DWORD style
= ::GetWindowLong(layout
.hWnd
, GWL_STYLE
);
328 switch (style
& SS_TYPEMASK
)
334 bRefresh
= bRefresh
|| (nDiffWidth
!= 0);
335 // vertically centered text
336 if (style
& SS_CENTERIMAGE
)
337 bRefresh
= bRefresh
|| (nDiffHeight
!= 0);
340 case SS_LEFTNOWORDWRAP
:
341 // text with ellipsis
342 if (style
& SS_ELLIPSISMASK
)
343 bRefresh
= bRefresh
|| (nDiffWidth
!= 0);
344 // vertically centered text
345 if (style
& SS_CENTERIMAGE
)
346 bRefresh
= bRefresh
|| (nDiffHeight
!= 0);
363 // window classes that don't redraw client area correctly
364 // when the hor scroll pos changes due to a resizing
365 BOOL bHScroll
= FALSE
;
366 if (layout
.sWndClass
== WC_LISTBOX
)
369 // fix for horizontally scrollable windows
370 if (bHScroll
&& (nDiffWidth
> 0))
372 // get max scroll position
374 info
.cbSize
= sizeof(SCROLLINFO
);
375 info
.fMask
= SIF_PAGE
| SIF_POS
| SIF_RANGE
;
376 if (::GetScrollInfo(layout
.hWnd
, SB_HORZ
, &info
))
378 // subtract the page size
379 info
.nMax
-= __max(info
.nPage
-1,0);
382 // resizing will cause the text to scroll on the right
383 // because the scrollbar is going beyond the right limit
384 if ((info
.nMax
> 0) && (info
.nPos
+ nDiffWidth
> info
.nMax
))
386 // needs repainting, due to horiz scrolling
394 BOOL
CResizableLayout::LikesClipping(const CResizableLayout::LayoutInfo
& layout
)
396 if (layout
.bMsgSupport
)
398 CLIPPINGPROPERTY clipping
;
399 if (Send_LikesClipping(layout
.hWnd
, &clipping
))
400 return clipping
.bLikesClipping
;
403 DWORD style
= ::GetWindowLong(layout
.hWnd
, GWL_STYLE
);
405 // skip windows that wants background repainted
406 if (layout
.sWndClass
== TOOLBARCLASSNAME
&& (style
& TBSTYLE_TRANSPARENT
))
408 else if (layout
.sWndClass
== WC_BUTTON
)
411 switch (style
& _BS_TYPEMASK
)
417 // ownerdraw buttons must return correct hittest code
418 // to notify their transparency to the system and this library
419 ::GetWindowRect(layout
.hWnd
, &rect
);
420 if ( HTTRANSPARENT
== ::SendMessage(layout
.hWnd
,
421 WM_NCHITTEST
, 0, MAKELPARAM(rect
.left
, rect
.top
)) )
427 else if (layout
.sWndClass
== WC_STATIC
)
429 switch (style
& SS_TYPEMASK
)
435 case SS_LEFTNOWORDWRAP
:
451 if (style
& SS_CENTERIMAGE
)
461 // assume the others like clipping
465 void CResizableLayout::CalcNewChildPosition(const CResizableLayout::LayoutInfo
& layout
,
466 const CRect
&rectParent
, CRect
&rectChild
, UINT
& uFlags
)
468 CWnd
* pParent
= GetResizableWnd();
470 ::GetWindowRect(layout
.hWnd
, &rectChild
);
471 ::MapWindowPoints(NULL
, pParent
->m_hWnd
, (LPPOINT
)&rectChild
, 2);
475 // calculate new top-left corner
476 rectNew
.left
= layout
.sizeMarginTL
.cx
+ rectParent
.Width() * layout
.sizeTypeTL
.cx
/ 100;
477 rectNew
.top
= layout
.sizeMarginTL
.cy
+ rectParent
.Height() * layout
.sizeTypeTL
.cy
/ 100;
479 // calculate new bottom-right corner
480 rectNew
.right
= layout
.sizeMarginBR
.cx
+ rectParent
.Width() * layout
.sizeTypeBR
.cx
/ 100;
481 rectNew
.bottom
= layout
.sizeMarginBR
.cy
+ rectParent
.Height() * layout
.sizeTypeBR
.cy
/ 100;
483 // adjust position, if client area has been scrolled
484 rectNew
.OffsetRect(rectParent
.TopLeft());
486 // get the refresh property
487 BOOL bRefresh
= layout
.properties
.bAskRefresh
?
488 NeedsRefresh(layout
, rectChild
, rectNew
) : layout
.properties
.bCachedNeedsRefresh
;
491 uFlags
= SWP_NOZORDER
| SWP_NOACTIVATE
| SWP_NOREPOSITION
;
493 uFlags
|= SWP_NOCOPYBITS
;
494 if (rectNew
.TopLeft() == rectChild
.TopLeft())
495 uFlags
|= SWP_NOMOVE
;
496 if (rectNew
.Size() == rectChild
.Size())
497 uFlags
|= SWP_NOSIZE
;
503 void CResizableLayout::InitResizeProperties(CResizableLayout::LayoutInfo
&layout
)
505 // check if custom window supports this library
506 // (properties must be correctly set by the window)
507 layout
.bMsgSupport
= Send_QueryProperties(layout
.hWnd
, &layout
.properties
);
509 // default properties
510 if (!layout
.bMsgSupport
)
512 // clipping property is assumed as static
513 layout
.properties
.bAskClipping
= FALSE
;
514 layout
.properties
.bCachedLikesClipping
= LikesClipping(layout
);
515 // refresh property is assumed as dynamic
516 layout
.properties
.bAskRefresh
= TRUE
;