Fix broken build in last commit
[TortoiseGit.git] / ext / ResizableLib / ResizableLayout.cpp
blobe838670aa2cb75342c217830455f3d7af9edf8ab
1 /////////////////////////////////////////////////////////////////////////////
2 //
3 // This file is part of ResizableLib
4 // http://sourceforge.net/projects/resizablelib
5 //
6 // Copyright (C) 2000-2004 by Paolo Messina
7 // http://www.geocities.com/ppescher - mailto:ppescher@hotmail.com
8 //
9 // The contents of this file are subject to the Artistic License (the "License").
10 // You may not use this file except in compliance with the License.
11 // You may obtain a copy of the License at:
12 // http://www.opensource.org/licenses/artistic-license.html
14 // If you find this code useful, credits would be nice!
16 /////////////////////////////////////////////////////////////////////////////
18 /*!
19 * @file
20 * @brief Implementation of the CResizableLayout class.
23 #include "stdafx.h"
24 #include "ResizableLayout.h"
25 #include "ResizableVersion.h"
27 #ifdef _DEBUG
28 #undef THIS_FILE
29 static char THIS_FILE[]=__FILE__;
30 #define new DEBUG_NEW
31 #endif
33 /*!
34 * @internal Constant used to detect clipping and refresh properties
36 * @note In August 2002 Platform SDK, some guy at MS thought it was time
37 * to add the missing symbol BS_TYPEMASK, but forgot its original
38 * meaning and so now he's telling us not to use that symbol because
39 * its value is likely to change in the future SDK releases, including
40 * all the BS_* style bits in the mask, not just the button's type
41 * as the symbol's name suggests.
42 * @n So now we're forced to define another symbol, great!
44 #define _BS_TYPEMASK 0x0000000FL
46 /*!
47 * This function adds a new control to the layout manager and sets anchor
48 * points for its top-left and bottom-right corners.
50 * @param hWnd Window handle to the control to be added
51 * @param anchorTopLeft Anchor point for the top-left corner
52 * @param anchorBottomRight Anchor point for the bottom-right corner
54 * @remarks Overlapping controls, like group boxes and the controls inside,
55 * must be added from the outer controls to the inner ones, to let
56 * the clipping routines work correctly.
58 * @sa AddAnchorCallback RemoveAnchor
60 void CResizableLayout::AddAnchor(HWND hWnd, ANCHOR anchorTopLeft, ANCHOR anchorBottomRight)
62 CWnd* pParent = GetResizableWnd();
64 // child window must be valid
65 ASSERT(::IsWindow(hWnd));
66 // must be child of parent window
67 ASSERT(::IsChild(pParent->GetSafeHwnd(), hWnd));
69 // get parent window's rect
70 CRect rectParent;
71 GetTotalClientRect(&rectParent);
72 // and child control's rect
73 CRect rectChild;
74 ::GetWindowRect(hWnd, &rectChild);
75 ::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2);
77 // adjust position, if client area has been scrolled
78 rectChild.OffsetRect(-rectParent.TopLeft());
80 // go calculate margins
81 CSize marginTopLeft, marginBottomRight;
83 // calculate margin for the top-left corner
85 marginTopLeft.cx = rectChild.left - rectParent.Width() * anchorTopLeft.cx / 100;
86 marginTopLeft.cy = rectChild.top - rectParent.Height() * anchorTopLeft.cy / 100;
88 // calculate margin for the bottom-right corner
90 marginBottomRight.cx = rectChild.right - rectParent.Width() * anchorBottomRight.cx / 100;
91 marginBottomRight.cy = rectChild.bottom - rectParent.Height() * anchorBottomRight.cy / 100;
93 // prepare the structure
94 LAYOUTINFO layout(hWnd, anchorTopLeft, marginTopLeft,
95 anchorBottomRight, marginBottomRight);
97 // get control's window class
98 GetClassName(hWnd, layout.sWndClass, MAX_PATH);
100 // initialize resize properties (overridable)
101 InitResizeProperties(layout);
103 // must not be already there!
104 // (this is probably due to a duplicate call to AddAnchor)
105 POSITION pos;
106 ASSERT(!m_mapLayout.Lookup(hWnd, pos));
108 // add to the list and the map
109 pos = m_listLayout.AddTail(layout);
110 m_mapLayout.SetAt(hWnd, pos);
114 * This function adds a placeholder to the layout manager, that will be
115 * dinamically set by a callback function whenever required.
117 * @return The return value is an integer used to distinguish between
118 * different placeholders in the callback implementation.
120 * @remarks You must override @ref ArrangeLayoutCallback to provide layout
121 * information.
123 * @sa AddAnchor ArrangeLayoutCallback ArrangeLayout
125 UINT_PTR CResizableLayout::AddAnchorCallback()
127 // one callback control cannot rely upon another callback control's
128 // size and/or position (they're updated all together at the end)
129 // it can however use a non-callback control, calling GetAnchorPosition()
131 // add to the list
132 LAYOUTINFO layout;
133 layout.nCallbackID = m_listLayoutCB.GetCount() + 1;
134 m_listLayoutCB.AddTail(layout);
135 return layout.nCallbackID;
139 * This function is called for each placeholder added to the layout manager
140 * and must be overridden to provide the necessary layout information.
142 * @param layout Reference to a LAYOUTINFO structure to be filled with
143 * layout information for the specified placeholder.
144 * On input, nCallbackID is the identification number
145 * returned by AddAnchorCallback. On output, anchor points and
146 * the window handle must be set and valid.
148 * @return The return value is @c TRUE if the layout information has been
149 * provided successfully, @c FALSE to skip this placeholder.
151 * @remarks When implementing this function, unknown placeholders should be
152 * passed to the base class. Unhandled cases will fire an assertion
153 * in the debug version.
155 * @sa AddAnchorCallback ArrangeLayout LAYOUTINFO
157 BOOL CResizableLayout::ArrangeLayoutCallback(LAYOUTINFO& layout) const
159 UNREFERENCED_PARAMETER(layout);
161 ASSERT(FALSE); // must be overridden, if callback is used
163 return FALSE; // no useful output data
167 * This function should be called in resizable window classes whenever the
168 * controls layout should be updated, usually after a resize operation.
170 * @remarks All the controls added to the layout are moved and resized at
171 * once for performace reasons, so all the controls are in their
172 * old position when AddAnchorCallback is called.
173 * To know where a control will be placed use GetAnchorPosition.
175 * @sa AddAnchor AddAnchorCallback ArrangeLayoutCallback GetAnchorPosition
177 void CResizableLayout::ArrangeLayout() const
179 // common vars
180 UINT uFlags;
181 LAYOUTINFO layout;
182 CRect rectParent, rectChild;
183 INT_PTR count = m_listLayout.GetCount();
184 INT_PTR countCB = m_listLayoutCB.GetCount();
186 if (count + countCB == 0)
187 return;
189 // get parent window's rect
190 GetTotalClientRect(&rectParent);
192 // reposition child windows
193 HDWP hdwp = ::BeginDeferWindowPos (static_cast<int>(count + countCB));
195 POSITION pos = m_listLayout.GetHeadPosition();
196 while (pos != NULL)
198 // get layout info
199 layout = m_listLayout.GetNext(pos);
201 // calculate new child's position, size and flags for SetWindowPos
202 CalcNewChildPosition(layout, rectParent, rectChild, uFlags);
204 // only if size or position changed
205 if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE))
207 hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left,
208 rectChild.top, rectChild.Width(), rectChild.Height(), uFlags);
212 // for callback items you may use GetAnchorPosition to know the
213 // new position and size of a non-callback item after resizing
215 pos = m_listLayoutCB.GetHeadPosition();
216 while (pos != NULL)
218 // get layout info
219 layout = m_listLayoutCB.GetNext(pos);
220 // request layout data
221 if (!ArrangeLayoutCallback(layout))
222 continue;
224 // calculate new child's position, size and flags for SetWindowPos
225 CalcNewChildPosition(layout, rectParent, rectChild, uFlags);
227 // only if size or position changed
228 if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE))
230 hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left,
231 rectChild.top, rectChild.Width(), rectChild.Height(), uFlags);
235 // finally move all the windows at once
236 ::EndDeferWindowPos(hdwp);
240 * @internal This function adds or removes a control window region
241 * to or from the specified clipping region, according to its layout
242 * properties.
244 void CResizableLayout::ClipChildWindow(const LAYOUTINFO& layout,
245 CRgn* pRegion) const
247 // obtain window position
248 CRect rect;
249 ::GetWindowRect(layout.hWnd, &rect);
250 #if (_WIN32_WINNT >= 0x0501)
251 //! @todo decide when to clip client only or non-client too (themes?)
252 //! (leave disabled meanwhile, until I find a good solution)
253 //! @note wizard97 with watermark bitmap and themes won't look good!
254 // if (real_WIN32_WINNT >= 0x501)
255 // ::SendMessage(layout.hWnd, WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
256 #endif
257 ::MapWindowPoints(NULL, GetResizableWnd()->m_hWnd, (LPPOINT)&rect, 2);
259 // use window region if any
260 CRgn rgn;
261 rgn.CreateRectRgn(0,0,0,0);
262 switch (::GetWindowRgn(layout.hWnd, rgn))
264 case COMPLEXREGION:
265 case SIMPLEREGION:
266 rgn.OffsetRgn(rect.TopLeft());
267 break;
269 default:
270 rgn.SetRectRgn(&rect);
273 // get the clipping property
274 BOOL bClipping = layout.properties.bAskClipping ?
275 LikesClipping(layout) : layout.properties.bCachedLikesClipping;
277 // modify region accordingly
278 if (bClipping)
279 pRegion->CombineRgn(pRegion, &rgn, RGN_DIFF);
280 else
281 pRegion->CombineRgn(pRegion, &rgn, RGN_OR);
285 * This function retrieves the clipping region for the current layout.
286 * It can be used to draw directly inside the region, without applying
287 * clipping as the ClipChildren function does.
289 * @param pRegion Pointer to a CRegion object that holds the
290 * calculated clipping region upon return
292 * @deprecated For anti-flickering ClipChildren should be preferred
293 * as it is more complete for platform compatibility.
294 * It will probably become a private function.
296 void CResizableLayout::GetClippingRegion(CRgn* pRegion) const
298 CWnd* pWnd = GetResizableWnd();
300 // System's default clipping area is screen's size,
301 // not enough for max track size, for example:
302 // if screen is 1024 x 768 and resizing border is 4 pixels,
303 // maximized size is 1024+4*2=1032 x 768+4*2=776,
304 // but max track size is 4 pixels bigger 1036 x 780 (don't ask me why!)
305 // So, if you resize the window to maximum size, the last 4 pixels
306 // are clipped out by the default clipping region, that gets created
307 // as soon as you call clipping functions (my guess).
309 // reset clipping region to the whole client area
310 CRect rect;
311 pWnd->GetClientRect(&rect);
312 pRegion->CreateRectRgnIndirect(&rect);
314 // clip only anchored controls
315 LAYOUTINFO layout;
316 POSITION pos = m_listLayout.GetHeadPosition();
317 while (pos != NULL)
319 // get layout info
320 layout = m_listLayout.GetNext(pos);
322 if (::IsWindowVisible(layout.hWnd))
323 ClipChildWindow(layout, pRegion);
325 pos = m_listLayoutCB.GetHeadPosition();
326 while (pos != NULL)
328 // get layout info
329 layout = m_listLayoutCB.GetNext(pos);
330 // request data
331 if (!ArrangeLayoutCallback(layout))
332 continue;
334 if (::IsWindowVisible(layout.hWnd))
335 ClipChildWindow(layout, pRegion);
337 //! @todo Has XP changed this??? It doesn't seem correct anymore!
339 // fix for RTL layouts (1 pixel of horz offset)
340 if (pWnd->GetExStyle() & WS_EX_LAYOUTRTL)
341 pRegion->OffsetRgn(-1,0);
345 //! @internal @brief Implements GetAncestor(pWnd->GetSafeHwnd(), GA_ROOT)
346 inline CWnd* GetRootParentWnd(CWnd* pWnd)
348 // GetAncestor API not present, emulate
349 if (!(pWnd->GetStyle() & WS_CHILD))
350 return NULL;
351 while (pWnd->GetStyle() & WS_CHILD)
352 pWnd = pWnd->GetParent();
353 return pWnd;
357 * This function enables or restores clipping on the specified DC when
358 * appropriate. It should be called whenever drawing on the window client
359 * area to avoid flickering.
361 * @param pDC Pointer to the target device context
362 * @param bUndo Flag that specifies wether to restore the clipping region
364 * @return The return value is @c TRUE if the clipping region has been
365 * modified, @c FALSE if clipping was not necessary.
367 * @remarks For anti-flickering to work, you should wrap your
368 * @c WM_ERASEBKGND message handler inside a pair of calls to
369 * this function, with the last parameter set to @c TRUE first
370 * and to @c FALSE at the end.
372 BOOL CResizableLayout::ClipChildren(CDC* pDC, BOOL bUndo)
374 #if (_WIN32_WINNT >= 0x0501 && !defined(RSZLIB_NO_XP_DOUBLE_BUFFER))
375 // clipping not necessary when double-buffering enabled
376 if (real_WIN32_WINNT >= 0x0501)
378 CWnd *pWnd = GetRootParentWnd(GetResizableWnd());
379 if (pWnd == NULL)
380 pWnd = GetResizableWnd();
381 if (pWnd->GetExStyle() & WS_EX_COMPOSITED)
382 return FALSE;
384 #endif
386 HDC hDC = pDC->GetSafeHdc();
387 HWND hWnd = GetResizableWnd()->GetSafeHwnd();
389 m_nOldClipRgn = -1; // invalid region by default
391 // Some controls (such as transparent toolbars and standard controls
392 // with XP theme enabled) send a WM_ERASEBKGND msg to the parent
393 // to draw themselves, in which case we must not enable clipping.
395 // We check that the window associated with the DC is the
396 // resizable window and not a child control.
398 if (!bUndo && (hWnd == ::WindowFromDC(hDC)))
400 // save old DC clipping region
401 m_nOldClipRgn = ::GetClipRgn(hDC, m_hOldClipRgn);
403 // clip out supported child windows
404 CRgn rgnClip;
405 GetClippingRegion(&rgnClip);
406 ::ExtSelectClipRgn(hDC, rgnClip, RGN_AND);
408 return TRUE;
411 // restore old clipping region, only if modified and valid
412 if (bUndo && m_nOldClipRgn >= 0)
414 if (m_nOldClipRgn == 1)
415 ::SelectClipRgn(hDC, m_hOldClipRgn);
416 else
417 ::SelectClipRgn(hDC, NULL);
419 return TRUE;
422 return FALSE;
426 * This function is used by this class, and should be used by derived
427 * classes too, in place of the standard GetClientRect. It can be useful
428 * for windows with scrollbars or expanding windows, to provide the true
429 * client area, including even those parts which are not visible.
431 * @param lpRect Pointer to the RECT structure that holds the result
433 * @remarks Override this function to provide the client area the class uses
434 * to perform layout calculations, both when adding controls and
435 * when rearranging the layout.
436 * @n The base implementation simply calls @c GetClientRect
438 void CResizableLayout::GetTotalClientRect(LPRECT lpRect) const
440 GetResizableWnd()->GetClientRect(lpRect);
444 * This function is used to determine if a control needs to be painted when
445 * it is moved or resized by the layout manager.
447 * @param layout Reference to a @c LAYOUTINFO structure for the control
448 * @param rectOld Reference to a @c RECT structure that holds the control
449 * position and size before the layout update
450 * @param rectNew Reference to a @c RECT structure that holds the control
451 * position and size after the layout update
453 * @return The return value is @c TRUE if the control should be freshly
454 * painted after a layout update, @c FALSE if not necessary.
456 * @remarks The default implementation tries to identify windows that
457 * need refresh by their class name and window style.
458 * @n Override this function if you need a different behavior or if
459 * you have custom controls that fail to be identified.
461 * @sa LikesClipping InitResizeProperties
463 BOOL CResizableLayout::NeedsRefresh(const LAYOUTINFO& layout,
464 const CRect& rectOld, const CRect& rectNew) const
466 if (layout.bMsgSupport)
468 REFRESHPROPERTY refresh;
469 refresh.rcOld = rectOld;
470 refresh.rcNew = rectNew;
471 if (Send_NeedsRefresh(layout.hWnd, &refresh))
472 return refresh.bNeedsRefresh;
475 int nDiffWidth = (rectNew.Width() - rectOld.Width());
476 int nDiffHeight = (rectNew.Height() - rectOld.Height());
478 // is the same size?
479 if (nDiffWidth == 0 && nDiffHeight == 0)
480 return FALSE;
482 // optimistic, no need to refresh
483 BOOL bRefresh = FALSE;
485 // window classes that need refresh when resized
486 if (0 == lstrcmp(layout.sWndClass, WC_STATIC))
488 DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE);
490 switch (style & SS_TYPEMASK)
492 case SS_LEFT:
493 case SS_CENTER:
494 case SS_RIGHT:
495 // word-wrapped text
496 bRefresh = bRefresh || (nDiffWidth != 0);
497 // vertically centered text
498 if (style & SS_CENTERIMAGE)
499 bRefresh = bRefresh || (nDiffHeight != 0);
500 break;
502 case SS_LEFTNOWORDWRAP:
503 // text with ellipsis
504 if (style & SS_ELLIPSISMASK)
505 bRefresh = bRefresh || (nDiffWidth != 0);
506 // vertically centered text
507 if (style & SS_CENTERIMAGE)
508 bRefresh = bRefresh || (nDiffHeight != 0);
509 break;
511 case SS_ENHMETAFILE:
512 case SS_BITMAP:
513 case SS_ICON:
514 // images
515 case SS_BLACKFRAME:
516 case SS_GRAYFRAME:
517 case SS_WHITEFRAME:
518 case SS_ETCHEDFRAME:
519 // and frames
520 bRefresh = TRUE;
521 break;
523 return bRefresh;
526 // window classes that don't redraw client area correctly
527 // when the hor scroll pos changes due to a resizing
528 BOOL bHScroll = FALSE;
529 if (0 == lstrcmp(layout.sWndClass, WC_LISTBOX))
530 bHScroll = TRUE;
532 // fix for horizontally scrollable windows, if wider
533 if (bHScroll && (nDiffWidth > 0))
535 // get max scroll position
536 SCROLLINFO info;
537 info.cbSize = sizeof(SCROLLINFO);
538 info.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
539 if (::GetScrollInfo(layout.hWnd, SB_HORZ, &info))
541 // subtract the page size
542 info.nMax -= __max(info.nPage - 1, 0);
545 // resizing will cause the text to scroll on the right
546 // because the scrollbar is going beyond the right limit
547 if ((info.nMax > 0) && (info.nPos + nDiffWidth > info.nMax))
549 // needs repainting, due to horiz scrolling
550 bRefresh = TRUE;
554 return bRefresh;
558 * This function is used to determine if a control can be safely clipped
559 * out of the parent window client area when it is repainted, usually
560 * after a resize operation.
562 * @param layout Reference to a @c LAYOUTINFO structure for the control
564 * @return The return value is @c TRUE if clipping is supported by the
565 * control, @c FALSE otherwise.
567 * @remarks The default implementation tries to identify @a clippable
568 * windows by their class name and window style.
569 * @n Override this function if you need a different behavior or if
570 * you have custom controls that fail to be identified.
572 * @sa NeedsRefresh InitResizeProperties
574 BOOL CResizableLayout::LikesClipping(const LAYOUTINFO& layout) const
576 if (layout.bMsgSupport)
578 CLIPPINGPROPERTY clipping;
579 if (Send_LikesClipping(layout.hWnd, &clipping))
580 return clipping.bLikesClipping;
583 DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE);
585 // skip windows that wants background repainted
586 if (0 == lstrcmp(layout.sWndClass, WC_BUTTON))
588 CRect rect;
589 switch (style & _BS_TYPEMASK)
591 case BS_GROUPBOX:
592 return FALSE;
594 case BS_OWNERDRAW:
595 // ownerdraw buttons must return correct hittest code
596 // to notify their transparency to the system and this library
597 // or they could use the registered message (more reliable)
598 ::GetWindowRect(layout.hWnd, &rect);
599 ::SendMessage(layout.hWnd, WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
600 if ( HTTRANSPARENT == ::SendMessage(layout.hWnd,
601 WM_NCHITTEST, 0, MAKELPARAM(rect.left, rect.top)) )
602 return FALSE;
603 break;
605 return TRUE;
607 else if (0 == lstrcmp(layout.sWndClass, WC_STATIC))
609 switch (style & SS_TYPEMASK)
611 case SS_LEFT:
612 case SS_CENTER:
613 case SS_RIGHT:
614 case SS_LEFTNOWORDWRAP:
615 // text
616 case SS_BLACKRECT:
617 case SS_GRAYRECT:
618 case SS_WHITERECT:
619 // filled rects
620 case SS_ETCHEDHORZ:
621 case SS_ETCHEDVERT:
622 // etched lines
623 case SS_BITMAP:
624 // bitmaps
625 return TRUE;
626 break;
628 case SS_ICON:
629 case SS_ENHMETAFILE:
630 if (style & SS_CENTERIMAGE)
631 return FALSE;
632 return TRUE;
633 break;
635 default:
636 return FALSE;
640 // assume the others like clipping
641 return TRUE;
646 * @internal This function calculates the new size and position of a
647 * control in the layout and flags for @c SetWindowPos
649 void CResizableLayout::CalcNewChildPosition(const LAYOUTINFO& layout,
650 const CRect &rectParent, CRect &rectChild, UINT& uFlags) const
652 CWnd* pParent = GetResizableWnd();
654 ::GetWindowRect(layout.hWnd, &rectChild);
655 ::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2);
657 CRect rectNew;
659 // calculate new top-left corner
660 rectNew.left = layout.marginTopLeft.cx + rectParent.Width() * layout.anchorTopLeft.cx / 100;
661 rectNew.top = layout.marginTopLeft.cy + rectParent.Height() * layout.anchorTopLeft.cy / 100;
663 // calculate new bottom-right corner
664 rectNew.right = layout.marginBottomRight.cx + rectParent.Width() * layout.anchorBottomRight.cx / 100;
665 rectNew.bottom = layout.marginBottomRight.cy + rectParent.Height() * layout.anchorBottomRight.cy / 100;
667 // adjust position, if client area has been scrolled
668 rectNew.OffsetRect(rectParent.TopLeft());
670 // get the refresh property
671 BOOL bRefresh = layout.properties.bAskRefresh ?
672 NeedsRefresh(layout, rectChild, rectNew) : layout.properties.bCachedNeedsRefresh;
674 // set flags
675 uFlags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION;
676 if (bRefresh)
677 uFlags |= SWP_NOCOPYBITS;
678 if (rectNew.TopLeft() == rectChild.TopLeft())
679 uFlags |= SWP_NOMOVE;
680 if (rectNew.Size() == rectChild.Size())
681 uFlags |= SWP_NOSIZE;
683 // update rect
684 rectChild = rectNew;
688 * This function calculates the top, left, bottom, right margins for a
689 * given size of the specified control.
691 * @param hWnd Window handle to a control in the layout
692 * @param sizeChild Size of the control to use in calculations
693 * @param rectMargins Holds the calculated margins
695 * @return The return value is @c TRUE if successful, @c FALSE otherwise
697 * @remarks This function can be used to infer the parent window size
698 * from the size of one of its child controls.
699 * It is used to implement cascading of size constraints.
701 BOOL CResizableLayout::GetAnchorMargins(HWND hWnd, const CSize &sizeChild, CRect &rectMargins) const
703 POSITION pos;
704 if (!m_mapLayout.Lookup(hWnd, pos))
705 return FALSE;
707 const LAYOUTINFO& layout = m_listLayout.GetAt(pos);
709 // augmented size, relative to anchor points
710 CSize size = sizeChild + layout.marginTopLeft - layout.marginBottomRight;
712 // percent of parent size occupied by this control
713 CSize percent(layout.anchorBottomRight.cx - layout.anchorTopLeft.cx,
714 layout.anchorBottomRight.cy - layout.anchorTopLeft.cy);
716 // calculate total margins
717 rectMargins.left = size.cx * layout.anchorTopLeft.cx / percent.cx + layout.marginTopLeft.cx;
718 rectMargins.top = size.cy * layout.anchorTopLeft.cy / percent.cy + layout.marginTopLeft.cy;
719 rectMargins.right = size.cx * (100 - layout.anchorBottomRight.cx) / percent.cx - layout.marginBottomRight.cx;
720 rectMargins.bottom = size.cy * (100 - layout.anchorBottomRight.cy) / percent.cy - layout.marginBottomRight.cy;
722 return TRUE;
726 * This function is used to set the initial resize properties of a control
727 * in the layout, that are stored in the @c properties member of the
728 * related @c LAYOUTINFO structure.
730 * @param layout Reference to the @c LAYOUTINFO structure to be set
732 * @remarks The various flags are used to specify whether the resize
733 * properties (clipping, refresh) can change at run-time, and a new
734 * call to the property querying functions is needed at every
735 * layout update, or they are static properties, and the cached
736 * value is used whenever necessary.
737 * @n The default implementation sends a registered message to the
738 * control, giving it the opportunity to specify its resize
739 * properties, which takes precedence if the message is supported.
740 * It then sets the @a clipping property as static, calling
741 * @c LikesClipping only once, and the @a refresh property as
742 * dynamic, causing @c NeedsRefresh to be called every time.
743 * @n This should be right for most situations, as the need for
744 * @a refresh usually depends on the size fo a control, while the
745 * support for @a clipping is usually linked to the specific type
746 * of control, which is unlikely to change at run-time, but you can
747 * still override this function if a different beahvior is needed.
749 * @sa LikesClipping NeedsRefresh LAYOUTINFO RESIZEPROPERTIES
751 void CResizableLayout::InitResizeProperties(LAYOUTINFO &layout) const
753 // check if custom window supports this library
754 // (properties must be correctly set by the window)
755 layout.bMsgSupport = Send_QueryProperties(layout.hWnd, &layout.properties);
757 // default properties
758 if (!layout.bMsgSupport)
760 // clipping property is assumed as static
761 layout.properties.bAskClipping = FALSE;
762 layout.properties.bCachedLikesClipping = LikesClipping(layout);
763 // refresh property is assumed as dynamic
764 layout.properties.bAskRefresh = TRUE;
769 * This function modifies a window to enable resizing functionality.
770 * This affects the window style, size, system menu and appearance.
772 * @param lpCreateStruct Pointer to a @c CREATESTRUCT structure, usually
773 * passed by the system to the window procedure in a @c WM_CREATE
774 * or @c WM_NCCREATE
776 * @remarks The function is intended to be called only inside a @c WM_CREATE
777 * or @c WM_NCCREATE message handler.
779 void CResizableLayout::MakeResizable(LPCREATESTRUCT lpCreateStruct)
781 if (lpCreateStruct->style & WS_CHILD)
782 return;
784 CWnd* pWnd = GetResizableWnd();
786 #if (_WIN32_WINNT >= 0x0501 && !defined(RSZLIB_NO_XP_DOUBLE_BUFFER))
787 // enable double-buffering on supported platforms
788 pWnd->ModifyStyleEx(0, WS_EX_COMPOSITED);
789 #endif
791 if (!(lpCreateStruct->style & WS_THICKFRAME))
793 // set resizable style
794 pWnd->ModifyStyle(DS_MODALFRAME, WS_THICKFRAME);
795 // keep client area
796 CRect rect(CPoint(lpCreateStruct->x, lpCreateStruct->y),
797 CSize(lpCreateStruct->cx, lpCreateStruct->cy));
798 pWnd->SendMessage(WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
799 // adjust size to reflect new style
800 ::AdjustWindowRectEx(&rect, pWnd->GetStyle(),
801 ::IsMenu(pWnd->GetMenu()->GetSafeHmenu()), pWnd->GetExStyle());
802 pWnd->SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(),
803 SWP_NOSENDCHANGING|SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOREPOSITION);
804 // update dimensions
805 lpCreateStruct->cx = rect.Width();
806 lpCreateStruct->cy = rect.Height();
811 * This function should be called inside the parent window @c WM_NCCALCSIZE
812 * message handler to help eliminate flickering.
814 * @param bAfterDefault Flag that specifies wether the call is made before
815 * or after the default handler
816 * @param lpncsp Pointer to the @c NCCALCSIZE_PARAMS structure that is
817 * passed to the message handler
818 * @param lResult Reference to the result of the message handler.
819 * It contains the default handler result on input and the value to
820 * return from the window procedure on output.
822 * @remarks This function fixes the annoying flickering effect that is
823 * visible when resizing the top or left edges of the window
824 * (at least on a "left to right" Windows localized version).
826 void CResizableLayout::HandleNcCalcSize(BOOL bAfterDefault, LPNCCALCSIZE_PARAMS lpncsp, LRESULT &lResult)
828 // prevent useless complication when size is not changing
829 // prevent recursion when resetting the window region (see below)
830 if ((lpncsp->lppos->flags & SWP_NOSIZE)
831 #if (_WIN32_WINNT >= 0x0501)
832 || m_bNoRecursion
833 #endif
835 return;
837 if (!bAfterDefault)
839 // save a copy before default handler gets called
840 m_rectClientBefore = lpncsp->rgrc[2];
842 else // after default WM_NCCALCSIZE msg processing
844 if (lResult != 0)
846 // default handler already uses an advanced validation policy, give up
847 return;
849 // default calculated client rect
850 RECT &rectClientAfter = lpncsp->rgrc[0];
852 // intersection between old and new client area is to be preserved
853 // set source and destination rects to this intersection
854 RECT &rectPreserve = lpncsp->rgrc[1];
855 ::IntersectRect(&rectPreserve, &rectClientAfter, &m_rectClientBefore);
856 lpncsp->rgrc[2] = rectPreserve;
858 lResult = WVR_VALIDRECTS;
860 // FIX: window region must be updated before the result of the
861 // WM_NCCALCSIZE message gets processed by the system,
862 // otherwise the old window region will clip the client
863 // area during the preservation process.
864 // This is especially evident on WinXP when the non-client
865 // area is rendered with Visual Styles enabled and the
866 // windows have a non rectangular region.
867 #if (_WIN32_WINNT >= 0x0501)
868 //! @todo change rt check to only if themed frame. what about custom wnd region?
869 if (real_WIN32_WINNT >= 0x0501)
871 CWnd* pWnd = GetResizableWnd();
872 DWORD dwStyle = pWnd->GetStyle();
873 if ((dwStyle & (WS_CAPTION|WS_MAXIMIZE)) == WS_CAPTION)
875 m_bNoRecursion = TRUE;
876 pWnd->SetWindowRgn(NULL, FALSE);
877 m_bNoRecursion = FALSE;
880 #endif