OSDN Git Service

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