The rounded corners look really dumb at this size.
[wxWidgets.git] / src / generic / splitter.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/splitter.cpp
3 // Purpose: wxSplitterWindow implementation
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 01/02/97
7 // Copyright: (c) Julian Smart
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 // For compilers that support precompilation, includes "wx.h".
12 #include "wx/wxprec.h"
13
14 #ifdef __BORLANDC__
15 #pragma hdrstop
16 #endif
17
18 #if wxUSE_SPLITTER
19
20 #include "wx/splitter.h"
21
22 #ifndef WX_PRECOMP
23 #include "wx/string.h"
24 #include "wx/utils.h"
25 #include "wx/log.h"
26
27 #include "wx/dcclient.h"
28 #include "wx/dcscreen.h"
29
30 #include "wx/window.h"
31 #include "wx/dialog.h"
32 #include "wx/frame.h"
33
34 #include "wx/settings.h"
35 #endif
36
37 #include "wx/renderer.h"
38
39 #include <stdlib.h>
40
41 wxDEFINE_EVENT( wxEVT_SPLITTER_SASH_POS_CHANGED, wxSplitterEvent );
42 wxDEFINE_EVENT( wxEVT_SPLITTER_SASH_POS_CHANGING, wxSplitterEvent );
43 wxDEFINE_EVENT( wxEVT_SPLITTER_DOUBLECLICKED, wxSplitterEvent );
44 wxDEFINE_EVENT( wxEVT_SPLITTER_UNSPLIT, wxSplitterEvent );
45
46 IMPLEMENT_DYNAMIC_CLASS(wxSplitterWindow, wxWindow)
47
48 /*
49 TODO PROPERTIES
50 style wxSP_3D
51 sashpos (long , 0 )
52 minsize (long -1 )
53 object, object_ref
54 orientation
55 */
56
57 IMPLEMENT_DYNAMIC_CLASS(wxSplitterEvent, wxNotifyEvent)
58
59 BEGIN_EVENT_TABLE(wxSplitterWindow, wxWindow)
60 EVT_PAINT(wxSplitterWindow::OnPaint)
61 EVT_SIZE(wxSplitterWindow::OnSize)
62 EVT_MOUSE_EVENTS(wxSplitterWindow::OnMouseEvent)
63 EVT_MOUSE_CAPTURE_LOST(wxSplitterWindow::OnMouseCaptureLost)
64
65 #if defined( __WXMSW__ ) || defined( __WXMAC__)
66 EVT_SET_CURSOR(wxSplitterWindow::OnSetCursor)
67 #endif // wxMSW
68 END_EVENT_TABLE()
69
70 static bool IsLive(wxSplitterWindow* wnd)
71 {
72 // with wxSP_LIVE_UPDATE style the splitter windows are always resized
73 // following the mouse movement while it drags the sash, without it we only
74 // draw the sash at the new position but only resize the windows when the
75 // dragging is finished
76 #if defined( __WXMAC__ ) && defined(TARGET_API_MAC_OSX) && TARGET_API_MAC_OSX == 1
77 return true; // Mac can't paint outside paint event - always need live mode
78 #else
79 return wnd->HasFlag(wxSP_LIVE_UPDATE);
80 #endif
81 }
82
83 bool wxSplitterWindow::Create(wxWindow *parent, wxWindowID id,
84 const wxPoint& pos,
85 const wxSize& size,
86 long style,
87 const wxString& name)
88 {
89 // allow TABbing from one window to the other
90 style |= wxTAB_TRAVERSAL;
91
92 if ( !wxWindow::Create(parent, id, pos, size, style, name) )
93 return false;
94
95 m_lastSize = GetClientSize();
96
97 m_permitUnsplitAlways = (style & wxSP_PERMIT_UNSPLIT) != 0;
98
99 // FIXME: with this line the background is not erased at all under GTK1,
100 // so temporary avoid it there
101 #if !defined(__WXGTK__) || defined(__WXGTK20__)
102 // don't erase the splitter background, it's pointless as we overwrite it
103 // anyhow
104 SetBackgroundStyle(wxBG_STYLE_CUSTOM);
105 #endif
106
107 return true;
108 }
109
110 void wxSplitterWindow::Init()
111 {
112 m_splitMode = wxSPLIT_VERTICAL;
113 m_permitUnsplitAlways = true;
114 m_windowOne = NULL;
115 m_windowTwo = NULL;
116 m_dragMode = wxSPLIT_DRAG_NONE;
117 m_oldX = 0;
118 m_oldY = 0;
119 m_sashStart = 0;
120 m_sashPosition = 0;
121 m_requestedSashPosition = INT_MAX;
122 m_sashGravity = 0.0;
123 m_lastSize = wxSize(0,0);
124 m_minimumPaneSize = 0;
125 m_sashCursorWE = wxCursor(wxCURSOR_SIZEWE);
126 m_sashCursorNS = wxCursor(wxCURSOR_SIZENS);
127 m_sashTrackerPen = new wxPen(*wxBLACK, 2, wxPENSTYLE_SOLID);
128
129 m_needUpdating = false;
130 m_isHot = false;
131 }
132
133 wxSplitterWindow::~wxSplitterWindow()
134 {
135 delete m_sashTrackerPen;
136 }
137
138 // ----------------------------------------------------------------------------
139 // entering/leaving sash
140 // ----------------------------------------------------------------------------
141
142 void wxSplitterWindow::RedrawIfHotSensitive(bool isHot)
143 {
144 if ( wxRendererNative::Get().GetSplitterParams(this).isHotSensitive )
145 {
146 m_isHot = isHot;
147
148 wxClientDC dc(this);
149 DrawSash(dc);
150 }
151 //else: we don't change our appearance, don't redraw to avoid flicker
152 }
153
154 void wxSplitterWindow::OnEnterSash()
155 {
156 SetResizeCursor();
157
158 RedrawIfHotSensitive(true);
159 }
160
161 void wxSplitterWindow::OnLeaveSash()
162 {
163 SetCursor(*wxSTANDARD_CURSOR);
164
165 RedrawIfHotSensitive(false);
166 }
167
168 void wxSplitterWindow::SetResizeCursor()
169 {
170 SetCursor(m_splitMode == wxSPLIT_VERTICAL ? m_sashCursorWE
171 : m_sashCursorNS);
172 }
173
174 // ----------------------------------------------------------------------------
175 // other event handlers
176 // ----------------------------------------------------------------------------
177
178 void wxSplitterWindow::OnPaint(wxPaintEvent& WXUNUSED(event))
179 {
180 wxPaintDC dc(this);
181 #ifdef __WXOSX__
182 // as subpanels might have a transparent background we must erase the background
183 // at least on OSX, otherwise traces of the sash will remain
184 // test with: splitter sample->replace right window
185 dc.Clear();
186 #endif
187
188 DrawSash(dc);
189 }
190
191 void wxSplitterWindow::OnInternalIdle()
192 {
193 wxWindow::OnInternalIdle();
194
195 // We may need to update the children sizes in two cases: either because
196 // we're in the middle of a live update as indicated by m_needUpdating or
197 // because we have a requested but not yet set sash position as indicated
198 // by m_requestedSashPosition having a valid value.
199 if ( m_needUpdating )
200 {
201 m_needUpdating = false;
202 }
203 else if ( m_requestedSashPosition == INT_MAX )
204 {
205 // We don't need to resize the children.
206 return;
207 }
208
209 SizeWindows();
210 }
211
212 void wxSplitterWindow::OnMouseEvent(wxMouseEvent& event)
213 {
214 int x = (int)event.GetX(),
215 y = (int)event.GetY();
216
217 if ( GetWindowStyle() & wxSP_NOSASH )
218 {
219 event.Skip();
220 return;
221 }
222
223 bool isLive = IsLive(this);
224
225 if (event.LeftDown())
226 {
227 if ( SashHitTest(x, y) )
228 {
229 // Start the drag now
230 m_dragMode = wxSPLIT_DRAG_DRAGGING;
231
232 // Capture mouse and set the cursor
233 CaptureMouse();
234 SetResizeCursor();
235
236 if ( !isLive )
237 {
238 // remember the initial sash position and draw the initial
239 // shadow sash
240 m_sashPositionCurrent = m_sashPosition;
241
242 m_oldX = (m_splitMode == wxSPLIT_VERTICAL ? m_sashPositionCurrent : x);
243 m_oldY = (m_splitMode != wxSPLIT_VERTICAL ? m_sashPositionCurrent : y);
244 DrawSashTracker(m_oldX, m_oldY);
245 }
246
247 m_ptStart = wxPoint(x,y);
248 m_sashStart = m_sashPosition;
249 return;
250 }
251 }
252 else if (event.LeftUp() && m_dragMode == wxSPLIT_DRAG_DRAGGING)
253 {
254 // We can stop dragging now and see what we've got.
255 m_dragMode = wxSPLIT_DRAG_NONE;
256
257 // Release mouse and unset the cursor
258 ReleaseMouse();
259 SetCursor(* wxSTANDARD_CURSOR);
260
261 // exit if unsplit after doubleclick
262 if ( !IsSplit() )
263 {
264 return;
265 }
266
267 // Erase old tracker
268 if ( !isLive )
269 {
270 DrawSashTracker(m_oldX, m_oldY);
271 }
272
273 // the position of the click doesn't exactly correspond to
274 // m_sashPosition, rather it changes it by the distance by which the
275 // mouse has moved
276 int diff = m_splitMode == wxSPLIT_VERTICAL ? x - m_ptStart.x : y - m_ptStart.y;
277
278 int posSashNew = OnSashPositionChanging(m_sashStart + diff);
279 if ( posSashNew == -1 )
280 {
281 // change not allowed
282 return;
283 }
284
285 if ( m_permitUnsplitAlways || m_minimumPaneSize == 0 )
286 {
287 // Deal with possible unsplit scenarios
288 if ( posSashNew == 0 )
289 {
290 // We remove the first window from the view
291 wxWindow *removedWindow = m_windowOne;
292 m_windowOne = m_windowTwo;
293 m_windowTwo = NULL;
294 OnUnsplit(removedWindow);
295 wxSplitterEvent eventUnsplit(wxEVT_SPLITTER_UNSPLIT, this);
296 eventUnsplit.m_data.win = removedWindow;
297 (void)DoSendEvent(eventUnsplit);
298 SetSashPositionAndNotify(0);
299 }
300 else if ( posSashNew == GetWindowSize() )
301 {
302 // We remove the second window from the view
303 wxWindow *removedWindow = m_windowTwo;
304 m_windowTwo = NULL;
305 OnUnsplit(removedWindow);
306 wxSplitterEvent eventUnsplit(wxEVT_SPLITTER_UNSPLIT, this);
307 eventUnsplit.m_data.win = removedWindow;
308 (void)DoSendEvent(eventUnsplit);
309 SetSashPositionAndNotify(0);
310 }
311 else
312 {
313 SetSashPositionAndNotify(posSashNew);
314 }
315 }
316 else
317 {
318 SetSashPositionAndNotify(posSashNew);
319 }
320
321 SizeWindows();
322 } // left up && dragging
323 else if ((event.Moving() || event.Leaving() || event.Entering()) && (m_dragMode == wxSPLIT_DRAG_NONE))
324 {
325 if ( event.Leaving() || !SashHitTest(x, y) )
326 OnLeaveSash();
327 else
328 OnEnterSash();
329 }
330 else if (event.Dragging() && (m_dragMode == wxSPLIT_DRAG_DRAGGING))
331 {
332 int diff = m_splitMode == wxSPLIT_VERTICAL ? x - m_ptStart.x : y - m_ptStart.y;
333
334 int posSashNew = OnSashPositionChanging(m_sashStart + diff);
335 if ( posSashNew == -1 )
336 {
337 // change not allowed
338 return;
339 }
340
341 if ( !isLive )
342 {
343 if ( posSashNew == m_sashPositionCurrent )
344 return;
345
346 m_sashPositionCurrent = posSashNew;
347
348 // Erase old tracker
349 DrawSashTracker(m_oldX, m_oldY);
350
351 m_oldX = (m_splitMode == wxSPLIT_VERTICAL ? m_sashPositionCurrent : x);
352 m_oldY = (m_splitMode != wxSPLIT_VERTICAL ? m_sashPositionCurrent : y);
353
354 #ifdef __WXMSW__
355 // As we captured the mouse, we may get the mouse events from outside
356 // our window - for example, negative values in x, y. This has a weird
357 // consequence under MSW where we use unsigned values sometimes and
358 // signed ones other times: the coordinates turn as big positive
359 // numbers and so the sash is drawn on the *right* side of the window
360 // instead of the left (or bottom instead of top). Correct this.
361 if ( (short)m_oldX < 0 )
362 m_oldX = 0;
363 if ( (short)m_oldY < 0 )
364 m_oldY = 0;
365 #endif // __WXMSW__
366
367 // Draw new one
368 DrawSashTracker(m_oldX, m_oldY);
369 }
370 else
371 {
372 if ( posSashNew == m_sashPosition )
373 return;
374
375 DoSetSashPosition(posSashNew);
376
377 // in live mode, the new position is the actual sash position, clear requested position!
378 m_requestedSashPosition = INT_MAX;
379 m_needUpdating = true;
380 }
381 }
382 else if ( event.LeftDClick() && m_windowTwo )
383 {
384 OnDoubleClickSash(x, y);
385 }
386 else
387 {
388 event.Skip();
389 }
390 }
391
392 void wxSplitterWindow::OnMouseCaptureLost(wxMouseCaptureLostEvent& WXUNUSED(event))
393 {
394 if (m_dragMode != wxSPLIT_DRAG_DRAGGING)
395 return;
396
397 m_dragMode = wxSPLIT_DRAG_NONE;
398
399 SetCursor(* wxSTANDARD_CURSOR);
400
401 // Erase old tracker
402 if ( !IsLive(this) )
403 {
404 DrawSashTracker(m_oldX, m_oldY);
405 }
406 }
407
408 void wxSplitterWindow::OnSize(wxSizeEvent& event)
409 {
410 // only process this message if we're not iconized - otherwise iconizing
411 // and restoring a window containing the splitter has a funny side effect
412 // of changing the splitter position!
413 wxWindow *parent = wxGetTopLevelParent(this);
414 bool iconized;
415
416 wxTopLevelWindow *winTop = wxDynamicCast(parent, wxTopLevelWindow);
417 if ( winTop )
418 {
419 iconized = winTop->IsIconized();
420 }
421 else
422 {
423 wxFAIL_MSG(wxT("should have a top level parent!"));
424
425 iconized = false;
426 }
427
428 if ( iconized )
429 {
430 m_lastSize = wxSize(0,0);
431
432 event.Skip();
433
434 return;
435 }
436
437 const wxSize curSize = event.GetSize();
438
439 // Update the sash position if needed.
440 //
441 // Notice that we shouldn't do this if the sash position requested by user
442 // couldn't be set yet as it would never be taken into account at all if we
443 // modified it before this happens.
444 if ( m_windowTwo && m_requestedSashPosition == INT_MAX )
445 {
446 int size = m_splitMode == wxSPLIT_VERTICAL ? curSize.x : curSize.y;
447
448 int old_size = m_splitMode == wxSPLIT_VERTICAL ? m_lastSize.x : m_lastSize.y;
449
450 // Don't do anything if the size didn't really change.
451 if ( size != old_size )
452 {
453 int newPosition = -1;
454
455 // Apply gravity if we use it.
456 int delta = (int) ( (size - old_size)*m_sashGravity );
457 if ( delta != 0 )
458 {
459 newPosition = m_sashPosition + delta;
460 if( newPosition < m_minimumPaneSize )
461 newPosition = m_minimumPaneSize;
462 }
463
464 // Also check if the second window became too small.
465 newPosition = AdjustSashPosition(newPosition == -1
466 ? m_sashPosition
467 : newPosition);
468 if ( newPosition != m_sashPosition )
469 SetSashPositionAndNotify(newPosition);
470 }
471 }
472
473 m_lastSize = curSize;
474
475 SizeWindows();
476 }
477
478 void wxSplitterWindow::SetSashGravity(double gravity)
479 {
480 wxCHECK_RET( gravity >= 0. && gravity <= 1.,
481 wxT("invalid gravity value") );
482
483 m_sashGravity = gravity;
484 }
485
486 bool wxSplitterWindow::SashHitTest(int x, int y)
487 {
488 if ( m_windowTwo == NULL || m_sashPosition == 0)
489 return false; // No sash
490
491 int z = m_splitMode == wxSPLIT_VERTICAL ? x : y;
492 int hitMax = m_sashPosition + GetSashSize() - 1;
493
494 return z >= m_sashPosition && z <= hitMax;
495 }
496
497 void wxSplitterWindow::SetSashInvisible(bool invisible)
498 {
499 if ( IsSashInvisible() != invisible )
500 ToggleWindowStyle(wxSP_NOSASH);
501 }
502
503 int wxSplitterWindow::GetSashSize() const
504 {
505 return IsSashInvisible() ? 0 : GetDefaultSashSize();
506 }
507
508 int wxSplitterWindow::GetDefaultSashSize() const
509 {
510 return wxRendererNative::Get().GetSplitterParams(this).widthSash;
511 }
512
513 int wxSplitterWindow::GetBorderSize() const
514 {
515 return wxRendererNative::Get().GetSplitterParams(this).border;
516 }
517
518 // Draw the sash
519 void wxSplitterWindow::DrawSash(wxDC& dc)
520 {
521 if (HasFlag(wxSP_3DBORDER))
522 wxRendererNative::Get().DrawSplitterBorder
523 (
524 this,
525 dc,
526 GetClientRect()
527 );
528
529 // don't draw sash if we're not split
530 if ( m_sashPosition == 0 || !m_windowTwo )
531 return;
532
533 // nor if we're configured to not show it
534 if ( IsSashInvisible() )
535 return;
536
537 wxRendererNative::Get().DrawSplitterSash
538 (
539 this,
540 dc,
541 GetClientSize(),
542 m_sashPosition,
543 m_splitMode == wxSPLIT_VERTICAL ? wxVERTICAL
544 : wxHORIZONTAL,
545 m_isHot ? (int)wxCONTROL_CURRENT : 0
546 );
547 }
548
549 // Draw the sash tracker (for whilst moving the sash)
550 void wxSplitterWindow::DrawSashTracker(int x, int y)
551 {
552 int w, h;
553 GetClientSize(&w, &h);
554
555 wxScreenDC screenDC;
556 int x1, y1;
557 int x2, y2;
558
559 if ( m_splitMode == wxSPLIT_VERTICAL )
560 {
561 x1 = x2 = wxClip(x, 0, w) + m_sashTrackerPen->GetWidth()/2;
562 y1 = 2;
563 y2 = h-2;
564 }
565 else
566 {
567 y1 = y2 = wxClip(y, 0, h) + m_sashTrackerPen->GetWidth()/2;
568 x1 = 2;
569 x2 = w-2;
570 }
571
572 ClientToScreen(&x1, &y1);
573 ClientToScreen(&x2, &y2);
574
575 screenDC.SetLogicalFunction(wxINVERT);
576 screenDC.SetPen(*m_sashTrackerPen);
577 screenDC.SetBrush(*wxTRANSPARENT_BRUSH);
578
579 screenDC.DrawLine(x1, y1, x2, y2);
580
581 screenDC.SetLogicalFunction(wxCOPY);
582 }
583
584 int wxSplitterWindow::GetWindowSize() const
585 {
586 wxSize size = GetClientSize();
587
588 return m_splitMode == wxSPLIT_VERTICAL ? size.x : size.y;
589 }
590
591 int wxSplitterWindow::AdjustSashPosition(int sashPos) const
592 {
593 wxWindow *win;
594
595 win = GetWindow1();
596 if ( win )
597 {
598 // the window shouldn't be smaller than its own minimal size nor
599 // smaller than the minimual pane size specified for this splitter
600 int minSize = m_splitMode == wxSPLIT_VERTICAL ? win->GetMinWidth()
601 : win->GetMinHeight();
602
603 if ( minSize == -1 || m_minimumPaneSize > minSize )
604 minSize = m_minimumPaneSize;
605
606 minSize += GetBorderSize();
607
608 if ( sashPos < minSize )
609 sashPos = minSize;
610 }
611
612 win = GetWindow2();
613 if ( win )
614 {
615 int minSize = m_splitMode == wxSPLIT_VERTICAL ? win->GetMinWidth()
616 : win->GetMinHeight();
617
618 if ( minSize == -1 || m_minimumPaneSize > minSize )
619 minSize = m_minimumPaneSize;
620
621 int maxSize = GetWindowSize() - minSize - GetBorderSize() - GetSashSize();
622 if ( maxSize > 0 && sashPos > maxSize && maxSize >= m_minimumPaneSize)
623 sashPos = maxSize;
624 }
625
626 return sashPos;
627 }
628
629 bool wxSplitterWindow::DoSetSashPosition(int sashPos)
630 {
631 int newSashPosition = AdjustSashPosition(sashPos);
632
633 if ( newSashPosition == m_sashPosition )
634 return false;
635
636 m_sashPosition = newSashPosition;
637
638 return true;
639 }
640
641 void wxSplitterWindow::SetSashPositionAndNotify(int sashPos)
642 {
643 // we must reset the request here, otherwise the sash would be stuck at
644 // old position if the user attempted to move the sash after invalid
645 // (e.g. smaller than minsize) sash position was requested using
646 // SetSashPosition():
647 m_requestedSashPosition = INT_MAX;
648
649 // note that we must send the event in any case, i.e. even if the sash
650 // position hasn't changed and DoSetSashPosition() returns false because we
651 // must generate a CHANGED event at the end of resizing
652 DoSetSashPosition(sashPos);
653
654 wxSplitterEvent event(wxEVT_SPLITTER_SASH_POS_CHANGED, this);
655 event.m_data.pos = m_sashPosition;
656
657 (void)DoSendEvent(event);
658 }
659
660 // Position and size subwindows.
661 // Note that the border size applies to each subwindow, not
662 // including the edges next to the sash.
663 void wxSplitterWindow::SizeWindows()
664 {
665 // check if we have delayed setting the real sash position
666 if ( m_requestedSashPosition != INT_MAX )
667 {
668 int newSashPosition = ConvertSashPosition(m_requestedSashPosition);
669 if ( newSashPosition != m_sashPosition )
670 {
671 DoSetSashPosition(newSashPosition);
672 }
673
674 if ( newSashPosition <= m_sashPosition
675 && newSashPosition >= m_sashPosition - GetBorderSize() )
676 {
677 // don't update it any more
678 m_requestedSashPosition = INT_MAX;
679 }
680 }
681
682 int w, h;
683 GetClientSize(&w, &h);
684
685 if ( GetWindow1() && !GetWindow2() )
686 {
687 GetWindow1()->SetSize(GetBorderSize(), GetBorderSize(),
688 w - 2*GetBorderSize(), h - 2*GetBorderSize());
689 }
690 else if ( GetWindow1() && GetWindow2() )
691 {
692 const int border = GetBorderSize(),
693 sash = GetSashSize();
694
695 int size1 = GetSashPosition() - border,
696 size2 = GetSashPosition() + sash;
697
698 int x2, y2, w1, h1, w2, h2;
699 if ( GetSplitMode() == wxSPLIT_VERTICAL )
700 {
701 w1 = size1;
702 w2 = w - 2*border - sash - w1;
703 if (w2 < 0)
704 w2 = 0;
705 h2 = h - 2*border;
706 if (h2 < 0)
707 h2 = 0;
708 h1 = h2;
709 x2 = size2;
710 y2 = border;
711 }
712 else // horz splitter
713 {
714 w2 = w - 2*border;
715 if (w2 < 0)
716 w2 = 0;
717 w1 = w2;
718 h1 = size1;
719 h2 = h - 2*border - sash - h1;
720 if (h2 < 0)
721 h2 = 0;
722 x2 = border;
723 y2 = size2;
724 }
725
726 GetWindow2()->SetSize(x2, y2, w2, h2);
727 GetWindow1()->SetSize(border, border, w1, h1);
728 }
729
730 wxClientDC dc(this);
731 DrawSash(dc);
732 }
733
734 // Set pane for unsplit window
735 void wxSplitterWindow::Initialize(wxWindow *window)
736 {
737 wxASSERT_MSG( (!window || window->GetParent() == this),
738 wxT("windows in the splitter should have it as parent!") );
739
740 if (window && !window->IsShown())
741 window->Show();
742
743 m_windowOne = window;
744 m_windowTwo = NULL;
745 DoSetSashPosition(0);
746 }
747
748 // Associates the given window with window 2, drawing the appropriate sash
749 // and changing the split mode.
750 // Does nothing and returns false if the window is already split.
751 bool wxSplitterWindow::DoSplit(wxSplitMode mode,
752 wxWindow *window1, wxWindow *window2,
753 int sashPosition)
754 {
755 if ( IsSplit() )
756 return false;
757
758 wxCHECK_MSG( window1 && window2, false,
759 wxT("cannot split with NULL window(s)") );
760
761 wxCHECK_MSG( window1->GetParent() == this && window2->GetParent() == this, false,
762 wxT("windows in the splitter should have it as parent!") );
763
764 if (! window1->IsShown())
765 window1->Show();
766 if (! window2->IsShown())
767 window2->Show();
768
769 m_splitMode = mode;
770 m_windowOne = window1;
771 m_windowTwo = window2;
772
773
774 SetSashPosition(sashPosition, true);
775 return true;
776 }
777
778 int wxSplitterWindow::ConvertSashPosition(int sashPosition) const
779 {
780 if ( sashPosition > 0 )
781 {
782 return sashPosition;
783 }
784 else if ( sashPosition < 0 )
785 {
786 // It's negative so adding is subtracting
787 return GetWindowSize() + sashPosition;
788 }
789 else // sashPosition == 0
790 {
791 // default, put it in the centre
792 return GetWindowSize() / 2;
793 }
794 }
795
796 // Remove the specified (or second) window from the view
797 // Doesn't actually delete the window.
798 bool wxSplitterWindow::Unsplit(wxWindow *toRemove)
799 {
800 if ( ! IsSplit() )
801 return false;
802
803 wxWindow *win;
804 if ( toRemove == NULL || toRemove == m_windowTwo)
805 {
806 win = m_windowTwo ;
807 m_windowTwo = NULL;
808 }
809 else if ( toRemove == m_windowOne )
810 {
811 win = m_windowOne ;
812 m_windowOne = m_windowTwo;
813 m_windowTwo = NULL;
814 }
815 else
816 {
817 wxFAIL_MSG(wxT("splitter: attempt to remove a non-existent window"));
818
819 return false;
820 }
821
822 OnUnsplit(win);
823 DoSetSashPosition(0);
824 SizeWindows();
825
826 return true;
827 }
828
829 // Replace a window with another one
830 bool wxSplitterWindow::ReplaceWindow(wxWindow *winOld, wxWindow *winNew)
831 {
832 wxCHECK_MSG( winOld, false, wxT("use one of Split() functions instead") );
833 wxCHECK_MSG( winNew, false, wxT("use Unsplit() functions instead") );
834
835 if ( winOld == m_windowTwo )
836 {
837 m_windowTwo = winNew;
838 }
839 else if ( winOld == m_windowOne )
840 {
841 m_windowOne = winNew;
842 }
843 else
844 {
845 wxFAIL_MSG(wxT("splitter: attempt to replace a non-existent window"));
846
847 return false;
848 }
849
850 SizeWindows();
851
852 return true;
853 }
854
855 void wxSplitterWindow::SetMinimumPaneSize(int min)
856 {
857 m_minimumPaneSize = min;
858 int pos = m_requestedSashPosition != INT_MAX ? m_requestedSashPosition : m_sashPosition;
859 SetSashPosition(pos); // re-check limits
860 }
861
862 void wxSplitterWindow::SetSashPosition(int position, bool redraw)
863 {
864 // remember the sash position we want to set for later if we can't set it
865 // right now (e.g. because the window is too small)
866 m_requestedSashPosition = position;
867
868 DoSetSashPosition(ConvertSashPosition(position));
869
870 if ( redraw )
871 {
872 SizeWindows();
873 }
874 }
875
876 // Make sure the child window sizes are updated. This is useful
877 // for reducing flicker by updating the sizes before a
878 // window is shown, if you know the overall size is correct.
879 void wxSplitterWindow::UpdateSize()
880 {
881 SizeWindows();
882 }
883
884 bool wxSplitterWindow::DoSendEvent(wxSplitterEvent& event)
885 {
886 return !GetEventHandler()->ProcessEvent(event) || event.IsAllowed();
887 }
888
889 wxSize wxSplitterWindow::DoGetBestSize() const
890 {
891 // get best sizes of subwindows
892 wxSize size1, size2;
893 if ( m_windowOne )
894 size1 = m_windowOne->GetEffectiveMinSize();
895 if ( m_windowTwo )
896 size2 = m_windowTwo->GetEffectiveMinSize();
897
898 // sum them
899 //
900 // pSash points to the size component to which sash size must be added
901 int *pSash;
902 wxSize sizeBest;
903 if ( m_splitMode == wxSPLIT_VERTICAL )
904 {
905 sizeBest.y = wxMax(size1.y, size2.y);
906 sizeBest.x = wxMax(size1.x, m_minimumPaneSize) +
907 wxMax(size2.x, m_minimumPaneSize);
908
909 pSash = &sizeBest.x;
910 }
911 else // wxSPLIT_HORIZONTAL
912 {
913 sizeBest.x = wxMax(size1.x, size2.x);
914 sizeBest.y = wxMax(size1.y, m_minimumPaneSize) +
915 wxMax(size2.y, m_minimumPaneSize);
916
917 pSash = &sizeBest.y;
918 }
919
920 // account for the sash if the window is actually split
921 if ( m_windowOne && m_windowTwo )
922 *pSash += GetSashSize();
923
924 // account for the border too
925 int border = 2*GetBorderSize();
926 sizeBest.x += border;
927 sizeBest.y += border;
928
929 return sizeBest;
930 }
931
932 // ---------------------------------------------------------------------------
933 // wxSplitterWindow virtual functions: they now just generate the events
934 // ---------------------------------------------------------------------------
935
936 bool wxSplitterWindow::OnSashPositionChange(int WXUNUSED(newSashPosition))
937 {
938 // always allow by default
939 return true;
940 }
941
942 int wxSplitterWindow::OnSashPositionChanging(int newSashPosition)
943 {
944 // If within UNSPLIT_THRESHOLD from edge, set to edge to cause closure.
945 const int UNSPLIT_THRESHOLD = 4;
946
947 // first of all, check if OnSashPositionChange() doesn't forbid this change
948 if ( !OnSashPositionChange(newSashPosition) )
949 {
950 // it does
951 return -1;
952 }
953
954 // Obtain relevant window dimension for bottom / right threshold check
955 int window_size = GetWindowSize();
956
957 bool unsplit_scenario = false;
958 if ( m_permitUnsplitAlways || m_minimumPaneSize == 0 )
959 {
960 // Do edge detection if unsplit premitted
961 if ( newSashPosition <= UNSPLIT_THRESHOLD )
962 {
963 // threshold top / left check
964 newSashPosition = 0;
965 unsplit_scenario = true;
966 }
967 if ( newSashPosition >= window_size - UNSPLIT_THRESHOLD )
968 {
969 // threshold bottom/right check
970 newSashPosition = window_size;
971 unsplit_scenario = true;
972 }
973 }
974
975 if ( !unsplit_scenario )
976 {
977 // If resultant pane would be too small, enlarge it
978 newSashPosition = AdjustSashPosition(newSashPosition);
979
980 // If the result is out of bounds it means minimum size is too big,
981 // so split window in half as best compromise.
982 if ( newSashPosition < 0 || newSashPosition > window_size )
983 newSashPosition = window_size / 2;
984 }
985
986 // now let the event handler have it
987 //
988 // FIXME: shouldn't we do it before the adjustments above so as to ensure
989 // that the sash position is always reasonable?
990 wxSplitterEvent event(wxEVT_SPLITTER_SASH_POS_CHANGING, this);
991 event.m_data.pos = newSashPosition;
992
993 if ( !DoSendEvent(event) )
994 {
995 // the event handler vetoed the change
996 newSashPosition = -1;
997 }
998 else
999 {
1000 // it could have been changed by it
1001 newSashPosition = event.GetSashPosition();
1002 }
1003
1004 return newSashPosition;
1005 }
1006
1007 // Called when the sash is double-clicked. The default behaviour is to remove
1008 // the sash if the minimum pane size is zero.
1009 void wxSplitterWindow::OnDoubleClickSash(int x, int y)
1010 {
1011 wxCHECK_RET(m_windowTwo, wxT("splitter: no window to remove"));
1012
1013 // new code should handle events instead of using the virtual functions
1014 wxSplitterEvent event(wxEVT_SPLITTER_DOUBLECLICKED, this);
1015 event.m_data.pt.x = x;
1016 event.m_data.pt.y = y;
1017 if ( DoSendEvent(event) )
1018 {
1019 if ( GetMinimumPaneSize() == 0 || m_permitUnsplitAlways )
1020 {
1021 wxWindow* win = m_windowTwo;
1022 if ( Unsplit(win) )
1023 {
1024 wxSplitterEvent unsplitEvent(wxEVT_SPLITTER_UNSPLIT, this);
1025 unsplitEvent.m_data.win = win;
1026 (void)DoSendEvent(unsplitEvent);
1027 }
1028 }
1029 }
1030 //else: blocked by user
1031 }
1032
1033 void wxSplitterWindow::OnUnsplit(wxWindow *winRemoved)
1034 {
1035 // call this before calling the event handler which may delete the window
1036 winRemoved->Show(false);
1037 }
1038
1039 #if defined( __WXMSW__ ) || defined( __WXMAC__)
1040
1041 // this is currently called (and needed) under MSW only...
1042 void wxSplitterWindow::OnSetCursor(wxSetCursorEvent& event)
1043 {
1044 // if we don't do it, the resizing cursor might be set for child window:
1045 // and like this we explicitly say that our cursor should not be used for
1046 // children windows which overlap us
1047
1048 if ( SashHitTest(event.GetX(), event.GetY()) )
1049 {
1050 // default processing is ok
1051 event.Skip();
1052 }
1053 //else: do nothing, in particular, don't call Skip()
1054 }
1055
1056 #endif // wxMSW || wxMac
1057
1058 #endif // wxUSE_SPLITTER
1059