Skip to content
This repository has been archived by the owner on May 24, 2021. It is now read-only.

Commit

Permalink
Update the wx splitter implementation.
Browse files Browse the repository at this point in the history
This fixes several issues with the default Wx implementation with
regards to allocating space for the split items. Several private
methods were overridden, but such is life. The best long term
solution is to re-implement this widget from scratch, but this
should suffice for at least a while.
  • Loading branch information
sccolbert committed Jan 10, 2013
1 parent 904406e commit dfa542b
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 17 deletions.
59 changes: 47 additions & 12 deletions enaml/wx/wx_split_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ class wxSplitItem(wx.Panel):
""" A wxPanel subclass which acts as an item in a wxSplitter.
"""
def __init__(self, *args, **kwargs):
def __init__(self, parent):
""" Initialize a wxSplitItem.
Parameters
----------
*args, **kwargs
The position and keyword arguments required to initialize
a wxPanel.
parent : wx.Window
The parent widget of the split item.
"""
super(wxSplitItem, self).__init__(*args, **kwargs)
super(wxSplitItem, self).__init__(parent)
self._split_widget = None
self._stretch = 0
self.SetSizer(wxSingleWidgetSizer())

def GetSplitWidget(self):
Expand All @@ -50,6 +50,28 @@ def SetSplitWidget(self, widget):
self._split_widget = widget
self.GetSizer().Add(widget)

def GetStretch(self):
""" Get the stretch factor for the widget.
Returns
-------
result : int
The stretch factor for the widget.
"""
return self._stretch

def SetStretch(self, stretch):
""" Set the stretch factor for the widget.
Parameters
----------
stretch : int
The stretch factor for the widget.
"""
self._stretch = stretch


class WxSplitItem(WxWidget):
""" A Wx implementation of an Enaml SplitItem.
Expand All @@ -69,7 +91,8 @@ def create(self, tree):
"""
super(WxSplitItem, self).create(tree)
self.set_preferred_size(tree['preferred_size'])
self.set_stretch(tree['stretch'])
self.set_collapsible(tree['collapsible'])

def init_layout(self):
""" Initialize the layout for the underyling widget.
Expand Down Expand Up @@ -117,19 +140,31 @@ def child_added(self, child):
#--------------------------------------------------------------------------
# Message Handlers
#--------------------------------------------------------------------------
def on_action_set_preferred_size(self, content):
""" Handle the 'set_preferred_size' action from the Enaml widget.
def on_action_set_stretch(self, content):
""" Handle the 'set_stretch' action from the Enaml widget.
"""
self.set_preferred_size(content['preferred_size'])
self.set_stretch(content['stretch'])

def on_action_set_collapsible(self, content):
""" Handle the 'set_collapsible' action from the Enaml widget.
"""
self.set_collapsible(content['collapsible'])

#--------------------------------------------------------------------------
# Widget Update Methods
#--------------------------------------------------------------------------
def set_preferred_size(self, size):
""" Set the preferred size for this item in the splitter.
def set_stretch(self, stretch):
""" Set the stretch factor for the underlying widget.
"""
self.widget().SetStretch(stretch)

def set_collapsible(self, collapsible):
""" Set the collapsible flag for the underlying widget.
"""
# XXX implement me
# Not supported on Wx
pass

135 changes: 130 additions & 5 deletions enaml/wx/wx_splitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ class wxSplitter(MultiSplitterWindow):
""" A wx.lib.splitter.MultiSplitterWindow subclass that changes
the behavior of resizing neighbors to be consistent with Qt.
TODO - Fix the problem with the splitter not resizing its children
smaller when possible when the splitter window shrinks.
Fix the problem with initial sash positions.
"""
def _OnMouse(self, event):
""" Overriden parent class mouse event handler which fakes the
Expand All @@ -44,6 +40,136 @@ def _OnMouse(self, event):
event.m_shiftDown = True
return super(wxSplitter, self)._OnMouse(event)

def _GetWindowMin(self, window):
""" Overriden parent class method which properly computes the
window min size.
"""
size = window.GetEffectiveMinSize()
if self._orient == wx.HORIZONTAL:
res = size.GetWidth()
else:
res = size.GetHeight()
return res

def _GetSashSize(self):
""" Overridden parent class method to return a proper sash size
for the custom sash painting.
"""
return 4

def _DrawSash(self, dc):
""" Overridden parent class method which draws a custom sash.
On Windows, the default themed sash drawing causes the sash to
not be visible; this method corrects that problem and draws a
sash which is visibly similar to Enaml's Qt Windows version.
"""
sash_size = self._GetSashSize()
width, height = self.GetClientSize()
light_pen = wx.WHITE_PEN
dark_pen = wx.GREY_PEN
brush = wx.Brush(self.GetBackgroundColour())
if self._orient == wx.HORIZONTAL:
pos = 0
for sash in self._sashes[:-1]:
pos += sash
dc.SetPen(wx.TRANSPARENT_PEN)
dc.SetBrush(brush)
dc.DrawRectangle(pos, 0, sash_size, height)
dc.SetPen(light_pen)
dc.DrawLine(pos + 1, 0, pos + 1, height)
dc.SetPen(dark_pen)
dc.DrawLine(pos + 2, 0, pos + 2, height)
pos += sash_size
else:
pos = 0
for sash in self._sashes[:-1]:
pos += sash
dc.SetPen(wx.TRANSPARENT_PEN)
dc.SetBrush(brush)
dc.DrawRectangle(0, pos, width, sash_size)
dc.SetPen(light_pen)
dc.DrawLine(0, pos + 1, width, pos + 1)
dc.SetPen(dark_pen)
dc.DrawLine(0, pos + 2, width, pos + 2)
pos += sash_size

def _OnSize(self, event):
""" Overridden parent class method which resizes the sashes.
The default Wx behavior allocates all extra space to the last
split item, and it will clip the items when the window size is
reduced. This override uses a weighted algorithm to allocate
the free space among the items and will not allow the items
to be clipped by a window resize.
"""
# Pre-fetch some commonly used objects
get_min = self._GetWindowMin
windows = self._windows
sashes = self._sashes

# Compute the total space available for the sashes
sash_widths = self._GetSashSize() * (len(windows) - 1)
offset = sash_widths + 2 * self._GetBorderSize()
if self._orient == wx.HORIZONTAL:
free_space = self.GetClientSize().GetWidth() - offset
else:
free_space = self.GetClientSize().GetHeight() - offset

# Compute the effective stretch factors for each window. The
# effective stretch factor is the greater of the current or
# minimum width of the window, multiplied by the window's
# stretch factor.
parts = []
total_stretch = 0
for idx, (sash, window) in enumerate(zip(sashes, windows)):
minw = get_min(window)
if sash < minw:
sash = sashes[idx] = minw
stretch = window.GetStretch() * sash
parts.append((stretch, idx, minw, window))
total_stretch += stretch

# Add (or remove) the extra space by fairly allocating it to
# each window based on their effective stretch factor.
diff_space = free_space - sum(sashes)
for stretch, idx, minw, window in parts:
if stretch > 0:
d = diff_space * stretch / total_stretch
new = max(sashes[idx] + d, minw)
sashes[idx] = new

# Since the windows are clipped to their minimum width, it's
# possible that the current space occupied by the windows will
# be too large. In that case, the overage is distributed to the
# windows fairly, based on their relative capacity for shrink.
curr_space = sum(sashes)
if curr_space > free_space:
diffs = []
total_diff = 0
for stretch, idx, minw, window in parts:
diff = sashes[idx] - minw
if diff > 0:
diffs.append((diff, window, idx, minw))
total_diff += diff
remaining = curr_space - free_space
diffs.sort()
for diff, window, idx, minw in reversed(diffs):
delta = remaining * diff / total_diff
old = sashes[idx]
new = max(old - delta, minw)
actual_diff = old - new
remaining -= actual_diff
total_diff -= actual_diff
sashes[idx] = new

# The superclass handler which will actually perform the layout.
super(wxSplitter, self)._OnSize(event)


class WxSplitter(WxConstraintsWidget):
""" A Wx implementation of an Enaml Splitter.
Expand Down Expand Up @@ -74,7 +200,6 @@ def init_layout(self):
for child in self.children():
if isinstance(child, WxSplitItem):
widget.AppendWindow(child.widget())
widget.SizeWindows()

#--------------------------------------------------------------------------
# Child Events
Expand Down

0 comments on commit dfa542b

Please sign in to comment.