--- /dev/null
+#----------------------------------------------------------------------
+# Name: sized_controls.py
+# Purpose: Implements default, HIG-compliant sizers under the hood
+# and provides a simple interface for customizing those sizers.
+#
+# Author: Kevin Ollivier
+#
+# Created: 26-May-2006
+# Copyright: (c) 2006 Kevin Ollivier
+# Licence: wxWindows license
+#----------------------------------------------------------------------
+
+import wx
+
+# For HIG info: links to all the HIGs can be found here:
+# http://en.wikipedia.org/wiki/Human_Interface_Guidelines
+
+
+# useful defines for sizer prop values
+
+halign = { "left": wx.ALIGN_LEFT,
+ "center": wx.ALIGN_CENTER_HORIZONTAL,
+ "centre": wx.ALIGN_CENTRE_HORIZONTAL,
+ "right": wx.ALIGN_RIGHT,
+ }
+
+valign = { "top": wx.ALIGN_TOP,
+ "bottom": wx.ALIGN_BOTTOM,
+ "center": wx.ALIGN_CENTER_VERTICAL,
+ "centre": wx.ALIGN_CENTRE_VERTICAL,
+ }
+
+align = { "center": wx.ALIGN_CENTER,
+ "centre": wx.ALIGN_CENTRE,
+ }
+
+border = { "left": wx.LEFT,
+ "right": wx.RIGHT,
+ "top": wx.TOP,
+ "bottom": wx.BOTTOM,
+ "all": wx.ALL,
+ }
+
+minsize = { "fixed": wx.FIXED_MINSIZE,
+ "adjust": wx.ADJUST_MINSIZE,
+ }
+
+misc_flags = { "expand": wx.EXPAND, }
+
+
+# My attempt at creating a more intuitive replacement for nesting box sizers
+class TableSizer(wx.PySizer):
+ def __init__(self, rows=0, cols=0):
+ wx.PySizer.__init__(self)
+ self.rows = rows
+ self.cols = cols
+ self.fixed_width = 0
+ self.fixed_height = 0
+ self.hgrow = 0
+ self.vgrow = 0
+
+ self.row_widths = []
+ self.col_heights = []
+
+ # allow us to use 'old-style' proportions when emulating box sizers
+ self.isHorizontal = (self.rows == 1 and self.cols == 0)
+ self.isVertical = (self.cols == 1 and self.rows == 0)
+
+ def CalcNumRowsCols(self):
+ numrows = self.rows
+ numcols = self.cols
+ numchild = len(self.GetChildren())
+
+ if numrows == 0 and numcols == 0:
+ return 0, 0
+
+ if numrows == 0:
+ rows, mod = divmod(numchild, self.cols)
+ if mod > 0:
+ rows += 1
+ numrows = rows
+
+ if numcols == 0:
+ cols, mod = divmod(numchild, self.rows)
+ if mod > 0:
+ cols += 1
+ numcols = cols
+
+ return numrows, numcols
+
+ def CalcMin(self):
+ numrows, numcols = self.CalcNumRowsCols()
+ numchild = len(self.GetChildren())
+
+ if numchild == 0:
+ return wx.Size(10, 10)
+
+ if numrows == 0 and numcols == 0:
+ print "TableSizer must have the number of rows or columns set. Cannot continue."
+ return wx.Size(10, 10)
+
+ self.row_widths = [0 for x in range(0, numrows)]
+ self.col_heights = [0 for x in range(0, numcols)]
+ currentRow = 0
+ currentCol = 0
+ counter = 0
+ self.hgrow = 0
+ self.vgrow = 0
+
+ # get the max row width and max column height
+ for item in self.GetChildren():
+ if self.cols != 0:
+ currentRow, currentCol = divmod(counter, numcols)
+ else:
+ currentCol, currentRow = divmod(counter, numrows)
+
+ if item.IsShown():
+ width, height = item.CalcMin()
+
+ if self.isVertical and item.GetProportion() > 0:
+ self.hgrow += item.GetProportion()
+ elif self.isHorizontal and item.GetProportion() > 0:
+ self.vgrow += item.GetProportion()
+
+ if width > self.row_widths[currentRow]:
+ self.row_widths[currentRow] = width
+
+ if height > self.col_heights[currentCol]:
+ self.col_heights[currentCol] = height
+
+ counter += 1
+
+ minwidth = 0
+ for row_width in self.row_widths:
+ minwidth += row_width
+
+ minheight = 0
+ for col_height in self.col_heights:
+ minheight += col_height
+
+ self.fixed_width = minwidth
+ self.fixed_height = minheight
+
+ return wx.Size(minwidth, minheight)
+
+ def RecalcSizes(self):
+ numrows, numcols = self.CalcNumRowsCols()
+ numchild = len(self.GetChildren())
+
+ if numchild == 0:
+ return
+ currentRow = 0
+ currentCol = 0
+ counter = 0
+
+ print "cols %d, rows %d" % (self.cols, self.rows)
+ print "fixed_height %d, fixed_width %d" % (self.fixed_height, self.fixed_width)
+ #print "self.GetSize() = " + `self.GetSize()`
+
+ row_widths = [0 for x in range(0, numrows)]
+ col_heights = [0 for x in range(0, numcols)]
+ item_sizes = [0 for x in range(0, len(self.GetChildren()))]
+ grow_sizes = [0 for x in range(0, len(self.GetChildren()))]
+
+ curHPos = 0
+ curVPos = 0
+ curCol = 0
+ curRow = 0
+ # first, we set sizes for all children, and while doing so, calc
+ # the maximum row heights and col widths. Then, afterwards we handle
+ # the positioning of the controls
+
+ for item in self.GetChildren():
+ if self.cols != 0:
+ currentRow, currentCol = divmod(counter, numcols)
+ else:
+ currentCol, currentRow = divmod(counter, numrows)
+ if item.IsShown():
+ item_minsize = item.GetMinSizeWithBorder()
+ width = item_minsize[0]
+ height = item_minsize[1]
+
+ print "row_height %d, row_width %d" % (self.col_heights[currentCol], self.row_widths[currentRow])
+ growable_width = (self.GetSize()[0]) - width
+ growable_height = (self.GetSize()[1]) - height
+
+ #if not self.isVertical and not self.isHorizontal:
+ # growable_width = self.GetSize()[0] - self.row_widths[currentRow]
+ # growable_height = self.GetSize()[1] - self.col_heights[currentCol]
+
+ #print "grow_height %d, grow_width %d" % (growable_height, growable_width)
+
+ item_vgrow = 0
+ item_hgrow = 0
+ # support wx.EXPAND for box sizers to be compatible
+ if item.GetFlag() & wx.EXPAND:
+ if self.isVertical:
+ if self.hgrow > 0 and item.GetProportion() > 0:
+ item_hgrow = (growable_width * item.GetProportion()) / self.hgrow
+ item_vgrow = growable_height
+
+ elif self.isHorizontal:
+ if self.vgrow > 0 and item.GetProportion() > 0:
+ item_vgrow = (growable_height * item.GetProportion()) / self.vgrow
+ item_hgrow = growable_width
+
+ if growable_width > 0 and item.GetHGrow() > 0:
+ item_hgrow = (growable_width * item.GetHGrow()) / 100
+ print "hgrow = %d" % (item_hgrow)
+
+ if growable_height > 0 and item.GetVGrow() > 0:
+ item_vgrow = (growable_height * item.GetVGrow()) / 100
+ print "vgrow = %d" % (item_vgrow)
+
+ grow_size = wx.Size(item_hgrow, item_vgrow)
+ size = item_minsize #wx.Size(item_minsize[0] + item_hgrow, item_minsize[1] + item_vgrow)
+ if size[0] + grow_size[0] > row_widths[currentRow]:
+ row_widths[currentRow] = size[0] + grow_size[0]
+ if size[1] + grow_size[1] > col_heights[currentCol]:
+ col_heights[currentCol] = size[1] + grow_size[1]
+
+ grow_sizes[counter] = grow_size
+ item_sizes[counter] = size
+
+ counter += 1
+
+ counter = 0
+ for item in self.GetChildren():
+ if self.cols != 0:
+ currentRow, currentCol = divmod(counter, numcols)
+ else:
+ currentCol, currentRow = divmod(counter, numrows)
+
+ itempos = self.GetPosition()
+ if item.IsShown():
+ rowstart = itempos[0]
+ for row in range(0, currentRow):
+ rowstart += row_widths[row]
+
+ colstart = itempos[1]
+ for col in range(0, currentCol):
+ #print "numcols = %d, currentCol = %d, col = %d" % (numcols, currentCol, col)
+ colstart += col_heights[col]
+
+ itempos[0] += rowstart
+ itempos[1] += colstart
+
+ if item.GetFlag() & wx.ALIGN_RIGHT:
+ itempos[0] += (row_widths[currentRow] - item_sizes[counter][0])
+ elif item.GetFlag() & (wx.ALIGN_CENTER | wx.ALIGN_CENTER_HORIZONTAL):
+ itempos[0] += (row_widths[currentRow] - item_sizes[counter][0]) / 2
+
+ if item.GetFlag() & wx.ALIGN_BOTTOM:
+ itempos[1] += (col_heights[currentCol] - item_sizes[counter][1])
+ elif item.GetFlag() & (wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL):
+ itempos[1] += (col_heights[currentCol] - item_sizes[counter][1]) / 2
+
+ hgrowth = (grow_sizes[counter][0] - itempos[0])
+ if hgrowth > 0:
+ item_sizes[counter][0] += hgrowth
+
+ vgrowth = (grow_sizes[counter][1] - itempos[1])
+ if vgrowth > 0:
+ item_sizes[counter][1] += vgrowth
+ #item_sizes[counter][1] -= itempos[1]
+ item.SetDimension(itempos, item_sizes[counter])
+
+ counter += 1
+
+def GetDefaultBorder(self):
+ border = 4
+ if wx.Platform == "__WXMAC__":
+ border = 6
+ elif wx.Platform == "__WXMSW__":
+ # MSW HIGs use dialog units, not pixels
+ pnt = self.ConvertDialogPointToPixels(wx.Point(4, 4))
+ border = pnt[0]
+ elif wx.Platform == "__WXGTK__":
+ border = 3
+
+ return border
+
+def SetDefaultSizerProps(self):
+ item = self.GetParent().GetSizer().GetItem(self)
+ item.SetProportion(0)
+ item.SetFlag(wx.ALL)
+ item.SetBorder(self.GetDefaultBorder())
+
+def GetSizerProps(self):
+ """
+ Returns a dictionary of prop name + value
+ """
+ props = {}
+ item = self.GetParent().GetSizer().GetItem(self)
+
+ props['proportion'] = item.GetProportion()
+ flags = item.GetFlag()
+
+ if flags & border['all'] == border['all']:
+ props['border'] = (['all'], item.GetBorder())
+ else:
+ borders = []
+ for key in border:
+ if flags & border[key]:
+ borders.append(key)
+
+ props['border'] = (borders, item.GetBorder())
+
+ if flags & align['center'] == align['center']:
+ props['align'] = 'center'
+ else:
+ for key in halign:
+ if flags & halign[key]:
+ props['halign'] = key
+
+ for key in valign:
+ if flags & valign[key]:
+ props['valign'] = key
+
+ for key in minsize:
+ if flags & minsize[key]:
+ props['minsize'] = key
+
+ for key in misc_flags:
+ if flags & misc_flags[key]:
+ props[key] = "true"
+
+ return props
+
+def SetSizerProp(self, prop, value):
+
+ lprop = prop.lower()
+ item = self.GetParent().GetSizer().GetItem(self)
+ flag = item.GetFlag()
+ if lprop == "proportion":
+ item.SetProportion(int(value))
+ elif lprop == "hgrow":
+ item.SetHGrow(int(value))
+ elif lprop == "vgrow":
+ item.SetVGrow(int(value))
+ elif lprop == "align":
+ flag = flag | align[value]
+ elif lprop == "halign":
+ flag = flag | halign[value]
+ elif lprop == "valign":
+ flag = flag | valign[value]
+ elif lprop == "border":
+ # this arg takes a tuple (dir, pixels)
+ dirs, amount = value
+ if dirs == "all":
+ dirs = ["all"]
+ for dir in dirs:
+ flag = flag | border[dir]
+ item.SetBorder(amount)
+ elif lprop == "minsize":
+ flag = flag | minsize[value]
+ elif lprop in misc_flags:
+ if not value or str(value) == "" or str(value).lower() == "false":
+ flag = flag &~ misc_flags[lprop]
+ else:
+ flag = flag | misc_flags[lprop]
+
+ item.SetFlag(flag)
+
+def SetSizerProps(self, props={}, **kwargs):
+ allprops = {}
+ allprops.update(props)
+ allprops.update(kwargs)
+
+ for prop in allprops:
+ self.SetSizerProp(prop, allprops[prop])
+
+def GetDialogBorder(self):
+ border = 6
+ if wx.Platform == "__WXMAC__" or wx.Platform == "__WXGTK__":
+ border = 12
+ elif wx.Platform == "__WXMSW__":
+ pnt = self.ConvertDialogPointToPixels(wx.Point(7, 7))
+ border = pnt[0]
+
+ return border
+
+def SetHGrow(self, proportion):
+ data = self.GetUserData()
+ if "HGrow" in data:
+ data["HGrow"] = proportion
+ self.SetUserData(data)
+
+def GetHGrow(self):
+ if self.GetUserData() and "HGrow" in self.GetUserData():
+ return self.GetUserData()["HGrow"]
+ else:
+ return 0
+
+def SetVGrow(self, proportion):
+ data = self.GetUserData()
+ if "VGrow" in data:
+ data["VGrow"] = proportion
+ self.SetUserData(data)
+
+
+def GetVGrow(self):
+ if self.GetUserData() and "VGrow" in self.GetUserData():
+ return self.GetUserData()["VGrow"]
+ else:
+ return 0
+
+def GetDefaultPanelBorder(self):
+ # child controls will handle their borders, so don't pad the panel.
+ return 0
+
+# Why, Python?! Why do you make it so easy?! ;-)
+wx.Dialog.GetDialogBorder = GetDialogBorder
+wx.Panel.GetDefaultBorder = GetDefaultPanelBorder
+wx.Notebook.GetDefaultBorder = GetDefaultPanelBorder
+wx.SplitterWindow.GetDefaultBorder = GetDefaultPanelBorder
+
+wx.Window.GetDefaultBorder = GetDefaultBorder
+wx.Window.SetDefaultSizerProps = SetDefaultSizerProps
+wx.Window.SetSizerProp = SetSizerProp
+wx.Window.SetSizerProps = SetSizerProps
+wx.Window.GetSizerProps = GetSizerProps
+
+wx.SizerItem.SetHGrow = SetHGrow
+wx.SizerItem.GetHGrow = GetHGrow
+wx.SizerItem.SetVGrow = SetVGrow
+wx.SizerItem.GetVGrow = GetVGrow
+
+
+class SizedPanel(wx.PyPanel):
+ def __init__(self, *args, **kwargs):
+ wx.PyPanel.__init__(self, *args, **kwargs)
+ sizer = wx.BoxSizer(wx.VERTICAL) #TableSizer(1, 0)
+ self.SetSizer(sizer)
+ self.sizerType = "vertical"
+
+ def AddChild(self, child):
+ wx.PyPanel.base_AddChild(self, child)
+
+ sizer = self.GetSizer()
+ item = sizer.Add(child)
+ item.SetUserData({"HGrow":0, "VGrow":0})
+
+ # Note: One problem is that the child class given to AddChild
+ # is the underlying wxWidgets control, not its Python subclass. So if
+ # you derive your own class, and override that class' GetDefaultBorder(),
+ # etc. methods, it will have no effect.
+ child.SetDefaultSizerProps()
+
+ def GetSizerType(self):
+ return self.sizerType
+
+ def SetSizerType(self, type, options={}):
+ sizer = None
+ self.sizerType = type
+ if type == "horizontal":
+ sizer = wx.BoxSizer(wx.HORIZONTAL) # TableSizer(0, 1)
+
+ elif type == "vertical":
+ sizer = wx.BoxSizer(wx.VERTICAL) # TableSizer(1, 0)
+
+ elif type == "form":
+ #sizer = TableSizer(2, 0)
+ sizer = wx.FlexGridSizer(0, 2, 0, 0)
+ sizer.AddGrowableCol(1)
+
+ elif type == "table":
+ rows = cols = 0
+ if options.has_key('rows'):
+ rows = int(options['rows'])
+
+ if options.has_key('cols'):
+ cols = int(options['cols'])
+
+ sizer = TableSizer(rows, cols)
+
+ elif type == "grid":
+ sizer = wx.FlexGridSizer(0, 0, 0, 0)
+ if options.has_key('rows'):
+ sizer.SetRows(int(options['rows']))
+ else:
+ sizer.SetRows(0)
+ if options.has_key('cols'):
+ sizer.SetCols(int(options['cols']))
+ else:
+ sizer.SetCols(0)
+
+ if options.has_key('growable_row'):
+ row, proportion = options['growable_row']
+ sizer.SetGrowableRow(row, proportion)
+
+ if options.has_key('growable_col'):
+ col, proportion = options['growable_col']
+ sizer.SetGrowableCol(col, proportion)
+
+ if options.has_key('hgap'):
+ sizer.SetHGap(options['hgap'])
+
+ if options.has_key('vgap'):
+ sizer.SetVGap(options['vgap'])
+ if sizer:
+ self._SetNewSizer(sizer)
+
+ def _SetNewSizer(self, sizer):
+ props = {}
+ for child in self.GetChildren():
+ props[child.GetId()] = child.GetSizerProps()
+ self.GetSizer().Detach(child)
+
+ wx.PyPanel.SetSizer(self, sizer)
+
+ for child in self.GetChildren():
+ self.GetSizer().Add(child)
+ child.SetSizerProps(props[child.GetId()])
+
+class SizedDialog(wx.Dialog):
+ def __init__(self, *args, **kwargs):
+ wx.Dialog.__init__(self, *args, **kwargs)
+
+ self.borderLen = 12
+ self.mainPanel = SizedPanel(self, -1)
+
+ mysizer = wx.BoxSizer(wx.VERTICAL)
+ mysizer.Add(self.mainPanel, 1, wx.EXPAND | wx.ALL, self.GetDialogBorder())
+ self.SetSizer(mysizer)
+
+ self.SetAutoLayout(True)
+
+ def GetContentsPane(self):
+ return self.mainPanel
+
+ def SetButtonSizer(self, sizer):
+ self.GetSizer().Add(sizer, 0, wx.EXPAND | wx.BOTTOM | wx.RIGHT, self.GetDialogBorder())
+
+ # Temporary hack to fix button ordering problems.
+ cancel = self.FindWindowById(wx.ID_CANCEL)
+ no = self.FindWindowById(wx.ID_NO)
+ if no and cancel:
+ cancel.MoveAfterInTabOrder(no)
+
+class SizedFrame(wx.Frame):
+ def __init__(self, *args, **kwargs):
+ wx.Frame.__init__(self, *args, **kwargs)
+
+ self.borderLen = 12
+ # this probably isn't needed, but I thought it would help to make it consistent
+ # with SizedDialog, and creating a panel to hold things is often good practice.
+ self.mainPanel = SizedPanel(self, -1)
+
+ mysizer = wx.BoxSizer(wx.VERTICAL)
+ mysizer.Add(self.mainPanel, 1, wx.EXPAND)
+ self.SetSizer(mysizer)
+
+ self.SetAutoLayout(True)
+
+ def GetContentsPane(self):
+ return self.mainPanel
\ No newline at end of file