1 ///////////////////////////////////////////////////////////////////////////// 
   3 // Purpose:     wxSashWindow implementation. A sash window has an optional 
   4 //              sash on each edge, allowing it to be dragged. An event 
   5 //              is generated when the sash is released. 
   6 // Author:      Julian Smart 
  10 // Copyright:   (c) Julian Smart 
  11 // Licence:     wxWindows licence 
  12 ///////////////////////////////////////////////////////////////////////////// 
  14 // For compilers that support precompilation, includes "wx.h". 
  15 #include "wx/wxprec.h" 
  24     #include "wx/dialog.h" 
  26     #include "wx/settings.h" 
  33 #include "wx/dcscreen.h" 
  34 #include "wx/dcclient.h" 
  35 #include "wx/sashwin.h" 
  36 #include "wx/laywin.h" 
  38 DEFINE_EVENT_TYPE(wxEVT_SASH_DRAGGED
) 
  40 IMPLEMENT_DYNAMIC_CLASS(wxSashWindow
, wxWindow
) 
  41 IMPLEMENT_DYNAMIC_CLASS(wxSashEvent
, wxCommandEvent
) 
  43 BEGIN_EVENT_TABLE(wxSashWindow
, wxWindow
) 
  44     EVT_PAINT(wxSashWindow::OnPaint
) 
  45     EVT_SIZE(wxSashWindow::OnSize
) 
  46     EVT_MOUSE_EVENTS(wxSashWindow::OnMouseEvent
) 
  47 #if defined( __WXMSW__ ) || defined( __WXMAC__) 
  48     EVT_SET_CURSOR(wxSashWindow::OnSetCursor
) 
  53 bool wxSashWindow::Create(wxWindow 
*parent
, wxWindowID id
, const wxPoint
& pos
, 
  54     const wxSize
& size
, long style
, const wxString
& name
) 
  56     return wxWindow::Create(parent
, id
, pos
, size
, style
, name
); 
  59 wxSashWindow::~wxSashWindow() 
  61     delete m_sashCursorWE
; 
  62     delete m_sashCursorNS
; 
  65 void wxSashWindow::Init() 
  67     m_draggingEdge 
= wxSASH_NONE
; 
  68     m_dragMode 
= wxSASH_DRAG_NONE
; 
  74     m_extraBorderSize 
= 0; 
  75     m_minimumPaneSizeX 
= 0; 
  76     m_minimumPaneSizeY 
= 0; 
  77     m_maximumPaneSizeX 
= 10000; 
  78     m_maximumPaneSizeY 
= 10000; 
  79     m_sashCursorWE 
= new wxCursor(wxCURSOR_SIZEWE
); 
  80     m_sashCursorNS 
= new wxCursor(wxCURSOR_SIZENS
); 
  81     m_mouseCaptured 
= false; 
  82     m_currentCursor 
= NULL
; 
  84     // Eventually, we'll respond to colour change messages 
  88 void wxSashWindow::OnPaint(wxPaintEvent
& WXUNUSED(event
)) 
  96 void wxSashWindow::OnMouseEvent(wxMouseEvent
& event
) 
  99     event
.GetPosition(&x
, &y
); 
 101     wxSashEdgePosition sashHit 
= SashHitTest(x
, y
); 
 103     if (event
.LeftDown()) 
 106         m_mouseCaptured 
= true; 
 108         if ( sashHit 
!= wxSASH_NONE 
) 
 110             // Required for X to specify that 
 111             // that we wish to draw on top of all windows 
 112             // - and we optimise by specifying the area 
 113             // for creating the overlap window. 
 114             // Find the first frame or dialog and use this to specify 
 115             // the area to draw on. 
 116             wxWindow
* parent 
= this; 
 118             while (parent 
&& !parent
->IsKindOf(CLASSINFO(wxDialog
)) && 
 119                              !parent
->IsKindOf(CLASSINFO(wxFrame
))) 
 120               parent 
= parent
->GetParent(); 
 122             wxScreenDC::StartDrawingOnTop(parent
); 
 124             // We don't say we're dragging yet; we leave that 
 125             // decision for the Dragging() branch, to ensure 
 126             // the user has dragged a little bit. 
 127             m_dragMode 
= wxSASH_DRAG_LEFT_DOWN
; 
 128             m_draggingEdge 
= sashHit
; 
 132             if ( (sashHit 
== wxSASH_LEFT
) || (sashHit 
== wxSASH_RIGHT
) ) 
 134                 if (m_currentCursor 
!= m_sashCursorWE
) 
 136                     SetCursor(*m_sashCursorWE
); 
 138                 m_currentCursor 
= m_sashCursorWE
; 
 142                 if (m_currentCursor 
!= m_sashCursorNS
) 
 144                     SetCursor(*m_sashCursorNS
); 
 146                 m_currentCursor 
= m_sashCursorNS
; 
 150     else if ( event
.LeftUp() && m_dragMode 
== wxSASH_DRAG_LEFT_DOWN 
) 
 152         // Wasn't a proper drag 
 155         m_mouseCaptured 
= false; 
 157         wxScreenDC::EndDrawingOnTop(); 
 158         m_dragMode 
= wxSASH_DRAG_NONE
; 
 159         m_draggingEdge 
= wxSASH_NONE
; 
 161     else if (event
.LeftUp() && m_dragMode 
== wxSASH_DRAG_DRAGGING
) 
 163         // We can stop dragging now and see what we've got. 
 164         m_dragMode 
= wxSASH_DRAG_NONE
; 
 167         m_mouseCaptured 
= false; 
 170         DrawSashTracker(m_draggingEdge
, m_oldX
, m_oldY
); 
 172         // End drawing on top (frees the window used for drawing 
 174         wxScreenDC::EndDrawingOnTop(); 
 179         GetPosition(&xp
, &yp
); 
 181         wxSashEdgePosition edge 
= m_draggingEdge
; 
 182         m_draggingEdge 
= wxSASH_NONE
; 
 185         wxSashDragStatus status 
= wxSASH_STATUS_OK
; 
 187         // the new height and width of the window - if -1, it didn't change 
 188         int newHeight 
= wxDefaultCoord
, 
 189             newWidth 
= wxDefaultCoord
; 
 191         // NB: x and y may be negative and they're relative to the sash window 
 192         //     upper left corner, while xp and yp are expressed in the parent 
 193         //     window system of coordinates, so adjust them! After this 
 194         //     adjustment, all coordinates are relative to the parent window. 
 203                     // top sash shouldn't get below the bottom one 
 204                     status 
= wxSASH_STATUS_OUT_OF_RANGE
; 
 208                     newHeight 
= h 
- (y 
- yp
); 
 215                     // bottom sash shouldn't get above the top one 
 216                     status 
= wxSASH_STATUS_OUT_OF_RANGE
; 
 227                     // left sash shouldn't get beyond the right one 
 228                     status 
= wxSASH_STATUS_OUT_OF_RANGE
; 
 232                     newWidth 
= w 
- (x 
- xp
); 
 239                     // and the right sash, finally, shouldn't be beyond the 
 241                     status 
= wxSASH_STATUS_OUT_OF_RANGE
; 
 250                 // can this happen at all? 
 254         if ( newHeight 
== wxDefaultCoord 
) 
 261             // make sure it's in m_minimumPaneSizeY..m_maximumPaneSizeY range 
 262             newHeight 
= wxMax(newHeight
, m_minimumPaneSizeY
); 
 263             newHeight 
= wxMin(newHeight
, m_maximumPaneSizeY
); 
 266         if ( newWidth 
== wxDefaultCoord 
) 
 273             // make sure it's in m_minimumPaneSizeY..m_maximumPaneSizeY range 
 274             newWidth 
= wxMax(newWidth
, m_minimumPaneSizeX
); 
 275             newWidth 
= wxMin(newWidth
, m_maximumPaneSizeX
); 
 278         dragRect 
= wxRect(x
, y
, newWidth
, newHeight
); 
 280         wxSashEvent 
eventSash(GetId(), edge
); 
 281         eventSash
.SetEventObject(this); 
 282         eventSash
.SetDragStatus(status
); 
 283         eventSash
.SetDragRect(dragRect
); 
 284         GetEventHandler()->ProcessEvent(eventSash
); 
 286     else if ( event
.LeftUp() ) 
 290         m_mouseCaptured 
= false; 
 292     else if (event
.Moving() && !event
.Dragging()) 
 294         // Just change the cursor if required 
 295         if ( sashHit 
!= wxSASH_NONE 
) 
 297             if ( (sashHit 
== wxSASH_LEFT
) || (sashHit 
== wxSASH_RIGHT
) ) 
 299                 if (m_currentCursor 
!= m_sashCursorWE
) 
 301                     SetCursor(*m_sashCursorWE
); 
 303                 m_currentCursor 
= m_sashCursorWE
; 
 307                 if (m_currentCursor 
!= m_sashCursorNS
) 
 309                     SetCursor(*m_sashCursorNS
); 
 311                 m_currentCursor 
= m_sashCursorNS
; 
 316             SetCursor(wxNullCursor
); 
 317             m_currentCursor 
= NULL
; 
 320     else if ( event
.Dragging() && 
 321               ((m_dragMode 
== wxSASH_DRAG_DRAGGING
) || 
 322                (m_dragMode 
== wxSASH_DRAG_LEFT_DOWN
)) ) 
 324         if ( (m_draggingEdge 
== wxSASH_LEFT
) || (m_draggingEdge 
== wxSASH_RIGHT
) ) 
 326             if (m_currentCursor 
!= m_sashCursorWE
) 
 328                 SetCursor(*m_sashCursorWE
); 
 330             m_currentCursor 
= m_sashCursorWE
; 
 334             if (m_currentCursor 
!= m_sashCursorNS
) 
 336                 SetCursor(*m_sashCursorNS
); 
 338             m_currentCursor 
= m_sashCursorNS
; 
 341         if (m_dragMode 
== wxSASH_DRAG_LEFT_DOWN
) 
 343             m_dragMode 
= wxSASH_DRAG_DRAGGING
; 
 344             DrawSashTracker(m_draggingEdge
, x
, y
); 
 348             if ( m_dragMode 
== wxSASH_DRAG_DRAGGING 
) 
 351                 DrawSashTracker(m_draggingEdge
, m_oldX
, m_oldY
); 
 354                 DrawSashTracker(m_draggingEdge
, x
, y
); 
 360     else if ( event
.LeftDClick() ) 
 369 void wxSashWindow::OnSize(wxSizeEvent
& WXUNUSED(event
)) 
 374 wxSashEdgePosition 
wxSashWindow::SashHitTest(int x
, int y
, int WXUNUSED(tolerance
)) 
 377     GetClientSize(& cx
, & cy
); 
 380     for (i 
= 0; i 
< 4; i
++) 
 382         wxSashEdge
& edge 
= m_sashes
[i
]; 
 383         wxSashEdgePosition position 
= (wxSashEdgePosition
) i 
; 
 391                     if (y 
>= 0 && y 
<= GetEdgeMargin(position
)) 
 397                     if ((x 
>= cx 
- GetEdgeMargin(position
)) && (x 
<= cx
)) 
 403                     if ((y 
>= cy 
- GetEdgeMargin(position
)) && (y 
<= cy
)) 
 404                         return wxSASH_BOTTOM
; 
 409                     if ((x 
<= GetEdgeMargin(position
)) && (x 
>= 0)) 
 423 // Draw 3D effect borders 
 424 void wxSashWindow::DrawBorders(wxDC
& dc
) 
 427     GetClientSize(&w
, &h
); 
 429     wxPen 
mediumShadowPen(m_mediumShadowColour
, 1, wxSOLID
); 
 430     wxPen 
darkShadowPen(m_darkShadowColour
, 1, wxSOLID
); 
 431     wxPen 
lightShadowPen(m_lightShadowColour
, 1, wxSOLID
); 
 432     wxPen 
hilightPen(m_hilightColour
, 1, wxSOLID
); 
 434     if ( GetWindowStyleFlag() & wxSW_3DBORDER 
) 
 436         dc
.SetPen(mediumShadowPen
); 
 437         dc
.DrawLine(0, 0, w
-1, 0); 
 438         dc
.DrawLine(0, 0, 0, h 
- 1); 
 440         dc
.SetPen(darkShadowPen
); 
 441         dc
.DrawLine(1, 1, w
-2, 1); 
 442         dc
.DrawLine(1, 1, 1, h
-2); 
 444         dc
.SetPen(hilightPen
); 
 445         dc
.DrawLine(0, h
-1, w
-1, h
-1); 
 446         dc
.DrawLine(w
-1, 0, w
-1, h
); // Surely the maximum y pos. should be h - 1. 
 447                                      /// Anyway, h is required for MSW. 
 449         dc
.SetPen(lightShadowPen
); 
 450         dc
.DrawLine(w
-2, 1, w
-2, h
-2); // Right hand side 
 451         dc
.DrawLine(1, h
-2, w
-1, h
-2);     // Bottom 
 453     else if ( GetWindowStyleFlag() & wxSW_BORDER 
) 
 455         dc
.SetBrush(*wxTRANSPARENT_BRUSH
); 
 456         dc
.SetPen(*wxBLACK_PEN
); 
 457         dc
.DrawRectangle(0, 0, w
-1, h
-1); 
 460     dc
.SetPen(wxNullPen
); 
 461     dc
.SetBrush(wxNullBrush
); 
 464 void wxSashWindow::DrawSashes(wxDC
& dc
) 
 467     for (i 
= 0; i 
< 4; i
++) 
 468         if (m_sashes
[i
].m_show
) 
 469             DrawSash((wxSashEdgePosition
) i
, dc
); 
 473 void wxSashWindow::DrawSash(wxSashEdgePosition edge
, wxDC
& dc
) 
 476     GetClientSize(&w
, &h
); 
 478     wxPen 
facePen(m_faceColour
, 1, wxSOLID
); 
 479     wxBrush 
faceBrush(m_faceColour
, wxSOLID
); 
 480     wxPen 
mediumShadowPen(m_mediumShadowColour
, 1, wxSOLID
); 
 481     wxPen 
darkShadowPen(m_darkShadowColour
, 1, wxSOLID
); 
 482     wxPen 
lightShadowPen(m_lightShadowColour
, 1, wxSOLID
); 
 483     wxPen 
hilightPen(m_hilightColour
, 1, wxSOLID
); 
 484     wxColour 
blackClr(0, 0, 0); 
 485     wxColour 
whiteClr(255, 255, 255); 
 486     wxPen 
blackPen(blackClr
, 1, wxSOLID
); 
 487     wxPen 
whitePen(whiteClr
, 1, wxSOLID
); 
 489     if ( edge 
== wxSASH_LEFT 
|| edge 
== wxSASH_RIGHT 
) 
 491         int sashPosition 
= (edge 
== wxSASH_LEFT
) ? 0 : ( w 
- GetEdgeMargin(edge
) ); 
 494         dc
.SetBrush(faceBrush
); 
 495         dc
.DrawRectangle(sashPosition
, 0, GetEdgeMargin(edge
), h
); 
 497         if (GetWindowStyleFlag() & wxSW_3DSASH
) 
 499             if (edge 
== wxSASH_LEFT
) 
 501                 // Draw a dark grey line on the left to indicate that the 
 503                 dc
.SetPen(mediumShadowPen
); 
 504                 dc
.DrawLine(GetEdgeMargin(edge
), 0, GetEdgeMargin(edge
), h
); 
 508                 // Draw a highlight line on the right to indicate that the 
 510                 dc
.SetPen(hilightPen
); 
 511                 dc
.DrawLine(w 
- GetEdgeMargin(edge
), 0, w 
- GetEdgeMargin(edge
), h
); 
 515     else // top or bottom 
 517         int sashPosition 
= (edge 
== wxSASH_TOP
) ? 0 : ( h 
- GetEdgeMargin(edge
) ); 
 520         dc
.SetBrush(faceBrush
); 
 521         dc
.DrawRectangle(0, sashPosition
, w
, GetEdgeMargin(edge
)); 
 523         if (GetWindowStyleFlag() & wxSW_3DSASH
) 
 525             if (edge 
== wxSASH_BOTTOM
) 
 527                 // Draw a highlight line on the bottom to indicate that the 
 529                 dc
.SetPen(hilightPen
); 
 530                 dc
.DrawLine(0, h 
- GetEdgeMargin(edge
), w
, h 
- GetEdgeMargin(edge
)); 
 534                 // Draw a drak grey line on the top to indicate that the 
 536                 dc
.SetPen(mediumShadowPen
); 
 537                 dc
.DrawLine(1, GetEdgeMargin(edge
), w
-1, GetEdgeMargin(edge
)); 
 542     dc
.SetPen(wxNullPen
); 
 543     dc
.SetBrush(wxNullBrush
); 
 546 // Draw the sash tracker (for whilst moving the sash) 
 547 void wxSashWindow::DrawSashTracker(wxSashEdgePosition edge
, int x
, int y
) 
 550     GetClientSize(&w
, &h
); 
 556     if ( edge 
== wxSASH_LEFT 
|| edge 
== wxSASH_RIGHT 
) 
 561         if ( (edge 
== wxSASH_LEFT
) && (x1 
> w
) ) 
 565         else if ( (edge 
== wxSASH_RIGHT
) && (x1 
< 0) ) 
 575         if ( (edge 
== wxSASH_TOP
) && (y1 
> h
) ) 
 580         else if ( (edge 
== wxSASH_BOTTOM
) && (y1 
< 0) ) 
 587     ClientToScreen(&x1
, &y1
); 
 588     ClientToScreen(&x2
, &y2
); 
 590     wxPen 
sashTrackerPen(*wxBLACK
, 2, wxSOLID
); 
 592     screenDC
.SetLogicalFunction(wxINVERT
); 
 593     screenDC
.SetPen(sashTrackerPen
); 
 594     screenDC
.SetBrush(*wxTRANSPARENT_BRUSH
); 
 596     screenDC
.DrawLine(x1
, y1
, x2
, y2
); 
 598     screenDC
.SetLogicalFunction(wxCOPY
); 
 600     screenDC
.SetPen(wxNullPen
); 
 601     screenDC
.SetBrush(wxNullBrush
); 
 604 // Position and size subwindows. 
 605 // Note that the border size applies to each subwindow, not 
 606 // including the edges next to the sash. 
 607 void wxSashWindow::SizeWindows() 
 610     GetClientSize(&cw
, &ch
); 
 612     if (GetChildren().GetCount() == 1) 
 614         wxWindow
* child 
= GetChildren().GetFirst()->GetData(); 
 622         if (m_sashes
[0].m_show
) 
 625             height 
-= m_borderSize
; 
 627         y 
+= m_extraBorderSize
; 
 630         if (m_sashes
[3].m_show
) 
 633             width 
-= m_borderSize
; 
 635         x 
+= m_extraBorderSize
; 
 638         if (m_sashes
[1].m_show
) 
 640             width 
-= m_borderSize
; 
 642         width 
-= 2*m_extraBorderSize
; 
 645         if (m_sashes
[2].m_show
) 
 647             height 
-= m_borderSize
; 
 649         height 
-= 2*m_extraBorderSize
; 
 651         child
->SetSize(x
, y
, width
, height
); 
 653     else if (GetChildren().GetCount() > 1) 
 655         // Perhaps multiple children are themselves sash windows. 
 656         // TODO: this doesn't really work because the subwindows sizes/positions 
 657         // must be set to leave a gap for the parent's sash (hit-test and decorations). 
 658         // Perhaps we can allow for this within LayoutWindow, testing whether the parent 
 659         // is a sash window, and if so, allowing some space for the edges. 
 660         wxLayoutAlgorithm layout
; 
 661         layout
.LayoutWindow(this); 
 669 // Initialize colours 
 670 void wxSashWindow::InitColours() 
 673     m_faceColour 
= wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE
); 
 674     m_mediumShadowColour 
= wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW
); 
 675     m_darkShadowColour 
= wxSystemSettings::GetColour(wxSYS_COLOUR_3DDKSHADOW
); 
 676     m_lightShadowColour 
= wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT
); 
 677     m_hilightColour 
= wxSystemSettings::GetColour(wxSYS_COLOUR_3DHILIGHT
); 
 680 void wxSashWindow::SetSashVisible(wxSashEdgePosition edge
, bool sash
) 
 682      m_sashes
[edge
].m_show 
= sash
; 
 684         m_sashes
[edge
].m_margin 
= m_borderSize
; 
 686         m_sashes
[edge
].m_margin 
= 0; 
 689 #if defined( __WXMSW__ ) || defined( __WXMAC__) 
 691 // this is currently called (and needed) under MSW only... 
 692 void wxSashWindow::OnSetCursor(wxSetCursorEvent
& event
) 
 694     // if we don't do it, the resizing cursor might be set for child window: 
 695     // and like this we explicitly say that our cursor should not be used for 
 696     // children windows which overlap us 
 698     if ( SashHitTest(event
.GetX(), event
.GetY()) != wxSASH_NONE
) 
 700         // default processing is ok 
 703     //else: do nothing, in particular, don't call Skip()