| 1 | #---------------------------------------------------------------------- |
| 2 | # Name: wxPython.lib.grids |
| 3 | # Purpose: An example sizer derived from the C++ wxPySizer that |
| 4 | # sizes items in a fixed or flexible grid. |
| 5 | # |
| 6 | # Author: Robin Dunn |
| 7 | # |
| 8 | # Created: 21-Sept-1999 |
| 9 | # RCS-ID: $Id$ |
| 10 | # Copyright: (c) 1999 by Total Control Software |
| 11 | # Licence: wxWindows license |
| 12 | #---------------------------------------------------------------------- |
| 13 | # 12/07/2003 - Jeff Grimmett (grimmtooth@softhome.net) |
| 14 | # |
| 15 | # o 2.5 Compatability changes |
| 16 | # |
| 17 | # 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net) |
| 18 | # |
| 19 | # o In keeping with the common idiom, the sizers in this module |
| 20 | # have been given the 'Py' prefix to avoid confusion with the |
| 21 | # native sizers of the same name. However, the reverse renamer |
| 22 | # still has the old wx*Sizer since the whole point of the |
| 23 | # reverse renamer is backward compatability. |
| 24 | # o wxGridSizer -> PyGridSizer |
| 25 | # o wxFlexGridSizer -> PyFlexGridSizer |
| 26 | # o Deprecation warning added. |
| 27 | # |
| 28 | |
| 29 | """ |
| 30 | In this module you will find PyGridSizer and PyFlexGridSizer. Please |
| 31 | note that these sizers have since been ported to C++ (as wx.GridSizer |
| 32 | and wx.FlexGridSizer) and those versions are now exposed in the regular |
| 33 | wxPython wrappers. However I am also leaving them here in the library |
| 34 | so they can serve as an example of how to implement sizers in Python. |
| 35 | |
| 36 | PyGridSizer: Sizes and positions items such that all rows are the same |
| 37 | height and all columns are the same width. You can specify a gap in |
| 38 | pixels to be used between the rows and/or the columns. When you |
| 39 | create the sizer you specify the number of rows or the number of |
| 40 | columns and then as you add items it figures out the other dimension |
| 41 | automatically. Like other sizers, items can be set to fill their |
| 42 | available space, or to be aligned on a side, in a corner, or in the |
| 43 | center of the space. When the sizer is resized, all the items are |
| 44 | resized the same amount so all rows and all columns remain the same |
| 45 | size. |
| 46 | |
| 47 | PyFlexGridSizer: Derives from PyGridSizer and adds the ability for |
| 48 | particular rows and/or columns to be marked as growable. This means |
| 49 | that when the sizer changes size, the growable rows and colums are the |
| 50 | ones that stretch. The others remain at their initial size. |
| 51 | """ |
| 52 | |
| 53 | |
| 54 | import operator |
| 55 | import warnings |
| 56 | import wx |
| 57 | |
| 58 | warningmsg = r"""\ |
| 59 | |
| 60 | ################################################\ |
| 61 | # THIS MODULE IS DEPRECATED | |
| 62 | # | |
| 63 | # You should use the native wx.GridSizer and | |
| 64 | # wx.FlexGridSizer unless there is a compelling | |
| 65 | # need to use this module. | |
| 66 | ################################################/ |
| 67 | |
| 68 | |
| 69 | """ |
| 70 | |
| 71 | warnings.warn(warningmsg, DeprecationWarning, stacklevel=2) |
| 72 | |
| 73 | |
| 74 | #---------------------------------------------------------------------- |
| 75 | |
| 76 | class PyGridSizer(wx.PySizer): |
| 77 | def __init__(self, rows=0, cols=0, hgap=0, vgap=0): |
| 78 | wx.PySizer.__init__(self) |
| 79 | if rows == 0 and cols == 0: |
| 80 | raise ValueError, "rows and cols cannot both be zero" |
| 81 | |
| 82 | self.rows = rows |
| 83 | self.cols = cols |
| 84 | self.hgap = hgap |
| 85 | self.vgap = vgap |
| 86 | |
| 87 | |
| 88 | def SetRows(self, rows): |
| 89 | if rows == 0 and self.cols == 0: |
| 90 | raise ValueError, "rows and cols cannot both be zero" |
| 91 | self.rows = rows |
| 92 | |
| 93 | def SetColumns(self, cols): |
| 94 | if self.rows == 0 and cols == 0: |
| 95 | raise ValueError, "rows and cols cannot both be zero" |
| 96 | self.cols = cols |
| 97 | |
| 98 | def GetRows(self): |
| 99 | return self.rows |
| 100 | |
| 101 | def GetColumns(self): |
| 102 | return self.cols |
| 103 | |
| 104 | def SetHgap(self, hgap): |
| 105 | self.hgap = hgap |
| 106 | |
| 107 | def SetVgap(self, vgap): |
| 108 | self.vgap = vgap |
| 109 | |
| 110 | def GetHgap(self, hgap): |
| 111 | return self.hgap |
| 112 | |
| 113 | def GetVgap(self, vgap): |
| 114 | return self.vgap |
| 115 | |
| 116 | #-------------------------------------------------- |
| 117 | def CalcMin(self): |
| 118 | items = self.GetChildren() |
| 119 | nitems = len(items) |
| 120 | nrows = self.rows |
| 121 | ncols = self.cols |
| 122 | |
| 123 | if ncols > 0: |
| 124 | nrows = (nitems + ncols-1) / ncols |
| 125 | else: |
| 126 | ncols = (nitems + nrows-1) / nrows |
| 127 | |
| 128 | # Find the max width and height for any component. |
| 129 | w = 0 |
| 130 | h = 0 |
| 131 | for item in items: |
| 132 | size = item.CalcMin() |
| 133 | w = max(w, size.width) |
| 134 | h = max(h, size.height) |
| 135 | |
| 136 | return wx.Size(ncols * w + (ncols-1) * self.hgap, |
| 137 | nrows * h + (nrows-1) * self.vgap) |
| 138 | |
| 139 | |
| 140 | #-------------------------------------------------- |
| 141 | def RecalcSizes(self): |
| 142 | items = self.GetChildren() |
| 143 | if not items: |
| 144 | return |
| 145 | |
| 146 | nitems = len(items) |
| 147 | nrows = self.rows |
| 148 | ncols = self.cols |
| 149 | |
| 150 | if ncols > 0: |
| 151 | nrows = (nitems + ncols-1) / ncols |
| 152 | else: |
| 153 | ncols = (nitems + nrows-1) / nrows |
| 154 | |
| 155 | |
| 156 | sz = self.GetSize() |
| 157 | pt = self.GetPosition() |
| 158 | w = (sz.width - (ncols - 1) * self.hgap) / ncols; |
| 159 | h = (sz.height - (nrows - 1) * self.vgap) / nrows; |
| 160 | |
| 161 | x = pt.x |
| 162 | for c in range(ncols): |
| 163 | y = pt.y |
| 164 | for r in range(nrows): |
| 165 | i = r * ncols + c |
| 166 | if i < nitems: |
| 167 | self.SetItemBounds(items[i], x, y, w, h) |
| 168 | |
| 169 | y = y + h + self.vgap |
| 170 | |
| 171 | x = x + w + self.hgap |
| 172 | |
| 173 | |
| 174 | #-------------------------------------------------- |
| 175 | def SetItemBounds(self, item, x, y, w, h): |
| 176 | # calculate the item's size and position within |
| 177 | # its grid cell |
| 178 | ipt = wx.Point(x, y) |
| 179 | isz = item.CalcMin() |
| 180 | flag = item.GetFlag() |
| 181 | |
| 182 | if flag & wx.EXPAND or flag & wx.SHAPED: |
| 183 | isz = (w, h) |
| 184 | else: |
| 185 | if flag & wx.ALIGN_CENTER_HORIZONTAL: |
| 186 | ipt.x = x + (w - isz.width) / 2 |
| 187 | elif flag & wx.ALIGN_RIGHT: |
| 188 | ipt.x = x + (w - isz.width) |
| 189 | |
| 190 | if flag & wx.ALIGN_CENTER_VERTICAL: |
| 191 | ipt.y = y + (h - isz.height) / 2 |
| 192 | elif flag & wx.ALIGN_BOTTOM: |
| 193 | ipt.y = y + (h - isz.height) |
| 194 | |
| 195 | item.SetDimension(ipt, isz) |
| 196 | |
| 197 | |
| 198 | #---------------------------------------------------------------------- |
| 199 | |
| 200 | |
| 201 | |
| 202 | class PyFlexGridSizer(PyGridSizer): |
| 203 | def __init__(self, rows=0, cols=0, hgap=0, vgap=0): |
| 204 | PyGridSizer.__init__(self, rows, cols, hgap, vgap) |
| 205 | self.rowHeights = [] |
| 206 | self.colWidths = [] |
| 207 | self.growableRows = [] |
| 208 | self.growableCols = [] |
| 209 | |
| 210 | def AddGrowableRow(self, idx): |
| 211 | self.growableRows.append(idx) |
| 212 | |
| 213 | def AddGrowableCol(self, idx): |
| 214 | self.growableCols.append(idx) |
| 215 | |
| 216 | #-------------------------------------------------- |
| 217 | def CalcMin(self): |
| 218 | items = self.GetChildren() |
| 219 | nitems = len(items) |
| 220 | nrows = self.rows |
| 221 | ncols = self.cols |
| 222 | |
| 223 | if ncols > 0: |
| 224 | nrows = (nitems + ncols-1) / ncols |
| 225 | else: |
| 226 | ncols = (nitems + nrows-1) / nrows |
| 227 | |
| 228 | # Find the max width and height for any component. |
| 229 | self.rowHeights = [0] * nrows |
| 230 | self.colWidths = [0] * ncols |
| 231 | |
| 232 | for i in range(len(items)): |
| 233 | size = items[i].CalcMin() |
| 234 | row = i / ncols |
| 235 | col = i % ncols |
| 236 | self.rowHeights[row] = max(size.height, self.rowHeights[row]) |
| 237 | self.colWidths[col] = max(size.width, self.colWidths[col]) |
| 238 | |
| 239 | # Add up all the widths and heights |
| 240 | cellsWidth = reduce(operator.__add__, self.colWidths) |
| 241 | cellHeight = reduce(operator.__add__, self.rowHeights) |
| 242 | |
| 243 | return wx.Size(cellsWidth + (ncols-1) * self.hgap, |
| 244 | cellHeight + (nrows-1) * self.vgap) |
| 245 | |
| 246 | |
| 247 | #-------------------------------------------------- |
| 248 | def RecalcSizes(self): |
| 249 | items = self.GetChildren() |
| 250 | if not items: |
| 251 | return |
| 252 | |
| 253 | nitems = len(items) |
| 254 | nrows = self.rows |
| 255 | ncols = self.cols |
| 256 | |
| 257 | if ncols > 0: |
| 258 | nrows = (nitems + ncols-1) / ncols |
| 259 | else: |
| 260 | ncols = (nitems + nrows-1) / nrows |
| 261 | |
| 262 | minsz = self.CalcMin() |
| 263 | sz = self.GetSize() |
| 264 | pt = self.GetPosition() |
| 265 | |
| 266 | # Check for growables |
| 267 | if self.growableRows and sz.height > minsz.height: |
| 268 | delta = (sz.height - minsz.height) / len(self.growableRows) |
| 269 | for idx in self.growableRows: |
| 270 | self.rowHeights[idx] = self.rowHeights[idx] + delta |
| 271 | |
| 272 | if self.growableCols and sz.width > minsz.width: |
| 273 | delta = (sz.width - minsz.width) / len(self.growableCols) |
| 274 | for idx in self.growableCols: |
| 275 | self.colWidths[idx] = self.colWidths[idx] + delta |
| 276 | |
| 277 | # bottom right corner |
| 278 | sz = wx.Size(pt.x + sz.width, pt.y + sz.height) |
| 279 | |
| 280 | # Layout each cell |
| 281 | x = pt.x |
| 282 | for c in range(ncols): |
| 283 | y = pt.y |
| 284 | for r in range(nrows): |
| 285 | i = r * ncols + c |
| 286 | |
| 287 | if i < nitems: |
| 288 | w = max(0, min(self.colWidths[c], sz.width - x)) |
| 289 | h = max(0, min(self.rowHeights[r], sz.height - y)) |
| 290 | self.SetItemBounds(items[i], x, y, w, h) |
| 291 | |
| 292 | y = y + self.rowHeights[r] + self.vgap |
| 293 | |
| 294 | x = x + self.colWidths[c] + self.hgap |
| 295 | |
| 296 | #---------------------------------------------------------------------- |
| 297 | |
| 298 | |
| 299 | |
| 300 | |
| 301 | |
| 302 | |