From: Kevin Ollivier Date: Fri, 17 Nov 2006 16:12:36 +0000 (+0000) Subject: Adding a copy of sized_controls.py to the wxPython distro so I can use it for develop... X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/61574b34df542a0ea96a153b2bad1537c50d40a7 Adding a copy of sized_controls.py to the wxPython distro so I can use it for developing GUIs. :-) git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@43472 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/wxPython/wxaddons/sized_controls.py b/wxPython/wxaddons/sized_controls.py new file mode 100644 index 0000000000..67a839fa09 --- /dev/null +++ b/wxPython/wxaddons/sized_controls.py @@ -0,0 +1,557 @@ +#---------------------------------------------------------------------- +# 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