ResizableLayout.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. // ResizableLayout.cpp: implementation of the CResizableLayout class.
  2. //
  3. /////////////////////////////////////////////////////////////////////////////
  4. //
  5. // Copyright (C) 2000-2002 by Paolo Messina
  6. // (http://www.geocities.com/ppescher - ppescher@yahoo.com)
  7. //
  8. // The contents of this file are subject to the Artistic License (the "License").
  9. // You may not use this file except in compliance with the License.
  10. // You may obtain a copy of the License at:
  11. // http://www.opensource.org/licenses/artistic-license.html
  12. //
  13. // If you find this code useful, credits would be nice!
  14. //
  15. /////////////////////////////////////////////////////////////////////////////
  16. #include "stdafx.h"
  17. #include "ResizableLayout.h"
  18. #include "ResizableMsgSupport.inl"
  19. #ifdef _DEBUG
  20. #undef THIS_FILE
  21. static char THIS_FILE[]=__FILE__;
  22. #define new DEBUG_NEW
  23. #endif
  24. //////////////////////////////////////////////////////////////////////
  25. // Construction/Destruction
  26. //////////////////////////////////////////////////////////////////////
  27. // In August 2002 Platform SDK, some guy at MS thought it was time to
  28. // add the missing symbol BS_TYPEMASK, but forgot its original meaning
  29. // and so now he's telling us not to use that symbol because its
  30. // value is likely to change in the future SDK releases, including all
  31. // the BS_* style bits in the mask, not just the button's type as the
  32. // symbol's name suggests. So now we're forced to use another symbol!
  33. #define _BS_TYPEMASK 0x0000000FL
  34. void CResizableLayout::AddAnchor(HWND hWnd, CSize sizeTypeTL, CSize sizeTypeBR)
  35. {
  36. CWnd* pParent = GetResizableWnd();
  37. // child window must be valid
  38. ASSERT(::IsWindow(hWnd));
  39. // must be child of parent window
  40. ASSERT(::IsChild(pParent->GetSafeHwnd(), hWnd));
  41. // top-left anchor must be valid
  42. ASSERT(sizeTypeTL != NOANCHOR);
  43. // get control's window class
  44. CString sClassName;
  45. GetClassName(hWnd, sClassName.GetBufferSetLength(MAX_PATH), MAX_PATH);
  46. sClassName.ReleaseBuffer();
  47. // get parent window's rect
  48. CRect rectParent;
  49. GetTotalClientRect(&rectParent);
  50. // and child control's rect
  51. CRect rectChild;
  52. ::GetWindowRect(hWnd, &rectChild);
  53. ::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2);
  54. // adjust position, if client area has been scrolled
  55. rectChild.OffsetRect(-rectParent.TopLeft());
  56. // go calculate margins
  57. CSize sizeMarginTL, sizeMarginBR;
  58. if (sizeTypeBR == NOANCHOR)
  59. sizeTypeBR = sizeTypeTL;
  60. // calculate margin for the top-left corner
  61. sizeMarginTL.cx = rectChild.left - rectParent.Width() * sizeTypeTL.cx / 100;
  62. sizeMarginTL.cy = rectChild.top - rectParent.Height() * sizeTypeTL.cy / 100;
  63. // calculate margin for the bottom-right corner
  64. sizeMarginBR.cx = rectChild.right - rectParent.Width() * sizeTypeBR.cx / 100;
  65. sizeMarginBR.cy = rectChild.bottom - rectParent.Height() * sizeTypeBR.cy / 100;
  66. // prepare the structure
  67. LayoutInfo layout(hWnd, sizeTypeTL, sizeMarginTL,
  68. sizeTypeBR, sizeMarginBR, sClassName);
  69. // initialize resize properties (overridable)
  70. InitResizeProperties(layout);
  71. // must not be already there!
  72. // (this is probably due to a duplicate call to AddAnchor)
  73. POSITION pos;
  74. ASSERT(!m_mapLayout.Lookup(hWnd, pos));
  75. // add to the list and the map
  76. pos = m_listLayout.AddTail(layout);
  77. m_mapLayout.SetAt(hWnd, pos);
  78. }
  79. void CResizableLayout::AddAnchorCallback(UINT nCallbackID)
  80. {
  81. // one callback control cannot rely upon another callback control's
  82. // size and/or position (they're updated all together at the end)
  83. // it can however use a non-callback control, which is updated before
  84. // add to the list
  85. LayoutInfo layout;
  86. layout.nCallbackID = nCallbackID;
  87. m_listLayoutCB.AddTail(layout);
  88. }
  89. BOOL CResizableLayout::ArrangeLayoutCallback(CResizableLayout::LayoutInfo& /*layout*/)
  90. {
  91. ASSERT(FALSE);
  92. // must be overridden, if callback is used
  93. return FALSE; // no output data
  94. }
  95. void CResizableLayout::ArrangeLayout()
  96. {
  97. // common vars
  98. UINT uFlags;
  99. LayoutInfo layout;
  100. CRect rectParent, rectChild;
  101. GetTotalClientRect(&rectParent); // get parent window's rect
  102. int count = m_listLayout.GetCount();
  103. int countCB = m_listLayoutCB.GetCount();
  104. // reposition child windows
  105. HDWP hdwp = ::BeginDeferWindowPos(count + countCB);
  106. POSITION pos = m_listLayout.GetHeadPosition();
  107. while (pos != NULL)
  108. {
  109. // get layout info
  110. layout = m_listLayout.GetNext(pos);
  111. // calculate new child's position, size and flags for SetWindowPos
  112. CalcNewChildPosition(layout, rectParent, rectChild, uFlags);
  113. // only if size or position changed
  114. if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE))
  115. {
  116. hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left,
  117. rectChild.top, rectChild.Width(), rectChild.Height(), uFlags);
  118. }
  119. }
  120. // for callback items you may use GetAnchorPosition to know the
  121. // new position and size of a non-callback item after resizing
  122. pos = m_listLayoutCB.GetHeadPosition();
  123. while (pos != NULL)
  124. {
  125. // get layout info
  126. layout = m_listLayoutCB.GetNext(pos);
  127. // request layout data
  128. if (!ArrangeLayoutCallback(layout))
  129. continue;
  130. // calculate new child's position, size and flags for SetWindowPos
  131. CalcNewChildPosition(layout, rectParent, rectChild, uFlags);
  132. // only if size or position changed
  133. if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE))
  134. {
  135. hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left,
  136. rectChild.top, rectChild.Width(), rectChild.Height(), uFlags);
  137. }
  138. }
  139. // finally move all the windows at once
  140. ::EndDeferWindowPos(hdwp);
  141. }
  142. void CResizableLayout::ClipChildWindow(const CResizableLayout::LayoutInfo& layout,
  143. CRgn* pRegion)
  144. {
  145. // obtain window position
  146. CRect rect;
  147. ::GetWindowRect(layout.hWnd, &rect);
  148. ::MapWindowPoints(NULL, GetResizableWnd()->m_hWnd, (LPPOINT)&rect, 2);
  149. // use window region if any
  150. CRgn rgn;
  151. rgn.CreateRectRgn(0,0,0,0);
  152. switch (::GetWindowRgn(layout.hWnd, rgn))
  153. {
  154. case COMPLEXREGION:
  155. case SIMPLEREGION:
  156. rgn.OffsetRgn(rect.TopLeft());
  157. break;
  158. default:
  159. rgn.SetRectRgn(&rect);
  160. }
  161. // get the clipping property
  162. BOOL bClipping = layout.properties.bAskClipping ?
  163. LikesClipping(layout) : layout.properties.bCachedLikesClipping;
  164. // modify region accordingly
  165. if (bClipping)
  166. pRegion->CombineRgn(pRegion, &rgn, RGN_DIFF);
  167. else
  168. pRegion->CombineRgn(pRegion, &rgn, RGN_OR);
  169. }
  170. void CResizableLayout::GetClippingRegion(CRgn* pRegion)
  171. {
  172. CWnd* pWnd = GetResizableWnd();
  173. // System's default clipping area is screen's size,
  174. // not enough for max track size, for example:
  175. // if screen is 1024 x 768 and resizing border is 4 pixels,
  176. // maximized size is 1024+4*2=1032 x 768+4*2=776,
  177. // but max track size is 4 pixels bigger 1036 x 780 (don't ask me why!)
  178. // So, if you resize the window to maximum size, the last 4 pixels
  179. // are clipped out by the default clipping region, that gets created
  180. // as soon as you call clipping functions (my guess).
  181. // reset clipping region to the whole client area
  182. CRect rect;
  183. pWnd->GetClientRect(&rect);
  184. pRegion->CreateRectRgnIndirect(&rect);
  185. // clip only anchored controls
  186. LayoutInfo layout;
  187. POSITION pos = m_listLayout.GetHeadPosition();
  188. while (pos != NULL)
  189. {
  190. // get layout info
  191. layout = m_listLayout.GetNext(pos);
  192. if (::IsWindowVisible(layout.hWnd))
  193. ClipChildWindow(layout, pRegion);
  194. }
  195. pos = m_listLayoutCB.GetHeadPosition();
  196. while (pos != NULL)
  197. {
  198. // get layout info
  199. layout = m_listLayoutCB.GetNext(pos);
  200. // request data
  201. if (!ArrangeLayoutCallback(layout))
  202. continue;
  203. if (::IsWindowVisible(layout.hWnd))
  204. ClipChildWindow(layout, pRegion);
  205. }
  206. // fix for RTL layouts (1 pixel of horz offset)
  207. if (pWnd->GetExStyle() & WS_EX_LAYOUTRTL)
  208. pRegion->OffsetRgn(-1,0);
  209. }
  210. void CResizableLayout::EraseBackground(CDC* pDC)
  211. {
  212. HWND hWnd = GetResizableWnd()->GetSafeHwnd();
  213. // retrieve the background brush
  214. HBRUSH hBrush = NULL;
  215. // is this a dialog box?
  216. // (using class atom is quickier than using the class name)
  217. ATOM atomWndClass = (ATOM)::GetClassLong(hWnd, GCW_ATOM);
  218. if (atomWndClass == (ATOM)0x8002)
  219. {
  220. // send a message to the dialog box
  221. hBrush = (HBRUSH)::SendMessage(hWnd, WM_CTLCOLORDLG,
  222. (WPARAM)pDC->GetSafeHdc(), (LPARAM)hWnd);
  223. }
  224. else
  225. {
  226. // take the background brush from the window's class
  227. hBrush = (HBRUSH)::GetClassLong(hWnd, GCL_HBRBACKGROUND);
  228. }
  229. // fill the clipped background
  230. CRgn rgn;
  231. GetClippingRegion(&rgn);
  232. ::FillRgn(pDC->GetSafeHdc(), rgn, hBrush);
  233. }
  234. // support legacy code (will disappear in future versions)
  235. void CResizableLayout::ClipChildren(CDC* pDC)
  236. {
  237. CRgn rgn;
  238. GetClippingRegion(&rgn);
  239. // the clipping region is in device units
  240. rgn.OffsetRgn(-pDC->GetWindowOrg());
  241. pDC->SelectClipRgn(&rgn);
  242. }
  243. void CResizableLayout::GetTotalClientRect(LPRECT lpRect)
  244. {
  245. GetResizableWnd()->GetClientRect(lpRect);
  246. }
  247. BOOL CResizableLayout::NeedsRefresh(const CResizableLayout::LayoutInfo& layout,
  248. const CRect& rectOld, const CRect& rectNew)
  249. {
  250. if (layout.bMsgSupport)
  251. {
  252. REFRESHPROPERTY refresh;
  253. refresh.rcOld = rectOld;
  254. refresh.rcNew = rectNew;
  255. if (Send_NeedsRefresh(layout.hWnd, &refresh))
  256. return refresh.bNeedsRefresh;
  257. }
  258. int nDiffWidth = (rectNew.Width() - rectOld.Width());
  259. int nDiffHeight = (rectNew.Height() - rectOld.Height());
  260. // is the same size?
  261. if (nDiffWidth == 0 && nDiffHeight == 0)
  262. return FALSE;
  263. // optimistic, no need to refresh
  264. BOOL bRefresh = FALSE;
  265. // window classes that need refresh when resized
  266. if (layout.sWndClass == WC_STATIC)
  267. {
  268. DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE);
  269. switch (style & SS_TYPEMASK)
  270. {
  271. case SS_LEFT:
  272. case SS_CENTER:
  273. case SS_RIGHT:
  274. // word-wrapped text
  275. bRefresh = bRefresh || (nDiffWidth != 0);
  276. // vertically centered text
  277. if (style & SS_CENTERIMAGE)
  278. bRefresh = bRefresh || (nDiffHeight != 0);
  279. break;
  280. case SS_LEFTNOWORDWRAP:
  281. // text with ellipsis
  282. if (style & SS_ELLIPSISMASK)
  283. bRefresh = bRefresh || (nDiffWidth != 0);
  284. // vertically centered text
  285. if (style & SS_CENTERIMAGE)
  286. bRefresh = bRefresh || (nDiffHeight != 0);
  287. break;
  288. case SS_ENHMETAFILE:
  289. case SS_BITMAP:
  290. case SS_ICON:
  291. // images
  292. case SS_BLACKFRAME:
  293. case SS_GRAYFRAME:
  294. case SS_WHITEFRAME:
  295. case SS_ETCHEDFRAME:
  296. // and frames
  297. bRefresh = TRUE;
  298. break;
  299. }
  300. }
  301. // window classes that don't redraw client area correctly
  302. // when the hor scroll pos changes due to a resizing
  303. BOOL bHScroll = FALSE;
  304. if (layout.sWndClass == WC_LISTBOX)
  305. bHScroll = TRUE;
  306. // fix for horizontally scrollable windows
  307. if (bHScroll && (nDiffWidth > 0))
  308. {
  309. // get max scroll position
  310. SCROLLINFO info;
  311. info.cbSize = sizeof(SCROLLINFO);
  312. info.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
  313. if (::GetScrollInfo(layout.hWnd, SB_HORZ, &info))
  314. {
  315. // subtract the page size
  316. info.nMax -= __max(info.nPage-1,0);
  317. }
  318. // resizing will cause the text to scroll on the right
  319. // because the scrollbar is going beyond the right limit
  320. if ((info.nMax > 0) && (info.nPos + nDiffWidth > info.nMax))
  321. {
  322. // needs repainting, due to horiz scrolling
  323. bRefresh = TRUE;
  324. }
  325. }
  326. return bRefresh;
  327. }
  328. BOOL CResizableLayout::LikesClipping(const CResizableLayout::LayoutInfo& layout)
  329. {
  330. if (layout.bMsgSupport)
  331. {
  332. CLIPPINGPROPERTY clipping;
  333. if (Send_LikesClipping(layout.hWnd, &clipping))
  334. return clipping.bLikesClipping;
  335. }
  336. DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE);
  337. // skip windows that wants background repainted
  338. if (layout.sWndClass == TOOLBARCLASSNAME && (style & TBSTYLE_TRANSPARENT))
  339. return FALSE;
  340. else if (layout.sWndClass == WC_BUTTON)
  341. {
  342. CRect rect;
  343. switch (style & _BS_TYPEMASK)
  344. {
  345. case BS_GROUPBOX:
  346. return FALSE;
  347. case BS_OWNERDRAW:
  348. // ownerdraw buttons must return correct hittest code
  349. // to notify their transparency to the system and this library
  350. ::GetWindowRect(layout.hWnd, &rect);
  351. if ( HTTRANSPARENT == ::SendMessage(layout.hWnd,
  352. WM_NCHITTEST, 0, MAKELPARAM(rect.left, rect.top)) )
  353. return FALSE;
  354. break;
  355. }
  356. return TRUE;
  357. }
  358. else if (layout.sWndClass == WC_STATIC)
  359. {
  360. switch (style & SS_TYPEMASK)
  361. {
  362. case SS_LEFT:
  363. case SS_CENTER:
  364. case SS_RIGHT:
  365. case SS_SIMPLE:
  366. case SS_LEFTNOWORDWRAP:
  367. // text
  368. case SS_BLACKRECT:
  369. case SS_GRAYRECT:
  370. case SS_WHITERECT:
  371. // filled rects
  372. case SS_ETCHEDHORZ:
  373. case SS_ETCHEDVERT:
  374. // etched lines
  375. case SS_BITMAP:
  376. // bitmaps
  377. return TRUE;
  378. break;
  379. case SS_ICON:
  380. case SS_ENHMETAFILE:
  381. if (style & SS_CENTERIMAGE)
  382. return FALSE;
  383. return TRUE;
  384. break;
  385. default:
  386. return FALSE;
  387. }
  388. }
  389. // assume the others like clipping
  390. return TRUE;
  391. }
  392. void CResizableLayout::CalcNewChildPosition(const CResizableLayout::LayoutInfo& layout,
  393. const CRect &rectParent, CRect &rectChild, UINT& uFlags)
  394. {
  395. CWnd* pParent = GetResizableWnd();
  396. ::GetWindowRect(layout.hWnd, &rectChild);
  397. ::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2);
  398. CRect rectNew;
  399. // calculate new top-left corner
  400. rectNew.left = layout.sizeMarginTL.cx + rectParent.Width() * layout.sizeTypeTL.cx / 100;
  401. rectNew.top = layout.sizeMarginTL.cy + rectParent.Height() * layout.sizeTypeTL.cy / 100;
  402. // calculate new bottom-right corner
  403. rectNew.right = layout.sizeMarginBR.cx + rectParent.Width() * layout.sizeTypeBR.cx / 100;
  404. rectNew.bottom = layout.sizeMarginBR.cy + rectParent.Height() * layout.sizeTypeBR.cy / 100;
  405. // adjust position, if client area has been scrolled
  406. rectNew.OffsetRect(rectParent.TopLeft());
  407. // get the refresh property
  408. BOOL bRefresh = layout.properties.bAskRefresh ?
  409. NeedsRefresh(layout, rectChild, rectNew) : layout.properties.bCachedNeedsRefresh;
  410. // set flags
  411. uFlags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION;
  412. if (bRefresh)
  413. uFlags |= SWP_NOCOPYBITS;
  414. if (rectNew.TopLeft() == rectChild.TopLeft())
  415. uFlags |= SWP_NOMOVE;
  416. if (rectNew.Size() == rectChild.Size())
  417. uFlags |= SWP_NOSIZE;
  418. // update rect
  419. rectChild = rectNew;
  420. }
  421. void CResizableLayout::InitResizeProperties(CResizableLayout::LayoutInfo &layout)
  422. {
  423. // check if custom window supports this library
  424. // (properties must be correctly set by the window)
  425. layout.bMsgSupport = Send_QueryProperties(layout.hWnd, &layout.properties);
  426. // default properties
  427. if (!layout.bMsgSupport)
  428. {
  429. // clipping property is assumed as static
  430. layout.properties.bAskClipping = FALSE;
  431. layout.properties.bCachedLikesClipping = LikesClipping(layout);
  432. // refresh property is assumed as dynamic
  433. layout.properties.bAskRefresh = TRUE;
  434. }
  435. }