1# --------------------------------------------------------------------------------- #
2# FOURWAYSPLITTER wxPython IMPLEMENTATION
3#
4# Andrea Gavana, @ 03 Nov 2006
5# Latest Revision: 16 Jul 2012, 15.00 GMT
6#
7#
8# TODO List
9#
10# 1. Any idea?
11#
12# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
13# Write To Me At:
14#
15# andrea.gavana@maerskoil.com
16# andrea.gavana@gmail.com
17#
18# Or, Obviously, To The wxPython Mailing List!!!
19#
20# Tags:        phoenix-port, unittest, documented, py3-port
21#
22# End Of Comments
23# --------------------------------------------------------------------------------- #
24
25"""
26:class:`~wx.lib.agw.fourwaysplitter.FourWaySplitter` is a layout manager which manages 4 children like 4 panes in a
27window.
28
29
30Description
31===========
32
33The :class:`FourWaySplitter` is a layout manager which manages four children like four
34panes in a window. You can use a four-way splitter for example in a CAD program
35where you may want to maintain three orthographic views, and one oblique view of
36a model.
37
38The :class:`FourWaySplitter` allows interactive repartitioning of the panes by
39means of moving the central splitter bars. When the :class:`FourWaySplitter` is itself
40resized, each child is proportionally resized, maintaining the same split-percentage.
41
42The main characteristics of :class:`FourWaySplitter` are:
43
44- Handles horizontal, vertical or four way sizing via the sashes;
45- Delayed or live update when resizing;
46- Possibility to swap windows;
47- Setting the vertical and horizontal split fractions;
48- Possibility to expand a window by hiding the onther 3.
49
50And a lot more. See the demo for a complete review of the functionalities.
51
52
53Usage
54=====
55
56Usage example::
57
58    import wx
59    import wx.lib.agw.fourwaysplitter as fws
60
61    class MyFrame(wx.Frame):
62
63        def __init__(self, parent):
64
65            wx.Frame.__init__(self, parent, -1, "FourWaySplitter Demo")
66
67            splitter = fws.FourWaySplitter(self, -1, agwStyle=wx.SP_LIVE_UPDATE)
68
69            # Put in some coloured panels...
70            for colour in [wx.RED, wx.WHITE, wx.BLUE, wx.GREEN]:
71
72                panel = wx.Panel(splitter)
73                panel.SetBackgroundColour(colour)
74
75                splitter.AppendWindow(panel)
76
77
78    # our normal wxApp-derived class, as usual
79
80    app = wx.App(0)
81
82    frame = MyFrame(None)
83    app.SetTopWindow(frame)
84    frame.Show()
85
86    app.MainLoop()
87
88
89
90Supported Platforms
91===================
92
93:class:`FourWaySplitter` has been tested on the following platforms:
94  * Windows (Windows XP);
95  * Linux Ubuntu (Dapper 6.06)
96
97
98Window Styles
99=============
100
101This class supports the following window styles:
102
103================== =========== ==================================================
104Window Styles      Hex Value   Description
105================== =========== ==================================================
106``SP_NOSASH``             0x10 No sash will be drawn on :class:`FourWaySplitter`.
107``SP_LIVE_UPDATE``        0x80 Don't draw XOR line but resize the child windows immediately.
108``SP_3DBORDER``          0x200 Draws a 3D effect border.
109================== =========== ==================================================
110
111
112Events Processing
113=================
114
115This class processes the following events:
116
117================================== ==================================================
118Event Name                         Description
119================================== ==================================================
120``EVT_SPLITTER_SASH_POS_CHANGED``  The sash position was changed. This event is generated after the user releases the mouse after dragging the splitter. Processes a ``wx.wxEVT_COMMAND_SPLITTER_SASH_POS_CHANGED`` event.
121``EVT_SPLITTER_SASH_POS_CHANGING`` The sash position is in the process of being changed. You may prevent this change from happening by calling `Veto` or you may also modify the position of the tracking bar to properly reflect the position that would be set if the drag were to be completed at this point. Processes a ``wx.wxEVT_COMMAND_SPLITTER_SASH_POS_CHANGING`` event.
122================================== ==================================================
123
124
125License And Version
126===================
127
128:class:`FourWaySplitter` is distributed under the wxPython license.
129
130Latest Revision: Andrea Gavana @ 16 Jul 2012, 15.00 GMT
131
132Version 0.5
133
134"""
135
136
137import wx
138
139_RENDER_VER = (2,6,1,1)
140
141# Tolerance for mouse shape and sizing
142_TOLERANCE = 5
143
144# Modes
145NOWHERE = 0
146""" No sashes are changing position. """
147FLAG_CHANGED = 1
148""" Sashes are changing position. """
149FLAG_PRESSED = 2
150""" Sashes are in a pressed state. """
151
152# FourWaySplitter styles
153SP_NOSASH = wx.SP_NOSASH
154""" No sash will be drawn on :class:`FourWaySplitter`. """
155SP_LIVE_UPDATE = wx.SP_LIVE_UPDATE
156""" Don't draw XOR line but resize the child windows immediately. """
157SP_3DBORDER = wx.SP_3DBORDER
158""" Draws a 3D effect border. """
159
160# FourWaySplitter events
161EVT_SPLITTER_SASH_POS_CHANGING = wx.EVT_SPLITTER_SASH_POS_CHANGING
162""" The sash position is in the process of being changed. You may prevent this change""" \
163""" from happening by calling `Veto` or you may also modify the position of the tracking""" \
164""" bar to properly reflect the position that would be set if the drag were to be""" \
165""" completed at this point. Processes a ``wx.wxEVT_COMMAND_SPLITTER_SASH_POS_CHANGING`` event."""
166EVT_SPLITTER_SASH_POS_CHANGED = wx.EVT_SPLITTER_SASH_POS_CHANGED
167""" The sash position was changed. This event is generated after the user releases the""" \
168""" mouse after dragging the splitter. Processes a ``wx.wxEVT_COMMAND_SPLITTER_SASH_POS_CHANGED`` event. """
169
170# ---------------------------------------------------------------------------- #
171# Class FourWaySplitterEvent
172# ---------------------------------------------------------------------------- #
173
174class FourWaySplitterEvent(wx.CommandEvent):
175    """
176    This event class is almost the same as :class:`SplitterEvent` except
177    it adds an accessor for the sash index that is being changed.  The
178    same event type IDs and event binders are used as with
179    :class:`SplitterEvent`.
180    """
181
182    def __init__(self, evtType=wx.wxEVT_NULL, splitter=None):
183        """
184        Default class constructor.
185
186        :param `evtType`: the event type;
187        :param `splitter`: the associated :class:`FourWaySplitter` window.
188        """
189
190        wx.CommandEvent.__init__(self, evtType)
191
192        if splitter:
193            self.SetEventObject(splitter)
194            self.SetId(splitter.GetId())
195
196        self.sashIdx = -1
197        self.sashPos = -1
198        self.isAllowed = True
199
200
201    def SetSashIdx(self, idx):
202        """
203        Sets the index of the sash currently involved in the event.
204
205        :param `idx`: an integer between 0 and 3, representing the index of the
206         sash involved in the event.
207        """
208
209        self.sashIdx = idx
210
211
212    def SetSashPosition(self, pos):
213        """
214        In the case of ``EVT_SPLITTER_SASH_POS_CHANGED`` events, sets the new sash
215        position. In the case of ``EVT_SPLITTER_SASH_POS_CHANGING`` events, sets
216        the new tracking bar position so visual feedback during dragging will represent
217        that change that will actually take place. Set to -1 from the event handler
218        code to prevent repositioning.
219
220        :param `pos`: the new sash position.
221
222        :note: May only be called while processing ``EVT_SPLITTER_SASH_POS_CHANGING``
223         and ``EVT_SPLITTER_SASH_POS_CHANGED`` events.
224        """
225
226        self.sashPos = pos
227
228
229    def GetSashIdx(self):
230        """ Returns the index of the sash currently involved in the event. """
231
232        return self.sashIdx
233
234
235    def GetSashPosition(self):
236        """
237        Returns the new sash position.
238
239        :note: May only be called while processing ``EVT_SPLITTER_SASH_POS_CHANGING``
240         and ``EVT_SPLITTER_SASH_POS_CHANGED`` events.
241        """
242
243        return self.sashPos
244
245
246    # methods from wx.NotifyEvent
247    def Veto(self):
248        """
249        Prevents the change announced by this event from happening.
250
251        :note: It is in general a good idea to notify the user about the reasons
252         for vetoing the change because otherwise the applications behaviour (which
253         just refuses to do what the user wants) might be quite surprising.
254        """
255
256        self.isAllowed = False
257
258
259    def Allow(self):
260        """
261        This is the opposite of :meth:`~FourWaySplitterEvent.Veto`: it explicitly allows the event to be processed.
262        For most events it is not necessary to call this method as the events are
263        allowed anyhow but some are forbidden by default (this will be mentioned
264        in the corresponding event description).
265        """
266
267        self.isAllowed = True
268
269
270    def IsAllowed(self):
271        """
272        Returns ``True`` if the change is allowed (:meth:`~FourWaySplitterEvent.Veto` hasn't been called) or
273        ``False`` otherwise (if it was).
274        """
275
276        return self.isAllowed
277
278
279# ---------------------------------------------------------------------------- #
280# Class FourWaySplitter
281# ---------------------------------------------------------------------------- #
282
283class FourWaySplitter(wx.Panel):
284    """
285    This class is very similar to :class:`SplitterWindow` except that it
286    allows for four windows and two sashes.  Many of the same styles,
287    constants, and methods behave the same as in :class:`SplitterWindow`.
288    However, in addition of the ability to drag the vertical and the
289    horizontal sash, by dragging at the intersection between the two
290    sashes, it is possible to resize the four windows at the same time.
291
292    :note: These things are not yet supported:
293
294     * Minimum pane size (minimum of what? Width? Height?);
295     * Using negative sash positions to indicate a position offset from the end;
296     * User controlled unsplitting with double clicks on the sash (but supported via the
297       :meth:`FourWaySplitter.SetExpanded() <FourWaySplitter.SetExpanded>` method);
298     * Sash gravity.
299
300
301    """
302
303    def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
304                 size=wx.DefaultSize, style=0, agwStyle=0, name="FourWaySplitter"):
305        """
306        Default class constructor.
307
308        :param `parent`: parent window. Must not be ``None``;
309        :param `id`: window identifier. A value of -1 indicates a default value;
310        :param `pos`: the control position. A value of (-1, -1) indicates a default position,
311         chosen by either the windowing system or wxPython, depending on platform;
312        :param `size`: the control size. A value of (-1, -1) indicates a default size,
313         chosen by either the windowing system or wxPython, depending on platform;
314        :param `style`: the underlying :class:`Panel` window style;
315        :param `agwStyle`: the AGW-specific window style. It can be a combination of the
316         following bits:
317
318         ================== =========== ==================================================
319         Window Styles      Hex Value   Description
320         ================== =========== ==================================================
321         ``SP_NOSASH``             0x10 No sash will be drawn on :class:`FourWaySplitter`.
322         ``SP_LIVE_UPDATE``        0x80 Don't draw XOR line but resize the child windows immediately.
323         ``SP_3DBORDER``          0x200 Draws a 3D effect border.
324         ================== =========== ==================================================
325
326        :param `name`: the window name.
327        """
328
329        # always turn on tab traversal
330        style |= wx.TAB_TRAVERSAL
331
332        # and turn off any border styles
333        style &= ~wx.BORDER_MASK
334        style |= wx.BORDER_NONE
335
336        self._agwStyle = agwStyle
337
338        # initialize the base class
339        wx.Panel.__init__(self, parent, id, pos, size, style, name)
340        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
341
342        self._windows = []
343
344        self._splitx = 0
345        self._splity = 0
346        self._expanded = -1
347        self._fhor = 5000
348        self._fver = 5000
349        self._offx = 0
350        self._offy = 0
351        self._mode = NOWHERE
352        self._flags = 0
353        self._isHot = False
354
355        self._sashTrackerPen = wx.Pen(wx.BLACK, 2, wx.PENSTYLE_SOLID)
356
357        self._sashCursorWE = wx.Cursor(wx.CURSOR_SIZEWE)
358        self._sashCursorNS = wx.Cursor(wx.CURSOR_SIZENS)
359        self._sashCursorSIZING = wx.Cursor(wx.CURSOR_SIZING)
360
361        self.Bind(wx.EVT_PAINT, self.OnPaint)
362        self.Bind(wx.EVT_MOTION, self.OnMotion)
363        self.Bind(wx.EVT_SIZE, self.OnSize)
364        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
365        self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
366        self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
367        self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnterWindow)
368
369
370    def SetAGWWindowStyleFlag(self, agwStyle):
371        """
372        Sets the :class:`FourWaySplitter` window style flags.
373
374        :param `agwStyle`: the AGW-specific window style. This can be a combination of the
375         following bits:
376
377         ================== =========== ==================================================
378         Window Styles      Hex Value   Description
379         ================== =========== ==================================================
380         ``SP_NOSASH``             0x10 No sash will be drawn on :class:`FourWaySplitter`.
381         ``SP_LIVE_UPDATE``        0x80 Don't draw XOR line but resize the child windows immediately.
382         ``SP_3DBORDER``          0x200 Draws a 3D effect border.
383         ================== =========== ==================================================
384        """
385
386        self._agwStyle = agwStyle
387        self.Refresh()
388
389
390    def GetAGWWindowStyleFlag(self):
391        """
392        Returns the :class:`FourWaySplitter` window style.
393
394        :see: :meth:`~FourWaySplitter.SetAGWWindowStyleFlag` for a list of possible window styles.
395        """
396
397        return self._agwStyle
398
399
400    def AppendWindow(self, window):
401        """
402        Add a new window to the splitter at the right side or bottom
403        of the window stack.
404
405        :param `window`: an instance of :class:`wx.Window`.
406        """
407
408        self.InsertWindow(len(self._windows), window)
409
410
411    def InsertWindow(self, idx, window, sashPos=-1):
412        """
413        Insert a new window into the splitter at the position given in `idx`.
414
415        :param `idx`: the index at which the window will be inserted;
416        :param `window`: an instance of :class:`wx.Window`;
417        :param `sashPos`: the sash position after the window insertion.
418        """
419
420        assert window not in self._windows, "A window can only be in the splitter once!"
421
422        self._windows.insert(idx, window)
423
424        self._SizeWindows()
425
426
427    def DetachWindow(self, window):
428        """
429        Removes the window from the stack of windows managed by the splitter. The
430        window will still exist so you should `Hide` or `Destroy` it as needed.
431
432        :param `window`: an instance of :class:`wx.Window`.
433        """
434
435        assert window in self._windows, "Unknown window!"
436
437        idx = self._windows.index(window)
438        del self._windows[idx]
439
440        self._SizeWindows()
441
442
443    def ReplaceWindow(self, oldWindow, newWindow):
444        """
445        Replaces `oldWindow` (which is currently being managed by the
446        splitter) with `newWindow`.  The `oldWindow` window will still
447        exist so you should `Hide` or `Destroy` it as needed.
448
449        :param `oldWindow`: an instance of :class:`wx.Window`;
450        :param `newWindow`: another instance of :class:`wx.Window`.
451        """
452
453        assert oldWindow in self._windows, "Unknown window!"
454
455        idx = self._windows.index(oldWindow)
456        self._windows[idx] = newWindow
457
458        self._SizeWindows()
459
460
461    def ExchangeWindows(self, window1, window2):
462        """
463        Trade the positions in the splitter of the two windows.
464
465        :param `window1`: an instance of :class:`wx.Window`;
466        :param `window2`: another instance of :class:`wx.Window`.
467        """
468
469        assert window1 in self._windows, "Unknown window!"
470        assert window2 in self._windows, "Unknown window!"
471
472        idx1 = self._windows.index(window1)
473        idx2 = self._windows.index(window2)
474        self._windows[idx1] = window2
475        self._windows[idx2] = window1
476
477        if "__WXMSW__" in wx.Platform:
478            self.Freeze()
479
480        self._SizeWindows()
481
482        if "__WXMSW__" in wx.Platform:
483            self.Thaw()
484
485
486    def GetWindow(self, idx):
487        """
488        Returns the window at the index `idx`.
489
490        :param `idx`: the index at which the window is located.
491        """
492
493        if len(self._windows) > idx:
494            return self._windows[idx]
495
496        return None
497
498    # Get top left child
499    def GetTopLeft(self):
500        """ Returns the top left window (window index: 0). """
501
502        return self.GetWindow(0)
503
504
505    # Get top right child
506    def GetTopRight(self):
507        """ Returns the top right window (window index: 1). """
508
509        return self.GetWindow(1)
510
511
512    # Get bottom left child
513    def GetBottomLeft(self):
514        """ Returns the bottom left window (window index: 2). """
515
516        return self.GetWindow(2)
517
518
519    # Get bottom right child
520    def GetBottomRight(self):
521        """ Returns the bottom right window (window index: 3). """
522
523        return self.GetWindow(3)
524
525
526    def DoGetBestSize(self):
527        """
528        Gets the size which best suits the window: for a control, it would be the
529        minimal size which doesn't truncate the control, for a panel - the same size
530        as it would have after a call to `Fit()`.
531
532        :note: Overridden from :class:`Panel`.
533        """
534
535        if not self._windows:
536            # something is better than nothing...
537            return wx.Size(10, 10)
538
539        width = height = 0
540        border = self._GetBorderSize()
541
542        tl = self.GetTopLeft()
543        tr = self.GetTopRight()
544        bl = self.GetBottomLeft()
545        br = self.GetBottomRight()
546
547        for win in self._windows:
548            w, h = win.GetEffectiveMinSize()
549            width += w
550            height += h
551
552        if tl and tr:
553          width += self._GetSashSize()
554
555        if bl and br:
556          height += self._GetSashSize()
557
558        return wx.Size(width+2*border, height+2*border)
559
560
561    # Recompute layout
562    def _SizeWindows(self):
563        """
564        Recalculate the layout based on split positions and split fractions.
565
566        :see: :meth:`~FourWaySplitter.SetHSplit` and :meth:`~FourWaySplitter.SetVSplit` for more information about split fractions.
567        """
568
569        win0 = self.GetTopLeft()
570        win1 = self.GetTopRight()
571        win2 = self.GetBottomLeft()
572        win3 = self.GetBottomRight()
573
574        width, height = self.GetSize()
575        barSize = self._GetSashSize()
576        border = self._GetBorderSize()
577
578        if self._expanded < 0:
579            totw = width - barSize - 2*border
580            toth = height - barSize - 2*border
581            self._splitx = (self._fhor*totw)//10000
582            self._splity = (self._fver*toth)//10000
583            rightw = totw - self._splitx
584            bottomh = toth - self._splity
585            if win0:
586                win0.SetSize(0, 0, self._splitx, self._splity)
587                win0.Show()
588            if win1:
589                win1.SetSize(self._splitx + barSize, 0, rightw, self._splity)
590                win1.Show()
591            if win2:
592                win2.SetSize(0, self._splity + barSize, self._splitx, bottomh)
593                win2.Show()
594            if win3:
595                win3.SetSize(self._splitx + barSize, self._splity + barSize, rightw, bottomh)
596                win3.Show()
597
598        else:
599
600            if self._expanded < len(self._windows):
601                for ii, win in enumerate(self._windows):
602                    if ii == self._expanded:
603                        win.SetSize(0, 0, width-2*border, height-2*border)
604                        win.Show()
605                    else:
606                        win.Hide()
607
608
609    # Determine split mode
610    def GetMode(self, pt):
611        """
612        Determines the split mode for :class:`FourWaySplitter`.
613
614        :param `pt`: the point at which the mouse has been clicked, an instance of
615         :class:`wx.Point`.
616
617        :return: One of the following 3 split modes:
618
619         ================= ==============================
620         Split Mode        Description
621         ================= ==============================
622         ``wx.HORIZONTAL`` the user has clicked on the horizontal sash
623         ``wx.VERTICAL``   The user has clicked on the vertical sash
624         ``wx.BOTH``       The user has clicked at the intersection between the 2 sashes
625         ================= ==============================
626
627        """
628
629        barSize = self._GetSashSize()
630        flag = wx.BOTH
631
632        if pt.x < self._splitx - _TOLERANCE:
633            flag &= ~wx.VERTICAL
634
635        if pt.y < self._splity - _TOLERANCE:
636            flag &= ~wx.HORIZONTAL
637
638        if pt.x >= self._splitx + barSize + _TOLERANCE:
639            flag &= ~wx.VERTICAL
640
641        if pt.y >= self._splity + barSize + _TOLERANCE:
642            flag &= ~wx.HORIZONTAL
643
644        return flag
645
646
647    # Move the split intelligently
648    def MoveSplit(self, x, y):
649        """
650        Moves the split accordingly to user action.
651
652        :param `x`: the new splitter `x` coordinate;
653        :param `y`: the new splitter `y` coordinate.
654        """
655
656        width, height = self.GetSize()
657        barSize = self._GetSashSize()
658
659        if x < 0: x = 0
660        if y < 0: y = 0
661        if x > width - barSize: x = width - barSize
662        if y > height - barSize: y = height - barSize
663
664        self._splitx = x
665        self._splity = y
666
667
668    # Adjust layout
669    def AdjustLayout(self):
670        """
671        Adjust layout of :class:`FourWaySplitter`. Mainly used to recalculate the
672        correct values for split fractions.
673        """
674
675        width, height = self.GetSize()
676        barSize = self._GetSashSize()
677        border = self._GetBorderSize()
678
679        self._fhor = (width > barSize and \
680                      [(10000*self._splitx+(width-barSize-1))//(width-barSize)] \
681                      or [0])[0]
682
683        self._fver = (height > barSize and \
684                      [(10000*self._splity+(height-barSize-1))//(height-barSize)] \
685                      or [0])[0]
686
687        self._SizeWindows()
688
689
690    # Button being pressed
691    def OnLeftDown(self, event):
692        """
693        Handles the ``wx.EVT_LEFT_DOWN`` event for :class:`FourWaySplitter`.
694
695        :param `event`: a :class:`MouseEvent` event to be processed.
696        """
697
698        if not self.IsEnabled():
699            return
700
701        pt = event.GetPosition()
702        self.CaptureMouse()
703        self._mode = self.GetMode(pt)
704
705        if self._mode:
706            self._offx = pt.x - self._splitx
707            self._offy = pt.y - self._splity
708            if not self.GetAGWWindowStyleFlag() & wx.SP_LIVE_UPDATE:
709                self.DrawSplitter(wx.ClientDC(self))
710                self.DrawTrackSplitter(self._splitx, self._splity)
711
712            self._flags |= FLAG_PRESSED
713
714
715    # Button being released
716    def OnLeftUp(self, event):
717        """
718        Handles the ``wx.EVT_LEFT_UP`` event for :class:`FourWaySplitter`.
719
720        :param `event`: a :class:`MouseEvent` event to be processed.
721        """
722
723        if not self.IsEnabled():
724            return
725
726        if self.HasCapture():
727            self.ReleaseMouse()
728
729        flgs = self._flags
730
731        self._flags &= ~FLAG_CHANGED
732        self._flags &= ~FLAG_PRESSED
733
734        if flgs & FLAG_PRESSED:
735
736            if not self.GetAGWWindowStyleFlag() & wx.SP_LIVE_UPDATE:
737                self.DrawTrackSplitter(self._splitx, self._splity)
738                self.DrawSplitter(wx.ClientDC(self))
739                self.AdjustLayout()
740
741            if flgs & FLAG_CHANGED:
742                event = FourWaySplitterEvent(wx.wxEVT_COMMAND_SPLITTER_SASH_POS_CHANGED, self)
743                event.SetSashIdx(self._mode)
744                event.SetSashPosition(wx.Point(self._splitx, self._splity))
745                self.GetEventHandler().ProcessEvent(event)
746
747        self._mode = NOWHERE
748
749
750    def OnLeaveWindow(self, event):
751        """
752        Handles the ``wx.EVT_LEAVE_WINDOW`` event for :class:`FourWaySplitter`.
753
754        :param `event`: a :class:`MouseEvent` event to be processed.
755        """
756
757        self.SetCursor(wx.STANDARD_CURSOR)
758        self._RedrawIfHotSensitive(False)
759
760
761    def OnEnterWindow(self, event):
762        """
763        Handles the ``wx.EVT_ENTER_WINDOW`` event for :class:`FourWaySplitter`.
764
765        :param `event`: a :class:`MouseEvent` event to be processed.
766        """
767
768        self._RedrawIfHotSensitive(True)
769
770
771    def _RedrawIfHotSensitive(self, isHot):
772        """
773        Used internally. Redraw the splitter if we are using a hot-sensitive splitter.
774
775        :param `isHot`: ``True`` if the splitter is in a hot state, ``False`` otherwise.
776        """
777
778        if not wx.VERSION >= _RENDER_VER:
779            return
780
781        if wx.RendererNative.Get().GetSplitterParams(self).isHotSensitive:
782            self._isHot = isHot
783            dc = wx.ClientDC(self)
784            self.DrawSplitter(dc)
785
786
787    def OnMotion(self, event):
788        """
789        Handles the ``wx.EVT_MOTION`` event for :class:`FourWaySplitter`.
790
791        :param `event`: a :class:`MouseEvent` event to be processed.
792        """
793
794        if self.HasFlag(wx.SP_NOSASH):
795            return
796
797        pt = event.GetPosition()
798
799        # Moving split
800        if self._flags & FLAG_PRESSED:
801
802            oldsplitx = self._splitx
803            oldsplity = self._splity
804
805            if self._mode == wx.BOTH:
806                self.MoveSplit(pt.x - self._offx, pt.y - self._offy)
807
808            elif self._mode == wx.VERTICAL:
809                self.MoveSplit(pt.x - self._offx, self._splity)
810
811            elif self._mode == wx.HORIZONTAL:
812                self.MoveSplit(self._splitx, pt.y - self._offy)
813
814            # Send a changing event
815            if not self.DoSendChangingEvent(wx.Point(self._splitx, self._splity)):
816                self._splitx = oldsplitx
817                self._splity = oldsplity
818                return
819
820            if oldsplitx != self._splitx or oldsplity != self._splity:
821                if not self.GetAGWWindowStyleFlag() & wx.SP_LIVE_UPDATE:
822                    self.DrawTrackSplitter(oldsplitx, oldsplity)
823                    self.DrawTrackSplitter(self._splitx, self._splity)
824                else:
825                    self.AdjustLayout()
826
827                self._flags |= FLAG_CHANGED
828
829        # Change cursor based on position
830        ff = self.GetMode(pt)
831
832        if ff == wx.BOTH:
833            self.SetCursor(self._sashCursorSIZING)
834
835        elif ff == wx.VERTICAL:
836            self.SetCursor(self._sashCursorWE)
837
838        elif ff == wx.HORIZONTAL:
839            self.SetCursor(self._sashCursorNS)
840
841        else:
842            self.SetCursor(wx.STANDARD_CURSOR)
843
844        event.Skip()
845
846
847    def OnPaint(self, event):
848        """
849        Handles the ``wx.EVT_PAINT`` event for :class:`FourWaySplitter`.
850
851        :param `event`: a :class:`PaintEvent` event to be processed.
852        """
853
854        dc = wx.PaintDC(self)
855        self.DrawSplitter(dc)
856
857
858    def OnSize(self, event):
859        """
860        Handles the ``wx.EVT_SIZE`` event for :class:`FourWaySplitter`.
861
862        :param `event`: a :class:`wx.SizeEvent` event to be processed.
863        """
864
865        parent = wx.GetTopLevelParent(self)
866        if parent.IsIconized():
867            event.Skip()
868            return
869
870        self._SizeWindows()
871
872
873    def DoSendChangingEvent(self, pt):
874        """
875        Sends a ``EVT_SPLITTER_SASH_POS_CHANGING`` event.
876
877        :param `pt`: the point at which the splitter is being positioned.
878        """
879
880        # send the event
881        event = FourWaySplitterEvent(wx.wxEVT_COMMAND_SPLITTER_SASH_POS_CHANGING, self)
882        event.SetSashIdx(self._mode)
883        event.SetSashPosition(pt)
884
885        if self.GetEventHandler().ProcessEvent(event) and not event.IsAllowed():
886            # the event handler vetoed the change or missing event.Skip()
887            return False
888        else:
889            # or it might have changed the value
890            return True
891
892
893    def _GetSashSize(self):
894        """ Used internally. """
895
896        if self.HasFlag(wx.SP_NOSASH):
897            return 0
898
899        if wx.VERSION >= _RENDER_VER:
900            return wx.RendererNative.Get().GetSplitterParams(self).widthSash
901        else:
902            return 5
903
904
905    def _GetBorderSize(self):
906        """ Used internally. """
907
908        if wx.VERSION >= _RENDER_VER:
909            return wx.RendererNative.Get().GetSplitterParams(self).border
910        else:
911            return 0
912
913
914    # Draw the horizontal split
915    def DrawSplitter(self, dc):
916        """
917        Actually draws the sashes.
918
919        :param `dc`: an instance of :class:`wx.DC`.
920        """
921
922        backColour = self.GetBackgroundColour()
923        dc.SetBrush(wx.Brush(backColour))
924        dc.SetPen(wx.Pen(backColour))
925        dc.Clear()
926
927        if wx.VERSION >= _RENDER_VER:
928            if self.HasFlag(wx.SP_3DBORDER):
929                wx.RendererNative.Get().DrawSplitterBorder(
930                    self, dc, self.GetClientRect())
931        else:
932            barSize = self._GetSashSize()
933
934        # if we are not supposed to use a sash then we're done.
935        if self.HasFlag(wx.SP_NOSASH):
936            return
937
938        flag = 0
939        if self._isHot:
940            flag = wx.CONTROL_CURRENT
941
942        width, height = self.GetSize()
943
944        if self._mode & wx.VERTICAL:
945            if wx.VERSION >= _RENDER_VER:
946                wx.RendererNative.Get().DrawSplitterSash(self, dc,
947                                                         self.GetClientSize(),
948                                                         self._splitx, wx.VERTICAL, flag)
949            else:
950                dc.DrawRectangle(self._splitx, 0, barSize, height)
951
952        if self._mode & wx.HORIZONTAL:
953            if wx.VERSION >= _RENDER_VER:
954                wx.RendererNative.Get().DrawSplitterSash(self, dc,
955                                                         self.GetClientSize(),
956                                                         self._splity, wx.VERTICAL, flag)
957            else:
958                dc.DrawRectangle(0, self._splity, width, barSize)
959
960
961    def DrawTrackSplitter(self, x, y):
962        """
963        Draws a fake sash in case we don't have ``wx.SP_LIVE_UPDATE`` style.
964
965        :param `x`: the `x` position of the sash;
966        :param `y`: the `y` position of the sash.
967
968        :note: This method relies on :class:`ScreenDC` which is currently unavailable on wxMac.
969        """
970
971        # Draw a line to represent the dragging sash, for when not
972        # doing live updates
973        w, h = self.GetClientSize()
974        dc = wx.ScreenDC()
975
976        dc.SetLogicalFunction(wx.INVERT)
977        dc.SetPen(self._sashTrackerPen)
978        dc.SetBrush(wx.TRANSPARENT_BRUSH)
979
980        if self._mode == wx.VERTICAL:
981            x1 = x
982            y1 = 2
983            x2 = x
984            y2 = h-2
985            if x1 > w:
986                x1 = w
987                x2 = w
988            elif x1 < 0:
989                x1 = 0
990                x2 = 0
991
992            x1, y1 = self.ClientToScreen((x1, y1))
993            x2, y2 = self.ClientToScreen((x2, y2))
994
995            dc.DrawLine(x1, y1, x2, y2)
996            dc.SetLogicalFunction(wx.COPY)
997
998        elif self._mode == wx.HORIZONTAL:
999
1000            x1 = 2
1001            y1 = y
1002            x2 = w-2
1003            y2 = y
1004            if y1 > h:
1005                y1 = h
1006                y2 = h
1007            elif y1 < 0:
1008                y1 = 0
1009                y2 = 0
1010
1011            x1, y1 = self.ClientToScreen((x1, y1))
1012            x2, y2 = self.ClientToScreen((x2, y2))
1013
1014            dc.DrawLine(x1, y1, x2, y2)
1015            dc.SetLogicalFunction(wx.COPY)
1016
1017        elif self._mode == wx.BOTH:
1018
1019            x1 = 2
1020            x2 = w-2
1021            y1 = y
1022            y2 = y
1023
1024            x1, y1 = self.ClientToScreen((x1, y1))
1025            x2, y2 = self.ClientToScreen((x2, y2))
1026
1027            dc.DrawLine(x1, y1, x2, y2)
1028
1029            x1 = x
1030            x2 = x
1031            y1 = 2
1032            y2 = h-2
1033
1034            x1, y1 = self.ClientToScreen((x1, y1))
1035            x2, y2 = self.ClientToScreen((x2, y2))
1036
1037            dc.DrawLine(x1, y1, x2, y2)
1038            dc.SetLogicalFunction(wx.COPY)
1039
1040
1041    # Change horizontal split [fraction*10000]
1042    def SetHSplit(self, s):
1043        """
1044        Change horizontal split fraction.
1045
1046        :param `s`: the split fraction, which is an integer value between 0 and
1047         10000 (inclusive), indicating how much space to allocate to the leftmost
1048         panes. For example, to split the panes at 35 percent, use::
1049
1050            fourSplitter.SetHSplit(3500)
1051
1052        """
1053
1054        if s < 0: s = 0
1055        if s > 10000: s  =10000
1056        if s != self._fhor:
1057            self._fhor = s
1058            self._SizeWindows()
1059
1060
1061    # Change vertical split [fraction*10000]
1062    def SetVSplit(self, s):
1063        """
1064        Change vertical split fraction.
1065
1066        :param `s`: the split fraction, which is an integer value between 0 and
1067         10000 (inclusive), indicating how much space to allocate to the topmost
1068         panes. For example, to split the panes at 35 percent, use::
1069
1070            fourSplitter.SetVSplit(3500)
1071
1072        """
1073
1074        if s < 0: s = 0
1075        if s > 10000: s  =10000
1076        if s != self._fver:
1077            self._fver = s
1078            self._SizeWindows()
1079
1080
1081    # Expand one or all of the four panes
1082    def SetExpanded(self, expanded):
1083        """
1084        This method is used to expand one of the four window to fill the
1085        whole client size (when `expanded` >= 0) or to return to the four-window
1086        view (when `expanded` < 0).
1087
1088        :param `expanded`: an integer >= 0 to expand a window to fill the whole
1089         client size, or an integer < 0 to return to the four-window view.
1090        """
1091
1092        if expanded >= 4:
1093            raise Exception("ERROR: SetExpanded: index out of range: %d"%expanded)
1094
1095        if self._expanded != expanded:
1096            self._expanded = expanded
1097            self._SizeWindows()
1098
1099
1100
1101if __name__ == '__main__':
1102
1103    import wx
1104
1105    class MyFrame(wx.Frame):
1106
1107        def __init__(self, parent):
1108
1109            wx.Frame.__init__(self, parent, -1, "FourWaySplitter Demo")
1110
1111            splitter = FourWaySplitter(self, -1, agwStyle=wx.SP_LIVE_UPDATE)
1112
1113            # Put in some coloured panels...
1114            for colour in [wx.RED, wx.WHITE, wx.BLUE, wx.GREEN]:
1115
1116                panel = wx.Panel(splitter)
1117                panel.SetBackgroundColour(colour)
1118
1119                splitter.AppendWindow(panel)
1120
1121
1122    # our normal wxApp-derived class, as usual
1123
1124    app = wx.App(0)
1125
1126    frame = MyFrame(None)
1127    app.SetTopWindow(frame)
1128    frame.Show()
1129
1130    app.MainLoop()
1131