| // Windows Template Library - WTL version 10.0 |
| // Copyright (C) Microsoft Corporation, WTL Team. All rights reserved. |
| // |
| // This file is a part of the Windows Template Library. |
| // The use and distribution terms for this software are covered by the |
| // Microsoft Public License (http://opensource.org/licenses/MS-PL) |
| // which can be found in the file MS-PL.txt at the root folder. |
| |
| #ifndef __ATLSPLIT_H__ |
| #define __ATLSPLIT_H__ |
| |
| #pragma once |
| |
| #ifndef __ATLAPP_H__ |
| #error atlsplit.h requires atlapp.h to be included first |
| #endif |
| |
| #ifndef __ATLWIN_H__ |
| #error atlsplit.h requires atlwin.h to be included first |
| #endif |
| |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Classes in this file: |
| // |
| // CSplitterImpl<T> |
| // CSplitterWindowImpl<T, TBase, TWinTraits> |
| // CSplitterWindowT<t_bVertical> - CSplitterWindow, CHorSplitterWindow |
| |
| |
| namespace WTL |
| { |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // CSplitterImpl - Provides splitter support to any window |
| |
| // Splitter panes constants |
| #define SPLIT_PANE_LEFT 0 |
| #define SPLIT_PANE_RIGHT 1 |
| #define SPLIT_PANE_TOP SPLIT_PANE_LEFT |
| #define SPLIT_PANE_BOTTOM SPLIT_PANE_RIGHT |
| #define SPLIT_PANE_NONE -1 |
| |
| // Splitter extended styles |
| #define SPLIT_PROPORTIONAL 0x00000001 |
| #define SPLIT_NONINTERACTIVE 0x00000002 |
| #define SPLIT_RIGHTALIGNED 0x00000004 |
| #define SPLIT_BOTTOMALIGNED SPLIT_RIGHTALIGNED |
| #define SPLIT_GRADIENTBAR 0x00000008 |
| #define SPLIT_FLATBAR 0x00000020 |
| #define SPLIT_FIXEDBARSIZE 0x00000010 |
| |
| // Note: SPLIT_PROPORTIONAL and SPLIT_RIGHTALIGNED/SPLIT_BOTTOMALIGNED are |
| // mutually exclusive. If both are set, splitter defaults to SPLIT_PROPORTIONAL. |
| // Also, SPLIT_FLATBAR overrides SPLIT_GRADIENTBAR if both are set. |
| |
| |
| template <class T> |
| class CSplitterImpl |
| { |
| public: |
| enum { m_nPanesCount = 2, m_nPropMax = INT_MAX, m_cxyStep = 10 }; |
| |
| bool m_bVertical; |
| HWND m_hWndPane[m_nPanesCount]; |
| RECT m_rcSplitter; |
| int m_xySplitterPos; // splitter bar position |
| int m_xySplitterPosNew; // internal - new position while moving |
| HWND m_hWndFocusSave; |
| int m_nDefActivePane; |
| int m_cxySplitBar; // splitter bar width/height |
| HCURSOR m_hCursor; |
| int m_cxyMin; // minimum pane size |
| int m_cxyBarEdge; // splitter bar edge |
| bool m_bFullDrag; |
| int m_cxyDragOffset; // internal |
| int m_nProportionalPos; |
| bool m_bUpdateProportionalPos; |
| DWORD m_dwExtendedStyle; // splitter specific extended styles |
| int m_nSinglePane; // single pane mode |
| int m_xySplitterDefPos; // default position |
| bool m_bProportionalDefPos; // porportinal def pos |
| |
| // Constructor |
| CSplitterImpl(bool bVertical = true) : |
| m_bVertical(bVertical), m_xySplitterPos(-1), m_xySplitterPosNew(-1), m_hWndFocusSave(NULL), |
| m_nDefActivePane(SPLIT_PANE_NONE), m_cxySplitBar(4), m_hCursor(NULL), m_cxyMin(0), m_cxyBarEdge(0), |
| m_bFullDrag(true), m_cxyDragOffset(0), m_nProportionalPos(0), m_bUpdateProportionalPos(true), |
| m_dwExtendedStyle(SPLIT_PROPORTIONAL), m_nSinglePane(SPLIT_PANE_NONE), |
| m_xySplitterDefPos(-1), m_bProportionalDefPos(false) |
| { |
| m_hWndPane[SPLIT_PANE_LEFT] = NULL; |
| m_hWndPane[SPLIT_PANE_RIGHT] = NULL; |
| |
| ::SetRectEmpty(&m_rcSplitter); |
| } |
| |
| // Attributes |
| void SetSplitterRect(LPRECT lpRect = NULL, bool bUpdate = true) |
| { |
| if(lpRect == NULL) |
| { |
| T* pT = static_cast<T*>(this); |
| pT->GetClientRect(&m_rcSplitter); |
| } |
| else |
| { |
| m_rcSplitter = *lpRect; |
| } |
| |
| if(IsProportional()) |
| UpdateProportionalPos(); |
| else if(IsRightAligned()) |
| UpdateRightAlignPos(); |
| |
| if(bUpdate) |
| UpdateSplitterLayout(); |
| } |
| |
| void GetSplitterRect(LPRECT lpRect) const |
| { |
| ATLASSERT(lpRect != NULL); |
| *lpRect = m_rcSplitter; |
| } |
| |
| bool SetSplitterPos(int xyPos = -1, bool bUpdate = true) |
| { |
| if(xyPos == -1) // -1 == default position |
| { |
| if(m_bProportionalDefPos) |
| { |
| ATLASSERT((m_xySplitterDefPos >= 0) && (m_xySplitterDefPos <= m_nPropMax)); |
| |
| if(m_bVertical) |
| xyPos = ::MulDiv(m_xySplitterDefPos, m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge, m_nPropMax); |
| else |
| xyPos = ::MulDiv(m_xySplitterDefPos, m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge, m_nPropMax); |
| } |
| else if(m_xySplitterDefPos != -1) |
| { |
| xyPos = m_xySplitterDefPos; |
| } |
| else // not set, use middle position |
| { |
| if(m_bVertical) |
| xyPos = (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) / 2; |
| else |
| xyPos = (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge) / 2; |
| } |
| } |
| |
| // Adjust if out of valid range |
| int cxyMax = 0; |
| if(m_bVertical) |
| cxyMax = m_rcSplitter.right - m_rcSplitter.left; |
| else |
| cxyMax = m_rcSplitter.bottom - m_rcSplitter.top; |
| |
| if(xyPos < m_cxyMin + m_cxyBarEdge) |
| xyPos = m_cxyMin; |
| else if(xyPos > (cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin)) |
| xyPos = cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin; |
| |
| // Set new position and update if requested |
| bool bRet = (m_xySplitterPos != xyPos); |
| m_xySplitterPos = xyPos; |
| |
| if(m_bUpdateProportionalPos) |
| { |
| if(IsProportional()) |
| StoreProportionalPos(); |
| else if(IsRightAligned()) |
| StoreRightAlignPos(); |
| } |
| else |
| { |
| m_bUpdateProportionalPos = true; |
| } |
| |
| if(bUpdate && bRet) |
| UpdateSplitterLayout(); |
| |
| return bRet; |
| } |
| |
| int GetSplitterPos() const |
| { |
| return m_xySplitterPos; |
| } |
| |
| void SetSplitterPosPct(int nPct, bool bUpdate = true) |
| { |
| ATLASSERT((nPct >= 0) && (nPct <= 100)); |
| |
| m_nProportionalPos = ::MulDiv(nPct, m_nPropMax, 100); |
| UpdateProportionalPos(); |
| |
| if(bUpdate) |
| UpdateSplitterLayout(); |
| } |
| |
| int GetSplitterPosPct() const |
| { |
| int cxyTotal = m_bVertical ? (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) : (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge); |
| return ((cxyTotal > 0) && (m_xySplitterPos >= 0)) ? ::MulDiv(m_xySplitterPos, 100, cxyTotal) : -1; |
| } |
| |
| bool SetSinglePaneMode(int nPane = SPLIT_PANE_NONE) |
| { |
| ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT) || (nPane == SPLIT_PANE_NONE)); |
| if(!((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT) || (nPane == SPLIT_PANE_NONE))) |
| return false; |
| |
| if(nPane != SPLIT_PANE_NONE) |
| { |
| if(::IsWindowVisible(m_hWndPane[nPane]) == FALSE) |
| ::ShowWindow(m_hWndPane[nPane], SW_SHOW); |
| int nOtherPane = (nPane == SPLIT_PANE_LEFT) ? SPLIT_PANE_RIGHT : SPLIT_PANE_LEFT; |
| ::ShowWindow(m_hWndPane[nOtherPane], SW_HIDE); |
| if(m_nDefActivePane != nPane) |
| m_nDefActivePane = nPane; |
| } |
| else if(m_nSinglePane != SPLIT_PANE_NONE) |
| { |
| int nOtherPane = (m_nSinglePane == SPLIT_PANE_LEFT) ? SPLIT_PANE_RIGHT : SPLIT_PANE_LEFT; |
| ::ShowWindow(m_hWndPane[nOtherPane], SW_SHOW); |
| } |
| |
| m_nSinglePane = nPane; |
| UpdateSplitterLayout(); |
| |
| return true; |
| } |
| |
| int GetSinglePaneMode() const |
| { |
| return m_nSinglePane; |
| } |
| |
| DWORD GetSplitterExtendedStyle() const |
| { |
| return m_dwExtendedStyle; |
| } |
| |
| DWORD SetSplitterExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0) |
| { |
| DWORD dwPrevStyle = m_dwExtendedStyle; |
| if(dwMask == 0) |
| m_dwExtendedStyle = dwExtendedStyle; |
| else |
| m_dwExtendedStyle = (m_dwExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask); |
| |
| #ifdef _DEBUG |
| if(IsProportional() && IsRightAligned()) |
| ATLTRACE2(atlTraceUI, 0, _T("CSplitterImpl::SetSplitterExtendedStyle - SPLIT_PROPORTIONAL and SPLIT_RIGHTALIGNED are mutually exclusive, defaulting to SPLIT_PROPORTIONAL.\n")); |
| #endif // _DEBUG |
| |
| return dwPrevStyle; |
| } |
| |
| void SetSplitterDefaultPos(int xyPos = -1) |
| { |
| m_xySplitterDefPos = xyPos; |
| m_bProportionalDefPos = false; |
| } |
| |
| void SetSplitterDefaultPosPct(int nPct) |
| { |
| ATLASSERT((nPct >= 0) && (nPct <= 100)); |
| |
| m_xySplitterDefPos = ::MulDiv(nPct, m_nPropMax, 100); |
| m_bProportionalDefPos = true; |
| } |
| |
| // Splitter operations |
| void SetSplitterPanes(HWND hWndLeftTop, HWND hWndRightBottom, bool bUpdate = true) |
| { |
| m_hWndPane[SPLIT_PANE_LEFT] = hWndLeftTop; |
| m_hWndPane[SPLIT_PANE_RIGHT] = hWndRightBottom; |
| ATLASSERT((m_hWndPane[SPLIT_PANE_LEFT] == NULL) || (m_hWndPane[SPLIT_PANE_RIGHT] == NULL) || (m_hWndPane[SPLIT_PANE_LEFT] != m_hWndPane[SPLIT_PANE_RIGHT])); |
| if(bUpdate) |
| UpdateSplitterLayout(); |
| } |
| |
| bool SetSplitterPane(int nPane, HWND hWnd, bool bUpdate = true) |
| { |
| ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT)); |
| if((nPane != SPLIT_PANE_LEFT) && (nPane != SPLIT_PANE_RIGHT)) |
| return false; |
| |
| m_hWndPane[nPane] = hWnd; |
| ATLASSERT((m_hWndPane[SPLIT_PANE_LEFT] == NULL) || (m_hWndPane[SPLIT_PANE_RIGHT] == NULL) || (m_hWndPane[SPLIT_PANE_LEFT] != m_hWndPane[SPLIT_PANE_RIGHT])); |
| if(bUpdate) |
| UpdateSplitterLayout(); |
| |
| return true; |
| } |
| |
| HWND GetSplitterPane(int nPane) const |
| { |
| ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT)); |
| if((nPane != SPLIT_PANE_LEFT) && (nPane != SPLIT_PANE_RIGHT)) |
| return NULL; |
| |
| return m_hWndPane[nPane]; |
| } |
| |
| bool SetActivePane(int nPane) |
| { |
| ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT)); |
| if((nPane != SPLIT_PANE_LEFT) && (nPane != SPLIT_PANE_RIGHT)) |
| return false; |
| if((m_nSinglePane != SPLIT_PANE_NONE) && (nPane != m_nSinglePane)) |
| return false; |
| |
| ::SetFocus(m_hWndPane[nPane]); |
| m_nDefActivePane = nPane; |
| |
| return true; |
| } |
| |
| int GetActivePane() const |
| { |
| int nRet = SPLIT_PANE_NONE; |
| HWND hWndFocus = ::GetFocus(); |
| if(hWndFocus != NULL) |
| { |
| for(int nPane = 0; nPane < m_nPanesCount; nPane++) |
| { |
| if((hWndFocus == m_hWndPane[nPane]) || (::IsChild(m_hWndPane[nPane], hWndFocus) != FALSE)) |
| { |
| nRet = nPane; |
| break; |
| } |
| } |
| } |
| |
| return nRet; |
| } |
| |
| bool ActivateNextPane(bool bNext = true) |
| { |
| int nPane = m_nSinglePane; |
| if(nPane == SPLIT_PANE_NONE) |
| { |
| switch(GetActivePane()) |
| { |
| case SPLIT_PANE_LEFT: |
| nPane = SPLIT_PANE_RIGHT; |
| break; |
| case SPLIT_PANE_RIGHT: |
| nPane = SPLIT_PANE_LEFT; |
| break; |
| default: |
| nPane = bNext ? SPLIT_PANE_LEFT : SPLIT_PANE_RIGHT; |
| break; |
| } |
| } |
| |
| return SetActivePane(nPane); |
| } |
| |
| bool SetDefaultActivePane(int nPane) |
| { |
| ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT)); |
| if((nPane != SPLIT_PANE_LEFT) && (nPane != SPLIT_PANE_RIGHT)) |
| return false; |
| |
| m_nDefActivePane = nPane; |
| |
| return true; |
| } |
| |
| bool SetDefaultActivePane(HWND hWnd) |
| { |
| for(int nPane = 0; nPane < m_nPanesCount; nPane++) |
| { |
| if(hWnd == m_hWndPane[nPane]) |
| { |
| m_nDefActivePane = nPane; |
| return true; |
| } |
| } |
| |
| return false; // not found |
| } |
| |
| int GetDefaultActivePane() const |
| { |
| return m_nDefActivePane; |
| } |
| |
| void DrawSplitter(CDCHandle dc) |
| { |
| ATLASSERT(dc.m_hDC != NULL); |
| if((m_nSinglePane == SPLIT_PANE_NONE) && (m_xySplitterPos == -1)) |
| return; |
| |
| T* pT = static_cast<T*>(this); |
| if(m_nSinglePane == SPLIT_PANE_NONE) |
| { |
| pT->DrawSplitterBar(dc); |
| |
| for(int nPane = 0; nPane < m_nPanesCount; nPane++) |
| { |
| if(m_hWndPane[nPane] == NULL) |
| pT->DrawSplitterPane(dc, nPane); |
| } |
| } |
| else |
| { |
| if(m_hWndPane[m_nSinglePane] == NULL) |
| pT->DrawSplitterPane(dc, m_nSinglePane); |
| } |
| } |
| |
| // call to initiate moving splitter bar with keyboard |
| void MoveSplitterBar() |
| { |
| T* pT = static_cast<T*>(this); |
| |
| int x = 0; |
| int y = 0; |
| if(m_bVertical) |
| { |
| x = m_xySplitterPos + (m_cxySplitBar / 2) + m_cxyBarEdge; |
| y = (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge) / 2; |
| } |
| else |
| { |
| x = (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) / 2; |
| y = m_xySplitterPos + (m_cxySplitBar / 2) + m_cxyBarEdge; |
| } |
| |
| POINT pt = { x, y }; |
| pT->ClientToScreen(&pt); |
| ::SetCursorPos(pt.x, pt.y); |
| |
| m_xySplitterPosNew = m_xySplitterPos; |
| pT->SetCapture(); |
| m_hWndFocusSave = pT->SetFocus(); |
| ::SetCursor(m_hCursor); |
| if(!m_bFullDrag) |
| DrawGhostBar(); |
| if(m_bVertical) |
| m_cxyDragOffset = x - m_rcSplitter.left - m_xySplitterPos; |
| else |
| m_cxyDragOffset = y - m_rcSplitter.top - m_xySplitterPos; |
| } |
| |
| void SetOrientation(bool bVertical, bool bUpdate = true) |
| { |
| if(m_bVertical != bVertical) |
| { |
| m_bVertical = bVertical; |
| |
| m_hCursor = ::LoadCursor(NULL, m_bVertical ? IDC_SIZEWE : IDC_SIZENS); |
| |
| T* pT = static_cast<T*>(this); |
| pT->GetSystemSettings(false); |
| |
| if(m_bVertical) |
| m_xySplitterPos = ::MulDiv(m_xySplitterPos, m_rcSplitter.right - m_rcSplitter.left, m_rcSplitter.bottom - m_rcSplitter.top); |
| else |
| m_xySplitterPos = ::MulDiv(m_xySplitterPos, m_rcSplitter.bottom - m_rcSplitter.top, m_rcSplitter.right - m_rcSplitter.left); |
| } |
| |
| if(bUpdate) |
| UpdateSplitterLayout(); |
| } |
| |
| // Overrideables |
| void DrawSplitterBar(CDCHandle dc) |
| { |
| RECT rect = {}; |
| if(GetSplitterBarRect(&rect)) |
| { |
| dc.FillRect(&rect, COLOR_3DFACE); |
| |
| if((m_dwExtendedStyle & SPLIT_FLATBAR) != 0) |
| { |
| RECT rect1 = rect; |
| if(m_bVertical) |
| rect1.right = rect1.left + 1; |
| else |
| rect1.bottom = rect1.top + 1; |
| dc.FillRect(&rect1, COLOR_WINDOW); |
| |
| rect1 = rect; |
| if(m_bVertical) |
| rect1.left = rect1.right - 1; |
| else |
| rect1.top = rect1.bottom - 1; |
| dc.FillRect(&rect1, COLOR_3DSHADOW); |
| } |
| else if((m_dwExtendedStyle & SPLIT_GRADIENTBAR) != 0) |
| { |
| RECT rect2 = rect; |
| if(m_bVertical) |
| rect2.left = (rect.left + rect.right) / 2 - 1; |
| else |
| rect2.top = (rect.top + rect.bottom) / 2 - 1; |
| |
| dc.GradientFillRect(rect2, ::GetSysColor(COLOR_3DFACE), ::GetSysColor(COLOR_3DSHADOW), m_bVertical); |
| } |
| |
| // draw 3D edge if needed |
| T* pT = static_cast<T*>(this); |
| if((pT->GetExStyle() & WS_EX_CLIENTEDGE) != 0) |
| dc.DrawEdge(&rect, EDGE_RAISED, m_bVertical ? (BF_LEFT | BF_RIGHT) : (BF_TOP | BF_BOTTOM)); |
| } |
| } |
| |
| // called only if pane is empty |
| void DrawSplitterPane(CDCHandle dc, int nPane) |
| { |
| RECT rect = {}; |
| if(GetSplitterPaneRect(nPane, &rect)) |
| { |
| T* pT = static_cast<T*>(this); |
| if((pT->GetExStyle() & WS_EX_CLIENTEDGE) == 0) |
| dc.DrawEdge(&rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); |
| dc.FillRect(&rect, COLOR_APPWORKSPACE); |
| } |
| } |
| |
| // Message map and handlers |
| BEGIN_MSG_MAP(CSplitterImpl) |
| MESSAGE_HANDLER(WM_CREATE, OnCreate) |
| MESSAGE_HANDLER(WM_PAINT, OnPaint) |
| MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint) |
| if(IsInteractive()) |
| { |
| MESSAGE_HANDLER(WM_SETCURSOR, OnSetCursor) |
| MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove) |
| MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown) |
| MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp) |
| MESSAGE_HANDLER(WM_LBUTTONDBLCLK, OnLButtonDoubleClick) |
| MESSAGE_HANDLER(WM_CAPTURECHANGED, OnCaptureChanged) |
| MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown) |
| } |
| MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) |
| MESSAGE_HANDLER(WM_MOUSEACTIVATE, OnMouseActivate) |
| MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange) |
| END_MSG_MAP() |
| |
| LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) |
| { |
| T* pT = static_cast<T*>(this); |
| pT->Init(); |
| |
| bHandled = FALSE; |
| return 1; |
| } |
| |
| LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/) |
| { |
| T* pT = static_cast<T*>(this); |
| |
| // try setting position if not set |
| if((m_nSinglePane == SPLIT_PANE_NONE) && (m_xySplitterPos == -1)) |
| pT->SetSplitterPos(); |
| |
| // do painting |
| if(wParam != NULL) |
| { |
| pT->DrawSplitter((HDC)wParam); |
| } |
| else |
| { |
| CPaintDC dc(pT->m_hWnd); |
| pT->DrawSplitter(dc.m_hDC); |
| } |
| |
| return 0; |
| } |
| |
| LRESULT OnSetCursor(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled) |
| { |
| T* pT = static_cast<T*>(this); |
| if(((HWND)wParam == pT->m_hWnd) && (LOWORD(lParam) == HTCLIENT)) |
| { |
| DWORD dwPos = ::GetMessagePos(); |
| POINT ptPos = { GET_X_LPARAM(dwPos), GET_Y_LPARAM(dwPos) }; |
| pT->ScreenToClient(&ptPos); |
| if(IsOverSplitterBar(ptPos.x, ptPos.y)) |
| return 1; |
| } |
| |
| bHandled = FALSE; |
| return 0; |
| } |
| |
| LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) |
| { |
| T* pT = static_cast<T*>(this); |
| int xPos = GET_X_LPARAM(lParam); |
| int yPos = GET_Y_LPARAM(lParam); |
| if(::GetCapture() == pT->m_hWnd) |
| { |
| int xyNewSplitPos = 0; |
| if(m_bVertical) |
| xyNewSplitPos = xPos - m_rcSplitter.left - m_cxyDragOffset; |
| else |
| xyNewSplitPos = yPos - m_rcSplitter.top - m_cxyDragOffset; |
| |
| if(xyNewSplitPos == -1) // avoid -1, that means default position |
| xyNewSplitPos = -2; |
| |
| if(m_xySplitterPos != xyNewSplitPos) |
| { |
| if(m_bFullDrag) |
| { |
| if(pT->SetSplitterPos(xyNewSplitPos, true)) |
| pT->UpdateWindow(); |
| } |
| else |
| { |
| DrawGhostBar(); |
| pT->SetSplitterPos(xyNewSplitPos, false); |
| DrawGhostBar(); |
| } |
| } |
| } |
| else // not dragging, just set cursor |
| { |
| if(IsOverSplitterBar(xPos, yPos)) |
| ::SetCursor(m_hCursor); |
| bHandled = FALSE; |
| } |
| |
| return 0; |
| } |
| |
| LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) |
| { |
| T* pT = static_cast<T*>(this); |
| int xPos = GET_X_LPARAM(lParam); |
| int yPos = GET_Y_LPARAM(lParam); |
| if((::GetCapture() != pT->m_hWnd) && IsOverSplitterBar(xPos, yPos)) |
| { |
| m_xySplitterPosNew = m_xySplitterPos; |
| pT->SetCapture(); |
| m_hWndFocusSave = pT->SetFocus(); |
| ::SetCursor(m_hCursor); |
| if(!m_bFullDrag) |
| DrawGhostBar(); |
| if(m_bVertical) |
| m_cxyDragOffset = xPos - m_rcSplitter.left - m_xySplitterPos; |
| else |
| m_cxyDragOffset = yPos - m_rcSplitter.top - m_xySplitterPos; |
| } |
| else if((::GetCapture() == pT->m_hWnd) && !IsOverSplitterBar(xPos, yPos)) |
| { |
| ::ReleaseCapture(); |
| } |
| |
| bHandled = FALSE; |
| return 1; |
| } |
| |
| LRESULT OnLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) |
| { |
| T* pT = static_cast<T*>(this); |
| if(::GetCapture() == pT->m_hWnd) |
| { |
| m_xySplitterPosNew = m_xySplitterPos; |
| ::ReleaseCapture(); |
| } |
| |
| bHandled = FALSE; |
| return 1; |
| } |
| |
| LRESULT OnLButtonDoubleClick(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) |
| { |
| T* pT = static_cast<T*>(this); |
| pT->SetSplitterPos(); // default |
| |
| return 0; |
| } |
| |
| LRESULT OnCaptureChanged(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) |
| { |
| if(!m_bFullDrag) |
| DrawGhostBar(); |
| |
| if((m_xySplitterPosNew != -1) && (!m_bFullDrag || (m_xySplitterPos != m_xySplitterPosNew))) |
| { |
| m_xySplitterPos = m_xySplitterPosNew; |
| m_xySplitterPosNew = -1; |
| UpdateSplitterLayout(); |
| T* pT = static_cast<T*>(this); |
| pT->UpdateWindow(); |
| } |
| |
| if(m_hWndFocusSave != NULL) |
| ::SetFocus(m_hWndFocusSave); |
| |
| return 0; |
| } |
| |
| LRESULT OnKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled) |
| { |
| T* pT = static_cast<T*>(this); |
| if(::GetCapture() == pT->m_hWnd) |
| { |
| switch(wParam) |
| { |
| case VK_RETURN: |
| m_xySplitterPosNew = m_xySplitterPos; |
| // FALLTHROUGH |
| case VK_ESCAPE: |
| ::ReleaseCapture(); |
| break; |
| case VK_LEFT: |
| case VK_RIGHT: |
| if(m_bVertical) |
| { |
| POINT pt = {}; |
| ::GetCursorPos(&pt); |
| int xyPos = m_xySplitterPos + ((wParam == VK_LEFT) ? -pT->m_cxyStep : pT->m_cxyStep); |
| int cxyMax = m_rcSplitter.right - m_rcSplitter.left; |
| if(xyPos < (m_cxyMin + m_cxyBarEdge)) |
| xyPos = m_cxyMin; |
| else if(xyPos > (cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin)) |
| xyPos = cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin; |
| pt.x += xyPos - m_xySplitterPos; |
| ::SetCursorPos(pt.x, pt.y); |
| } |
| break; |
| case VK_UP: |
| case VK_DOWN: |
| if(!m_bVertical) |
| { |
| POINT pt = {}; |
| ::GetCursorPos(&pt); |
| int xyPos = m_xySplitterPos + ((wParam == VK_UP) ? -pT->m_cxyStep : pT->m_cxyStep); |
| int cxyMax = m_rcSplitter.bottom - m_rcSplitter.top; |
| if(xyPos < (m_cxyMin + m_cxyBarEdge)) |
| xyPos = m_cxyMin; |
| else if(xyPos > (cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin)) |
| xyPos = cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin; |
| pt.y += xyPos - m_xySplitterPos; |
| ::SetCursorPos(pt.x, pt.y); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| else |
| { |
| bHandled = FALSE; |
| } |
| |
| return 0; |
| } |
| |
| LRESULT OnSetFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM, BOOL& bHandled) |
| { |
| T* pT = static_cast<T*>(this); |
| if(::GetCapture() != pT->m_hWnd) |
| { |
| if(m_nSinglePane == SPLIT_PANE_NONE) |
| { |
| if((m_nDefActivePane == SPLIT_PANE_LEFT) || (m_nDefActivePane == SPLIT_PANE_RIGHT)) |
| ::SetFocus(m_hWndPane[m_nDefActivePane]); |
| } |
| else |
| { |
| ::SetFocus(m_hWndPane[m_nSinglePane]); |
| } |
| } |
| |
| bHandled = FALSE; |
| return 1; |
| } |
| |
| LRESULT OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/) |
| { |
| T* pT = static_cast<T*>(this); |
| LRESULT lRet = pT->DefWindowProc(uMsg, wParam, lParam); |
| if((lRet == MA_ACTIVATE) || (lRet == MA_ACTIVATEANDEAT)) |
| { |
| DWORD dwPos = ::GetMessagePos(); |
| POINT pt = { GET_X_LPARAM(dwPos), GET_Y_LPARAM(dwPos) }; |
| pT->ScreenToClient(&pt); |
| RECT rcPane = {}; |
| for(int nPane = 0; nPane < m_nPanesCount; nPane++) |
| { |
| if(GetSplitterPaneRect(nPane, &rcPane) && (::PtInRect(&rcPane, pt) != FALSE)) |
| { |
| m_nDefActivePane = nPane; |
| break; |
| } |
| } |
| } |
| |
| return lRet; |
| } |
| |
| LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) |
| { |
| T* pT = static_cast<T*>(this); |
| pT->GetSystemSettings(true); |
| |
| return 0; |
| } |
| |
| // Implementation - internal helpers |
| void Init() |
| { |
| m_hCursor = ::LoadCursor(NULL, m_bVertical ? IDC_SIZEWE : IDC_SIZENS); |
| |
| T* pT = static_cast<T*>(this); |
| pT->GetSystemSettings(false); |
| } |
| |
| void UpdateSplitterLayout() |
| { |
| if((m_nSinglePane == SPLIT_PANE_NONE) && (m_xySplitterPos == -1)) |
| return; |
| |
| T* pT = static_cast<T*>(this); |
| RECT rect = {}; |
| if(m_nSinglePane == SPLIT_PANE_NONE) |
| { |
| if(GetSplitterBarRect(&rect)) |
| pT->InvalidateRect(&rect); |
| |
| for(int nPane = 0; nPane < m_nPanesCount; nPane++) |
| { |
| if(GetSplitterPaneRect(nPane, &rect)) |
| { |
| if(m_hWndPane[nPane] != NULL) |
| ::SetWindowPos(m_hWndPane[nPane], NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER); |
| else |
| pT->InvalidateRect(&rect); |
| } |
| } |
| } |
| else |
| { |
| if(GetSplitterPaneRect(m_nSinglePane, &rect)) |
| { |
| if(m_hWndPane[m_nSinglePane] != NULL) |
| ::SetWindowPos(m_hWndPane[m_nSinglePane], NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER); |
| else |
| pT->InvalidateRect(&rect); |
| } |
| } |
| } |
| |
| bool GetSplitterBarRect(LPRECT lpRect) const |
| { |
| ATLASSERT(lpRect != NULL); |
| if((m_nSinglePane != SPLIT_PANE_NONE) || (m_xySplitterPos == -1)) |
| return false; |
| |
| if(m_bVertical) |
| { |
| lpRect->left = m_rcSplitter.left + m_xySplitterPos; |
| lpRect->top = m_rcSplitter.top; |
| lpRect->right = m_rcSplitter.left + m_xySplitterPos + m_cxySplitBar + m_cxyBarEdge; |
| lpRect->bottom = m_rcSplitter.bottom; |
| } |
| else |
| { |
| lpRect->left = m_rcSplitter.left; |
| lpRect->top = m_rcSplitter.top + m_xySplitterPos; |
| lpRect->right = m_rcSplitter.right; |
| lpRect->bottom = m_rcSplitter.top + m_xySplitterPos + m_cxySplitBar + m_cxyBarEdge; |
| } |
| |
| return true; |
| } |
| |
| bool GetSplitterPaneRect(int nPane, LPRECT lpRect) const |
| { |
| ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT)); |
| ATLASSERT(lpRect != NULL); |
| bool bRet = true; |
| if(m_nSinglePane != SPLIT_PANE_NONE) |
| { |
| if(nPane == m_nSinglePane) |
| *lpRect = m_rcSplitter; |
| else |
| bRet = false; |
| } |
| else if(nPane == SPLIT_PANE_LEFT) |
| { |
| if(m_bVertical) |
| { |
| lpRect->left = m_rcSplitter.left; |
| lpRect->top = m_rcSplitter.top; |
| lpRect->right = m_rcSplitter.left + m_xySplitterPos; |
| lpRect->bottom = m_rcSplitter.bottom; |
| } |
| else |
| { |
| lpRect->left = m_rcSplitter.left; |
| lpRect->top = m_rcSplitter.top; |
| lpRect->right = m_rcSplitter.right; |
| lpRect->bottom = m_rcSplitter.top + m_xySplitterPos; |
| } |
| } |
| else if(nPane == SPLIT_PANE_RIGHT) |
| { |
| if(m_bVertical) |
| { |
| lpRect->left = m_rcSplitter.left + m_xySplitterPos + m_cxySplitBar + m_cxyBarEdge; |
| lpRect->top = m_rcSplitter.top; |
| lpRect->right = m_rcSplitter.right; |
| lpRect->bottom = m_rcSplitter.bottom; |
| } |
| else |
| { |
| lpRect->left = m_rcSplitter.left; |
| lpRect->top = m_rcSplitter.top + m_xySplitterPos + m_cxySplitBar + m_cxyBarEdge; |
| lpRect->right = m_rcSplitter.right; |
| lpRect->bottom = m_rcSplitter.bottom; |
| } |
| } |
| else |
| { |
| bRet = false; |
| } |
| |
| return bRet; |
| } |
| |
| bool IsOverSplitterRect(int x, int y) const |
| { |
| // -1 == don't check |
| return (((x == -1) || ((x >= m_rcSplitter.left) && (x <= m_rcSplitter.right))) && |
| ((y == -1) || ((y >= m_rcSplitter.top) && (y <= m_rcSplitter.bottom)))); |
| } |
| |
| bool IsOverSplitterBar(int x, int y) const |
| { |
| if(m_nSinglePane != SPLIT_PANE_NONE) |
| return false; |
| if((m_xySplitterPos == -1) || !IsOverSplitterRect(x, y)) |
| return false; |
| int xy = m_bVertical ? x : y; |
| int xyOff = m_bVertical ? m_rcSplitter.left : m_rcSplitter.top; |
| |
| return ((xy >= (xyOff + m_xySplitterPos)) && (xy < (xyOff + m_xySplitterPos + m_cxySplitBar + m_cxyBarEdge))); |
| } |
| |
| void DrawGhostBar() |
| { |
| RECT rect = {}; |
| if(GetSplitterBarRect(&rect)) |
| { |
| // convert client to window coordinates |
| T* pT = static_cast<T*>(this); |
| RECT rcWnd = {}; |
| pT->GetWindowRect(&rcWnd); |
| ::MapWindowPoints(NULL, pT->m_hWnd, (LPPOINT)&rcWnd, 2); |
| ::OffsetRect(&rect, -rcWnd.left, -rcWnd.top); |
| |
| // invert the brush pattern (looks just like frame window sizing) |
| CWindowDC dc(pT->m_hWnd); |
| CBrush brush(CDCHandle::GetHalftoneBrush()); |
| if(brush.m_hBrush != NULL) |
| { |
| CBrushHandle brushOld = dc.SelectBrush(brush); |
| dc.PatBlt(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, PATINVERT); |
| dc.SelectBrush(brushOld); |
| } |
| } |
| } |
| |
| void GetSystemSettings(bool bUpdate) |
| { |
| if((m_dwExtendedStyle & SPLIT_FIXEDBARSIZE) == 0) |
| { |
| m_cxySplitBar = ::GetSystemMetrics(m_bVertical ? SM_CXSIZEFRAME : SM_CYSIZEFRAME); |
| } |
| |
| T* pT = static_cast<T*>(this); |
| if((pT->GetExStyle() & WS_EX_CLIENTEDGE) != 0) |
| { |
| m_cxyBarEdge = 2 * ::GetSystemMetrics(m_bVertical ? SM_CXEDGE : SM_CYEDGE); |
| m_cxyMin = 0; |
| } |
| else |
| { |
| m_cxyBarEdge = 0; |
| m_cxyMin = 2 * ::GetSystemMetrics(m_bVertical ? SM_CXEDGE : SM_CYEDGE); |
| } |
| |
| ::SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, &m_bFullDrag, 0); |
| |
| if(bUpdate) |
| UpdateSplitterLayout(); |
| } |
| |
| bool IsProportional() const |
| { |
| return ((m_dwExtendedStyle & SPLIT_PROPORTIONAL) != 0); |
| } |
| |
| void StoreProportionalPos() |
| { |
| int cxyTotal = m_bVertical ? (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) : (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge); |
| if(cxyTotal > 0) |
| m_nProportionalPos = ::MulDiv(m_xySplitterPos, m_nPropMax, cxyTotal); |
| else |
| m_nProportionalPos = 0; |
| ATLTRACE2(atlTraceUI, 0, _T("CSplitterImpl::StoreProportionalPos - %i\n"), m_nProportionalPos); |
| } |
| |
| void UpdateProportionalPos() |
| { |
| int cxyTotal = m_bVertical ? (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) : (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge); |
| if(cxyTotal > 0) |
| { |
| int xyNewPos = ::MulDiv(m_nProportionalPos, cxyTotal, m_nPropMax); |
| m_bUpdateProportionalPos = false; |
| T* pT = static_cast<T*>(this); |
| pT->SetSplitterPos(xyNewPos, false); |
| } |
| } |
| |
| bool IsRightAligned() const |
| { |
| return ((m_dwExtendedStyle & SPLIT_RIGHTALIGNED) != 0); |
| } |
| |
| void StoreRightAlignPos() |
| { |
| int cxyTotal = m_bVertical ? (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) : (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge); |
| if(cxyTotal > 0) |
| m_nProportionalPos = cxyTotal - m_xySplitterPos; |
| else |
| m_nProportionalPos = 0; |
| ATLTRACE2(atlTraceUI, 0, _T("CSplitterImpl::StoreRightAlignPos - %i\n"), m_nProportionalPos); |
| } |
| |
| void UpdateRightAlignPos() |
| { |
| int cxyTotal = m_bVertical ? (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) : (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge); |
| if(cxyTotal > 0) |
| { |
| m_bUpdateProportionalPos = false; |
| T* pT = static_cast<T*>(this); |
| pT->SetSplitterPos(cxyTotal - m_nProportionalPos, false); |
| } |
| } |
| |
| bool IsInteractive() const |
| { |
| return ((m_dwExtendedStyle & SPLIT_NONINTERACTIVE) == 0); |
| } |
| }; |
| |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // CSplitterWindowImpl - Implements a splitter window |
| |
| template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits> |
| class ATL_NO_VTABLE CSplitterWindowImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >, public CSplitterImpl< T > |
| { |
| public: |
| DECLARE_WND_CLASS_EX2(NULL, T, CS_DBLCLKS, COLOR_WINDOW) |
| |
| CSplitterWindowImpl(bool bVertical = true) : CSplitterImpl< T >(bVertical) |
| { } |
| |
| BOOL SubclassWindow(HWND hWnd) |
| { |
| BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd); |
| if(bRet != FALSE) |
| { |
| T* pT = static_cast<T*>(this); |
| pT->Init(); |
| |
| this->SetSplitterRect(); |
| } |
| |
| return bRet; |
| } |
| |
| BEGIN_MSG_MAP(CSplitterWindowImpl) |
| MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground) |
| MESSAGE_HANDLER(WM_SIZE, OnSize) |
| CHAIN_MSG_MAP(CSplitterImpl< T >) |
| FORWARD_NOTIFICATIONS() |
| END_MSG_MAP() |
| |
| LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) |
| { |
| // handled, no background painting needed |
| return 1; |
| } |
| |
| LRESULT OnSize(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled) |
| { |
| if(wParam != SIZE_MINIMIZED) |
| this->SetSplitterRect(); |
| |
| bHandled = FALSE; |
| return 1; |
| } |
| }; |
| |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // CSplitterWindow/CHorSplitterWindow - Implements splitter windows to be used as is |
| |
| template <bool t_bVertical = true> |
| class CSplitterWindowT : public CSplitterWindowImpl<CSplitterWindowT<t_bVertical> > |
| { |
| public: |
| DECLARE_WND_CLASS_EX2(_T("WTL_SplitterWindow"), CSplitterWindowT<t_bVertical>, CS_DBLCLKS, COLOR_WINDOW) |
| |
| CSplitterWindowT() : CSplitterWindowImpl<CSplitterWindowT<t_bVertical> >(t_bVertical) |
| { } |
| }; |
| |
| typedef CSplitterWindowT<true> CSplitterWindow; |
| typedef CSplitterWindowT<false> CHorSplitterWindow; |
| |
| } // namespace WTL |
| |
| #endif // __ATLSPLIT_H__ |