1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: wxSplitterWindow implementation
4 // Author: Julian Smart
8 // Copyright: (c) Julian Smart and Markus Holzem
9 // Licence: wxWindows license
10 /////////////////////////////////////////////////////////////////////////////
13 #pragma implementation "splitter.h"
16 // For compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
29 #include "wx/string.h"
30 #include "wx/splitter.h"
31 #include "wx/dcscreen.h"
33 #if !USE_SHARED_LIBRARY
34 IMPLEMENT_DYNAMIC_CLASS(wxSplitterWindow
, wxWindow
)
35 IMPLEMENT_DYNAMIC_CLASS(wxSplitterEvent
, wxCommandEvent
)
37 BEGIN_EVENT_TABLE(wxSplitterWindow
, wxWindow
)
38 EVT_PAINT(wxSplitterWindow::OnPaint
)
39 EVT_SIZE(wxSplitterWindow::OnSize
)
40 EVT_MOUSE_EVENTS(wxSplitterWindow::OnMouseEvent
)
42 EVT_SPLITTER_SASH_POS_CHANGED(-1, wxSplitterWindow::OnSashPosChanged
)
43 EVT_SPLITTER_DCLICK(-1, wxSplitterWindow::OnDoubleClick
)
44 EVT_SPLITTER_UNSPLIT(-1, wxSplitterWindow::OnUnsplitEvent
)
48 wxSplitterWindow::wxSplitterWindow()
50 m_splitMode
= wxSPLIT_VERTICAL
;
51 m_windowOne
= (wxWindow
*) NULL
;
52 m_windowTwo
= (wxWindow
*) NULL
;
53 m_dragMode
= wxSPLIT_DRAG_NONE
;
61 m_sashCursorWE
= (wxCursor
*) NULL
;
62 m_sashCursorNS
= (wxCursor
*) NULL
;
63 m_sashTrackerPen
= (wxPen
*) NULL
;
64 m_lightShadowPen
= (wxPen
*) NULL
;
65 m_mediumShadowPen
= (wxPen
*) NULL
;
66 m_darkShadowPen
= (wxPen
*) NULL
;
67 m_faceBrush
= (wxBrush
*) NULL
;
68 m_facePen
= (wxPen
*) NULL
;
69 m_hilightPen
= (wxPen
*) NULL
;
70 m_minimumPaneSize
= 0;
73 wxSplitterWindow::wxSplitterWindow(wxWindow
*parent
, wxWindowID id
,
78 : wxWindow(parent
, id
, pos
, size
, style
, name
)
80 m_splitMode
= wxSPLIT_VERTICAL
;
81 m_windowOne
= (wxWindow
*) NULL
;
82 m_windowTwo
= (wxWindow
*) NULL
;
83 m_dragMode
= wxSPLIT_DRAG_NONE
;
91 m_minimumPaneSize
= 0;
92 m_sashCursorWE
= new wxCursor(wxCURSOR_SIZEWE
);
93 m_sashCursorNS
= new wxCursor(wxCURSOR_SIZENS
);
94 m_sashTrackerPen
= new wxPen(*wxBLACK
, 2, wxSOLID
);
95 m_lightShadowPen
= (wxPen
*) NULL
;
96 m_mediumShadowPen
= (wxPen
*) NULL
;
97 m_darkShadowPen
= (wxPen
*) NULL
;
98 m_faceBrush
= (wxBrush
*) NULL
;
99 m_facePen
= (wxPen
*) NULL
;
100 m_hilightPen
= (wxPen
*) NULL
;
102 if ( style
& wxSP_3D
)
107 else if ( style
& wxSP_BORDER
)
118 // Eventually, we'll respond to colour change messages
121 // For debugging purposes, to see the background.
122 // SetBackground(wxBLUE_BRUSH);
125 wxSplitterWindow::~wxSplitterWindow()
127 delete m_sashCursorWE
;
128 delete m_sashCursorNS
;
129 delete m_sashTrackerPen
;
130 delete m_lightShadowPen
;
131 delete m_darkShadowPen
;
132 delete m_mediumShadowPen
;
138 void wxSplitterWindow::OnPaint(wxPaintEvent
& WXUNUSED(event
))
142 if ( m_borderSize
> 0 )
148 void wxSplitterWindow::OnMouseEvent(wxMouseEvent
& event
)
150 long x
= event
.GetX();
151 long y
= event
.GetY();
155 SetCursor(* wxSTANDARD_CURSOR
);
158 SetCursor(wxCursor());
161 // under wxGTK the method above causes the mouse
162 // to flicker so we set the standard cursor only
163 // when leaving the window and when moving over
164 // non-sash parts of the window. this should work
165 // on the other platforms as well, but who knows.
168 SetCursor(* wxSTANDARD_CURSOR
);
171 if (event
.LeftDown())
173 if ( SashHitTest(x
, y
) )
177 m_dragMode
= wxSPLIT_DRAG_DRAGGING
;
179 DrawSashTracker(x
, y
);
185 else if (event
.LeftUp() && m_dragMode
== wxSPLIT_DRAG_DRAGGING
)
187 // We can stop dragging now and see what we've got.
188 m_dragMode
= wxSPLIT_DRAG_NONE
;
192 DrawSashTracker(m_oldX
, m_oldY
);
194 // Obtain window size. We are only interested in the dimension the sash
197 GetClientSize(&w
, &h
);
198 int window_size
= (m_splitMode
== wxSPLIT_VERTICAL
? w
: h
);
199 int new_sash_position
=
200 (int) ( m_splitMode
== wxSPLIT_VERTICAL
? x
: y
);
202 wxSplitterEvent
eventSplitter(wxEVT_COMMAND_SPLITTER_SASH_POS_CHANGED
,
204 eventSplitter
.m_data
.pos
= new_sash_position
;
205 if ( GetEventHandler()->ProcessEvent(eventSplitter
) )
207 new_sash_position
= eventSplitter
.GetSashPosition();
208 if ( new_sash_position
== -1 )
210 // change not allowed
215 if ( new_sash_position
== 0 )
217 // We remove the first window from the view
218 wxWindow
*removedWindow
= m_windowOne
;
219 m_windowOne
= m_windowTwo
;
220 m_windowTwo
= (wxWindow
*) NULL
;
221 SendUnsplitEvent(removedWindow
);
224 else if ( new_sash_position
== window_size
)
226 // We remove the second window from the view
227 wxWindow
*removedWindow
= m_windowTwo
;
228 m_windowTwo
= (wxWindow
*) NULL
;
229 SendUnsplitEvent(removedWindow
);
234 m_sashPosition
= new_sash_position
;
238 } // left up && dragging
239 else if (event
.Moving() && !event
.Dragging())
241 // Just change the cursor if required
242 if ( SashHitTest(x
, y
) )
244 if ( m_splitMode
== wxSPLIT_VERTICAL
)
246 SetCursor(*m_sashCursorWE
);
250 SetCursor(*m_sashCursorNS
);
256 // where else do we unset the cursor?
257 SetCursor(* wxSTANDARD_CURSOR
);
261 else if (event
.Dragging() && (m_dragMode
== wxSPLIT_DRAG_DRAGGING
))
264 DrawSashTracker(m_oldX
, m_oldY
);
267 // As we captured the mouse, we may get the mouse events from outside
268 // our window - for example, negative values in x, y. This has a weird
269 // consequence under MSW where we use unsigned values sometimes and
270 // signed ones other times: the coordinates turn as big positive
271 // numbers and so the sash is drawn on the *right* side of the window
272 // instead of the left (or bottom instead of top). Correct this.
276 if ( (short)m_oldX
< 0 )
278 if ( (short)m_oldY
< 0 )
283 DrawSashTracker(m_oldX
, m_oldY
);
285 else if ( event
.LeftDClick() )
287 wxSplitterEvent
eventSplitter(wxEVT_COMMAND_SPLITTER_DOUBLECLICKED
,
289 eventSplitter
.m_data
.pt
.x
= x
;
290 eventSplitter
.m_data
.pt
.y
= y
;
292 (void)GetEventHandler()->ProcessEvent(eventSplitter
);
296 void wxSplitterWindow::OnSize(wxSizeEvent
& WXUNUSED(event
))
299 GetClientSize( &cw
, &ch
);
302 if ( m_splitMode
== wxSPLIT_VERTICAL
)
304 if ( m_sashPosition
>= (cw
- 5) )
305 m_sashPosition
= wxMax(10, cw
- 40);
307 if ( m_splitMode
== wxSPLIT_HORIZONTAL
)
309 if ( m_sashPosition
>= (ch
- 5) )
310 m_sashPosition
= wxMax(10, ch
- 40);
316 bool wxSplitterWindow::SashHitTest(int x
, int y
, int tolerance
)
318 if ( m_windowTwo
== NULL
|| m_sashPosition
== 0)
319 return FALSE
; // No sash
321 if ( m_splitMode
== wxSPLIT_VERTICAL
)
323 if ( (x
>= m_sashPosition
- tolerance
) && (x
<= m_sashPosition
+ m_sashSize
+ tolerance
) )
330 if ( (y
>= (m_sashPosition
- tolerance
)) && (y
<= (m_sashPosition
+ m_sashSize
+ tolerance
)) )
339 // Draw 3D effect borders
340 void wxSplitterWindow::DrawBorders(wxDC
& dc
)
343 GetClientSize(&w
, &h
);
345 if ( GetWindowStyleFlag() & wxSP_3D
)
347 dc
.SetPen(*m_mediumShadowPen
);
348 dc
.DrawLine(0, 0, w
-1, 0);
349 dc
.DrawLine(0, 0, 0, h
- 1);
351 dc
.SetPen(*m_darkShadowPen
);
352 dc
.DrawLine(1, 1, w
-2, 1);
353 dc
.DrawLine(1, 1, 1, h
-2);
355 dc
.SetPen(*m_hilightPen
);
356 dc
.DrawLine(0, h
-1, w
-1, h
-1);
357 dc
.DrawLine(w
-1, 0, w
-1, h
); // Surely the maximum y pos. should be h - 1.
358 /// Anyway, h is required for MSW.
360 dc
.SetPen(*m_lightShadowPen
);
361 dc
.DrawLine(w
-2, 1, w
-2, h
-2); // Right hand side
362 dc
.DrawLine(1, h
-2, w
-1, h
-2); // Bottom
364 else if ( GetWindowStyleFlag() & wxSP_BORDER
)
366 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
367 dc
.SetPen(*wxBLACK_PEN
);
368 dc
.DrawRectangle(0, 0, w
-1, h
-1);
371 dc
.SetPen(wxNullPen
);
372 dc
.SetBrush(wxNullBrush
);
376 void wxSplitterWindow::DrawSash(wxDC
& dc
)
378 if ( m_sashPosition
== 0 || !m_windowTwo
)
382 GetClientSize(&w
, &h
);
384 if ( GetWindowStyleFlag() & wxSP_3D
)
386 if ( m_splitMode
== wxSPLIT_VERTICAL
)
388 dc
.SetPen(*m_facePen
);
389 dc
.SetBrush(*m_faceBrush
);
390 dc
.DrawRectangle(m_sashPosition
+ 2, 0, m_sashSize
- 4, h
);
392 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
394 dc
.SetPen(*m_lightShadowPen
);
395 dc
.DrawLine(m_sashPosition
, 1, m_sashPosition
, h
-2);
397 dc
.SetPen(*m_hilightPen
);
398 dc
.DrawLine(m_sashPosition
+1, 0, m_sashPosition
+1, h
);
400 dc
.SetPen(*m_mediumShadowPen
);
401 dc
.DrawLine(m_sashPosition
+m_sashSize
-2, 1, m_sashPosition
+m_sashSize
-2, h
-1);
403 dc
.SetPen(*m_darkShadowPen
);
404 dc
.DrawLine(m_sashPosition
+m_sashSize
-1, 2, m_sashPosition
+m_sashSize
-1, h
-2);
408 dc
.SetPen(*m_facePen
);
409 dc
.SetBrush(*m_faceBrush
);
410 dc
.DrawRectangle(0, m_sashPosition
+ 2, w
, m_sashSize
- 4);
412 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
414 dc
.SetPen(*m_lightShadowPen
);
415 dc
.DrawLine(1, m_sashPosition
, w
-2, m_sashPosition
);
417 dc
.SetPen(*m_hilightPen
);
418 dc
.DrawLine(0, m_sashPosition
+1, w
, m_sashPosition
+1);
420 dc
.SetPen(*m_mediumShadowPen
);
421 dc
.DrawLine(1, m_sashPosition
+m_sashSize
-2, w
-1, m_sashPosition
+m_sashSize
-2);
423 dc
.SetPen(*m_darkShadowPen
);
424 dc
.DrawLine(2, m_sashPosition
+m_sashSize
-1, w
-2, m_sashPosition
+m_sashSize
-1);
429 if ( m_splitMode
== wxSPLIT_VERTICAL
)
431 dc
.SetPen(*wxBLACK_PEN
);
432 dc
.SetBrush(*wxBLACK_BRUSH
);
434 if ( (GetWindowStyleFlag() & wxSP_BORDER
) != wxSP_BORDER
)
435 h1
+= 1; // Not sure why this is necessary...
436 dc
.DrawRectangle(m_sashPosition
, 0, m_sashSize
, h1
);
440 dc
.SetPen(*wxBLACK_PEN
);
441 dc
.SetBrush(*wxBLACK_BRUSH
);
443 if ( (GetWindowStyleFlag() & wxSP_BORDER
) != wxSP_BORDER
)
446 dc
.DrawRectangle(0, m_sashPosition
, w1
, m_sashSize
);
451 dc
.SetPen(wxNullPen
);
452 dc
.SetBrush(wxNullBrush
);
455 // Draw the sash tracker (for whilst moving the sash)
456 void wxSplitterWindow::DrawSashTracker(int x
, int y
)
459 GetClientSize(&w
, &h
);
465 if ( m_splitMode
== wxSPLIT_VERTICAL
)
496 ClientToScreen(&x1
, &y1
);
497 ClientToScreen(&x2
, &y2
);
499 screenDC
.SetLogicalFunction(wxXOR
);
500 screenDC
.SetPen(*m_sashTrackerPen
);
501 screenDC
.SetBrush(*wxTRANSPARENT_BRUSH
);
503 screenDC
.DrawLine(x1
, y1
, x2
, y2
);
505 screenDC
.SetLogicalFunction(wxCOPY
);
507 screenDC
.SetPen(wxNullPen
);
508 screenDC
.SetBrush(wxNullBrush
);
511 // Position and size subwindows.
512 // Note that the border size applies to each subwindow, not
513 // including the edges next to the sash.
514 void wxSplitterWindow::SizeWindows()
517 GetClientSize(&w
, &h
);
519 if ( m_windowOne
&& !m_windowTwo
)
521 m_windowOne
->SetSize(m_borderSize
, m_borderSize
, w
- 2*m_borderSize
, h
- 2*m_borderSize
);
523 if (m_windowOne
->GetAutoLayout())
524 m_windowOne
->Layout();
526 else if ( m_windowOne
&& m_windowTwo
)
528 if (m_splitMode
== wxSPLIT_VERTICAL
)
530 int x1
= m_borderSize
;
531 int y1
= m_borderSize
;
532 int w1
= m_sashPosition
- m_borderSize
;
533 int h1
= h
- 2*m_borderSize
;
535 int x2
= m_sashPosition
+ m_sashSize
;
536 int y2
= m_borderSize
;
537 int w2
= w
- 2*m_borderSize
- m_sashSize
- w1
;
538 int h2
= h
- 2*m_borderSize
;
540 m_windowOne
->SetSize(x1
, y1
, w1
, h1
);
541 m_windowTwo
->SetSize(x2
, y2
, w2
, h2
);
543 if (m_windowOne
->GetAutoLayout())
544 m_windowOne
->Layout();
545 if (m_windowTwo
->GetAutoLayout())
546 m_windowTwo
->Layout();
550 m_windowOne
->SetSize(m_borderSize
, m_borderSize
,
551 w
- 2*m_borderSize
, m_sashPosition
- m_borderSize
);
552 m_windowTwo
->SetSize(m_borderSize
, m_sashPosition
+ m_sashSize
,
553 w
- 2*m_borderSize
, h
- 2*m_borderSize
- m_sashSize
- (m_sashPosition
- m_borderSize
));
555 if (m_windowOne
->GetAutoLayout())
556 m_windowOne
->Layout();
557 if (m_windowTwo
->GetAutoLayout())
558 m_windowTwo
->Layout();
566 // Set pane for unsplit window
567 void wxSplitterWindow::Initialize(wxWindow
*window
)
569 m_windowOne
= window
;
570 m_windowTwo
= (wxWindow
*) NULL
;
574 // Associates the given window with window 2, drawing the appropriate sash
575 // and changing the split mode.
576 // Does nothing and returns FALSE if the window is already split.
577 bool wxSplitterWindow::SplitVertically(wxWindow
*window1
, wxWindow
*window2
, int sashPosition
)
583 GetClientSize(&w
, &h
);
585 m_splitMode
= wxSPLIT_VERTICAL
;
586 m_windowOne
= window1
;
587 m_windowTwo
= window2
;
588 if ( sashPosition
> 0 )
589 m_sashPosition
= sashPosition
;
590 else if ( sashPosition
< 0 )
591 m_sashPosition
= w
- sashPosition
;
593 m_sashPosition
= w
/2;
600 bool wxSplitterWindow::SplitHorizontally(wxWindow
*window1
, wxWindow
*window2
, int sashPosition
)
606 GetClientSize(&w
, &h
);
608 m_splitMode
= wxSPLIT_HORIZONTAL
;
609 m_windowOne
= window1
;
610 m_windowTwo
= window2
;
611 if ( sashPosition
> 0 )
612 m_sashPosition
= sashPosition
;
613 else if ( sashPosition
< 0 )
614 m_sashPosition
= h
- sashPosition
;
616 m_sashPosition
= h
/2;
624 // Remove the specified (or second) window from the view
625 // Doesn't actually delete the window.
626 bool wxSplitterWindow::Unsplit(wxWindow
*toRemove
)
631 wxWindow
*win
= NULL
;
632 if ( toRemove
== NULL
|| toRemove
== m_windowTwo
)
635 m_windowTwo
= (wxWindow
*) NULL
;
637 else if ( toRemove
== m_windowOne
)
640 m_windowOne
= m_windowTwo
;
641 m_windowTwo
= (wxWindow
*) NULL
;
645 wxFAIL_MSG(_T("splitter: attempt to remove a non-existent window"));
650 SendUnsplitEvent(win
);
657 // Replace a window with another one
658 bool wxSplitterWindow::ReplaceWindow(wxWindow
*winOld
, wxWindow
*winNew
)
660 wxCHECK_MSG( winOld
, FALSE
, _T("use one of Split() functions instead") );
661 wxCHECK_MSG( winNew
, FALSE
, _T("use Unsplit() functions instead") );
663 if ( winOld
== m_windowTwo
)
665 m_windowTwo
= winNew
;
667 else if ( winOld
== m_windowOne
)
669 m_windowOne
= winNew
;
673 wxFAIL_MSG(_T("splitter: attempt to replace a non-existent window"));
683 void wxSplitterWindow::SetSashPosition(int position
, bool redraw
)
685 m_sashPosition
= position
;
693 // Initialize colours
694 void wxSplitterWindow::InitColours()
696 wxDELETE( m_facePen
);
697 wxDELETE( m_faceBrush
);
698 wxDELETE( m_mediumShadowPen
);
699 wxDELETE( m_darkShadowPen
);
700 wxDELETE( m_lightShadowPen
);
701 wxDELETE( m_hilightPen
);
704 #if defined(__WIN95__)
705 wxColour
faceColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DFACE
));
706 m_facePen
= new wxPen(faceColour
, 1, wxSOLID
);
707 m_faceBrush
= new wxBrush(faceColour
, wxSOLID
);
709 wxColour
mediumShadowColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DSHADOW
));
710 m_mediumShadowPen
= new wxPen(mediumShadowColour
, 1, wxSOLID
);
712 wxColour
darkShadowColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DDKSHADOW
));
713 m_darkShadowPen
= new wxPen(darkShadowColour
, 1, wxSOLID
);
715 wxColour
lightShadowColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DLIGHT
));
716 m_lightShadowPen
= new wxPen(lightShadowColour
, 1, wxSOLID
);
718 wxColour
hilightColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DHILIGHT
));
719 m_hilightPen
= new wxPen(hilightColour
, 1, wxSOLID
);
721 m_facePen
= new wxPen("LIGHT GREY", 1, wxSOLID
);
722 m_faceBrush
= new wxBrush("LIGHT GREY", wxSOLID
);
723 m_mediumShadowPen
= new wxPen("GREY", 1, wxSOLID
);
724 m_darkShadowPen
= new wxPen("BLACK", 1, wxSOLID
);
725 m_lightShadowPen
= new wxPen("LIGHT GREY", 1, wxSOLID
);
726 m_hilightPen
= new wxPen("WHITE", 1, wxSOLID
);
727 #endif // Win32/!Win32
730 void wxSplitterWindow::SendUnsplitEvent(wxWindow
*winRemoved
)
732 wxSplitterEvent
event(wxEVT_COMMAND_SPLITTER_UNSPLIT
, this);
733 event
.m_data
.win
= winRemoved
;
735 (void)GetEventHandler()->ProcessEvent(event
);
738 // ---------------------------------------------------------------------------
739 // splitter event handlers
740 // ---------------------------------------------------------------------------
742 void wxSplitterWindow::OnSashPosChanged(wxSplitterEvent
& event
)
744 // If within UNSPLIT_THRESHOLD from edge, set to edge to cause closure.
745 const int UNSPLIT_THRESHOLD
= 4;
747 int newSashPosition
= event
.GetSashPosition();
749 // Obtain relevant window dimension for bottom / right threshold check
751 GetClientSize(&w
, &h
);
752 int window_size
= (m_splitMode
== wxSPLIT_VERTICAL
) ? w
: h
;
754 if ( newSashPosition
<= UNSPLIT_THRESHOLD
)
756 // threshold top / left check
759 else if ( newSashPosition
>= window_size
- UNSPLIT_THRESHOLD
)
761 // threshold bottom/right check
762 newSashPosition
= window_size
;
765 // If resultant pane would be too small, enlarge it.
767 // Check upper / left pane
768 if ( newSashPosition
< m_minimumPaneSize
)
769 newSashPosition
= m_minimumPaneSize
;
771 // Check lower / right pane (check even if sash was just adjusted)
772 if ( newSashPosition
> window_size
- m_minimumPaneSize
)
773 newSashPosition
= window_size
- m_minimumPaneSize
;
775 // If the result is out of bounds it means minimum size is too big,
776 // so split window in half as best compromise.
777 if ( newSashPosition
< 0 || newSashPosition
> window_size
)
778 newSashPosition
= window_size
/ 2;
780 // for compatibility, call the virtual function
781 if ( !OnSashPositionChange(newSashPosition
) )
783 newSashPosition
= -1;
786 event
.SetSashPosition(newSashPosition
);
789 // Called when the sash is double-clicked. The default behaviour is to remove
790 // the sash if the minimum pane size is zero.
791 void wxSplitterWindow::OnDoubleClick(wxSplitterEvent
& event
)
793 // for compatibility, call the virtual function
794 OnDoubleClickSash(event
.GetX(), event
.GetY());
796 if ( GetMinimumPaneSize() == 0 )
802 void wxSplitterWindow::OnUnsplitEvent(wxSplitterEvent
& event
)
804 wxWindow
*win
= event
.GetWindowBeingRemoved();
806 // for compatibility, call the virtual function