// =================================================================================== // Class Implementation : COXDragDockContext // =================================================================================== // Header file : OXDragDockContext.cpp // Version: 9.3 // This software along with its related components, documentation and files ("The Libraries") // is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is // governed by a software license agreement ("Agreement"). Copies of the Agreement are // available at The Code Project (www.codeproject.com), as part of the package you downloaded // to obtain this file, or directly from our office. For a copy of the license governing // this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900. // Some portions Copyright (C)1994-5 Micro Focus Inc, 2465 East Bayshore Rd, Palo Alto, CA 94303. // ////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include #include "UTB64Bit.h" #ifndef __OXMFCIMPL_H__ #if _MFC_VER >= 0x0700 #if _MFC_VER >= 1400 #include #endif #include <..\src\mfc\afximpl.h> #else #include <..\src\afximpl.h> #endif #define __OXMFCIMPL_H__ #endif #include "OXDragDockContext.h" #include "OXMainRes.h" // for some resource strings #include "OXSizeCtrlBar.h" #include "OXSizeDockBar.h" #include "OXMDIFloatWnd.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif #define new DEBUG_NEW #define HORZF(dw) (dw & CBRS_ORIENT_HORZ) #define VERTF(dw) (dw & CBRS_ORIENT_VERT) // adjust supplied rectangle, so that point, pt is inside it. // Adjusted slightly from original orientation to ensure point is now // within specificed rectangle. static void AdjustRectangle(CRect& rect, CPoint pt) { int nXOffset = (pt.x < rect.left) ? (pt.x - rect.left) : (pt.x > rect.right) ? (pt.x - rect.right) : 0; // int nYOffset = (pt.y < rect.top) ? (pt.y - rect.top) : // (pt.y > rect.bottom) ? (pt.y - rect.bottom) : 0; // rect.OffsetRect(nXOffset, nYOffset); rect.OffsetRect(nXOffset, 0); } ///////////////////////////////////////////////////////////////////////////// // Definition of static members // Data members ------------------------------------------------------------- // protected: // private: // Member functions --------------------------------------------------------- // public: COXDragDockContext::COXDragDockContext(CControlBar* pBar) : CDockContext(pBar) { } COXDragDockContext::~COXDragDockContext() { } ///////////////////////////////////////////////////////////////////////////// // COXDragDockContext operations // start dragging. This is the only routine exposed externally. // pt = mouse position at start of drag (screen co-ords) void COXDragDockContext::StartDrag(CPoint pt) { ASSERT_VALID(m_pBar); ASSERT(m_pBar->IsKindOf(RUNTIME_CLASS(COXSizeControlBar))); COXSizeControlBar* pSzBar = (COXSizeControlBar*)m_pBar; // get styles from bar m_dwDockStyle = m_pBar->m_dwDockStyle; m_dwStyle = m_pBar->m_dwStyle & CBRS_ALIGN_ANY; ASSERT(m_dwStyle != 0); // check to see we're not hanging from a COXMDIFloatWnd. // Disallow dragging if we are... if (m_pBar->IsFloating()) { CFrameWnd* pFrameWnd = m_pBar->GetParentFrame(); ASSERT(pFrameWnd != NULL); ASSERT(pFrameWnd->IsKindOf(RUNTIME_CLASS(CFrameWnd))); if (pFrameWnd->IsKindOf(RUNTIME_CLASS(COXMDIFloatWnd))) return; // do nothing if floating inside a COXMDIFloatWnd } // dragging has started message (only if window will actually dock !) if ((m_dwDockStyle & CBRS_ALIGN_ANY) == CBRS_ALIGN_ANY) AfxGetMainWnd()->SendMessage(WM_SETMESSAGESTRING, IDS_OX_MRC_STARTDOCKING); // handle pending WM_PAINT messages MSG msg; while (::PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_NOREMOVE)) { if (!GetMessage(&msg, NULL, WM_PAINT, WM_PAINT)) return; ASSERT(msg.message == WM_PAINT); DispatchMessage(&msg); } // initialize drag state m_rectLast.SetRectEmpty(); m_sizeLast.cx = m_sizeLast.cy = 0; m_bForceFrame = m_bFlip = m_bDitherLast = FALSE; // get current bar location CRect rect; m_pBar->GetWindowRect(rect); m_ptLast = pt; m_ptStart = pt; BOOL bHorz = HORZF(m_dwStyle); // MFC includes code for flipping orientation using the shift key - I wasn't keen // on this... (sorry) so I've left it out for now. Some references are still left // in for it, in case I decide to implement it. // Start by working out the possible rectangles that dragging could result in. // These are: // m_rectFrameDragHorz : floating frame, horizontal orientation // m_rectFrameDragVert : floating frame, vertical orientation (not used, 'cos // flipping not allowed) // // m_rectDragHorz : docking horizontally, another bar already on this row // m_rectDragVert : docking vertically, another bar already on this row // m_rectDragHorzAlone : docking horizontally, on a new row // m_rectDragVertAlone : docking vertically, on a new row // calculate dragging rects if you drag on the new row/column // CRect rectBorder; m_pDockSite->RepositionBars(0,0xffff,AFX_IDW_PANE_FIRST, CFrameWnd::reposQuery,&rectBorder); m_pDockSite->ClientToScreen(rectBorder); CWnd* pLeftDockBar=m_pDockSite->GetControlBar(AFX_IDW_DOCKBAR_LEFT); if(pLeftDockBar!=NULL && pLeftDockBar->GetStyle()&WS_VISIBLE) { CRect rectDockBar; pLeftDockBar->GetWindowRect(rectDockBar); rectBorder.left-=rectDockBar.Width(); } CWnd* pRightDockBar=m_pDockSite->GetControlBar(AFX_IDW_DOCKBAR_RIGHT); if(pRightDockBar!=NULL && pRightDockBar->GetStyle()&WS_VISIBLE) { CRect rectDockBar; pRightDockBar->GetWindowRect(rectDockBar); rectBorder.right+=rectDockBar.Width(); } m_rectDragHorzAlone=CRect(CPoint(rectBorder.left,rect.top),rectBorder.Size()); m_rectDragVertAlone=CRect(CPoint(rect.left,rectBorder.top),rectBorder.Size()); m_rectDragHorzAlone.bottom=m_rectDragHorzAlone.top+pSzBar->m_HorzDockSize.cy; m_rectDragVertAlone.right=m_rectDragVertAlone.left+pSzBar->m_VertDockSize.cx; // ////////////////////////////////////////////////////////////// ////////////////// // int nDockAreaWidth = rectBorder.Width(); int nDockAreaHeight = rectBorder.Height(); CSize HorzAloneSize(nDockAreaWidth, pSzBar->m_HorzDockSize.cy); CSize VertAloneSize(pSzBar->m_VertDockSize.cx, nDockAreaHeight); // sizes to use when docking into a row that already has some bars. // use the stored sizes - unless they are > the max dock area - // in which case make a guess. if (pSzBar->m_VertDockSize.cy >= nDockAreaHeight - 16) VertAloneSize.cy = nDockAreaHeight / 3; else VertAloneSize.cy = pSzBar->m_VertDockSize.cy; if (pSzBar->m_HorzDockSize.cx >= nDockAreaWidth - 16) HorzAloneSize.cx = nDockAreaWidth / 3; else HorzAloneSize.cx = pSzBar->m_HorzDockSize.cx; m_rectDragHorz = CRect(rect.TopLeft(), HorzAloneSize); m_rectDragVert = CRect(rect.TopLeft(), VertAloneSize); // /////////////////// // rectangle for the floating frame... m_rectFrameDragVert = m_rectFrameDragHorz = CRect(rect.TopLeft(), pSzBar->m_FloatSize); // To work out the size we actually create a floating mini frame, and then see how big // it is CMiniDockFrameWnd* pFloatFrame = m_pDockSite->CreateFloatingFrame(bHorz ? CBRS_ALIGN_TOP : CBRS_ALIGN_LEFT); if (pFloatFrame == NULL) AfxThrowMemoryException(); pFloatFrame->CalcWindowRect(&m_rectFrameDragHorz); pFloatFrame->CalcWindowRect(&m_rectFrameDragVert); // m_rectFrameDragHorz.InflateRect(-afxData.cxBorder2, -afxData.cyBorder2); // m_rectFrameDragVert.InflateRect(-afxData.cxBorder2, -afxData.cyBorder2); pFloatFrame->DestroyWindow(); // adjust rectangles so that point is inside AdjustRectangle(m_rectDragHorzAlone, pt); AdjustRectangle(m_rectDragVertAlone, pt); AdjustRectangle(m_rectDragHorz, pt); AdjustRectangle(m_rectDragVert, pt); AdjustRectangle(m_rectFrameDragHorz, pt); AdjustRectangle(m_rectFrameDragVert, pt); // lock window update while dragging ASSERT(m_pDC == NULL); CWnd* pWnd = CWnd::GetDesktopWindow(); #ifndef _MAC if (pWnd->LockWindowUpdate()) m_pDC = pWnd->GetDCEx(NULL, DCX_WINDOW|DCX_CACHE|DCX_LOCKWINDOWUPDATE); else #endif m_pDC = pWnd->GetDCEx(NULL, DCX_WINDOW|DCX_CACHE); ASSERT(m_pDC != NULL); // initialize tracking state and enter tracking loop m_dwOverDockStyle = CanDock(); Move(pt); // call it here to handle special keys Track(); } void COXDragDockContext::Move(CPoint pt) { CPoint ptOffset = pt - m_ptLast; #ifdef _MAC // prevent dragging the floating window completely under the menu bar GDHandle hgd = _AfxFindDevice(pt.x, pt.y); if (hgd == NULL || hgd == GetMainDevice() || TestDeviceAttribute(hgd, hasAuxMenuBar)) { CRect rect; if ((HORZF(m_dwStyle) && !m_bFlip) || (VERTF(m_dwStyle) && m_bFlip)) rect = m_rectFrameDragHorz; else rect = m_rectFrameDragVert; // determine our new position rect.OffsetRect(ptOffset); // keep us on the screen if we were getting too close to the menu bar int yMBarBottom = (*hgd)->gdRect.top + GetMBarHeight() + 4; if (rect.bottom < yMBarBottom) { pt.y += yMBarBottom - rect.bottom; ptOffset.y += yMBarBottom - rect.bottom; } } #endif // offset all drag rects to new position m_rectDragHorz.OffsetRect(ptOffset); m_rectFrameDragHorz.OffsetRect(ptOffset); m_rectDragVert.OffsetRect(ptOffset); m_rectFrameDragVert.OffsetRect(ptOffset); // these rectangles only move in 1 direction m_rectDragHorzAlone.top += ptOffset.y; m_rectDragHorzAlone.bottom += ptOffset.y; m_rectDragVertAlone.left += ptOffset.x; m_rectDragVertAlone.right += ptOffset.x; m_ptLast = pt; // if control key is down don't dock m_dwOverDockStyle = m_bForceFrame ? 0 : CanDock(); // update feedback DrawFocusRect(); } void COXDragDockContext::OnKey(int nChar, BOOL bDown) { if (nChar == VK_CONTROL) UpdateState(&m_bForceFrame, bDown); } void COXDragDockContext::EndDrag() { CancelDrag(); if (m_ptStart == m_ptLast) return; m_dwOverDockStyle = m_bForceFrame ? 0 : CanDock(); if (m_dwOverDockStyle != 0) { // dockbar we're going to dock at. CDockBar* pDockBar = GetDockBar(); ASSERT(pDockBar != NULL); // check the original dockbar - if a valid CSizeDockBar... // work out the row number. CDockBar* pOrigDockBar = m_pBar->m_pDockBar; int nOrigCheckSum = -1; if (pOrigDockBar != NULL && pOrigDockBar->IsKindOf(RUNTIME_CLASS(COXSizeDockBar))) nOrigCheckSum = ((COXSizeDockBar*)pOrigDockBar)->CheckSumBars(); // Now we're going to actually dock the window. // Update the appropriate size in the control bar. if (HORZF(m_dwOverDockStyle)) { ((COXSizeControlBar*)m_pBar)->m_HorzDockSize = m_rectDragDock.Size(); } else { ((COXSizeControlBar*)m_pBar)->m_VertDockSize = m_rectDragDock.Size(); } m_pDockSite->DockControlBar(m_pBar, pDockBar, m_rectDragDock); // if into a sizeable dockbar (always we be !), then adjust other bars in the same row // to attempt to maintain size if (pDockBar->IsKindOf(RUNTIME_CLASS(COXSizeDockBar))) { if (pOrigDockBar != pDockBar || ((COXSizeDockBar*)pDockBar)->CheckSumBars() != nOrigCheckSum) { ((COXSizeDockBar*)pDockBar)->AdjustForNewBar(m_pBar); } // force RecalcLayout below to adjust sizes always for the bar into // which we have docked - this is needed as if the bar doesn't // actually changed position in the array, but has changed size // (because the docking algorithm above guess the size wrong, then // we need to set it back again. ((COXSizeDockBar*)pDockBar)->m_CountBars = 0; } // This RecalcLayout is what will adjust the size. m_pDockSite->RecalcLayout(); } else { m_ptMRUFloatPos = m_rectFrameDragHorz.TopLeft(); m_pDockSite->FloatControlBar(m_pBar, m_rectFrameDragHorz.TopLeft(), CBRS_ALIGN_TOP | (m_dwDockStyle & CBRS_FLOAT_MULTI)); m_pBar->SendMessage(WM_OX_APP_AFTERFLOAT_MSG); // set flag to indicate user has moved the bar - done as a style flag on the window. CWnd* pFrameWnd = m_pBar->GetParentFrame(); ASSERT(pFrameWnd->IsKindOf(RUNTIME_CLASS(CMiniDockFrameWnd))); pFrameWnd->ModifyStyle(0, CBRS_MOVED_BY_USER); } } void COXDragDockContext::CancelDrag() { DrawFocusRect(TRUE); // gets rid of focus rect ReleaseCapture(); CWnd* pWnd = CWnd::GetDesktopWindow(); #ifndef _MAC pWnd->UnlockWindowUpdate(); #endif if (m_pDC != NULL) { pWnd->ReleaseDC(m_pDC); m_pDC = NULL; } // Tell main window to clear it's status bar CWnd* pMainWnd = AfxGetMainWnd(); ASSERT(pMainWnd != NULL); pMainWnd->SendMessage(WM_SETMESSAGESTRING, (WPARAM)AFX_IDS_IDLEMESSAGE); } ///////////////////////////////////////////////////////////////////////////// // Implementation void COXDragDockContext::DrawFocusRect(BOOL bRemoveRect) { ASSERT(m_pDC != NULL); // default to thin frame CSize size(CX_BORDER, CY_BORDER); // determine new rect and size CRect rect; CBrush* pWhiteBrush = CBrush::FromHandle((HBRUSH)::GetStockObject(WHITE_BRUSH)); CBrush* pDitherBrush = CDC::GetHalftoneBrush(); CBrush* pBrush = pWhiteBrush; if (m_dwOverDockStyle != 0) { rect = m_rectDragDock; } else { #ifndef _MAC // use thick frame instead size.cx = GetSystemMetrics(SM_CXFRAME) - CX_BORDER; size.cy = GetSystemMetrics(SM_CYFRAME) - CY_BORDER; #endif rect = m_rectFrameDragHorz; pBrush = pDitherBrush; } if (bRemoveRect) size.cx = size.cy = 0; #if _MFC_VER < 0x0700 if ( afxData.bWin4 && (HORZF(m_dwOverDockStyle) || VERTF(m_dwOverDockStyle))) #else if (HORZF(m_dwOverDockStyle) || VERTF(m_dwOverDockStyle)) #endif { // looks better one pixel in (makes the bar look pushed down) rect.InflateRect(-CX_BORDER, -CY_BORDER); } // draw it and remember last size m_pDC->DrawDragRect(&rect, size, &m_rectLast, m_sizeLast, pBrush, m_bDitherLast ? pDitherBrush : pWhiteBrush); m_rectLast = rect; m_sizeLast = size; m_bDitherLast = (pBrush == pDitherBrush); } void COXDragDockContext::UpdateState(BOOL* pFlag, BOOL bNewValue) { if (*pFlag != bNewValue) { *pFlag = bNewValue; m_dwOverDockStyle = (m_bForceFrame) ? 0 : CanDock(); DrawFocusRect(); } } DWORD COXDragDockContext::CanDock() { BOOL bStyleHorz; DWORD dwDock = 0; // Dock Canidate DWORD dwCurr = 0; // Current Orientation // let's check for something in our current orientation first // then if the shift key is not forcing our orientation then // check for horizontal or vertical orientations as long // as we are close enough ASSERT(m_dwStyle != 0); bStyleHorz = HORZF(m_dwStyle); m_pTargetDockBar = NULL; // can simplify this, 'cos most of the rectangles are actually the same if (dwDock == 0 && HORZF(m_dwDockStyle)/* && m_rectDragHorzAlone.PtInRect(m_ptLast)*/) { dwDock = m_pDockSite->CanDock(m_rectDragHorzAlone, m_dwDockStyle & ~CBRS_ORIENT_VERT, (CDockBar**)&m_pTargetDockBar); } if (dwDock == 0 && VERTF(m_dwDockStyle)/* && m_rectDragVertAlone.PtInRect(m_ptLast)*/) { dwDock = m_pDockSite->CanDock(m_rectDragVertAlone, m_dwDockStyle & ~CBRS_ORIENT_HORZ, (CDockBar**)&m_pTargetDockBar); } if (dwDock != 0) // will dock somewhere - now look for 1/2 bars on the row... { if (HORZF(dwDock)) { m_rectDragDock = m_rectDragHorzAlone; dwCurr = m_pDockSite->CanDock(m_rectDragHorz, m_dwDockStyle & ~CBRS_ORIENT_VERT, (CDockBar**)&m_pTargetDockBar); if (dwCurr != 0) { int nBars=m_pTargetDockBar->BarsOnThisRow(m_pBar,m_rectDragHorzAlone); if(nBars!=0) { m_rectDragDock = m_rectDragHorz; m_rectDragDock.right = m_rectDragDock.left+ m_rectDragHorzAlone.Width()/(nBars+1); } } } if (VERTF(dwDock)) { m_rectDragDock = m_rectDragVertAlone; dwCurr = m_pDockSite->CanDock(m_rectDragVert, m_dwDockStyle & ~CBRS_ORIENT_HORZ, (CDockBar**)&m_pTargetDockBar); if (dwCurr != 0) { int nBars=m_pTargetDockBar->BarsOnThisRow(m_pBar,m_rectDragVertAlone); if(nBars!=0) { m_rectDragDock = m_rectDragVert; m_rectDragDock.bottom = m_rectDragDock.top+ m_rectDragVertAlone.Height()/(nBars+1); } } } } return dwCurr; } // should have m_dwOverDockStyle and m_rectDragDock set before calling this // routine. CDockBar* COXDragDockContext::GetDockBar() { DWORD dw = 0; CDockBar* pBar; if (HORZF(m_dwOverDockStyle)) { dw = m_pDockSite->CanDock(m_rectDragDock, m_dwOverDockStyle & ~CBRS_ORIENT_VERT, &pBar); ASSERT(dw != 0); ASSERT(pBar != NULL); return pBar; } if (VERTF(m_dwOverDockStyle)) { dw = m_pDockSite->CanDock(m_rectDragDock, m_dwOverDockStyle & ~CBRS_ORIENT_HORZ, &pBar); ASSERT(dw != 0); ASSERT(pBar != NULL); return pBar; } return NULL; } BOOL COXDragDockContext::Track() { // don't handle if capture already set if (::GetCapture() != NULL) return FALSE; // set capture to the window which received this message m_pBar->SetCapture(); ASSERT(m_pBar == CWnd::GetCapture()); // get messages until capture lost or cancelled/accepted while(CWnd::GetCapture() == m_pBar) { MSG msg; #ifndef _MAC if (!::GetMessage(&msg, NULL, 0, 0)) #else // don't allow yielding while tracking since we don't have LockWindowUpdate if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE|PM_NOYIELD)) continue; if (msg.message == WM_QUIT) #endif { AfxPostQuitMessage(PtrToInt(msg.wParam)); break; } switch (msg.message) { case WM_LBUTTONUP: // drag finished EndDrag(); return TRUE; case WM_MOUSEMOVE: Move(msg.pt); break; case WM_KEYUP: OnKey((int)msg.wParam, FALSE); break; case WM_KEYDOWN: OnKey((int)msg.wParam, TRUE); if (msg.wParam == VK_ESCAPE) goto exit_cancel_drag; break; case WM_RBUTTONDOWN: goto exit_cancel_drag; // just dispatch rest of the messages default: DispatchMessage(&msg); break; } } exit_cancel_drag: // goto - can't use break as we're inside a switch() CancelDrag(); return FALSE; } // Wow - another genuine virtual function in CDockContext ! // overridden to prevent windows floated in an MDI window from being double-clicked void COXDragDockContext::ToggleDocking() { ASSERT_VALID(m_pBar); ASSERT(m_pBar->IsKindOf(RUNTIME_CLASS(COXSizeControlBar))); // check to see we're not hanging from a COXMDIFloatWnd. // Disallow toggle docking if we are if (m_pBar->IsFloating()) { CFrameWnd* pFrameWnd = m_pBar->GetParentFrame(); ASSERT(pFrameWnd != NULL); ASSERT(pFrameWnd->IsKindOf(RUNTIME_CLASS(CFrameWnd))); if (pFrameWnd->IsKindOf(RUNTIME_CLASS(COXMDIFloatWnd))) return; // do nothing if floating inside a COXMDIFloatWnd } CDockContext::ToggleDocking(); }