1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/sashwin.cpp
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"
23 #include "wx/sashwin.h"
26 #include "wx/dialog.h"
28 #include "wx/settings.h"
29 #include "wx/dcclient.h"
30 #include "wx/dcscreen.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
)
49 #endif // __WXMSW__ || __WXMAC__
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()
706 #endif // __WXMSW__ || __WXMAC__