]> git.saurik.com Git - wxWidgets.git/blame_incremental - wxPython/wxaddons/sized_controls.py
fixed deadlock when calling wxPostEvent() from worker thread
[wxWidgets.git] / wxPython / wxaddons / sized_controls.py
... / ...
CommitLineData
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
13import 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
21halign = { "left": wx.ALIGN_LEFT,
22 "center": wx.ALIGN_CENTER_HORIZONTAL,
23 "centre": wx.ALIGN_CENTRE_HORIZONTAL,
24 "right": wx.ALIGN_RIGHT,
25 }
26
27valign = { "top": wx.ALIGN_TOP,
28 "bottom": wx.ALIGN_BOTTOM,
29 "center": wx.ALIGN_CENTER_VERTICAL,
30 "centre": wx.ALIGN_CENTRE_VERTICAL,
31 }
32
33align = { "center": wx.ALIGN_CENTER,
34 "centre": wx.ALIGN_CENTRE,
35 }
36
37border = { "left": wx.LEFT,
38 "right": wx.RIGHT,
39 "top": wx.TOP,
40 "bottom": wx.BOTTOM,
41 "all": wx.ALL,
42 }
43
44minsize = { "fixed": wx.FIXED_MINSIZE,
45 "adjust": wx.ADJUST_MINSIZE,
46 }
47
48misc_flags = { "expand": wx.EXPAND, }
49
50
51# My attempt at creating a more intuitive replacement for nesting box sizers
52class 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
270def 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
283def SetDefaultSizerProps(self):
284 item = self.GetParent().GetSizer().GetItem(self)
285 item.SetProportion(0)
286 item.SetFlag(wx.ALL)
287 item.SetBorder(self.GetDefaultBorder())
288
289def 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
330def SetSizerProp(self, prop, value):
331
332 lprop = prop.lower()
333 item = self.GetParent().GetSizer().GetItem(self)
334 flag = item.GetFlag()
335 if lprop == "proportion":
336 item.SetProportion(int(value))
337 elif lprop == "hgrow":
338 item.SetHGrow(int(value))
339 elif lprop == "vgrow":
340 item.SetVGrow(int(value))
341 elif lprop == "align":
342 flag = flag | align[value]
343 elif lprop == "halign":
344 flag = flag | halign[value]
345 elif lprop == "valign":
346 flag = flag | valign[value]
347 elif lprop == "border":
348 # this arg takes a tuple (dir, pixels)
349 dirs, amount = value
350 if dirs == "all":
351 dirs = ["all"]
352 for dir in dirs:
353 flag = flag | border[dir]
354 item.SetBorder(amount)
355 elif lprop == "minsize":
356 flag = flag | minsize[value]
357 elif lprop in misc_flags:
358 if not value or str(value) == "" or str(value).lower() == "false":
359 flag = flag &~ misc_flags[lprop]
360 else:
361 flag = flag | misc_flags[lprop]
362
363 item.SetFlag(flag)
364
365def SetSizerProps(self, props={}, **kwargs):
366 allprops = {}
367 allprops.update(props)
368 allprops.update(kwargs)
369
370 for prop in allprops:
371 self.SetSizerProp(prop, allprops[prop])
372
373def GetDialogBorder(self):
374 border = 6
375 if wx.Platform == "__WXMAC__" or wx.Platform == "__WXGTK__":
376 border = 12
377 elif wx.Platform == "__WXMSW__":
378 pnt = self.ConvertDialogPointToPixels(wx.Point(7, 7))
379 border = pnt[0]
380
381 return border
382
383def SetHGrow(self, proportion):
384 data = self.GetUserData()
385 if "HGrow" in data:
386 data["HGrow"] = proportion
387 self.SetUserData(data)
388
389def GetHGrow(self):
390 if self.GetUserData() and "HGrow" in self.GetUserData():
391 return self.GetUserData()["HGrow"]
392 else:
393 return 0
394
395def SetVGrow(self, proportion):
396 data = self.GetUserData()
397 if "VGrow" in data:
398 data["VGrow"] = proportion
399 self.SetUserData(data)
400
401
402def GetVGrow(self):
403 if self.GetUserData() and "VGrow" in self.GetUserData():
404 return self.GetUserData()["VGrow"]
405 else:
406 return 0
407
408def GetDefaultPanelBorder(self):
409 # child controls will handle their borders, so don't pad the panel.
410 return 0
411
412# Why, Python?! Why do you make it so easy?! ;-)
413wx.Dialog.GetDialogBorder = GetDialogBorder
414wx.Panel.GetDefaultBorder = GetDefaultPanelBorder
415wx.Notebook.GetDefaultBorder = GetDefaultPanelBorder
416wx.SplitterWindow.GetDefaultBorder = GetDefaultPanelBorder
417
418wx.Window.GetDefaultBorder = GetDefaultBorder
419wx.Window.SetDefaultSizerProps = SetDefaultSizerProps
420wx.Window.SetSizerProp = SetSizerProp
421wx.Window.SetSizerProps = SetSizerProps
422wx.Window.GetSizerProps = GetSizerProps
423
424wx.SizerItem.SetHGrow = SetHGrow
425wx.SizerItem.GetHGrow = GetHGrow
426wx.SizerItem.SetVGrow = SetVGrow
427wx.SizerItem.GetVGrow = GetVGrow
428
429
430class SizedPanel(wx.PyPanel):
431 def __init__(self, *args, **kwargs):
432 wx.PyPanel.__init__(self, *args, **kwargs)
433 sizer = wx.BoxSizer(wx.VERTICAL) #TableSizer(1, 0)
434 self.SetSizer(sizer)
435 self.sizerType = "vertical"
436
437 def AddChild(self, child):
438 wx.PyPanel.base_AddChild(self, child)
439
440 sizer = self.GetSizer()
441 item = sizer.Add(child)
442 item.SetUserData({"HGrow":0, "VGrow":0})
443
444 # Note: One problem is that the child class given to AddChild
445 # is the underlying wxWidgets control, not its Python subclass. So if
446 # you derive your own class, and override that class' GetDefaultBorder(),
447 # etc. methods, it will have no effect.
448 child.SetDefaultSizerProps()
449
450 def GetSizerType(self):
451 return self.sizerType
452
453 def SetSizerType(self, type, options={}):
454 sizer = None
455 self.sizerType = type
456 if type == "horizontal":
457 sizer = wx.BoxSizer(wx.HORIZONTAL) # TableSizer(0, 1)
458
459 elif type == "vertical":
460 sizer = wx.BoxSizer(wx.VERTICAL) # TableSizer(1, 0)
461
462 elif type == "form":
463 #sizer = TableSizer(2, 0)
464 sizer = wx.FlexGridSizer(0, 2, 0, 0)
465 sizer.AddGrowableCol(1)
466
467 elif type == "table":
468 rows = cols = 0
469 if options.has_key('rows'):
470 rows = int(options['rows'])
471
472 if options.has_key('cols'):
473 cols = int(options['cols'])
474
475 sizer = TableSizer(rows, cols)
476
477 elif type == "grid":
478 sizer = wx.FlexGridSizer(0, 0, 0, 0)
479 if options.has_key('rows'):
480 sizer.SetRows(int(options['rows']))
481 else:
482 sizer.SetRows(0)
483 if options.has_key('cols'):
484 sizer.SetCols(int(options['cols']))
485 else:
486 sizer.SetCols(0)
487
488 if options.has_key('growable_row'):
489 row, proportion = options['growable_row']
490 sizer.SetGrowableRow(row, proportion)
491
492 if options.has_key('growable_col'):
493 col, proportion = options['growable_col']
494 sizer.SetGrowableCol(col, proportion)
495
496 if options.has_key('hgap'):
497 sizer.SetHGap(options['hgap'])
498
499 if options.has_key('vgap'):
500 sizer.SetVGap(options['vgap'])
501 if sizer:
502 self._SetNewSizer(sizer)
503
504 def _SetNewSizer(self, sizer):
505 props = {}
506 for child in self.GetChildren():
507 props[child.GetId()] = child.GetSizerProps()
508 self.GetSizer().Detach(child)
509
510 wx.PyPanel.SetSizer(self, sizer)
511
512 for child in self.GetChildren():
513 self.GetSizer().Add(child)
514 child.SetSizerProps(props[child.GetId()])
515
516class SizedDialog(wx.Dialog):
517 def __init__(self, *args, **kwargs):
518 wx.Dialog.__init__(self, *args, **kwargs)
519
520 self.borderLen = 12
521 self.mainPanel = SizedPanel(self, -1)
522
523 mysizer = wx.BoxSizer(wx.VERTICAL)
524 mysizer.Add(self.mainPanel, 1, wx.EXPAND | wx.ALL, self.GetDialogBorder())
525 self.SetSizer(mysizer)
526
527 self.SetAutoLayout(True)
528
529 def GetContentsPane(self):
530 return self.mainPanel
531
532 def SetButtonSizer(self, sizer):
533 self.GetSizer().Add(sizer, 0, wx.EXPAND | wx.BOTTOM | wx.RIGHT, self.GetDialogBorder())
534
535 # Temporary hack to fix button ordering problems.
536 cancel = self.FindWindowById(wx.ID_CANCEL)
537 no = self.FindWindowById(wx.ID_NO)
538 if no and cancel:
539 cancel.MoveAfterInTabOrder(no)
540
541class SizedFrame(wx.Frame):
542 def __init__(self, *args, **kwargs):
543 wx.Frame.__init__(self, *args, **kwargs)
544
545 self.borderLen = 12
546 # this probably isn't needed, but I thought it would help to make it consistent
547 # with SizedDialog, and creating a panel to hold things is often good practice.
548 self.mainPanel = SizedPanel(self, -1)
549
550 mysizer = wx.BoxSizer(wx.VERTICAL)
551 mysizer.Add(self.mainPanel, 1, wx.EXPAND)
552 self.SetSizer(mysizer)
553
554 self.SetAutoLayout(True)
555
556 def GetContentsPane(self):
557 return self.mainPanel