ResizableLayout.cpp

00001 // ResizableLayout.cpp: implementation of the CResizableLayout class.
00002 //
00004 //
00005 // Copyright (C) 2000-2002 by Paolo Messina
00006 // (http://www.geocities.com/ppescher - [email protected])
00007 //
00008 // The contents of this file are subject to the Artistic License (the "License").
00009 // You may not use this file except in compliance with the License. 
00010 // You may obtain a copy of the License at:
00011 // http://www.opensource.org/licenses/artistic-license.html
00012 //
00013 // If you find this code useful, credits would be nice!
00014 //
00016 
00017 #include "stdafx.h"
00018 #include "commctrl.h"
00019 #include "ResizableLayout.h"
00020 #include "ResizableMsgSupport.inl"
00021 
00022 #ifdef _DEBUG
00023 #undef THIS_FILE
00024 static char THIS_FILE[]=__FILE__;
00025 #define new DEBUG_NEW
00026 #endif
00027 
00029 // Construction/Destruction
00031 
00032 // In August 2002 Platform SDK, some guy at MS thought it was time to
00033 // add the missing symbol BS_TYPEMASK, but forgot its original meaning
00034 // and so now he's telling us not to use that symbol because its
00035 // value is likely to change in the future SDK releases, including all
00036 // the BS_* style bits in the mask, not just the button's type as the
00037 // symbol's name suggests. So now we're forced to use another symbol!
00038 #define _BS_TYPEMASK 0x0000000FL
00039 
00040 void CResizableLayout::AddAnchor(HWND hWnd, CSize sizeTypeTL, CSize sizeTypeBR)
00041 {
00042         CWnd* pParent = GetResizableWnd();
00043 
00044         // child window must be valid
00045         ASSERT(::IsWindow(hWnd));
00046         // must be child of parent window
00047 //      ASSERT(::IsChild(pParent->GetSafeHwnd(), hWnd));
00048         // top-left anchor must be valid
00049         ASSERT(sizeTypeTL != NOANCHOR);
00050 
00051         // get control's window class
00052         CString sClassName;
00053         GetClassName(hWnd, sClassName.GetBufferSetLength(MAX_PATH), MAX_PATH);
00054         sClassName.ReleaseBuffer();
00055 
00056         // get parent window's rect
00057         CRect rectParent;
00058         GetTotalClientRect(&rectParent);
00059         // and child control's rect
00060         CRect rectChild;
00061         ::GetWindowRect(hWnd, &rectChild);
00062         ::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2);
00063 
00064         // adjust position, if client area has been scrolled
00065         rectChild.OffsetRect(-rectParent.TopLeft());
00066 
00067         // go calculate margins
00068         CSize sizeMarginTL, sizeMarginBR;
00069 
00070         if (sizeTypeBR == NOANCHOR)
00071                 sizeTypeBR = sizeTypeTL;
00072         
00073         // calculate margin for the top-left corner
00074 
00075         sizeMarginTL.cx = rectChild.left - rectParent.Width() * sizeTypeTL.cx / 100;
00076         sizeMarginTL.cy = rectChild.top - rectParent.Height() * sizeTypeTL.cy / 100;
00077         
00078         // calculate margin for the bottom-right corner
00079 
00080         sizeMarginBR.cx = rectChild.right - rectParent.Width() * sizeTypeBR.cx / 100;
00081         sizeMarginBR.cy = rectChild.bottom - rectParent.Height() * sizeTypeBR.cy / 100;
00082 
00083         // prepare the structure
00084         LayoutInfo layout(hWnd, sizeTypeTL, sizeMarginTL,
00085                 sizeTypeBR, sizeMarginBR, sClassName);
00086 
00087         // initialize resize properties (overridable)
00088         InitResizeProperties(layout);
00089 
00090         // must not be already there!
00091         // (this is probably due to a duplicate call to AddAnchor)
00092         POSITION pos;
00093         ASSERT(!m_mapLayout.Lookup(hWnd, pos));
00094 
00095         // add to the list and the map
00096         pos = m_listLayout.AddTail(layout);
00097         m_mapLayout.SetAt(hWnd, pos);
00098 }
00099 
00100 void CResizableLayout::AddAnchorCallback(UINT nCallbackID)
00101 {
00102         // one callback control cannot rely upon another callback control's
00103         // size and/or position (they're updated all together at the end)
00104         // it can however use a non-callback control, which is updated before
00105 
00106         // add to the list
00107         LayoutInfo layout;
00108         layout.nCallbackID = nCallbackID;
00109         m_listLayoutCB.AddTail(layout);
00110 }
00111 
00112 BOOL CResizableLayout::ArrangeLayoutCallback(CResizableLayout::LayoutInfo& /*layout*/)
00113 {
00114         ASSERT(FALSE);
00115         // must be overridden, if callback is used
00116         
00117         return FALSE;   // no output data
00118 }
00119 
00120 void CResizableLayout::ArrangeLayout()
00121 {
00122         // common vars
00123         UINT uFlags;
00124         LayoutInfo layout;
00125         CRect rectParent, rectChild;
00126         GetTotalClientRect(&rectParent); // get parent window's rect
00127         int count = m_listLayout.GetCount();
00128         int countCB = m_listLayoutCB.GetCount();
00129 
00130         // reposition child windows
00131         HDWP hdwp = ::BeginDeferWindowPos(count + countCB);
00132         
00133         POSITION pos = m_listLayout.GetHeadPosition();
00134         while (pos != NULL)
00135         {
00136                 // get layout info
00137                 layout = m_listLayout.GetNext(pos);
00138                 
00139                 // calculate new child's position, size and flags for SetWindowPos
00140                 CalcNewChildPosition(layout, rectParent, rectChild, uFlags);
00141 
00142                 // only if size or position changed
00143                 if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE))
00144                 {
00145                         hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left,
00146                                 rectChild.top, rectChild.Width(), rectChild.Height(), uFlags);
00147                 }
00148         }
00149 
00150         // for callback items you may use GetAnchorPosition to know the
00151         // new position and size of a non-callback item after resizing
00152 
00153         pos = m_listLayoutCB.GetHeadPosition();
00154         while (pos != NULL)
00155         {
00156                 // get layout info
00157                 layout = m_listLayoutCB.GetNext(pos);
00158                 // request layout data
00159                 if (!ArrangeLayoutCallback(layout))
00160                         continue;
00161 
00162                 // calculate new child's position, size and flags for SetWindowPos
00163                 CalcNewChildPosition(layout, rectParent, rectChild, uFlags);
00164 
00165                 // only if size or position changed
00166                 if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE))
00167                 {
00168                         hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left,
00169                                 rectChild.top, rectChild.Width(), rectChild.Height(), uFlags);
00170                 }
00171         }
00172 
00173         // finally move all the windows at once
00174         ::EndDeferWindowPos(hdwp);
00175 }
00176 
00177 void CResizableLayout::ClipChildWindow(const CResizableLayout::LayoutInfo& layout,
00178                                                                            CRgn* pRegion)
00179 {
00180         // obtain window position
00181         CRect rect;
00182         ::GetWindowRect(layout.hWnd, &rect);
00183         ::MapWindowPoints(NULL, GetResizableWnd()->m_hWnd, (LPPOINT)&rect, 2);
00184 
00185         // use window region if any
00186         CRgn rgn;
00187         rgn.CreateRectRgn(0,0,0,0);
00188         switch (::GetWindowRgn(layout.hWnd, rgn))
00189         {
00190         case COMPLEXREGION:
00191         case SIMPLEREGION:
00192                 rgn.OffsetRgn(rect.TopLeft());
00193                 break;
00194 
00195         default:
00196                 rgn.SetRectRgn(&rect);
00197         }
00198 
00199         // get the clipping property
00200         BOOL bClipping = layout.properties.bAskClipping ?
00201                 LikesClipping(layout) : layout.properties.bCachedLikesClipping;
00202 
00203         // modify region accordingly
00204         if (bClipping)
00205                 pRegion->CombineRgn(pRegion, &rgn, RGN_DIFF);
00206         else
00207                 pRegion->CombineRgn(pRegion, &rgn, RGN_OR);
00208 }
00209 
00210 void CResizableLayout::GetClippingRegion(CRgn* pRegion)
00211 {
00212         CWnd* pWnd = GetResizableWnd();
00213 
00214         // System's default clipping area is screen's size,
00215         // not enough for max track size, for example:
00216         // if screen is 1024 x 768 and resizing border is 4 pixels,
00217         // maximized size is 1024+4*2=1032 x 768+4*2=776,
00218         // but max track size is 4 pixels bigger 1036 x 780 (don't ask me why!)
00219         // So, if you resize the window to maximum size, the last 4 pixels
00220         // are clipped out by the default clipping region, that gets created
00221         // as soon as you call clipping functions (my guess).
00222 
00223         // reset clipping region to the whole client area
00224         CRect rect;
00225         pWnd->GetClientRect(&rect);
00226         pRegion->CreateRectRgnIndirect(&rect);
00227 
00228         // clip only anchored controls
00229         LayoutInfo layout;
00230         POSITION pos = m_listLayout.GetHeadPosition();
00231         while (pos != NULL)
00232         {
00233                 // get layout info
00234                 layout = m_listLayout.GetNext(pos);
00235                 
00236                 if (::IsWindowVisible(layout.hWnd))
00237                         ClipChildWindow(layout, pRegion);
00238         }
00239         pos = m_listLayoutCB.GetHeadPosition();
00240         while (pos != NULL)
00241         {
00242                 // get layout info
00243                 layout = m_listLayoutCB.GetNext(pos);
00244                 // request data
00245                 if (!ArrangeLayoutCallback(layout))
00246                         continue;
00247 
00248                 if (::IsWindowVisible(layout.hWnd))
00249                         ClipChildWindow(layout, pRegion);
00250         }
00251 
00252         // fix for RTL layouts (1 pixel of horz offset)
00253         if (pWnd->GetExStyle() & 0x00400000L/*WS_EX_LAYOUTRTL*/)
00254                 pRegion->OffsetRgn(-1,0);
00255 }
00256 
00257 void CResizableLayout::EraseBackground(CDC* pDC)
00258 {
00259         HWND hWnd = GetResizableWnd()->GetSafeHwnd();
00260 
00261         // retrieve the background brush
00262         HBRUSH hBrush = NULL;
00263 
00264         // is this a dialog box?
00265         // (using class atom is quickier than using the class name)
00266         ATOM atomWndClass = (ATOM)::GetClassLong(hWnd, GCW_ATOM);
00267         if (atomWndClass == (ATOM)0x8002)
00268         {
00269                 // send a message to the dialog box
00270                 hBrush = (HBRUSH)::SendMessage(hWnd, WM_CTLCOLORDLG,
00271                         (WPARAM)pDC->GetSafeHdc(), (LPARAM)hWnd);
00272         }
00273         else
00274         {
00275                 // take the background brush from the window's class
00276                 hBrush = (HBRUSH)::GetClassLongPtr(hWnd, GCL_HBRBACKGROUND);
00277         }
00278 
00279         // fill the clipped background
00280         CRgn rgn;
00281         GetClippingRegion(&rgn);
00282 
00283         ::FillRgn(pDC->GetSafeHdc(), rgn, hBrush);
00284 }
00285 
00286 // support legacy code (will disappear in future versions)
00287 void CResizableLayout::ClipChildren(CDC* pDC)
00288 {
00289         CRgn rgn;
00290         GetClippingRegion(&rgn);
00291         // the clipping region is in device units
00292         rgn.OffsetRgn(-pDC->GetWindowOrg());
00293         pDC->SelectClipRgn(&rgn);
00294 }
00295 
00296 void CResizableLayout::GetTotalClientRect(LPRECT lpRect)
00297 {
00298         GetResizableWnd()->GetClientRect(lpRect);
00299 }
00300 
00301 BOOL CResizableLayout::NeedsRefresh(const CResizableLayout::LayoutInfo& layout,
00302                                                                         const CRect& rectOld, const CRect& rectNew)
00303 {
00304         if (layout.bMsgSupport)
00305         {
00306                 REFRESHPROPERTY refresh;
00307                 refresh.rcOld = rectOld;
00308                 refresh.rcNew = rectNew;
00309                 if (Send_NeedsRefresh(layout.hWnd, &refresh))
00310                         return refresh.bNeedsRefresh;
00311         }
00312 
00313         int nDiffWidth = (rectNew.Width() - rectOld.Width());
00314         int nDiffHeight = (rectNew.Height() - rectOld.Height());
00315 
00316         // is the same size?
00317         if (nDiffWidth == 0 && nDiffHeight == 0)
00318                 return FALSE;
00319 
00320         // optimistic, no need to refresh
00321         BOOL bRefresh = FALSE;
00322 
00323         // window classes that need refresh when resized
00324         if (layout.sWndClass == WC_STATIC)
00325         {
00326                 DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE);
00327 
00328                 switch (style & SS_TYPEMASK)
00329                 {
00330                 case SS_LEFT:
00331                 case SS_CENTER:
00332                 case SS_RIGHT:
00333                         // word-wrapped text
00334                         bRefresh = bRefresh || (nDiffWidth != 0);
00335                         // vertically centered text
00336                         if (style & SS_CENTERIMAGE)
00337                                 bRefresh = bRefresh || (nDiffHeight != 0);
00338                         break;
00339 
00340                 case SS_LEFTNOWORDWRAP:
00341                         // text with ellipsis
00342                         if (style & SS_ELLIPSISMASK)
00343                                 bRefresh = bRefresh || (nDiffWidth != 0);
00344                         // vertically centered text
00345                         if (style & SS_CENTERIMAGE)
00346                                 bRefresh = bRefresh || (nDiffHeight != 0);
00347                         break;
00348 
00349                 case SS_ENHMETAFILE:
00350                 case SS_BITMAP:
00351                 case SS_ICON:
00352                         // images
00353                 case SS_BLACKFRAME:
00354                 case SS_GRAYFRAME:
00355                 case SS_WHITEFRAME:
00356                 case SS_ETCHEDFRAME:
00357                         // and frames
00358                         bRefresh = TRUE;
00359                         break;
00360                 }
00361         }
00362 
00363         // window classes that don't redraw client area correctly
00364         // when the hor scroll pos changes due to a resizing
00365         BOOL bHScroll = FALSE;
00366         if (layout.sWndClass == WC_LISTBOX)
00367                 bHScroll = TRUE;
00368 
00369         // fix for horizontally scrollable windows
00370         if (bHScroll && (nDiffWidth > 0))
00371         {
00372                 // get max scroll position
00373                 SCROLLINFO info;
00374                 info.cbSize = sizeof(SCROLLINFO);
00375                 info.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
00376                 if (::GetScrollInfo(layout.hWnd, SB_HORZ, &info))
00377                 {
00378                         // subtract the page size
00379                         info.nMax -= __max(info.nPage-1,0);
00380                 }
00381 
00382                 // resizing will cause the text to scroll on the right
00383                 // because the scrollbar is going beyond the right limit
00384                 if ((info.nMax > 0) && (info.nPos + nDiffWidth > info.nMax))
00385                 {
00386                         // needs repainting, due to horiz scrolling
00387                         bRefresh = TRUE;
00388                 }
00389         }
00390 
00391         return bRefresh;
00392 }
00393 
00394 BOOL CResizableLayout::LikesClipping(const CResizableLayout::LayoutInfo& layout)
00395 {
00396         if (layout.bMsgSupport)
00397         {
00398                 CLIPPINGPROPERTY clipping;
00399                 if (Send_LikesClipping(layout.hWnd, &clipping))
00400                         return clipping.bLikesClipping;
00401         }
00402 
00403         DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE);
00404 
00405         // skip windows that wants background repainted
00406         if (layout.sWndClass == TOOLBARCLASSNAME && (style & TBSTYLE_TRANSPARENT))
00407                 return FALSE;
00408         else if (layout.sWndClass == WC_BUTTON)
00409         {
00410                 CRect rect;
00411                 switch (style & _BS_TYPEMASK)
00412                 {
00413                 case BS_GROUPBOX:
00414                         return FALSE;
00415 
00416                 case BS_OWNERDRAW:
00417                         // ownerdraw buttons must return correct hittest code
00418                         // to notify their transparency to the system and this library
00419                         ::GetWindowRect(layout.hWnd, &rect);
00420                         if ( HTTRANSPARENT == ::SendMessage(layout.hWnd,
00421                                 WM_NCHITTEST, 0, MAKELPARAM(rect.left, rect.top)) )
00422                                 return FALSE;
00423                         break;
00424                 }
00425                 return TRUE;
00426         }
00427         else if (layout.sWndClass == WC_STATIC)
00428         {
00429                 switch (style & SS_TYPEMASK)
00430                 {
00431                 case SS_LEFT:
00432                 case SS_CENTER:
00433                 case SS_RIGHT:
00434                 case SS_SIMPLE:
00435                 case SS_LEFTNOWORDWRAP:
00436                         // text
00437                 case SS_BLACKRECT:
00438                 case SS_GRAYRECT:
00439                 case SS_WHITERECT:
00440                         // filled rects
00441                 case SS_ETCHEDHORZ:
00442                 case SS_ETCHEDVERT:
00443                         // etched lines
00444                 case SS_BITMAP:
00445                         // bitmaps
00446                         return TRUE;
00447                         break;
00448 
00449                 case SS_ICON:
00450                 case SS_ENHMETAFILE:
00451                         if (style & SS_CENTERIMAGE)
00452                                 return FALSE;
00453                         return TRUE;
00454                         break;
00455 
00456                 default:
00457                         return FALSE;
00458                 }
00459         }
00460 
00461         // assume the others like clipping
00462         return TRUE;
00463 }
00464 
00465 void CResizableLayout::CalcNewChildPosition(const CResizableLayout::LayoutInfo& layout,
00466                                                                 const CRect &rectParent, CRect &rectChild, UINT& uFlags)
00467 {
00468         CWnd* pParent = GetResizableWnd();
00469 
00470         ::GetWindowRect(layout.hWnd, &rectChild);
00471         ::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2);
00472         
00473         CRect rectNew;
00474 
00475         // calculate new top-left corner
00476         rectNew.left = layout.sizeMarginTL.cx + rectParent.Width() * layout.sizeTypeTL.cx / 100;
00477         rectNew.top = layout.sizeMarginTL.cy + rectParent.Height() * layout.sizeTypeTL.cy / 100;
00478         
00479         // calculate new bottom-right corner
00480         rectNew.right = layout.sizeMarginBR.cx + rectParent.Width() * layout.sizeTypeBR.cx / 100;
00481         rectNew.bottom = layout.sizeMarginBR.cy + rectParent.Height() * layout.sizeTypeBR.cy / 100;
00482 
00483         // adjust position, if client area has been scrolled
00484         rectNew.OffsetRect(rectParent.TopLeft());
00485 
00486         // get the refresh property
00487         BOOL bRefresh = layout.properties.bAskRefresh ?
00488                 NeedsRefresh(layout, rectChild, rectNew) : layout.properties.bCachedNeedsRefresh;
00489 
00490         // set flags 
00491         uFlags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION;
00492         if (bRefresh)
00493                 uFlags |= SWP_NOCOPYBITS;
00494         if (rectNew.TopLeft() == rectChild.TopLeft())
00495                 uFlags |= SWP_NOMOVE;
00496         if (rectNew.Size() == rectChild.Size())
00497                 uFlags |= SWP_NOSIZE;
00498 
00499         // update rect
00500         rectChild = rectNew;
00501 }
00502 
00503 void CResizableLayout::InitResizeProperties(CResizableLayout::LayoutInfo &layout)
00504 {
00505         // check if custom window supports this library
00506         // (properties must be correctly set by the window)
00507         layout.bMsgSupport = Send_QueryProperties(layout.hWnd, &layout.properties);
00508 
00509         // default properties
00510         if (!layout.bMsgSupport)
00511         {
00512                 // clipping property is assumed as static
00513                 layout.properties.bAskClipping = FALSE;
00514                 layout.properties.bCachedLikesClipping = LikesClipping(layout);
00515                 // refresh property is assumed as dynamic
00516                 layout.properties.bAskRefresh = TRUE;
00517         }
00518 }

Generated on Tue Dec 13 14:47:57 2005 for guliverkli by  doxygen 1.4.5