diff --git a/enaml/wx/wx_split_item.py b/enaml/wx/wx_split_item.py index 19f315ce..1b1c2b52 100644 --- a/enaml/wx/wx_split_item.py +++ b/enaml/wx/wx_split_item.py @@ -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): @@ -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. @@ -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. @@ -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 diff --git a/enaml/wx/wx_splitter.py b/enaml/wx/wx_splitter.py index 785b28ec..03383b63 100644 --- a/enaml/wx/wx_splitter.py +++ b/enaml/wx/wx_splitter.py @@ -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 @@ -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. @@ -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