]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxaddons/sized_controls.py
Have SetSizerProps internally handle the assignment of growable rows and cols.
[wxWidgets.git] / wxPython / wxaddons / sized_controls.py
1 #----------------------------------------------------------------------
2 # Name: sized_controls.py
3 # Purpose: Implements default, HIG-compliant sizers under the hood
4 # and provides a simple interface for customizing those sizers.
5 #
6 # Author: Kevin Ollivier
7 #
8 # Created: 26-May-2006
9 # Copyright: (c) 2006 Kevin Ollivier
10 # Licence: wxWindows license
11 #----------------------------------------------------------------------
12
13 import wx
14
15 # For HIG info: links to all the HIGs can be found here:
16 # http://en.wikipedia.org/wiki/Human_Interface_Guidelines
17
18
19 # useful defines for sizer prop values
20
21 halign = { "left": wx.ALIGN_LEFT,
22 "center": wx.ALIGN_CENTER_HORIZONTAL,
23 "centre": wx.ALIGN_CENTRE_HORIZONTAL,
24 "right": wx.ALIGN_RIGHT,
25 }
26
27 valign = { "top": wx.ALIGN_TOP,
28 "bottom": wx.ALIGN_BOTTOM,
29 "center": wx.ALIGN_CENTER_VERTICAL,
30 "centre": wx.ALIGN_CENTRE_VERTICAL,
31 }
32
33 align = { "center": wx.ALIGN_CENTER,
34 "centre": wx.ALIGN_CENTRE,
35 }
36
37 border = { "left": wx.LEFT,
38 "right": wx.RIGHT,
39 "top": wx.TOP,
40 "bottom": wx.BOTTOM,
41 "all": wx.ALL,
42 }
43
44 minsize = { "fixed": wx.FIXED_MINSIZE,
45 "adjust": wx.ADJUST_MINSIZE,
46 }
47
48 misc_flags = { "expand": wx.EXPAND, }
49
50
51 # My attempt at creating a more intuitive replacement for nesting box sizers
52 class TableSizer(wx.PySizer):
53 def __init__(self, rows=0, cols=0):
54 wx.PySizer.__init__(self)
55 self.rows = rows
56 self.cols = cols
57 self.fixed_width = 0
58 self.fixed_height = 0
59 self.hgrow = 0
60 self.vgrow = 0
61
62 self.row_widths = []
63 self.col_heights = []
64
65 # allow us to use 'old-style' proportions when emulating box sizers
66 self.isHorizontal = (self.rows == 1 and self.cols == 0)
67 self.isVertical = (self.cols == 1 and self.rows == 0)
68
69 def CalcNumRowsCols(self):
70 numrows = self.rows
71 numcols = self.cols
72 numchild = len(self.GetChildren())
73
74 if numrows == 0 and numcols == 0:
75 return 0, 0
76
77 if numrows == 0:
78 rows, mod = divmod(numchild, self.cols)
79 if mod > 0:
80 rows += 1
81 numrows = rows
82
83 if numcols == 0:
84 cols, mod = divmod(numchild, self.rows)
85 if mod > 0:
86 cols += 1
87 numcols = cols
88
89 return numrows, numcols
90
91 def CalcMin(self):
92 numrows, numcols = self.CalcNumRowsCols()
93 numchild = len(self.GetChildren())
94
95 if numchild == 0:
96 return wx.Size(10, 10)
97
98 if numrows == 0 and numcols == 0:
99 print "TableSizer must have the number of rows or columns set. Cannot continue."
100 return wx.Size(10, 10)
101
102 self.row_widths = [0 for x in range(0, numrows)]
103 self.col_heights = [0 for x in range(0, numcols)]
104 currentRow = 0
105 currentCol = 0
106 counter = 0
107 self.hgrow = 0
108 self.vgrow = 0
109
110 # get the max row width and max column height
111 for item in self.GetChildren():
112 if self.cols != 0:
113 currentRow, currentCol = divmod(counter, numcols)
114 else:
115 currentCol, currentRow = divmod(counter, numrows)
116
117 if item.IsShown():
118 width, height = item.CalcMin()
119
120 if self.isVertical and item.GetProportion() > 0:
121 self.hgrow += item.GetProportion()
122 elif self.isHorizontal and item.GetProportion() > 0:
123 self.vgrow += item.GetProportion()
124
125 if width > self.row_widths[currentRow]:
126 self.row_widths[currentRow] = width
127
128 if height > self.col_heights[currentCol]:
129 self.col_heights[currentCol] = height
130
131 counter += 1
132
133 minwidth = 0
134 for row_width in self.row_widths:
135 minwidth += row_width
136
137 minheight = 0
138 for col_height in self.col_heights:
139 minheight += col_height
140
141 self.fixed_width = minwidth
142 self.fixed_height = minheight
143
144 return wx.Size(minwidth, minheight)
145
146 def RecalcSizes(self):
147 numrows, numcols = self.CalcNumRowsCols()
148 numchild = len(self.GetChildren())
149
150 if numchild == 0:
151 return
152 currentRow = 0
153 currentCol = 0
154 counter = 0
155
156 print "cols %d, rows %d" % (self.cols, self.rows)
157 print "fixed_height %d, fixed_width %d" % (self.fixed_height, self.fixed_width)
158 #print "self.GetSize() = " + `self.GetSize()`
159
160 row_widths = [0 for x in range(0, numrows)]
161 col_heights = [0 for x in range(0, numcols)]
162 item_sizes = [0 for x in range(0, len(self.GetChildren()))]
163 grow_sizes = [0 for x in range(0, len(self.GetChildren()))]
164
165 curHPos = 0
166 curVPos = 0
167 curCol = 0
168 curRow = 0
169 # first, we set sizes for all children, and while doing so, calc
170 # the maximum row heights and col widths. Then, afterwards we handle
171 # the positioning of the controls
172
173 for item in self.GetChildren():
174 if self.cols != 0:
175 currentRow, currentCol = divmod(counter, numcols)
176 else:
177 currentCol, currentRow = divmod(counter, numrows)
178 if item.IsShown():
179 item_minsize = item.GetMinSizeWithBorder()
180 width = item_minsize[0]
181 height = item_minsize[1]
182
183 print "row_height %d, row_width %d" % (self.col_heights[currentCol], self.row_widths[currentRow])
184 growable_width = (self.GetSize()[0]) - width
185 growable_height = (self.GetSize()[1]) - height
186
187 #if not self.isVertical and not self.isHorizontal:
188 # growable_width = self.GetSize()[0] - self.row_widths[currentRow]
189 # growable_height = self.GetSize()[1] - self.col_heights[currentCol]
190
191 #print "grow_height %d, grow_width %d" % (growable_height, growable_width)
192
193 item_vgrow = 0
194 item_hgrow = 0
195 # support wx.EXPAND for box sizers to be compatible
196 if item.GetFlag() & wx.EXPAND:
197 if self.isVertical:
198 if self.hgrow > 0 and item.GetProportion() > 0:
199 item_hgrow = (growable_width * item.GetProportion()) / self.hgrow
200 item_vgrow = growable_height
201
202 elif self.isHorizontal:
203 if self.vgrow > 0 and item.GetProportion() > 0:
204 item_vgrow = (growable_height * item.GetProportion()) / self.vgrow
205 item_hgrow = growable_width
206
207 if growable_width > 0 and item.GetHGrow() > 0:
208 item_hgrow = (growable_width * item.GetHGrow()) / 100
209 print "hgrow = %d" % (item_hgrow)
210
211 if growable_height > 0 and item.GetVGrow() > 0:
212 item_vgrow = (growable_height * item.GetVGrow()) / 100
213 print "vgrow = %d" % (item_vgrow)
214
215 grow_size = wx.Size(item_hgrow, item_vgrow)
216 size = item_minsize #wx.Size(item_minsize[0] + item_hgrow, item_minsize[1] + item_vgrow)
217 if size[0] + grow_size[0] > row_widths[currentRow]:
218 row_widths[currentRow] = size[0] + grow_size[0]
219 if size[1] + grow_size[1] > col_heights[currentCol]:
220 col_heights[currentCol] = size[1] + grow_size[1]
221
222 grow_sizes[counter] = grow_size
223 item_sizes[counter] = size
224
225 counter += 1
226
227 counter = 0
228 for item in self.GetChildren():
229 if self.cols != 0:
230 currentRow, currentCol = divmod(counter, numcols)
231 else:
232 currentCol, currentRow = divmod(counter, numrows)
233
234 itempos = self.GetPosition()
235 if item.IsShown():
236 rowstart = itempos[0]
237 for row in range(0, currentRow):
238 rowstart += row_widths[row]
239
240 colstart = itempos[1]
241 for col in range(0, currentCol):
242 #print "numcols = %d, currentCol = %d, col = %d" % (numcols, currentCol, col)
243 colstart += col_heights[col]
244
245 itempos[0] += rowstart
246 itempos[1] += colstart
247
248 if item.GetFlag() & wx.ALIGN_RIGHT:
249 itempos[0] += (row_widths[currentRow] - item_sizes[counter][0])
250 elif item.GetFlag() & (wx.ALIGN_CENTER | wx.ALIGN_CENTER_HORIZONTAL):
251 itempos[0] += (row_widths[currentRow] - item_sizes[counter][0]) / 2
252
253 if item.GetFlag() & wx.ALIGN_BOTTOM:
254 itempos[1] += (col_heights[currentCol] - item_sizes[counter][1])
255 elif item.GetFlag() & (wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL):
256 itempos[1] += (col_heights[currentCol] - item_sizes[counter][1]) / 2
257
258 hgrowth = (grow_sizes[counter][0] - itempos[0])
259 if hgrowth > 0:
260 item_sizes[counter][0] += hgrowth
261
262 vgrowth = (grow_sizes[counter][1] - itempos[1])
263 if vgrowth > 0:
264 item_sizes[counter][1] += vgrowth
265 #item_sizes[counter][1] -= itempos[1]
266 item.SetDimension(itempos, item_sizes[counter])
267
268 counter += 1
269
270 def GetDefaultBorder(self):
271 border = 4
272 if wx.Platform == "__WXMAC__":
273 border = 6
274 elif wx.Platform == "__WXMSW__":
275 # MSW HIGs use dialog units, not pixels
276 pnt = self.ConvertDialogPointToPixels(wx.Point(4, 4))
277 border = pnt[0]
278 elif wx.Platform == "__WXGTK__":
279 border = 3
280
281 return border
282
283 def SetDefaultSizerProps(self):
284 item = self.GetParent().GetSizer().GetItem(self)
285 item.SetProportion(0)
286 item.SetFlag(wx.ALL)
287 item.SetBorder(self.GetDefaultBorder())
288
289 def GetSizerProps(self):
290 """
291 Returns a dictionary of prop name + value
292 """
293 props = {}
294 item = self.GetParent().GetSizer().GetItem(self)
295
296 props['proportion'] = item.GetProportion()
297 flags = item.GetFlag()
298
299 if flags & border['all'] == border['all']:
300 props['border'] = (['all'], item.GetBorder())
301 else:
302 borders = []
303 for key in border:
304 if flags & border[key]:
305 borders.append(key)
306
307 props['border'] = (borders, item.GetBorder())
308
309 if flags & align['center'] == align['center']:
310 props['align'] = 'center'
311 else:
312 for key in halign:
313 if flags & halign[key]:
314 props['halign'] = key
315
316 for key in valign:
317 if flags & valign[key]:
318 props['valign'] = key
319
320 for key in minsize:
321 if flags & minsize[key]:
322 props['minsize'] = key
323
324 for key in misc_flags:
325 if flags & misc_flags[key]:
326 props[key] = "true"
327
328 return props
329
330 def SetSizerProp(self, prop, value):
331
332 lprop = prop.lower()
333 sizer = self.GetParent().GetSizer()
334 item = sizer.GetItem(self)
335 flag = item.GetFlag()
336 if lprop == "proportion":
337 item.SetProportion(int(value))
338 elif lprop == "hgrow":
339 item.SetHGrow(int(value))
340 elif lprop == "vgrow":
341 item.SetVGrow(int(value))
342 elif lprop == "align":
343 flag = flag | align[value]
344 elif lprop == "halign":
345 flag = flag | halign[value]
346 elif lprop == "valign":
347 flag = flag | valign[value]
348 elif lprop == "border":
349 # this arg takes a tuple (dir, pixels)
350 dirs, amount = value
351 if dirs == "all":
352 dirs = ["all"]
353 for dir in dirs:
354 flag = flag | border[dir]
355 item.SetBorder(amount)
356 elif lprop == "minsize":
357 flag = flag | minsize[value]
358 elif lprop in misc_flags:
359 if not value or str(value) == "" or str(value).lower() == "false":
360 flag = flag &~ misc_flags[lprop]
361 else:
362 flag = flag | misc_flags[lprop]
363
364 # auto-adjust growable rows/columns if expand or proportion is set
365 # on a sizer item in a FlexGridSizer
366 if lprop in ["expand", "proportion"] and isinstance(sizer, wx.FlexGridSizer):
367 cols = sizer.GetCols()
368 rows = sizer.GetRows()
369 # FIXME: I'd like to get the item index in the sizer instead, but
370 # doing sizer.GetChildren.index(item) always gives an error
371 itemnum = self.GetParent().GetChildren().index(self)
372
373 col = 0
374 row = 0
375 if cols == 0:
376 col, row = divmod( itemnum, rows )
377 else:
378 row, col = divmod( itemnum, cols )
379
380 if lprop == "expand":
381 sizer.AddGrowableCol(col)
382 elif lprop == "proportion" and int(value) != 0:
383 sizer.AddGrowableRow(row)
384
385 item.SetFlag(flag)
386
387 def SetSizerProps(self, props={}, **kwargs):
388 allprops = {}
389 allprops.update(props)
390 allprops.update(kwargs)
391
392 for prop in allprops:
393 self.SetSizerProp(prop, allprops[prop])
394
395 def GetDialogBorder(self):
396 border = 6
397 if wx.Platform == "__WXMAC__" or wx.Platform == "__WXGTK__":
398 border = 12
399 elif wx.Platform == "__WXMSW__":
400 pnt = self.ConvertDialogPointToPixels(wx.Point(7, 7))
401 border = pnt[0]
402
403 return border
404
405 def SetHGrow(self, proportion):
406 data = self.GetUserData()
407 if "HGrow" in data:
408 data["HGrow"] = proportion
409 self.SetUserData(data)
410
411 def GetHGrow(self):
412 if self.GetUserData() and "HGrow" in self.GetUserData():
413 return self.GetUserData()["HGrow"]
414 else:
415 return 0
416
417 def SetVGrow(self, proportion):
418 data = self.GetUserData()
419 if "VGrow" in data:
420 data["VGrow"] = proportion
421 self.SetUserData(data)
422
423
424 def GetVGrow(self):
425 if self.GetUserData() and "VGrow" in self.GetUserData():
426 return self.GetUserData()["VGrow"]
427 else:
428 return 0
429
430 def GetDefaultPanelBorder(self):
431 # child controls will handle their borders, so don't pad the panel.
432 return 0
433
434 # Why, Python?! Why do you make it so easy?! ;-)
435 wx.Dialog.GetDialogBorder = GetDialogBorder
436 wx.Panel.GetDefaultBorder = GetDefaultPanelBorder
437 wx.Notebook.GetDefaultBorder = GetDefaultPanelBorder
438 wx.SplitterWindow.GetDefaultBorder = GetDefaultPanelBorder
439
440 wx.Window.GetDefaultBorder = GetDefaultBorder
441 wx.Window.SetDefaultSizerProps = SetDefaultSizerProps
442 wx.Window.SetSizerProp = SetSizerProp
443 wx.Window.SetSizerProps = SetSizerProps
444 wx.Window.GetSizerProps = GetSizerProps
445
446 wx.SizerItem.SetHGrow = SetHGrow
447 wx.SizerItem.GetHGrow = GetHGrow
448 wx.SizerItem.SetVGrow = SetVGrow
449 wx.SizerItem.GetVGrow = GetVGrow
450
451
452 class SizedPanel(wx.PyPanel):
453 def __init__(self, *args, **kwargs):
454 wx.PyPanel.__init__(self, *args, **kwargs)
455 sizer = wx.BoxSizer(wx.VERTICAL) #TableSizer(1, 0)
456 self.SetSizer(sizer)
457 self.sizerType = "vertical"
458
459 def AddChild(self, child):
460 wx.PyPanel.base_AddChild(self, child)
461
462 sizer = self.GetSizer()
463 item = sizer.Add(child)
464 item.SetUserData({"HGrow":0, "VGrow":0})
465
466 # Note: One problem is that the child class given to AddChild
467 # is the underlying wxWidgets control, not its Python subclass. So if
468 # you derive your own class, and override that class' GetDefaultBorder(),
469 # etc. methods, it will have no effect.
470 child.SetDefaultSizerProps()
471
472 def GetSizerType(self):
473 return self.sizerType
474
475 def SetSizerType(self, type, options={}):
476 sizer = None
477 self.sizerType = type
478 if type == "horizontal":
479 sizer = wx.BoxSizer(wx.HORIZONTAL) # TableSizer(0, 1)
480
481 elif type == "vertical":
482 sizer = wx.BoxSizer(wx.VERTICAL) # TableSizer(1, 0)
483
484 elif type == "form":
485 #sizer = TableSizer(2, 0)
486 sizer = wx.FlexGridSizer(0, 2, 0, 0)
487 #sizer.AddGrowableCol(1)
488
489 elif type == "table":
490 rows = cols = 0
491 if options.has_key('rows'):
492 rows = int(options['rows'])
493
494 if options.has_key('cols'):
495 cols = int(options['cols'])
496
497 sizer = TableSizer(rows, cols)
498
499 elif type == "grid":
500 sizer = wx.FlexGridSizer(0, 0, 0, 0)
501 if options.has_key('rows'):
502 sizer.SetRows(int(options['rows']))
503 else:
504 sizer.SetRows(0)
505 if options.has_key('cols'):
506 sizer.SetCols(int(options['cols']))
507 else:
508 sizer.SetCols(0)
509
510 if options.has_key('growable_row'):
511 row, proportion = options['growable_row']
512 sizer.SetGrowableRow(row, proportion)
513
514 if options.has_key('growable_col'):
515 col, proportion = options['growable_col']
516 sizer.SetGrowableCol(col, proportion)
517
518 if options.has_key('hgap'):
519 sizer.SetHGap(options['hgap'])
520
521 if options.has_key('vgap'):
522 sizer.SetVGap(options['vgap'])
523 if sizer:
524 self._SetNewSizer(sizer)
525
526 def _SetNewSizer(self, sizer):
527 props = {}
528 for child in self.GetChildren():
529 props[child.GetId()] = child.GetSizerProps()
530 self.GetSizer().Detach(child)
531
532 wx.PyPanel.SetSizer(self, sizer)
533
534 for child in self.GetChildren():
535 self.GetSizer().Add(child)
536 child.SetSizerProps(props[child.GetId()])
537
538 class SizedDialog(wx.Dialog):
539 def __init__(self, *args, **kwargs):
540 wx.Dialog.__init__(self, *args, **kwargs)
541
542 self.borderLen = 12
543 self.mainPanel = SizedPanel(self, -1)
544
545 mysizer = wx.BoxSizer(wx.VERTICAL)
546 mysizer.Add(self.mainPanel, 1, wx.EXPAND | wx.ALL, self.GetDialogBorder())
547 self.SetSizer(mysizer)
548
549 self.SetAutoLayout(True)
550
551 def GetContentsPane(self):
552 return self.mainPanel
553
554 def SetButtonSizer(self, sizer):
555 self.GetSizer().Add(sizer, 0, wx.EXPAND | wx.BOTTOM | wx.RIGHT, self.GetDialogBorder())
556
557 # Temporary hack to fix button ordering problems.
558 cancel = self.FindWindowById(wx.ID_CANCEL)
559 no = self.FindWindowById(wx.ID_NO)
560 if no and cancel:
561 cancel.MoveAfterInTabOrder(no)
562
563 class SizedFrame(wx.Frame):
564 def __init__(self, *args, **kwargs):
565 wx.Frame.__init__(self, *args, **kwargs)
566
567 self.borderLen = 12
568 # this probably isn't needed, but I thought it would help to make it consistent
569 # with SizedDialog, and creating a panel to hold things is often good practice.
570 self.mainPanel = SizedPanel(self, -1)
571
572 mysizer = wx.BoxSizer(wx.VERTICAL)
573 mysizer.Add(self.mainPanel, 1, wx.EXPAND)
574 self.SetSizer(mysizer)
575
576 self.SetAutoLayout(True)
577
578 def GetContentsPane(self):
579 return self.mainPanel