ResizableLayout.cpp 29 KB

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