- Main Page
- Namespaces
- Classes
- Files
- Directories

- File List
- File Members

# 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 - ppescher@yahoo.com)
    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 }