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