// This is a part of the Microsoft Foundation Classes C++ library. // Copyright (C) 1992-1998 Microsoft Corporation // All rights reserved. // // This source code is only intended as a supplement to the // Microsoft Foundation Classes Reference and related // electronic documentation provided with the library. // See these sources for detailed information regarding the // Microsoft Foundation Classes product. #include "stdafx.h" #ifdef AFX_CORE3_SEG #pragma code_seg(AFX_CORE3_SEG) #endif #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // Visual attributes and other constants // HitTest return values (values and spacing between values is important) enum HitTestValue { noHit = 0, vSplitterBox = 1, hSplitterBox = 2, bothSplitterBox = 3, // just for keyboard vSplitterBar1 = 101, vSplitterBar15 = 115, hSplitterBar1 = 201, hSplitterBar15 = 215, splitterIntersection1 = 301, splitterIntersection225 = 525 }; ///////////////////////////////////////////////////////////////////////////// // CSplitterWnd BEGIN_MESSAGE_MAP(CSplitterWnd, CWnd) //{{AFX_MSG_MAP(CSplitterWnd) ON_WM_SETCURSOR() ON_WM_MOUSEMOVE() ON_WM_PAINT() ON_WM_LBUTTONDOWN() ON_WM_LBUTTONDBLCLK() ON_WM_LBUTTONUP() ON_WM_KEYDOWN() ON_WM_SIZE() ON_WM_HSCROLL() ON_WM_VSCROLL() ON_WM_NCCREATE() ON_WM_SYSCOMMAND() ON_WM_CANCELMODE() ON_MESSAGE_VOID(WM_DISPLAYCHANGE, OnDisplayChange) ON_MESSAGE_VOID(WM_WININICHANGE, OnDisplayChange) ON_MESSAGE_VOID(WM_SETTINGCHANGE, OnDisplayChange) ON_WM_MOUSEWHEEL() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CSplitterWnd construction/destruction CSplitterWnd::CSplitterWnd() { AFX_ZERO_INIT_OBJECT(CWnd); // default splitter box/bar sizes (includes borders) if (!afxData.bWin4) { m_cxSplitter = m_cySplitter = 4; m_cxBorderShare = m_cyBorderShare = 1; m_cxSplitterGap = m_cySplitterGap = 4 + 1 + 1; ASSERT(m_cxBorder == 0 && m_cyBorder == 0); } else { m_cxSplitter = m_cySplitter = 3 + 2 + 2; m_cxBorderShare = m_cyBorderShare = 0; m_cxSplitterGap = m_cySplitterGap = 3 + 2 + 2; m_cxBorder = m_cyBorder = 2; } #ifdef _DEBUG if (GetSystemMetrics(SM_CXBORDER) != 1 || GetSystemMetrics(SM_CYBORDER) != 1) { TRACE0("Warning: CSplitterWnd assumes 1 pixel border.\n"); // will look ugly if borders are not 1 pixel wide and 1 pixel high } #endif } CSplitterWnd::~CSplitterWnd() { delete[] m_pRowInfo; delete[] m_pColInfo; } BOOL CSplitterWnd::Create(CWnd* pParentWnd, int nMaxRows, int nMaxCols, SIZE sizeMin, CCreateContext* pContext, DWORD dwStyle, UINT nID) { ASSERT(pParentWnd != NULL); ASSERT(sizeMin.cx > 0 && sizeMin.cy > 0); // minimum must be non-zero ASSERT(pContext != NULL); ASSERT(pContext->m_pNewViewClass != NULL); ASSERT(dwStyle & WS_CHILD); ASSERT(dwStyle & SPLS_DYNAMIC_SPLIT); // must have dynamic split behavior // Dynamic splitters are limited to 2x2 ASSERT(nMaxRows >= 1 && nMaxRows <= 2); ASSERT(nMaxCols >= 1 && nMaxCols <= 2); ASSERT(nMaxCols > 1 || nMaxRows > 1); // 1x1 is not permitted m_nMaxRows = nMaxRows; m_nMaxCols = nMaxCols; ASSERT(m_nRows == 0 && m_nCols == 0); // none yet m_nRows = m_nCols = 1; // start off as 1x1 if (!CreateCommon(pParentWnd, sizeMin, dwStyle, nID)) return FALSE; ASSERT(m_nRows == 1 && m_nCols == 1); // still 1x1 ASSERT(pContext->m_pNewViewClass->IsDerivedFrom(RUNTIME_CLASS(CWnd))); m_pDynamicViewClass = pContext->m_pNewViewClass; // save for later dynamic creations // add the first initial pane if (!CreateView(0, 0, m_pDynamicViewClass, sizeMin, pContext)) { DestroyWindow(); // will clean up child windows return FALSE; } m_pColInfo[0].nIdealSize = sizeMin.cx; m_pRowInfo[0].nIdealSize = sizeMin.cy; return TRUE; } // simple "wiper" splitter BOOL CSplitterWnd::CreateStatic(CWnd* pParentWnd, int nRows, int nCols, DWORD dwStyle, UINT nID) { ASSERT(pParentWnd != NULL); ASSERT(nRows >= 1 && nRows <= 16); ASSERT(nCols >= 1 && nCols <= 16); ASSERT(nCols > 1 || nRows > 1); // 1x1 is not permitted ASSERT(dwStyle & WS_CHILD); ASSERT(!(dwStyle & SPLS_DYNAMIC_SPLIT)); // can't have dynamic split ASSERT(m_nRows == 0 && m_nCols == 0); // none yet m_nRows = m_nMaxRows = nRows; m_nCols = m_nMaxCols = nCols; // create with zero minimum pane size if (!CreateCommon(pParentWnd, CSize(0, 0), dwStyle, nID)) return FALSE; // all panes must be created with explicit calls to CreateView return TRUE; } BOOL CSplitterWnd::CreateCommon(CWnd* pParentWnd, SIZE sizeMin, DWORD dwStyle, UINT nID) { ASSERT(pParentWnd != NULL); ASSERT(sizeMin.cx >= 0 && sizeMin.cy >= 0); ASSERT(dwStyle & WS_CHILD); ASSERT(nID != 0); ASSERT(m_pColInfo == NULL && m_pRowInfo == NULL); // only do once ASSERT(m_nMaxCols > 0 && m_nMaxRows > 0); // the Windows scroll bar styles bits turn on the smart scrollbars DWORD dwCreateStyle = dwStyle & ~(WS_HSCROLL|WS_VSCROLL); if (afxData.bWin4) dwCreateStyle &= ~WS_BORDER; VERIFY(AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG)); // create with the same wnd-class as MDI-Frame (no erase bkgnd) if (!CreateEx(0, _afxWndMDIFrame, NULL, dwCreateStyle, 0, 0, 0, 0, pParentWnd->m_hWnd, (HMENU)nID, NULL)) return FALSE; // create invisible // attach the initial splitter parts TRY { m_pColInfo = new CRowColInfo[m_nMaxCols]; for (int col = 0; col < m_nMaxCols; col++) { m_pColInfo[col].nMinSize = m_pColInfo[col].nIdealSize = sizeMin.cx; m_pColInfo[col].nCurSize = -1; // will be set in RecalcLayout } m_pRowInfo = new CRowColInfo[m_nMaxRows]; for (int row = 0; row < m_nMaxRows; row++) { m_pRowInfo[row].nMinSize = m_pRowInfo[row].nIdealSize = sizeMin.cy; m_pRowInfo[row].nCurSize = -1; // will be set in RecalcLayout } // create scroll bars by setting the style SetScrollStyle(dwStyle); } CATCH_ALL(e) { DestroyWindow(); // will clean up child windows // Note: DELETE_EXCEPTION(e) not required return FALSE; } END_CATCH_ALL return TRUE; } BOOL CSplitterWnd::OnNcCreate(LPCREATESTRUCT lpcs) { if (!CWnd::OnNcCreate(lpcs)) return FALSE; // remove WS_EX_CLIENTEDGE style from parent window // (the splitter itself will provide the 3d look) CWnd* pParent = GetParent(); ASSERT_VALID(pParent); pParent->ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_DRAWFRAME); return TRUE; } ///////////////////////////////////////////////////////////////////////////// // CSplitterWnd default creation of parts // You must create ALL panes unless DYNAMIC_SPLIT is defined! // Usually the splitter window is invisible when creating a pane BOOL CSplitterWnd::CreateView(int row, int col, CRuntimeClass* pViewClass, SIZE sizeInit, CCreateContext* pContext) { #ifdef _DEBUG ASSERT_VALID(this); ASSERT(row >= 0 && row < m_nRows); ASSERT(col >= 0 && col < m_nCols); ASSERT(pViewClass != NULL); ASSERT(pViewClass->IsDerivedFrom(RUNTIME_CLASS(CWnd))); ASSERT(AfxIsValidAddress(pViewClass, sizeof(CRuntimeClass), FALSE)); if (GetDlgItem(IdFromRowCol(row, col)) != NULL) { TRACE2("Error: CreateView - pane already exists for row %d, col %d.\n", row, col); ASSERT(FALSE); return FALSE; } #endif // set the initial size for that pane m_pColInfo[col].nIdealSize = sizeInit.cx; m_pRowInfo[row].nIdealSize = sizeInit.cy; BOOL bSendInitialUpdate = FALSE; CCreateContext contextT; if (pContext == NULL) { // if no context specified, generate one from the currently selected // client if possible CView* pOldView = (CView*)GetActivePane(); if (pOldView != NULL && pOldView->IsKindOf(RUNTIME_CLASS(CView))) { // set info about last pane ASSERT(contextT.m_pCurrentFrame == NULL); contextT.m_pLastView = pOldView; contextT.m_pCurrentDoc = pOldView->GetDocument(); if (contextT.m_pCurrentDoc != NULL) contextT.m_pNewDocTemplate = contextT.m_pCurrentDoc->GetDocTemplate(); } pContext = &contextT; bSendInitialUpdate = TRUE; } CWnd* pWnd; TRY { pWnd = (CWnd*)pViewClass->CreateObject(); if (pWnd == NULL) AfxThrowMemoryException(); } CATCH_ALL(e) { TRACE0("Out of memory creating a splitter pane.\n"); // Note: DELETE_EXCEPTION(e) not required return FALSE; } END_CATCH_ALL ASSERT_KINDOF(CWnd, pWnd); ASSERT(pWnd->m_hWnd == NULL); // not yet created DWORD dwStyle = AFX_WS_DEFAULT_VIEW; if (afxData.bWin4) dwStyle &= ~WS_BORDER; // Create with the right size (wrong position) CRect rect(CPoint(0,0), sizeInit); if (!pWnd->Create(NULL, NULL, dwStyle, rect, this, IdFromRowCol(row, col), pContext)) { TRACE0("Warning: couldn't create client pane for splitter.\n"); // pWnd will be cleaned up by PostNcDestroy return FALSE; } ASSERT((int)_AfxGetDlgCtrlID(pWnd->m_hWnd) == IdFromRowCol(row, col)); // send initial notification message if (bSendInitialUpdate) pWnd->SendMessage(WM_INITIALUPDATE); return TRUE; } BOOL CSplitterWnd::CreateScrollBarCtrl(DWORD dwStyle, UINT nID) { ASSERT_VALID(this); ASSERT(m_hWnd != NULL); HWND hWnd = ::CreateWindow(_T("SCROLLBAR"), NULL, dwStyle | WS_VISIBLE | WS_CHILD, 0, 0, 1, 1, m_hWnd, (HMENU)nID, AfxGetInstanceHandle(), NULL); #ifdef _DEBUG if (hWnd == NULL) TRACE1("Warning: Window creation failed: GetLastError returns 0x%8.8X\n", GetLastError()); #endif return hWnd != NULL; } int CSplitterWnd::IdFromRowCol(int row, int col) const { ASSERT_VALID(this); ASSERT(row >= 0); ASSERT(row < m_nRows); ASSERT(col >= 0); ASSERT(col < m_nCols); return AFX_IDW_PANE_FIRST + row * 16 + col; } ///////////////////////////////////////////////////////////////////////////// // CSplitterWnd attributes CWnd* CSplitterWnd::GetPane(int row, int col) const { ASSERT_VALID(this); CWnd* pView = GetDlgItem(IdFromRowCol(row, col)); ASSERT(pView != NULL); // panes can be a CWnd, but are usually CViews return pView; } BOOL CSplitterWnd::IsChildPane(CWnd* pWnd, int* pRow, int* pCol) { ASSERT_VALID(this); ASSERT_VALID(pWnd); UINT nID = _AfxGetDlgCtrlID(pWnd->m_hWnd); if (IsChild(pWnd) && nID >= AFX_IDW_PANE_FIRST && nID <= AFX_IDW_PANE_LAST) { if (pRow != NULL) *pRow = (nID - AFX_IDW_PANE_FIRST) / 16; if (pCol != NULL) *pCol = (nID - AFX_IDW_PANE_FIRST) % 16; ASSERT(pRow == NULL || *pRow < m_nRows); ASSERT(pCol == NULL || *pCol < m_nCols); return TRUE; } else { if (pRow != NULL) *pRow = -1; if (pCol != NULL) *pCol = -1; return FALSE; } } ///////////////////////////////////////////////////////////////////////////// // CSplitterWnd information access // The get routines return the current size // The set routines set the ideal size // RecalcLayout must be called to update current size void CSplitterWnd::GetRowInfo(int row, int& cyCur, int& cyMin) const { ASSERT_VALID(this); ASSERT(row >= 0 && row < m_nMaxRows); cyCur = m_pRowInfo[row].nCurSize; cyMin = m_pRowInfo[row].nMinSize; } void CSplitterWnd::SetRowInfo(int row, int cyIdeal, int cyMin) { ASSERT_VALID(this); ASSERT(row >= 0 && row < m_nMaxRows); ASSERT(cyIdeal >= 0); ASSERT(cyMin >= 0); m_pRowInfo[row].nIdealSize = cyIdeal; m_pRowInfo[row].nMinSize = cyMin; } void CSplitterWnd::GetColumnInfo(int col, int& cxCur, int& cxMin) const { ASSERT_VALID(this); ASSERT(col >= 0 && col < m_nMaxCols); cxCur = m_pColInfo[col].nCurSize; cxMin = m_pColInfo[col].nMinSize; } void CSplitterWnd::SetColumnInfo(int col, int cxIdeal, int cxMin) { ASSERT_VALID(this); ASSERT(col >= 0 && col < m_nMaxCols); ASSERT(cxIdeal >= 0); ASSERT(cxMin >= 0); m_pColInfo[col].nIdealSize = cxIdeal; m_pColInfo[col].nMinSize = cxMin; } DWORD CSplitterWnd::GetScrollStyle() const { DWORD dwStyle = 0; if (m_bHasHScroll) dwStyle |= WS_HSCROLL; if (m_bHasVScroll) dwStyle |= WS_VSCROLL; return dwStyle; } void CSplitterWnd::SetScrollStyle(DWORD dwStyle) { // optimize for scroll info already set correctly dwStyle &= (WS_HSCROLL|WS_VSCROLL); if (GetScrollStyle() == dwStyle) return; // update to new state m_bHasHScroll = (dwStyle & WS_HSCROLL) != 0; m_bHasVScroll = (dwStyle & WS_VSCROLL) != 0; CWnd* pScrollBar; // show/hide all the shared horz scroll bars for (int col = 0; col < m_nCols; col++) { pScrollBar = GetDlgItem(AFX_IDW_HSCROLL_FIRST + col); if (pScrollBar == NULL) { // create the scroll bar when necessary if (!CreateScrollBarCtrl(SBS_HORZ, AFX_IDW_HSCROLL_FIRST + col)) AfxThrowResourceException(); pScrollBar = GetDlgItem(AFX_IDW_HSCROLL_FIRST + col); } pScrollBar->ShowWindow(m_bHasHScroll ? SW_SHOW : SW_HIDE); } // show/hide all the shared vert scroll bars for (int row = 0; row < m_nRows; row++) { pScrollBar = GetDlgItem(AFX_IDW_VSCROLL_FIRST + row); if (pScrollBar == NULL) { // create the scroll bar when necessary if (!CreateScrollBarCtrl(SBS_VERT, AFX_IDW_VSCROLL_FIRST + row)) AfxThrowResourceException(); pScrollBar = GetDlgItem(AFX_IDW_VSCROLL_FIRST + row); } pScrollBar->ShowWindow(m_bHasVScroll ? SW_SHOW : SW_HIDE); } // show/destroy size box if necessary if (m_bHasVScroll && m_bHasHScroll) { pScrollBar = GetDlgItem(AFX_IDW_SIZE_BOX); if (pScrollBar == NULL) { // create size box when necessary if (!CreateScrollBarCtrl(SBS_SIZEBOX|WS_DISABLED, AFX_IDW_SIZE_BOX)) AfxThrowResourceException(); pScrollBar = GetDlgItem(AFX_IDW_SIZE_BOX); } pScrollBar->ShowWindow(SW_SHOW); } else { // the size box can be destroyed instead of hidden pScrollBar = GetDlgItem(AFX_IDW_SIZE_BOX); if (pScrollBar != NULL) pScrollBar->DestroyWindow(); } // Note: call RecalcLayout for the new layout to take effect } ///////////////////////////////////////////////////////////////////////////// // CSplitterWnd client operations/overridables void CSplitterWnd::DeleteView(int row, int col) { ASSERT_VALID(this); // if active child is being deleted - activate next CWnd* pPane = GetPane(row, col); ASSERT_KINDOF(CView, pPane); if (GetActivePane() == pPane) ActivateNext(FALSE); // default implementation assumes view will auto delete in PostNcDestroy pPane->DestroyWindow(); } void CSplitterWnd::OnDrawSplitter(CDC* pDC, ESplitType nType, const CRect& rectArg) { // if pDC == NULL, then just invalidate if (pDC == NULL) { RedrawWindow(rectArg, NULL, RDW_INVALIDATE|RDW_NOCHILDREN); return; } ASSERT_VALID(pDC); // otherwise, actually draw CRect rect = rectArg; switch (nType) { case splitBorder: ASSERT(afxData.bWin4); pDC->Draw3dRect(rect, afxData.clrBtnShadow, afxData.clrBtnHilite); rect.InflateRect(-CX_BORDER, -CY_BORDER); pDC->Draw3dRect(rect, afxData.clrWindowFrame, afxData.clrBtnFace); return; case splitIntersection: ASSERT(!afxData.bWin4); break; case splitBox: if (afxData.bWin4) { pDC->Draw3dRect(rect, afxData.clrBtnFace, afxData.clrWindowFrame); rect.InflateRect(-CX_BORDER, -CY_BORDER); pDC->Draw3dRect(rect, afxData.clrBtnHilite, afxData.clrBtnShadow); rect.InflateRect(-CX_BORDER, -CY_BORDER); break; } // fall through... case splitBar: if (!afxData.bWin4) { pDC->Draw3dRect(rect, afxData.clrBtnHilite, afxData.clrBtnShadow); rect.InflateRect(-CX_BORDER, -CY_BORDER); } break; default: ASSERT(FALSE); // unknown splitter type } // fill the middle COLORREF clr = afxData.clrBtnFace; pDC->FillSolidRect(rect, clr); } ///////////////////////////////////////////////////////////////////////////// // Dynamic row/col split etc AFX_STATIC int AFXAPI _AfxCanSplitRowCol(CSplitterWnd::CRowColInfo* pInfoBefore, int nBeforeSize, int nSizeSplitter) // request to split Before row at point nBeforeSize // returns size of new pane (nBeforeSize will be new size of Before pane) // return -1 if not big enough { ASSERT(pInfoBefore->nCurSize > 0); ASSERT(pInfoBefore->nMinSize > 0); ASSERT(nBeforeSize <= pInfoBefore->nCurSize); // space gets take from before pane (weird UI for > 2 splits) if (nBeforeSize < pInfoBefore->nMinSize) { TRACE0("Warning: split too small to fit in a new pane.\n"); return -1; } int nNewSize = pInfoBefore->nCurSize - nBeforeSize - nSizeSplitter; if (nBeforeSize < pInfoBefore->nMinSize) { TRACE0("Warning: split too small to shrink old pane.\n"); return -1; } if (nNewSize < (pInfoBefore+1)->nMinSize) { TRACE0("Warning: split too small to create new pane.\n"); return -1; } return nNewSize; } BOOL CSplitterWnd::SplitRow(int cyBefore) { ASSERT_VALID(this); ASSERT(GetStyle() & SPLS_DYNAMIC_SPLIT); ASSERT(m_pDynamicViewClass != NULL); ASSERT(m_nRows < m_nMaxRows); cyBefore -= m_cyBorder; int rowNew = m_nRows; int cyNew = _AfxCanSplitRowCol(&m_pRowInfo[rowNew-1], cyBefore, m_cySplitter); if (cyNew == -1) return FALSE; // too small to split // create the scroll bar first (so new views can see that it is there) if (m_bHasVScroll && !CreateScrollBarCtrl(SBS_VERT, AFX_IDW_VSCROLL_FIRST + rowNew)) { TRACE0("Warning: SplitRow failed to create scroll bar.\n"); return FALSE; } m_nRows++; // bump count during view creation // create new views to fill the new row (RecalcLayout will position) for (int col = 0; col < m_nCols; col++) { CSize size(m_pColInfo[col].nCurSize, cyNew); if (!CreateView(rowNew, col, m_pDynamicViewClass, size, NULL)) { TRACE0("Warning: SplitRow failed to create new row.\n"); // delete anything we partially created 'col' = # columns created while (col > 0) DeleteView(rowNew, --col); if (m_bHasVScroll) GetDlgItem(AFX_IDW_VSCROLL_FIRST + rowNew)->DestroyWindow(); m_nRows--; // it didn't work out return FALSE; } } // new parts created - resize and re-layout m_pRowInfo[rowNew-1].nIdealSize = cyBefore; m_pRowInfo[rowNew].nIdealSize = cyNew; ASSERT(m_nRows == rowNew+1); RecalcLayout(); return TRUE; } BOOL CSplitterWnd::SplitColumn(int cxBefore) { ASSERT_VALID(this); ASSERT(GetStyle() & SPLS_DYNAMIC_SPLIT); ASSERT(m_pDynamicViewClass != NULL); ASSERT(m_nCols < m_nMaxCols); cxBefore -= m_cxBorder; int colNew = m_nCols; int cxNew = _AfxCanSplitRowCol(&m_pColInfo[colNew-1], cxBefore, m_cxSplitter); if (cxNew == -1) return FALSE; // too small to split // create the scroll bar first (so new views can see that it is there) if (m_bHasHScroll && !CreateScrollBarCtrl(SBS_HORZ, AFX_IDW_HSCROLL_FIRST + colNew)) { TRACE0("Warning: SplitRow failed to create scroll bar.\n"); return FALSE; } m_nCols++; // bump count during view creation // create new views to fill the new column (RecalcLayout will position) for (int row = 0; row < m_nRows; row++) { CSize size(cxNew, m_pRowInfo[row].nCurSize); if (!CreateView(row, colNew, m_pDynamicViewClass, size, NULL)) { TRACE0("Warning: SplitColumn failed to create new column.\n"); // delete anything we partially created 'col' = # columns created while (row > 0) DeleteView(--row, colNew); if (m_bHasHScroll) GetDlgItem(AFX_IDW_HSCROLL_FIRST + colNew)->DestroyWindow(); m_nCols--; // it didn't work out return FALSE; } } // new parts created - resize and re-layout m_pColInfo[colNew-1].nIdealSize = cxBefore; m_pColInfo[colNew].nIdealSize = cxNew; ASSERT(m_nCols == colNew+1); RecalcLayout(); return TRUE; } void CSplitterWnd::DeleteRow(int rowDelete) { ASSERT_VALID(this); ASSERT(GetStyle() & SPLS_DYNAMIC_SPLIT); ASSERT(m_nRows > 1); ASSERT(rowDelete < m_nRows); int rowActive, colActive; if (GetActivePane(&rowActive, &colActive) != NULL && rowActive == rowDelete) { if (++rowActive >= m_nRows) rowActive = 0; SetActivePane(rowActive, colActive); } CWnd* pScrollDel = m_bHasVScroll ? GetDlgItem(AFX_IDW_VSCROLL_FIRST+rowDelete) : NULL; for (int col = 0; col < m_nCols; col++) { DeleteView(rowDelete, col); for (int row = rowDelete+1; row < m_nRows; row++) { CWnd* pPane = GetPane(row, col); ASSERT(pPane != NULL); pPane->SetDlgCtrlID(IdFromRowCol(row-1, col)); if (m_bHasVScroll && col == m_nCols-1) { CWnd* pScroll = GetDlgItem(AFX_IDW_VSCROLL_FIRST+row); if (pScroll != NULL) pScroll->SetDlgCtrlID(AFX_IDW_VSCROLL_FIRST+row-1); } } } m_nRows--; if (pScrollDel != NULL) pScrollDel->DestroyWindow(); RecalcLayout(); // re-assign the space } void CSplitterWnd::DeleteColumn(int colDelete) { ASSERT_VALID(this); ASSERT(GetStyle() & SPLS_DYNAMIC_SPLIT); ASSERT(m_nCols > 1); ASSERT(colDelete < m_nCols); int rowActive, colActive; if (GetActivePane(&rowActive, &colActive) != NULL && colActive == colDelete) { if (++colActive >= m_nCols) colActive = 0; SetActivePane(rowActive, colActive); } CWnd* pScrollDel = m_bHasHScroll ? GetDlgItem(AFX_IDW_HSCROLL_FIRST+colDelete) : NULL; for (int row = 0; row < m_nRows; row++) { DeleteView(row, colDelete); for (int col = colDelete+1; col < m_nCols; col++) { CWnd* pPane = GetPane(row, col); ASSERT(pPane != NULL); pPane->SetDlgCtrlID(IdFromRowCol(row, col-1)); if (m_bHasHScroll && row == m_nRows-1) { CWnd* pScroll = GetDlgItem(AFX_IDW_HSCROLL_FIRST+col); if (pScroll != NULL) pScroll->SetDlgCtrlID(AFX_IDW_HSCROLL_FIRST+col-1); } } } m_nCols--; if (pScrollDel != NULL) pScrollDel->DestroyWindow(); RecalcLayout(); // re-assign the space } ///////////////////////////////////////////////////////////////////////////// // CSplitterWnd tracking support // like GetClientRect but inset by shared scrollbars void CSplitterWnd::GetInsideRect(CRect& rect) const { ASSERT_VALID(this); GetClientRect(rect); ASSERT(rect.left == 0 && rect.top == 0); // subtract space for 3d borders rect.InflateRect(-m_cxBorder, -m_cyBorder); // subtract scrollbar clearance if (m_bHasVScroll) rect.right -= afxData.cxVScroll - CX_BORDER; if (m_bHasHScroll) rect.bottom -= afxData.cyHScroll - CY_BORDER; } void CSplitterWnd::StartTracking(int ht) { ASSERT_VALID(this); if (ht == noHit) return; // GetHitRect will restrict 'm_rectLimit' as appropriate GetInsideRect(m_rectLimit); if (ht >= splitterIntersection1 && ht <= splitterIntersection225) { // split two directions (two tracking rectangles) int row = (ht - splitterIntersection1) / 15; int col = (ht - splitterIntersection1) % 15; GetHitRect(row + vSplitterBar1, m_rectTracker); int yTrackOffset = m_ptTrackOffset.y; m_bTracking2 = TRUE; GetHitRect(col + hSplitterBar1, m_rectTracker2); m_ptTrackOffset.y = yTrackOffset; } else if (ht == bothSplitterBox) { // hit on splitter boxes (for keyboard) GetHitRect(vSplitterBox, m_rectTracker); int yTrackOffset = m_ptTrackOffset.y; m_bTracking2 = TRUE; GetHitRect(hSplitterBox, m_rectTracker2); m_ptTrackOffset.y = yTrackOffset; // center it m_rectTracker.OffsetRect(0, m_rectLimit.Height()/2); m_rectTracker2.OffsetRect(m_rectLimit.Width()/2, 0); } else { // only hit one bar GetHitRect(ht, m_rectTracker); } // allow active view to preserve focus before taking it away CView* pView = (CView*)GetActivePane(); if (pView != NULL && pView->IsKindOf(RUNTIME_CLASS(CView))) { ASSERT_VALID(pView); CFrameWnd* pFrameWnd = GetParentFrame(); ASSERT_VALID(pFrameWnd); pView->OnActivateFrame(WA_INACTIVE, pFrameWnd); } // steal focus and capture SetCapture(); SetFocus(); // make sure no updates are pending RedrawWindow(NULL, NULL, RDW_ALLCHILDREN | RDW_UPDATENOW); // set tracking state and appropriate cursor m_bTracking = TRUE; OnInvertTracker(m_rectTracker); if (m_bTracking2) OnInvertTracker(m_rectTracker2); m_htTrack = ht; SetSplitCursor(ht); } void CSplitterWnd::TrackRowSize(int y, int row) { ASSERT_VALID(this); ASSERT(m_nRows > 1); CPoint pt(0, y); ClientToScreen(&pt); GetPane(row, 0)->ScreenToClient(&pt); m_pRowInfo[row].nIdealSize = pt.y; // new size if (pt.y < m_pRowInfo[row].nMinSize) { // resized too small m_pRowInfo[row].nIdealSize = 0; // make it go away if (GetStyle() & SPLS_DYNAMIC_SPLIT) DeleteRow(row); } else if (m_pRowInfo[row].nCurSize + m_pRowInfo[row+1].nCurSize < pt.y + m_pRowInfo[row+1].nMinSize) { // not enough room for other pane if (GetStyle() & SPLS_DYNAMIC_SPLIT) DeleteRow(row + 1); } } void CSplitterWnd::TrackColumnSize(int x, int col) { ASSERT_VALID(this); ASSERT(m_nCols > 1); CPoint pt(x, 0); ClientToScreen(&pt); GetPane(0, col)->ScreenToClient(&pt); m_pColInfo[col].nIdealSize = pt.x; // new size if (pt.x < m_pColInfo[col].nMinSize) { // resized too small m_pColInfo[col].nIdealSize = 0; // make it go away if (GetStyle() & SPLS_DYNAMIC_SPLIT) DeleteColumn(col); } else if (m_pColInfo[col].nCurSize + m_pColInfo[col+1].nCurSize < pt.x + m_pColInfo[col+1].nMinSize) { // not enough room for other pane if (GetStyle() & SPLS_DYNAMIC_SPLIT) DeleteColumn(col + 1); } } void CSplitterWnd::StopTracking(BOOL bAccept) { ASSERT_VALID(this); if (!m_bTracking) return; ReleaseCapture(); // erase tracker rectangle OnInvertTracker(m_rectTracker); if (m_bTracking2) OnInvertTracker(m_rectTracker2); m_bTracking = m_bTracking2 = FALSE; // save old active view CWnd* pOldActiveView = GetActivePane(); // m_rectTracker is set to the new splitter position (without border) // (so, adjust relative to where the border will be) m_rectTracker.OffsetRect(-CX_BORDER , -CY_BORDER); m_rectTracker2.OffsetRect(-CX_BORDER, -CY_BORDER); if (bAccept) { if (m_htTrack == vSplitterBox) { SplitRow(m_rectTracker.top); } else if (m_htTrack >= vSplitterBar1 && m_htTrack <= vSplitterBar15) { // set row height TrackRowSize(m_rectTracker.top, m_htTrack - vSplitterBar1); RecalcLayout(); } else if (m_htTrack == hSplitterBox) { SplitColumn(m_rectTracker.left); } else if (m_htTrack >= hSplitterBar1 && m_htTrack <= hSplitterBar15) { // set column width TrackColumnSize(m_rectTracker.left, m_htTrack - hSplitterBar1); RecalcLayout(); } else if (m_htTrack >= splitterIntersection1 && m_htTrack <= splitterIntersection225) { // set row height and column width int row = (m_htTrack - splitterIntersection1) / 15; int col = (m_htTrack - splitterIntersection1) % 15; TrackRowSize(m_rectTracker.top, row); TrackColumnSize(m_rectTracker2.left, col); RecalcLayout(); } else if (m_htTrack == bothSplitterBox) { // rectTracker is vSplitter (splits rows) // rectTracker2 is hSplitter (splits cols) SplitRow(m_rectTracker.top); SplitColumn(m_rectTracker2.left); } } if (pOldActiveView == GetActivePane()) { if (pOldActiveView != NULL) { SetActivePane(-1, -1, pOldActiveView); // re-activate pOldActiveView->SetFocus(); // make sure focus is restored } } } void CSplitterWnd::GetHitRect(int ht, CRect& rectHit) { ASSERT_VALID(this); CRect rectClient; GetClientRect(&rectClient); rectClient.InflateRect(-m_cxBorder, -m_cyBorder); int cx = rectClient.Width(); int cy = rectClient.Height(); int x = rectClient.top; int y = rectClient.left; // hit rectangle does not include border // m_rectLimit will be limited to valid tracking rect // m_ptTrackOffset will be set to appropriate tracking offset m_ptTrackOffset.x = 0; m_ptTrackOffset.y = 0; if (ht == vSplitterBox) { cy = m_cySplitter - (2*m_cyBorder - afxData.bWin4); m_ptTrackOffset.y = -(cy / 2); ASSERT(m_pRowInfo[0].nCurSize > 0); m_rectLimit.bottom -= cy; } else if (ht == hSplitterBox) { cx = m_cxSplitter - (2*m_cxBorder - afxData.bWin4); m_ptTrackOffset.x = -(cx / 2); ASSERT(m_pColInfo[0].nCurSize > 0); m_rectLimit.right -= cx; } else if (ht >= vSplitterBar1 && ht <= vSplitterBar15) { cy = m_cySplitter - (2*m_cyBorder - afxData.bWin4); m_ptTrackOffset.y = -(cy / 2); for (int row = 0; row < ht - vSplitterBar1; row++) y += m_pRowInfo[row].nCurSize + m_cySplitterGap; m_rectLimit.top = y; y += m_pRowInfo[row].nCurSize + m_cyBorderShare + afxData.bWin4; m_rectLimit.bottom -= cy; } else if (ht >= hSplitterBar1 && ht <= hSplitterBar15) { cx = m_cxSplitter - (2*m_cxBorder - afxData.bWin4); m_ptTrackOffset.x = -(cx / 2); for (int col = 0; col < ht - hSplitterBar1; col++) x += m_pColInfo[col].nCurSize + m_cxSplitterGap; m_rectLimit.left = x; x += m_pColInfo[col].nCurSize + m_cxBorderShare + afxData.bWin4; m_rectLimit.right -= cx; } else { TRACE1("Error: GetHitRect(%d): Not Found!\n", ht); ASSERT(FALSE); } rectHit.right = (rectHit.left = x) + cx; rectHit.bottom = (rectHit.top = y) + cy; } int CSplitterWnd::HitTest(CPoint pt) const { ASSERT_VALID(this); CRect rectClient; GetClientRect(&rectClient); rectClient.InflateRect(-m_cxBorder, -m_cyBorder); CRect rectInside; GetInsideRect(rectInside); if (m_bHasVScroll && m_nRows < m_nMaxRows && CRect(rectInside.right, rectClient.top, rectClient.right, rectClient.top + m_cySplitter - afxData.bWin4).PtInRect(pt)) { return vSplitterBox; } if (m_bHasHScroll && m_nCols < m_nMaxCols && CRect(rectClient.left, rectInside.bottom, rectClient.left + m_cxSplitter - afxData.bWin4, rectClient.bottom).PtInRect(pt)) { return hSplitterBox; } // for hit detect, include the border of splitters CRect rect; rect = rectClient; for (int col = 0; col < m_nCols - 1; col++) { rect.left += m_pColInfo[col].nCurSize; rect.right = rect.left + m_cxSplitterGap; if (rect.PtInRect(pt)) break; rect.left = rect.right; } rect = rectClient; for (int row = 0; row < m_nRows - 1; row++) { rect.top += m_pRowInfo[row].nCurSize; rect.bottom = rect.top + m_cySplitterGap; if (rect.PtInRect(pt)) break; rect.top = rect.bottom; } // row and col set for hit splitter (if not hit will be past end) if (col != m_nCols - 1) { if (row != m_nRows - 1) return splitterIntersection1 + row * 15 + col; return hSplitterBar1 + col; } if (row != m_nRows - 1) return vSplitterBar1 + row; return noHit; } ///////////////////////////////////////////////////////////////////////////// // CSplitterWnd tracking visuals void CSplitterWnd::OnInvertTracker(const CRect& rect) { ASSERT_VALID(this); ASSERT(!rect.IsRectEmpty()); ASSERT((GetStyle() & WS_CLIPCHILDREN) == 0); // pat-blt without clip children on CDC* pDC = GetDC(); // invert the brush pattern (looks just like frame window sizing) CBrush* pBrush = CDC::GetHalftoneBrush(); HBRUSH hOldBrush = NULL; if (pBrush != NULL) hOldBrush = (HBRUSH)SelectObject(pDC->m_hDC, pBrush->m_hObject); pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATINVERT); if (hOldBrush != NULL) SelectObject(pDC->m_hDC, hOldBrush); ReleaseDC(pDC); } ///////////////////////////////////////////////////////////////////////////// // CSplitterWnd commands // Keyboard interface BOOL CSplitterWnd::DoKeyboardSplit() { ASSERT_VALID(this); int ht; if (m_nRows > 1 && m_nCols > 1) ht = splitterIntersection1; // split existing row+col else if (m_nRows > 1) ht = vSplitterBar1; // split existing row else if (m_nCols > 1) ht = hSplitterBar1; // split existing col else if (m_nMaxRows > 1 && m_nMaxCols > 1) ht = bothSplitterBox; // we can split both else if (m_nMaxRows > 1) ht = vSplitterBox; // we can split rows else if (m_nMaxCols > 1) ht = hSplitterBox; // we can split columns else return FALSE; // can't split // start tracking StartTracking(ht); CRect rect; rect.left = m_rectTracker.Width() / 2; rect.top = m_rectTracker.Height() / 2; if (m_ptTrackOffset.y != 0) rect.top = m_rectTracker.top; if (m_ptTrackOffset.x != 0) rect.left = m_bTracking2 ? m_rectTracker2.left :m_rectTracker.left; rect.OffsetRect(-m_ptTrackOffset.x, -m_ptTrackOffset.y); ClientToScreen(&rect); SetCursorPos(rect.left, rect.top); return TRUE; } ///////////////////////////////////////////////////////////////////////////// // Main drawing and layout void CSplitterWnd::OnDisplayChange() { if (!IsIconic() && IsWindowVisible()) RecalcLayout(); } void CSplitterWnd::OnSize(UINT nType, int cx, int cy) { if (nType != SIZE_MINIMIZED && cx > 0 && cy > 0) RecalcLayout(); CWnd::OnSize(nType, cx, cy); } // Generic routine: // for X direction: pInfo = m_pColInfo, nMax = m_nMaxCols, nSize = cx // for Y direction: pInfo = m_pRowInfo, nMax = m_nMaxRows, nSize = cy AFX_STATIC void AFXAPI _AfxLayoutRowCol(CSplitterWnd::CRowColInfo* pInfoArray, int nMax, int nSize, int nSizeSplitter) { ASSERT(pInfoArray != NULL); ASSERT(nMax > 0); ASSERT(nSizeSplitter > 0); CSplitterWnd::CRowColInfo* pInfo; int i; if (nSize < 0) nSize = 0; // if really too small, layout as zero size // start with ideal sizes for (i = 0, pInfo = pInfoArray; i < nMax-1; i++, pInfo++) { if (pInfo->nIdealSize < pInfo->nMinSize) pInfo->nIdealSize = 0; // too small to see pInfo->nCurSize = pInfo->nIdealSize; } pInfo->nCurSize = INT_MAX; // last row/column takes the rest for (i = 0, pInfo = pInfoArray; i < nMax; i++, pInfo++) { ASSERT(nSize >= 0); if (nSize == 0) { // no more room (set pane to be invisible) pInfo->nCurSize = 0; continue; // don't worry about splitters } else if (nSize < pInfo->nMinSize && i != 0) { // additional panes below the recommended minimum size // aren't shown and the size goes to the previous pane pInfo->nCurSize = 0; // previous pane already has room for splitter + border // add remaining size and remove the extra border ASSERT(afxData.cxBorder2 == afxData.cyBorder2); (pInfo-1)->nCurSize += nSize + afxData.cxBorder2; nSize = 0; } else { // otherwise we can add the second pane ASSERT(nSize > 0); if (pInfo->nCurSize == 0) { // too small to see if (i != 0) pInfo->nCurSize = 0; } else if (nSize < pInfo->nCurSize) { // this row/col won't fit completely - make as small as possible pInfo->nCurSize = nSize; nSize = 0; } else { // can fit everything nSize -= pInfo->nCurSize; } } // see if we should add a splitter ASSERT(nSize >= 0); if (i != nMax - 1) { // should have a splitter if (nSize > nSizeSplitter) { nSize -= nSizeSplitter; // leave room for splitter + border ASSERT(nSize > 0); } else { // not enough room - add left over less splitter size ASSERT(afxData.cxBorder2 == afxData.cyBorder2); pInfo->nCurSize += nSize; if (pInfo->nCurSize > (nSizeSplitter - afxData.cxBorder2)) pInfo->nCurSize -= (nSizeSplitter - afxData.cyBorder2); nSize = 0; } } } ASSERT(nSize == 0); // all space should be allocated } // repositions client area of specified window // assumes everything has WS_BORDER or is inset like it does // (includes scroll bars) AFX_STATIC void AFXAPI _AfxDeferClientPos(AFX_SIZEPARENTPARAMS* lpLayout, CWnd* pWnd, int x, int y, int cx, int cy, BOOL bScrollBar) { ASSERT(pWnd != NULL); ASSERT(pWnd->m_hWnd != NULL); if (bScrollBar) { // if there is enough room, draw scroll bar without border // if there is not enough room, set the WS_BORDER bit so that // we will at least get a proper border drawn BOOL bNeedBorder = (cx <= CX_BORDER || cy <= CY_BORDER); pWnd->ModifyStyle(bNeedBorder ? 0 : WS_BORDER, bNeedBorder ? WS_BORDER : 0); } CRect rect(x, y, x+cx, y+cy); // adjust for border size (even if zero client size) if (!afxData.bWin4) { if (bScrollBar) rect.InflateRect(CX_BORDER, CY_BORDER); else pWnd->CalcWindowRect(&rect); } // adjust for 3d border (splitter windows have implied border) if ((pWnd->GetExStyle() & WS_EX_CLIENTEDGE) || pWnd->IsKindOf(RUNTIME_CLASS(CSplitterWnd))) rect.InflateRect(afxData.cxBorder2, afxData.cyBorder2); // first check if the new rectangle is the same as the current CRect rectOld; pWnd->GetWindowRect(rectOld); pWnd->GetParent()->ScreenToClient(&rectOld); if (rect != rectOld) AfxRepositionWindow(lpLayout, pWnd->m_hWnd, rect); } CWnd* CSplitterWnd::GetSizingParent() { ASSERT_VALID(this); if (!afxData.bWin4) return NULL; // size box is in lower right corner of this window CRect rectClient; GetClientRect(rectClient); // find sizeable parent window CWnd* pParent = this; if (!(pParent->GetStyle() & WS_THICKFRAME)) pParent = GetParent(); // only allow if not maximized and has thick frame ASSERT_VALID(pParent); if ((pParent->GetStyle() & (WS_THICKFRAME|WS_MAXIMIZE)) == WS_THICKFRAME) { // convert client area of frame window relative to splitter CRect rect; pParent->GetClientRect(rect); pParent->ClientToScreen(rect); ScreenToClient(rect); // must match exactly to get the size box if (rectClient.BottomRight() == rect.BottomRight()) return pParent; } return NULL; // no sizeable parent found } void CSplitterWnd::RecalcLayout() { ASSERT_VALID(this); ASSERT(m_nRows > 0 && m_nCols > 0); // must have at least one pane CRect rectClient; GetClientRect(rectClient); rectClient.InflateRect(-m_cxBorder, -m_cyBorder); CRect rectInside; GetInsideRect(rectInside); // layout columns (restrict to possible sizes) _AfxLayoutRowCol(m_pColInfo, m_nCols, rectInside.Width(), m_cxSplitterGap); _AfxLayoutRowCol(m_pRowInfo, m_nRows, rectInside.Height(), m_cySplitterGap); // adjust the panes (and optionally scroll bars) // give the hint for the maximum number of HWNDs AFX_SIZEPARENTPARAMS layout; layout.hDWP = ::BeginDeferWindowPos((m_nCols + 1) * (m_nRows + 1) + 1); // size of scrollbars int cx = (rectClient.right - rectInside.right) - afxData.bNotWin4; int cy = (rectClient.bottom - rectInside.bottom) - afxData.bNotWin4; // reposition size box if (m_bHasHScroll && m_bHasVScroll) { CWnd* pScrollBar = GetDlgItem(AFX_IDW_SIZE_BOX); ASSERT(pScrollBar != NULL); // fix style if necessary BOOL bSizingParent = (GetSizingParent() != NULL); // modifyStyle returns TRUE if style changes if (pScrollBar->ModifyStyle(SBS_SIZEGRIP|SBS_SIZEBOX, bSizingParent ? SBS_SIZEGRIP : SBS_SIZEBOX)) pScrollBar->Invalidate(); pScrollBar->EnableWindow(bSizingParent); // reposition the size box _AfxDeferClientPos(&layout, pScrollBar, rectInside.right + afxData.bNotWin4, rectInside.bottom + afxData.bNotWin4, cx, cy, TRUE); } // reposition scroll bars if (m_bHasHScroll) { int cxSplitterBox = m_cxSplitter + afxData.bNotWin4;// split box bigger int x = rectClient.left; int y = rectInside.bottom + afxData.bNotWin4; for (int col = 0; col < m_nCols; col++) { CWnd* pScrollBar = GetDlgItem(AFX_IDW_HSCROLL_FIRST + col); ASSERT(pScrollBar != NULL); int cx = m_pColInfo[col].nCurSize; if (col == 0 && m_nCols < m_nMaxCols) x += cxSplitterBox, cx -= cxSplitterBox; _AfxDeferClientPos(&layout, pScrollBar, x, y, cx, cy, TRUE); x += cx + m_cxSplitterGap; } } if (m_bHasVScroll) { int cySplitterBox = m_cySplitter + afxData.bNotWin4;// split box bigger int x = rectInside.right + afxData.bNotWin4; int y = rectClient.top; for (int row = 0; row < m_nRows; row++) { CWnd* pScrollBar = GetDlgItem(AFX_IDW_VSCROLL_FIRST + row); ASSERT(pScrollBar != NULL); int cy = m_pRowInfo[row].nCurSize; if (row == 0 && m_nRows < m_nMaxRows) y += cySplitterBox, cy -= cySplitterBox; _AfxDeferClientPos(&layout, pScrollBar, x, y, cx, cy, TRUE); y += cy + m_cySplitterGap; } } //BLOCK: Reposition all the panes { int x = rectClient.left; for (int col = 0; col < m_nCols; col++) { int cx = m_pColInfo[col].nCurSize; int y = rectClient.top; for (int row = 0; row < m_nRows; row++) { int cy = m_pRowInfo[row].nCurSize; CWnd* pWnd = GetPane(row, col); _AfxDeferClientPos(&layout, pWnd, x, y, cx, cy, FALSE); y += cy + m_cySplitterGap; } x += cx + m_cxSplitterGap; } } // move and resize all the windows at once! if (layout.hDWP == NULL || !::EndDeferWindowPos(layout.hDWP)) TRACE0("Warning: DeferWindowPos failed - low system resources.\n"); // invalidate all the splitter bars (with NULL pDC) DrawAllSplitBars(NULL, rectInside.right, rectInside.bottom); } void CSplitterWnd::DrawAllSplitBars(CDC* pDC, int cxInside, int cyInside) { ASSERT_VALID(this); // draw column split bars CRect rect; GetClientRect(rect); rect.left += m_cxBorder; for (int col = 0; col < m_nCols - 1; col++) { rect.left += m_pColInfo[col].nCurSize + m_cxBorderShare; rect.right = rect.left + m_cxSplitter; if (rect.left > cxInside) break; // stop if not fully visible OnDrawSplitter(pDC, splitBar, rect); rect.left = rect.right + m_cxBorderShare; } // draw row split bars GetClientRect(rect); rect.top += m_cyBorder; for (int row = 0; row < m_nRows - 1; row++) { rect.top += m_pRowInfo[row].nCurSize + m_cyBorderShare; rect.bottom = rect.top + m_cySplitter; if (rect.top > cyInside) break; // stop if not fully visible OnDrawSplitter(pDC, splitBar, rect); rect.top = rect.bottom + m_cyBorderShare; } // draw pane borders if (afxData.bWin4) { GetClientRect(rect); int x = rect.left; for (col = 0; col < m_nCols; col++) { int cx = m_pColInfo[col].nCurSize + 2*m_cxBorder; if (col == m_nCols-1 && m_bHasVScroll) cx += afxData.cxVScroll - CX_BORDER; int y = rect.top; for (int row = 0; row < m_nRows; row++) { int cy = m_pRowInfo[row].nCurSize + 2*m_cyBorder; if (row == m_nRows-1 && m_bHasHScroll) cy += afxData.cyHScroll - CX_BORDER; OnDrawSplitter(pDC, splitBorder, CRect(x, y, x+cx, y+cy)); y += cy + m_cySplitterGap - 2*m_cyBorder; } x += cx + m_cxSplitterGap - 2*m_cxBorder; } } } void CSplitterWnd::OnPaint() { ASSERT_VALID(this); CPaintDC dc(this); CRect rectClient; GetClientRect(&rectClient); rectClient.InflateRect(-m_cxBorder, -m_cyBorder); CRect rectInside; GetInsideRect(rectInside); // draw the splitter boxes if (m_bHasVScroll && m_nRows < m_nMaxRows) { OnDrawSplitter(&dc, splitBox, CRect(rectInside.right + afxData.bNotWin4, rectClient.top, rectClient.right, rectClient.top + m_cySplitter)); } if (m_bHasHScroll && m_nCols < m_nMaxCols) { OnDrawSplitter(&dc, splitBox, CRect(rectClient.left, rectInside.bottom + afxData.bNotWin4, rectClient.left + m_cxSplitter, rectClient.bottom)); } // extend split bars to window border (past margins) DrawAllSplitBars(&dc, rectInside.right, rectInside.bottom); if (!afxData.bWin4) { // draw splitter intersections (inside only) GetInsideRect(rectInside); dc.IntersectClipRect(rectInside); CRect rect; rect.top = rectInside.top; for (int row = 0; row < m_nRows - 1; row++) { rect.top += m_pRowInfo[row].nCurSize + m_cyBorderShare; rect.bottom = rect.top + m_cySplitter; rect.left = rectInside.left; for (int col = 0; col < m_nCols - 1; col++) { rect.left += m_pColInfo[col].nCurSize + m_cxBorderShare; rect.right = rect.left + m_cxSplitter; OnDrawSplitter(&dc, splitIntersection, rect); rect.left = rect.right + m_cxBorderShare; } rect.top = rect.bottom + m_cxBorderShare; } } } BOOL CSplitterWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { if (nHitTest == HTCLIENT && pWnd == this && !m_bTracking) return TRUE; // we will handle it in the mouse move return CWnd::OnSetCursor(pWnd, nHitTest, message); } // cache of last needed cursor AFX_STATIC_DATA HCURSOR _afx_hcurLast = NULL; AFX_STATIC_DATA HCURSOR _afx_hcurDestroy = NULL; AFX_STATIC_DATA UINT _afx_idcPrimaryLast = 0; // store the primary IDC void CSplitterWnd::SetSplitCursor(int ht) { UINT idcPrimary; // app supplied cursor LPCTSTR idcSecondary; // system supplied cursor (MAKEINTRESOURCE) AfxLockGlobals(CRIT_SPLITTERWND); if (ht == vSplitterBox || ht >= vSplitterBar1 && ht <= vSplitterBar15) { idcPrimary = AFX_IDC_VSPLITBAR; idcSecondary = IDC_SIZENS; } else if (ht == hSplitterBox || ht >= hSplitterBar1 && ht <= hSplitterBar15) { idcPrimary = AFX_IDC_HSPLITBAR; idcSecondary = IDC_SIZEWE; } else if (ht == bothSplitterBox || (ht >= splitterIntersection1 && ht <= splitterIntersection225)) { idcPrimary = AFX_IDC_SMALLARROWS; idcSecondary = IDC_SIZEALL; } else { SetCursor(afxData.hcurArrow); idcPrimary = 0; // don't use it idcSecondary = 0; // don't use it } if (idcPrimary != 0) { HCURSOR hcurToDestroy = NULL; if (idcPrimary != _afx_idcPrimaryLast) { HINSTANCE hInst = AfxFindResourceHandle( MAKEINTRESOURCE(idcPrimary), RT_GROUP_CURSOR); // load in another cursor hcurToDestroy = _afx_hcurDestroy; // Note: If this LoadCursor call fails, it is likely that // _AFX_NO_SPLITTER_RESOURCES is defined in your .RC file. // To correct the situation, remove the following line from your // resource script: // #define _AFX_NO_SPLITTER_RESOURCES // This should be done using the Resource.Set Includes... command. if ((_afx_hcurDestroy = _afx_hcurLast = ::LoadCursor(hInst, MAKEINTRESOURCE(idcPrimary))) == NULL) { // will not look as good TRACE0("Warning: Could not find splitter cursor - using system provided alternative.\n"); ASSERT(_afx_hcurDestroy == NULL); // will not get destroyed _afx_hcurLast = ::LoadCursor(NULL, idcSecondary); ASSERT(_afx_hcurLast != NULL); } _afx_idcPrimaryLast = idcPrimary; } ASSERT(_afx_hcurLast != NULL); ::SetCursor(_afx_hcurLast); ASSERT(_afx_hcurLast != hcurToDestroy); if (hcurToDestroy != NULL) ::DestroyCursor(hcurToDestroy); // destroy after being set } AfxUnlockGlobals(CRIT_SPLITTERWND); } void CSplitterWnd::OnMouseMove(UINT /*nFlags*/, CPoint pt) { if (GetCapture() != this) StopTracking(FALSE); if (m_bTracking) { // move tracker to current cursor position pt.Offset(m_ptTrackOffset); // pt is the upper right of hit detect // limit the point to the valid split range if (pt.y < m_rectLimit.top) pt.y = m_rectLimit.top; else if (pt.y > m_rectLimit.bottom) pt.y = m_rectLimit.bottom; if (pt.x < m_rectLimit.left) pt.x = m_rectLimit.left; else if (pt.x > m_rectLimit.right) pt.x = m_rectLimit.right; if (m_htTrack == vSplitterBox || m_htTrack >= vSplitterBar1 && m_htTrack <= vSplitterBar15) { if (m_rectTracker.top != pt.y) { OnInvertTracker(m_rectTracker); m_rectTracker.OffsetRect(0, pt.y - m_rectTracker.top); OnInvertTracker(m_rectTracker); } } else if (m_htTrack == hSplitterBox || m_htTrack >= hSplitterBar1 && m_htTrack <= hSplitterBar15) { if (m_rectTracker.left != pt.x) { OnInvertTracker(m_rectTracker); m_rectTracker.OffsetRect(pt.x - m_rectTracker.left, 0); OnInvertTracker(m_rectTracker); } } else if (m_htTrack == bothSplitterBox || (m_htTrack >= splitterIntersection1 && m_htTrack <= splitterIntersection225)) { if (m_rectTracker.top != pt.y) { OnInvertTracker(m_rectTracker); m_rectTracker.OffsetRect(0, pt.y - m_rectTracker.top); OnInvertTracker(m_rectTracker); } if (m_rectTracker2.left != pt.x) { OnInvertTracker(m_rectTracker2); m_rectTracker2.OffsetRect(pt.x - m_rectTracker2.left, 0); OnInvertTracker(m_rectTracker2); } } } else { // simply hit-test and set appropriate cursor int ht = HitTest(pt); SetSplitCursor(ht); } } void CSplitterWnd::OnLButtonDown(UINT /*nFlags*/, CPoint pt) { if (m_bTracking) return; StartTracking(HitTest(pt)); } void CSplitterWnd::OnLButtonDblClk(UINT /*nFlags*/, CPoint pt) { int ht = HitTest(pt); CRect rect; StopTracking(FALSE); if ((GetStyle() & SPLS_DYNAMIC_SPLIT) == 0) return; // do nothing if layout is static if (ht == vSplitterBox) { // half split SplitRow(m_pRowInfo[0].nCurSize / 2); } else if (ht == hSplitterBox) { // half split SplitColumn(m_pColInfo[0].nCurSize / 2); } else if (ht >= vSplitterBar1 && ht <= vSplitterBar15) { int rowDelete = ht - vSplitterBar1; // don't delete the active row int row; if (GetActivePane(&row, NULL) != NULL && rowDelete == row) ++rowDelete; DeleteRow(rowDelete); } else if (ht >= hSplitterBar1 && ht <= hSplitterBar15) { int colDelete = ht - hSplitterBar1; // don't delete the active column int col; if (GetActivePane(NULL, &col) != NULL && colDelete == col) ++colDelete; DeleteColumn(colDelete); } else if (ht >= splitterIntersection1 && ht <= splitterIntersection225) { int rowDelete = (ht - splitterIntersection1) / 15; int colDelete = (ht - splitterIntersection1) % 15; int row, col; if (GetActivePane(&row, &col) != NULL) { // don't delete the active row or column if (col == colDelete) ++colDelete; if (row == rowDelete) ++rowDelete; } DeleteRow(rowDelete); DeleteColumn(colDelete); } } void CSplitterWnd::OnLButtonUp(UINT /*nFlags*/, CPoint /*pt*/) { StopTracking(TRUE); } void CSplitterWnd::OnCancelMode() { StopTracking(FALSE); } void CSplitterWnd::OnKeyDown(UINT nChar, UINT /*nRepCnt*/, UINT /*nFlags*/) { CPoint pt; GetCursorPos(&pt); int cz = GetKeyState(VK_CONTROL) < 0 ? 1 : 16; int dx = 0; int dy = 0; switch (nChar) { case VK_ESCAPE: StopTracking(FALSE); return; case VK_RETURN: StopTracking(TRUE); return; case VK_LEFT: dx = -1; break; case VK_RIGHT: dx = +1; break; case VK_UP: dy = -1; break; case VK_DOWN: dy = +1; break; default: Default(); // pass other keys through return; } if (m_htTrack == vSplitterBox || m_htTrack >= vSplitterBar1 && m_htTrack <= vSplitterBar15) { // no movement along X axis dx = 0; } if (m_htTrack == hSplitterBox || m_htTrack >= hSplitterBar1 && m_htTrack <= hSplitterBar15) { // no movement along Y axis dy = 0; } // adjust to new position pt.x += dx * cz; pt.y += dy * cz; // make sure within valid limits ScreenToClient(&pt); if (pt.y < m_rectLimit.top) pt.y = m_rectLimit.top; else if (pt.y > m_rectLimit.bottom) pt.y = m_rectLimit.bottom; if (pt.x < m_rectLimit.left) pt.x = m_rectLimit.left; else if (pt.x > m_rectLimit.right) pt.x = m_rectLimit.right; ClientToScreen(&pt); // cause WM_MOUSEMOVE to filter through SetCursorPos(pt.x, pt.y); } void CSplitterWnd::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == SC_SIZE) { CWnd* pParent = GetSizingParent(); if (pParent != NULL) { pParent->SendMessage(WM_SYSCOMMAND, (WPARAM)nID, lParam); return; } } CWnd::OnSysCommand(nID, lParam); } ///////////////////////////////////////////////////////////////////////////// // CSplitterWnd command routing BOOL CSplitterWnd::OnCommand(WPARAM wParam, LPARAM lParam) { if (CWnd::OnCommand(wParam, lParam)) return TRUE; // route commands to the splitter to the parent frame window return GetParentFrame()->SendMessage(WM_COMMAND, wParam, lParam); } BOOL CSplitterWnd::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) { if (CWnd::OnNotify(wParam, lParam, pResult)) return TRUE; // route commands to the splitter to the parent frame window *pResult = GetParentFrame()->SendMessage(WM_NOTIFY, wParam, lParam); return TRUE; } ///////////////////////////////////////////////////////////////////////////// // Scroll messages BOOL CSplitterWnd::OnMouseWheel(UINT fFlags, short zDelta, CPoint point) { BOOL bRetVal = FALSE; int row; int col; // find panes in the splitter that has scroll bars // and have them do their scrolling for (row = 0; row < m_nRows; row++) { for (col = 0; col < m_nCols; col++) { // only do the scrolling if the window is-a CScrollView CWnd* pPane = GetPane(row, col); CScrollView* pView = DYNAMIC_DOWNCAST(CScrollView, pPane); if (pView != NULL) { // prefer to scroll vertically if available CScrollBar* pBar = pView->GetScrollBarCtrl(SB_VERT); if (pBar == NULL) { pBar = pView->GetScrollBarCtrl(SB_HORZ); if (pBar == NULL) continue; } // get the old position, do the scrolling, and // then trigger the repaint int nOldPos = pBar->GetScrollPos(); if (pView->DoMouseWheel(fFlags, zDelta, point)) bRetVal = TRUE; if (col < m_nCols -1) pBar->SetScrollPos(nOldPos, FALSE); } } } return TRUE; } void CSplitterWnd::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { ASSERT(pScrollBar != NULL); int col = _AfxGetDlgCtrlID(pScrollBar->m_hWnd) - AFX_IDW_HSCROLL_FIRST; ASSERT(col >= 0 && col < m_nMaxCols); ASSERT(m_nRows > 0); int nOldPos = pScrollBar->GetScrollPos(); #ifdef _DEBUG int nNewPos; #endif for (int row = 0; row < m_nRows; row++) { GetPane(row, col)->SendMessage(WM_HSCROLL, MAKELONG(nSBCode, nPos), (LPARAM)pScrollBar->m_hWnd); #ifdef _DEBUG if (row == 0) { nNewPos = pScrollBar->GetScrollPos(); if (pScrollBar->GetScrollPos() != nNewPos) { TRACE0("Warning: scroll panes setting different scroll positions.\n"); // stick with the last one set } } #endif //_DEBUG // set the scroll pos to the value it was originally for the next pane if (row < m_nRows - 1) pScrollBar->SetScrollPos(nOldPos, FALSE); } } void CSplitterWnd::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { ASSERT(pScrollBar != NULL); int row = _AfxGetDlgCtrlID(pScrollBar->m_hWnd) - AFX_IDW_VSCROLL_FIRST; ASSERT(row >= 0 && row < m_nMaxRows); ASSERT(m_nCols > 0); int nOldPos = pScrollBar->GetScrollPos(); #ifdef _DEBUG int nNewPos; #endif for (int col = 0; col < m_nCols; col++) { GetPane(row, col)->SendMessage(WM_VSCROLL, MAKELONG(nSBCode, nPos), (LPARAM)pScrollBar->m_hWnd); #ifdef _DEBUG if (col == 0) { nNewPos = pScrollBar->GetScrollPos(); if (pScrollBar->GetScrollPos() != nNewPos) { TRACE0("Warning: scroll panes setting different scroll positions.\n"); // stick with the last one set } } #endif //_DEBUG // set the scroll pos to the value it was originally for the next pane if (col < m_nCols - 1) pScrollBar->SetScrollPos(nOldPos, FALSE); } } // synchronized scrolling BOOL CSplitterWnd::DoScroll(CView* pViewFrom, UINT nScrollCode, BOOL bDoScroll) { ASSERT_VALID(pViewFrom); int rowFrom, colFrom; if (!IsChildPane(pViewFrom, &rowFrom, &colFrom)) return FALSE; BOOL bResult = FALSE; // save original positions int nOldVert = 0; CScrollBar* pScrollVert = pViewFrom->GetScrollBarCtrl(SB_VERT); if (pScrollVert != NULL) nOldVert = pScrollVert->GetScrollPos(); int nOldHorz = 0; CScrollBar* pScrollHorz = pViewFrom->GetScrollBarCtrl(SB_HORZ); if (pScrollHorz != NULL) nOldHorz = pScrollHorz->GetScrollPos(); // scroll the view from which the message is from if (pViewFrom->OnScroll(nScrollCode, 0, bDoScroll)) bResult = TRUE; if (pScrollVert != NULL) { #ifdef _DEBUG int nNewVert = pScrollVert->GetScrollPos(); #endif // scroll related columns for (int col = 0; col < m_nCols; col++) { if (col == colFrom) continue; // set the scroll pos to the value it was originally pScrollVert->SetScrollPos(nOldVert, FALSE); // scroll the pane CView* pView = (CView*)GetPane(rowFrom, col); ASSERT_KINDOF(CView, pView); ASSERT(pView != pViewFrom); if (pView->OnScroll(MAKEWORD(-1, HIBYTE(nScrollCode)), 0, bDoScroll)) { bResult = TRUE; } #ifdef _DEBUG if (pScrollVert->GetScrollPos() != nNewVert) { TRACE0("Warning: scroll panes setting different scroll positions.\n"); // stick with the last one set } #endif } } if (pScrollHorz != NULL) { #ifdef _DEBUG int nNewHorz = pScrollHorz->GetScrollPos(); #endif // scroll related rows for (int row = 0; row < m_nRows; row++) { if (row == rowFrom) continue; // set the scroll pos to the value it was originally pScrollHorz->SetScrollPos(nOldHorz, FALSE); // scroll the pane CView* pView = (CView*)GetPane(row, colFrom); ASSERT_KINDOF(CView, pView); ASSERT(pView != pViewFrom); if (pView->OnScroll(MAKEWORD(LOBYTE(nScrollCode), -1), 0, bDoScroll)) { bResult = TRUE; } #ifdef _DEBUG if (pScrollHorz->GetScrollPos() != nNewHorz) { TRACE0("Warning: scroll panes setting different scroll positions.\n"); // stick with the last one set } #endif } } return bResult; } BOOL CSplitterWnd::DoScrollBy(CView* pViewFrom, CSize sizeScroll, BOOL bDoScroll) { int rowFrom, colFrom; if (!IsChildPane(pViewFrom, &rowFrom, &colFrom)) return FALSE; BOOL bResult = FALSE; // save original positions int nOldVert = 0; CScrollBar* pScrollVert = pViewFrom->GetScrollBarCtrl(SB_VERT); if (pScrollVert != NULL) nOldVert = pScrollVert->GetScrollPos(); int nOldHorz = 0; CScrollBar* pScrollHorz = pViewFrom->GetScrollBarCtrl(SB_HORZ); if (pScrollHorz != NULL) nOldHorz = pScrollHorz->GetScrollPos(); // scroll the view from which the message is from if (pViewFrom->OnScrollBy(sizeScroll, bDoScroll)) bResult = TRUE; if (pScrollVert != NULL) { #ifdef _DEBUG int nNewVert = pScrollVert->GetScrollPos(); #endif // scroll related columns for (int col = 0; col < m_nCols; col++) { if (col == colFrom) continue; // set the scroll pos to the value it was originally for the next pane pScrollVert->SetScrollPos(nOldVert, FALSE); // scroll the pane CView* pView = (CView*)GetPane(rowFrom, col); ASSERT_KINDOF(CView, pView); ASSERT(pView != pViewFrom); if (pView->OnScrollBy(CSize(0, sizeScroll.cy), bDoScroll)) bResult = TRUE; #ifdef _DEBUG if (pScrollVert->GetScrollPos() != nNewVert) { TRACE0("Warning: scroll panes setting different scroll positions.\n"); // stick with the last one set } #endif } } if (pScrollHorz != NULL) { #ifdef _DEBUG int nNewHorz = pScrollHorz->GetScrollPos(); #endif // scroll related rows for (int row = 0; row < m_nRows; row++) { if (row == rowFrom) continue; // set the scroll pos to the value it was originally for the next pane pScrollHorz->SetScrollPos(nOldHorz, FALSE); // scroll the pane CView* pView = (CView*)GetPane(row, colFrom); ASSERT_KINDOF(CView, pView); ASSERT(pView != pViewFrom); if (pView->OnScrollBy(CSize(sizeScroll.cx, 0), bDoScroll)) bResult = TRUE; #ifdef _DEBUG if (pScrollHorz->GetScrollPos() != nNewHorz) { TRACE0("Warning: scroll panes setting different scroll positions.\n"); // stick with the last one set } #endif } } return bResult; } ///////////////////////////////////////////////////////////////////////////// // Focus control and control over the current pane/child BOOL CSplitterWnd::CanActivateNext(BOOL) { ASSERT_VALID(this); if (GetActivePane() == NULL) { TRACE0("Warning: Can't go to next pane - there is no current pane.\n"); return FALSE; } ASSERT(m_nRows != 0); ASSERT(m_nCols != 0); // if more than 1x1 we can go to the next or prev pane return (m_nRows > 1) || (m_nCols > 1); } void CSplitterWnd::ActivateNext(BOOL bPrev) { ASSERT_VALID(this); // find the coordinate of the current pane int row, col; if (GetActivePane(&row, &col) == NULL) { TRACE0("Warning: Cannot go to next pane - there is no current view.\n"); return; } ASSERT(row >= 0 && row < m_nRows); ASSERT(col >= 0 && col < m_nCols); // determine next pane if (bPrev) { // prev if (--col < 0) { col = m_nCols - 1; if (--row < 0) row = m_nRows - 1; } } else { // next if (++col >= m_nCols) { col = 0; if (++row >= m_nRows) row = 0; } } // set newly active pane SetActivePane(row, col); } void CSplitterWnd::SetActivePane(int row, int col, CWnd* pWnd) { // set the focus to the pane CWnd* pPane = pWnd == NULL ? GetPane(row, col) : pWnd; if (pPane->IsKindOf(RUNTIME_CLASS(CView))) { CFrameWnd* pFrameWnd = GetParentFrame(); ASSERT_VALID(pFrameWnd); pFrameWnd->SetActiveView((CView*)pPane); } else { TRACE0("Warning: Next pane is not a view - calling SetFocus.\n"); pPane->SetFocus(); } } CWnd* CSplitterWnd::GetActivePane(int* pRow, int* pCol) // return active view, NULL when no active view { ASSERT_VALID(this); // attempt to use active view of frame window CWnd* pView = NULL; CFrameWnd* pFrameWnd = GetParentFrame(); ASSERT_VALID(pFrameWnd); pView = pFrameWnd->GetActiveView(); // failing that, use the current focus if (pView == NULL) pView = GetFocus(); // make sure the pane is a child pane of the splitter if (pView != NULL && !IsChildPane(pView, pRow, pCol)) pView = NULL; return pView; } ///////////////////////////////////////////////////////////////////////////// // CSplitterWnd diagnostics #ifdef _DEBUG void CSplitterWnd::AssertValid() const { CWnd::AssertValid(); ASSERT(m_nMaxRows >= 1); ASSERT(m_nMaxCols >= 1); ASSERT(m_nMaxCols > 1 || m_nMaxRows > 1); // 1x1 is not permitted ASSERT(m_nRows >= 1); ASSERT(m_nCols >= 1); ASSERT(m_nRows <= m_nMaxRows); ASSERT(m_nCols <= m_nMaxCols); } void CSplitterWnd::Dump(CDumpContext& dc) const { CWnd::Dump(dc); if (m_pDynamicViewClass != NULL) dc << "m_pDynamicViewClass = " << m_pDynamicViewClass->m_lpszClassName; dc << "\nm_nMaxRows = " << m_nMaxRows; dc << "\nm_nMaxCols = " << m_nMaxCols; dc << "\nm_nRows = " << m_nRows; dc << "\nm_nCols = " << m_nCols; dc << "\nm_bHasHScroll = " << m_bHasHScroll; dc << "\nm_bHasVScroll = " << m_bHasVScroll; dc << "\nm_cxSplitter = " << m_cxSplitter; dc << "\nm_cySplitter = " << m_cySplitter; if (m_bTracking) { dc << "\nTRACKING: m_htTrack = " << m_htTrack; dc << "\nm_rectLimit = " << m_rectLimit; dc << "\nm_ptTrackOffset = " << m_ptTrackOffset; dc << "\nm_rectTracker = " << m_rectTracker; if (m_bTracking2) dc << "\nm_rectTracker2 = " << m_rectTracker2; } dc << "\n"; } #endif /////////////////////////////////////////////////////////////////////////////