////////////////// // Initialize map: set up all the next/prev pointers. This converts the // linear array to a more convenient linked list. Called from END_WINDOW_MAP. // WINRECT* WINRECT::InitMap(WINRECT* pWinMap, WINRECT* parent) { assert(pWinMap); WINRECT* pwrc = pWinMap; // current table entry WINRECT* prev = NULL; // previous entry starts out none while (!pwrc->IsEndGroup()) { pwrc->prev=prev; pwrc->next=NULL; if (prev) prev->next = pwrc; prev = pwrc; if (pwrc->IsGroup()) { pwrc = InitMap(pwrc+1,pwrc); // recurse! Returns end-of-grp assert(pwrc->IsEndGroup()); } pwrc++; } // safety checks assert(pwrc->IsEndGroup()); assert(prev); assert(prev->next==NULL); return parent ? pwrc : NULL; }
////////////////// // Position all the rects so they're as wide/high as the total and follow one // another; ie, are adjacent. This operation leaves the height (rows) and // width (columns) unaffected. For rows, set each row's width to rcTotal and // one below the other; for columns, set each column as tall as rcTotal and // each to the right of the previous. // void CWinMgr::PositionRects(WINRECT* pGroup, const CRect& rcTotal, BOOL bRow) { LONG xoryPos = bRow ? rcTotal.top : rcTotal.left; CWinGroupIterator it; for (it=pGroup; it; it.Next()) { WINRECT* wrc = it; CRect& rc = wrc->GetRect(); if (bRow) { // for ROWS: LONG height = rc.Height(); // height of row = total height rc.top = xoryPos; // top = running yPos rc.bottom = rc.top + height; // ... rc.left = rcTotal.left; // ... rc.right = rcTotal.right; // ... xoryPos += height; // increment yPos } else { // for COLS: LONG width = rc.Width(); // width = total width rc.left = xoryPos; // left = running xPos rc.right = rc.left + width; // ... rc.top = rcTotal.top; // ... rc.bottom = rcTotal.bottom; // ... xoryPos += width; // increment xPos } } }
////////////////// // Get the parent of a given WINRECT. To find the parent, chase the prev // pointer to the start of the list, then take the item before that in // memory. // WINRECT* WINRECT::Parent() { WINRECT* pEntry; for (pEntry=this; pEntry->Prev(); pEntry=pEntry->Prev()) { ; // go backwards to the end } // the entry before the first child is the group WINRECT *parent = pEntry-1; assert(parent->IsGroup()); return parent; }
////////////////// // Move desired rectangle by a given vector amount. // Call this when a sizer bar tells you it has moved. // void CWinMgr::MoveRect(WINRECT* pwrcMove, CPoint ptMove, CWnd* pParentWnd) { ASSERT(pwrcMove); WINRECT* prev = pwrcMove->Prev(); ASSERT(prev); WINRECT* next = pwrcMove->Next(); ASSERT(next); BOOL bIsRow = pwrcMove->Parent()->IsRowGroup(); CRect& rcNext = next->GetRect(); CRect& rcPrev = prev->GetRect(); if (bIsRow) { // a row can only be moved up or down ptMove.x = 0; rcPrev.bottom += ptMove.y; rcNext.top += ptMove.y; } else { // a column can only be moved left or right ptMove.y = 0; rcPrev.right += ptMove.x; rcNext.left += ptMove.x; } pwrcMove->GetRect() += ptMove; if (prev->IsGroup()) CalcGroup(prev, pParentWnd); if (next->IsGroup()) CalcGroup(next, pParentWnd); }
////////////////// // Move desired rectangle by a given vector amount. // Call this when a sizer bar tells you it has moved. // void CWinMgr::MoveRect(WINRECT* pwrcMove, POINT ptMove, HWND pParentWnd) { assert(pwrcMove); WINRECT* prev = pwrcMove->Prev(); assert(prev); WINRECT* next = pwrcMove->Next(); assert(next); BOOL bIsRow = pwrcMove->Parent()->IsRowGroup(); RECT& rcNext = next->GetRect(); RECT& rcPrev = prev->GetRect(); if (bIsRow) { // a row can only be moved up or down ptMove.x = 0; rcPrev.bottom += ptMove.y; rcNext.top += ptMove.y; } else { // a column can only be moved left or right ptMove.y = 0; rcPrev.right += ptMove.x; rcNext.left += ptMove.x; } OffsetRect(pwrcMove->GetRect(), ptMove); if (prev->IsGroup()) CalcGroup(prev, pParentWnd); if (next->IsGroup()) CalcGroup(next, pParentWnd); }
////////////////// // Set each control's tofit (desired) size to current size. Useful for // dialogs, to "remember" the current sizes as desired size. // void CWinMgr::InitToFitSizeFromCurrent(CWnd* pWnd) { ASSERT(pWnd); ASSERT(m_map); GetWindowPositions(pWnd); for (WINRECT* w = m_map; !w->IsEnd(); w++) { if (w->Type()==WRCT_TOFIT && !w->IsGroup()) { w->SetToFitSize(w->GetRect().Size()); } } }
////////////////// // Set each control's tofit (desired) size to current size. Useful for // dialogs, to "remember" the current sizes as desired size. // void CWinMgr::InitToFitSizeFromCurrent(HWND hWnd) { assert(hWnd); assert(m_map); GetWindowPositions(hWnd); for (WINRECT* w = m_map; !w->IsEnd(); ++w) { if (w->Type()==WRCT_TOFIT && !w->IsGroup()) { w->SetToFitSize(RectToSize(w->GetRect())); } } }
////////////////// // Get size information for a single entry (WINRECT). Returns size info in // the SIZEINFO argument. For a group, calculate size info as aggregate of // subentries. // void CWinMgr::OnGetSizeInfo(SIZEINFO& szi, WINRECT* wrc, CWnd* pWnd) { szi.szMin = SIZEZERO; // default min size = zero szi.szMax = SIZEMAX; // default max size = infinite szi.szDesired = wrc->GetRect().Size(); // default desired size = current if (wrc->IsGroup()) { // For groups, calculate min, max, desired size as aggregate of children szi.szDesired = SIZEZERO; BOOL bRow = wrc->IsRowGroup(); CWinGroupIterator it; for (it=wrc; it; it.Next()) { WINRECT* wrc2 = it; SIZEINFO szi2; OnGetSizeInfo(szi2, wrc2, pWnd); if (bRow) { szi.szMin.cx = max(szi.szMin.cx, szi2.szMin.cx); szi.szMin.cy += szi2.szMin.cy; szi.szMax.cx = min(szi.szMax.cx, szi2.szMax.cx); szi.szMax.cy = min(szi.szMax.cy + szi2.szMax.cy, _INFINITY); szi.szDesired.cx = max(szi.szDesired.cx, szi2.szDesired.cx); szi.szDesired.cy += szi2.szDesired.cy; } else { szi.szMin.cx += szi2.szMin.cx; szi.szMin.cy = max(szi.szMin.cy, szi2.szMin.cy); szi.szMax.cx = min(szi.szMax.cx + szi2.szMax.cx, _INFINITY); szi.szMax.cy = min(szi.szMax.cy, szi2.szMax.cy); szi.szDesired.cx += szi2.szDesired.cx; szi.szDesired.cy = max(szi.szDesired.cy, szi2.szDesired.cy); } } // Add margins. int w2,h2; wrc->GetMargins(w2,h2); // get margins w2<<=1; h2<<=1; // double szi.szMin.cx += max(0,w2); // negative margins ==> don't include in min szi.szMin.cy += max(0,h2); // ditto szi.szDesired.cx += abs(w2); // for desired size, use abs vallue szi.szDesired.cy += abs(h2); // ditto } else { // not a group WINRECT* parent = wrc->Parent(); ASSERT(parent); CRect& rcParent = parent->GetRect(); BOOL bRow = parent->IsRowGroup(); int hw, hwMin, hwTotal, pct; switch (wrc->Type()) { case WRCT_FIXED: hw = hwMin = wrc->GetParam(); // ht/wid is parameter if (hw<0) { // if fixed val is negative: hw = -hw; // use absolute val for desired.. hwMin = 0; // ..and zero for minimum } if (bRow) { szi.szMax.cy = szi.szDesired.cy = hw; szi.szMin.cy = hwMin; } else { szi.szMax.cx = szi.szDesired.cx = hw; szi.szMin.cx = hwMin; } break; case WRCT_PCT: pct = wrc->GetParam(); ASSERT(0<pct && pct<100); hwTotal = bRow ? rcParent.Height() : rcParent.Width(); hw = (hwTotal * pct) / 100; szi.szDesired = bRow ? CSize(rcParent.Width(), hw) : CSize(hw, rcParent.Height()); break; case WRCT_TOFIT: if (wrc->HasToFitSize()) { szi.szDesired = wrc->GetToFitSize(); } break; case WRCT_REST: break; default: ASSERT(FALSE); } // If the entry is a window, send message to get min/max/tofit size. // Only set tofit size if type is TOFIT. // if (wrc->IsWindow() && pWnd) { CWnd* pChild = pWnd->GetDlgItem(wrc->GetID()); if (pChild) { if (!pChild->IsWindowVisible() && pWnd->IsWindowVisible()) { // parent visible but child not ==> tofit size is zero // important so hidden windows use no space szi.szDesired = SIZEZERO; } else { szi.szAvail = rcParent.Size(); SendGetSizeInfo(szi, pWnd, wrc->GetID()); } } } szi.szDesired = maxsize(minsize(szi.szDesired,szi.szMax), szi.szMin); } }
////////////////// // Calculate size/positions for a row or column group This is the main // algorithm. If a window is given, it's used to get the min/max size and // desired size for TOFIT types. // void CWinMgr::CalcGroup(WINRECT* pGroup, CWnd* pWnd) { // If this bombs, most likely the first entry in your map is not a group! ASSERT(pGroup && pGroup->IsGroup()); ASSERT(pWnd); // adjust total avail by margins CRect rcTotal = pGroup->GetRect(); int w,h; if (pGroup->GetMargins(w,h)) { w = min(abs(w), rcTotal.Width()/2); h = min(abs(h), rcTotal.Height()/2); rcTotal.DeflateRect(w,h); } BOOL bRow = pGroup->IsRowGroup(); // Is this a row group? // Running height or width: start with total int hwRemaining = bRow ? rcTotal.Height() : rcTotal.Width(); // First, set all rects to their minimum sizes. // This ensures that each rect gets its min size. CWinGroupIterator it; for (it=pGroup; it; it.Next()) { WINRECT* wrc = it; SIZEINFO szi; OnGetSizeInfo(szi, wrc, pWnd); int hwMin = bRow ? szi.szMin.cy : szi.szMin.cx; hwMin = min(hwMin, hwRemaining); // truncate wrc->SetHeightOrWidth(hwMin, bRow); // set hwRemaining -= hwMin; // decrement remaining height/width ASSERT(hwRemaining>=0); } // Now adjust all rects upward to desired size. Save REST rect for last. WINRECT* pRestRect = NULL; for (it=pGroup; it; it.Next()) { WINRECT* wrc = it; if (wrc->Type()==WRCT_REST) { ASSERT(pRestRect==NULL); // can only be one REST rect! pRestRect = wrc; // remember it } else { AdjustSize(wrc, bRow, hwRemaining, pWnd); } } ASSERT(hwRemaining>=0); // Adjust REST rect if any if (pRestRect) { AdjustSize(pRestRect, bRow, hwRemaining, pWnd); ASSERT(hwRemaining==0); } // All the sizes of the entries have been calculated, including // groups (but not their children). Now move all the rects so they're // adjacent to one another, without altering sizes. PositionRects(pGroup, rcTotal, bRow); // Finally, descend recursively into each subgroup. for (it=pGroup; it; it.Next()) { WINRECT* wrc = it; if (wrc->IsGroup()) CalcGroup(wrc, pWnd); // recurse! } }
////////////////// // User pressed mouse: intialize and enter drag state // void CSizerBar::OnLButtonDown(UINT nFlags, CPoint pt) { m_bDragging=TRUE; m_ptOriginal = m_ptPrevious = Rectify(pt); GetWindowRect(&m_rcBar); // bar location in screen coords DrawBar(); // draw it SetCapture(); // all mouse messages are MINE m_hwndPrevFocus = ::SetFocus(m_hWnd); // set focus to me to get Escape key ASSERT(m_pWinMgr); CWinMgr& wm = *m_pWinMgr; // get WINRECTs on either side of me WINRECT* pwrcSizeBar = wm.FindRect(GetDlgCtrlID()); ASSERT(pwrcSizeBar); WINRECT* prev = pwrcSizeBar->Prev(); ASSERT(prev); WINRECT* next = pwrcSizeBar->Next(); ASSERT(next); // get rectangles on eithr side of me CRect rcPrev = prev->GetRect(); CRect rcNext = next->GetRect(); // get parent window CWnd * pParentWnd = GetParent(); ASSERT(pParentWnd); // Get size info for next/prev rectangles, so I know what the min/max // sizes are and don't violate them. Max size never tested. SIZEINFO szi; wm.OnGetSizeInfo(szi, prev, pParentWnd); CRect rcPrevMin(rcPrev.TopLeft(),szi.szMin); CRect rcPrevMax(rcPrev.TopLeft(),szi.szMax); wm.OnGetSizeInfo(szi, next, pParentWnd); CRect rcNextMin(rcNext.BottomRight()-szi.szMin, rcNext.BottomRight()); CRect rcNextMax(rcNext.BottomRight()-szi.szMax, rcNext.BottomRight()); // Initialize m_rcConstrain. This is the box the user is allowed to move // the sizer bar in. Can't go outside of this--would violate min/max // constraints of windows on either side. m_rcConstrain.SetRect( max(rcPrevMin.right, rcNextMax.left), max(rcPrevMin.bottom,rcNextMax.top), min(rcPrevMax.right, rcNextMin.left), min(rcPrevMax.bottom,rcNextMin.top)); // convert to my client coords pParentWnd->ClientToScreen(&m_rcConstrain); ScreenToClient(&m_rcConstrain); // Now adjust m_rcConstrain for the fact the bar is not a pure line, but // has solid width -- so I have to make a little bigger/smaller according // to the offset of mouse coords within the sizer bar rect iteself. ClientToScreen(&pt); m_rcConstrain.SetRect(m_rcConstrain.TopLeft() + (pt - m_rcBar.TopLeft()), m_rcConstrain.BottomRight() - (m_rcBar.BottomRight()-pt)); }