| 1 | # 12/09/2003 - Jeff Grimmett (grimmtooth@softhome.net) |
| 2 | # |
| 3 | # o Updated for wx namespace |
| 4 | # |
| 5 | # 12/18/2003 - Jeff Grimmett (grimmtooth@softhome.net) |
| 6 | # |
| 7 | # o wxScrolledMessageDialog -> ScrolledMessageDialog |
| 8 | # |
| 9 | |
| 10 | import re |
| 11 | import wx |
| 12 | |
| 13 | class Layoutf(wx.LayoutConstraints): |
| 14 | """ |
| 15 | The class Layoutf(wxLayoutConstraints) presents a simplification |
| 16 | of the wxLayoutConstraints syntax. The name Layoutf is choosen |
| 17 | because of the similarity with C's printf function. |
| 18 | |
| 19 | Quick Example: |
| 20 | |
| 21 | lc = Layoutf('t=t#1;l=r10#2;r!100;h%h50#1', (self, self.panel)) |
| 22 | |
| 23 | is equivalent to |
| 24 | |
| 25 | lc = wxLayoutContraints() |
| 26 | lc.top.SameAs(self, wxTop) |
| 27 | lc.left.SameAs(self.panel, wxRight, 10) |
| 28 | lc.right.Absolute(100) |
| 29 | lc.height.PercentOf(self, wxHeight, 50) |
| 30 | |
| 31 | Usage: |
| 32 | |
| 33 | You can give a constraint string to the Layoutf constructor, |
| 34 | or use the 'pack' method. The following are equivalent: |
| 35 | |
| 36 | lc = Layoutf('t=t#1;l=r#2;r!100;h%h50#1', (self, self.panel)) |
| 37 | |
| 38 | and |
| 39 | |
| 40 | lc = Layoutf() |
| 41 | lc.pack('t=t#1;l=r#2;r!100;h%h50#1', (self, self.panel)) |
| 42 | |
| 43 | Besides 'pack' there's also 'debug_pack' which does not set |
| 44 | constraints, but prints traditional wxLayoutConstraint calls to |
| 45 | stdout. |
| 46 | |
| 47 | The calls to the Layoutf constructor and pack methods have |
| 48 | the following argument list: |
| 49 | |
| 50 | (constraint_string, objects_tuple) |
| 51 | |
| 52 | Constraint String syntax: |
| 53 | |
| 54 | Constraint directives are separated by semi-colons. You |
| 55 | generally (always?) need four directives to completely describe a |
| 56 | subwindow's location. |
| 57 | |
| 58 | A single directive has either of the following forms: |
| 59 | |
| 60 | 1. <own attribute><compare operation>[numerical argument] |
| 61 | for example r!100 -> lc.right.Absolute(100) ) |
| 62 | and w* -> lc.width.AsIs() |
| 63 | |
| 64 | 2. <own attribute><compare operation>[numerical argument] |
| 65 | #<compare object nr.> |
| 66 | for example t_10#2 (lc.top.Below(<second obj>, 10) |
| 67 | |
| 68 | 3. <own attribute><compare operation><compare attribute> |
| 69 | [numerical argument]#<compare object nr.> |
| 70 | for example w%h50#2 ( lc.width.PercentOf(<second obj>, |
| 71 | wxHeight, 50) and t=b#1 ( lc.top.SameAs(<first obj>, |
| 72 | wxBottom) ) |
| 73 | |
| 74 | Which one you need is defined by the <compare operation> |
| 75 | type. The following take type 1 (no object to compare with): |
| 76 | |
| 77 | '!': 'Absolute', '?': 'Unconstrained', '*': 'AsIs' |
| 78 | |
| 79 | These take type 2 (need to be compared with another object) |
| 80 | |
| 81 | '<': 'LeftOf', '>': 'RightOf', '^': 'Above', '_': 'Below' |
| 82 | |
| 83 | These take type 3 (need to be compared to another object |
| 84 | attribute) |
| 85 | |
| 86 | '=': 'SameAs', '%': 'PercentOf' |
| 87 | |
| 88 | For all types, the <own attribute> letter can be any of |
| 89 | |
| 90 | 't': 'top', 'l': 'left', 'b': 'bottom', |
| 91 | 'r': 'right', 'h': 'height', 'w': 'width', |
| 92 | 'x': 'centreX', 'y': 'centreY' |
| 93 | |
| 94 | If the operation takes an (optional) numerical argument, place it |
| 95 | in [numerical argument]. For type 3 directives, the <compare |
| 96 | attribute> letter can be any of |
| 97 | |
| 98 | 't': 'wxTop', 'l': 'wxLeft', 'b': 'wxBottom' |
| 99 | 'r': 'wxRight', 'h': 'wxHeight', 'w': 'wxWidth', |
| 100 | 'x': 'wxCentreX', 'y': 'wxCentreY' |
| 101 | |
| 102 | Note that these are the same letters as used for <own attribute>, |
| 103 | so you'll only need to remember one set. Finally, the object |
| 104 | whose attribute is refered to, is specified by #<compare object |
| 105 | nr>, where <compare object nr> is the 1-based (stupid, I know, |
| 106 | but I've gotten used to it) index of the object in the |
| 107 | objects_tuple argument. |
| 108 | |
| 109 | Bugs: |
| 110 | |
| 111 | Not entirely happy about the logic in the order of arguments |
| 112 | after the <compare operation> character. |
| 113 | |
| 114 | Not all wxLayoutConstraint methods are included in the |
| 115 | syntax. However, the type 3 directives are generally the most |
| 116 | used. Further excuse: wxWindows layout constraints are at the |
| 117 | time of this writing not documented. |
| 118 | |
| 119 | """ |
| 120 | |
| 121 | attr_d = { 't': 'top', 'l': 'left', 'b': 'bottom', |
| 122 | 'r': 'right', 'h': 'height', 'w': 'width', |
| 123 | 'x': 'centreX', 'y': 'centreY' } |
| 124 | op_d = { '=': 'SameAs', '%': 'PercentOf', '<': 'LeftOf', |
| 125 | '>': 'RightOf', '^': 'Above', '_': 'Below', |
| 126 | '!': 'Absolute', '?': 'Unconstrained', '*': 'AsIs' } |
| 127 | cmp_d = { 't': 'wx.Top', 'l': 'wx.Left', 'b': 'wx.Bottom', |
| 128 | 'r': 'wx.Right', 'h': 'wx.Height', 'w': 'wx.Width', |
| 129 | 'x': 'wx.CentreX', 'y': 'wx.CentreY' } |
| 130 | |
| 131 | rexp1 = re.compile('^\s*([tlrbhwxy])\s*([!\?\*])\s*(\d*)\s*$') |
| 132 | rexp2 = re.compile('^\s*([tlrbhwxy])\s*([=%<>^_])\s*([tlrbhwxy]?)\s*(\d*)\s*#(\d+)\s*$') |
| 133 | |
| 134 | def __init__(self,pstr=None,winlist=None): |
| 135 | wx.LayoutConstraints.__init__(self) |
| 136 | if pstr: |
| 137 | self.pack(pstr,winlist) |
| 138 | |
| 139 | def pack(self, pstr, winlist): |
| 140 | pstr = pstr.lower() |
| 141 | for item in pstr.split(';'): |
| 142 | m = self.rexp1.match(item) |
| 143 | if m: |
| 144 | g = list(m.groups()) |
| 145 | attr = getattr(self, self.attr_d[g[0]]) |
| 146 | func = getattr(attr, self.op_d[g[1]]) |
| 147 | if g[1] == '!': |
| 148 | func(int(g[2])) |
| 149 | else: |
| 150 | func() |
| 151 | continue |
| 152 | m = self.rexp2.match(item) |
| 153 | if not m: raise ValueError |
| 154 | g = list(m.groups()) |
| 155 | attr = getattr(self, self.attr_d[g[0]]) |
| 156 | func = getattr(attr, self.op_d[g[1]]) |
| 157 | if g[3]: g[3] = int(g[3]) |
| 158 | else: g[3] = None; |
| 159 | g[4] = int(g[4]) - 1 |
| 160 | if g[1] in '<>^_': |
| 161 | if g[3]: func(winlist[g[4]], g[3]) |
| 162 | else: func(winlist[g[4]]) |
| 163 | else: |
| 164 | cmp = eval(self.cmp_d[g[2]]) |
| 165 | if g[3]: func(winlist[g[4]], cmp, g[3]) |
| 166 | else: func(winlist[g[4]], cmp) |
| 167 | |
| 168 | def debug_pack(self, pstr, winlist): |
| 169 | pstr = pstr.lower() |
| 170 | for item in pstr.split(';'): |
| 171 | m = self.rexp1.match(item) |
| 172 | if m: |
| 173 | g = list(m.groups()) |
| 174 | attr = getattr(self, self.attr_d[g[0]]) |
| 175 | func = getattr(attr, self.op_d[g[1]]) |
| 176 | if g[1] == '!': |
| 177 | print "%s.%s.%s(%s)" % \ |
| 178 | ('self',self.attr_d[g[0]],self.op_d[g[1]],g[2]) |
| 179 | else: |
| 180 | print "%s.%s.%s()" % \ |
| 181 | ('self',self.attr_d[g[0]],self.op_d[g[1]]) |
| 182 | continue |
| 183 | m = self.rexp2.match(item) |
| 184 | if not m: raise ValueError |
| 185 | g = list(m.groups()) |
| 186 | if g[3]: g[3] = int(g[3]) |
| 187 | else: g[3] = 0; |
| 188 | g[4] = int(g[4]) - 1 |
| 189 | if g[1] in '<>^_': |
| 190 | if g[3]: print "%s.%s.%s(%s,%d)" % \ |
| 191 | ('self',self.attr_d[g[0]],self.op_d[g[1]],winlist[g[4]], |
| 192 | g[3]) |
| 193 | else: print "%s.%s.%s(%s)" % \ |
| 194 | ('self',self.attr_d[g[0]],self.op_d[g[1]],winlist[g[4]]) |
| 195 | else: |
| 196 | if g[3]: print "%s.%s.%s(%s,%s,%d)" % \ |
| 197 | ('self',self.attr_d[g[0]],self.op_d[g[1]],winlist[g[4]], |
| 198 | self.cmp_d[g[2]],g[3]) |
| 199 | else: print "%s.%s.%s(%s,%s)" % \ |
| 200 | ('self',self.attr_d[g[0]],self.op_d[g[1]],winlist[g[4]], |
| 201 | self.cmp_d[g[2]]) |
| 202 | |
| 203 | if __name__=='__main__': |
| 204 | |
| 205 | class TestLayoutf(wx.Frame): |
| 206 | def __init__(self, parent): |
| 207 | wx.Frame.__init__(self, parent, -1, 'Test Layout Constraints', |
| 208 | wx.DefaultPosition, (500, 300)) |
| 209 | self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) |
| 210 | |
| 211 | self.SetAutoLayout(True) |
| 212 | |
| 213 | self.panelA = wx.Window(self, -1, style=wx.SIMPLE_BORDER) |
| 214 | self.panelA.SetBackgroundColour(wx.BLUE) |
| 215 | self.panelA.SetConstraints(Layoutf('t=t10#1;l=l10#1;b=b10#1;r%r50#1',(self,))) |
| 216 | |
| 217 | self.panelB = wx.Window(self, -1, style=wx.SIMPLE_BORDER) |
| 218 | self.panelB.SetBackgroundColour(wx.RED) |
| 219 | self.panelB.SetConstraints(Layoutf('t=t10#1;r=r10#1;b%b30#1;l>10#2', (self,self.panelA))) |
| 220 | |
| 221 | self.panelC = wx.Window(self, -1, style=wx.SIMPLE_BORDER) |
| 222 | self.panelC.SetBackgroundColour(wx.WHITE) |
| 223 | self.panelC.SetConstraints(Layoutf('t_10#3;r=r10#1;b=b10#1;l>10#2', (self,self.panelA,self.panelB))) |
| 224 | |
| 225 | b = wx.Button(self.panelA, -1, ' About: ') |
| 226 | b.SetConstraints(Layoutf('X=X#1;Y=Y#1;h*;w%w50#1', (self.panelA,))) |
| 227 | self.Bind(wx.EVT_BUTTON, self.OnAbout, b) |
| 228 | |
| 229 | b = wx.Button(self.panelB, 100, ' Panel B ') |
| 230 | b.SetConstraints(Layoutf('t=t2#1;r=r4#1;h*;w*', (self.panelB,))) |
| 231 | |
| 232 | self.panelD = wx.Window(self.panelC, -1, style=wx.SIMPLE_BORDER) |
| 233 | self.panelD.SetBackgroundColour(wx.GREEN) |
| 234 | self.panelD.SetConstraints(Layoutf('b%h50#1;r%w50#1;h=h#2;w=w#2', (self.panelC, b))) |
| 235 | |
| 236 | b = wx.Button(self.panelC, -1, ' Panel C ') |
| 237 | b.SetConstraints(Layoutf('t_#1;l>#1;h*;w*', (self.panelD,))) |
| 238 | self.Bind(wx.EVT_BUTTON, self.OnButton, b) |
| 239 | |
| 240 | wx.StaticText(self.panelD, -1, "Panel D", (4, 4)).SetBackgroundColour(wx.GREEN) |
| 241 | |
| 242 | def OnButton(self, event): |
| 243 | self.Close(True) |
| 244 | |
| 245 | def OnAbout(self, event): |
| 246 | import wx.lib.dialogs |
| 247 | msg = wx.lib.dialogs.ScrolledMessageDialog(self, Layoutf.__doc__, "about") |
| 248 | msg.ShowModal() |
| 249 | msg.Destroy() |
| 250 | |
| 251 | def OnCloseWindow(self, event): |
| 252 | self.Destroy() |
| 253 | |
| 254 | class TestApp(wx.App): |
| 255 | def OnInit(self): |
| 256 | frame = TestLayoutf(None) |
| 257 | frame.Show(1) |
| 258 | self.SetTopWindow(frame) |
| 259 | return 1 |
| 260 | |
| 261 | app = TestApp(0) |
| 262 | app.MainLoop() |
| 263 | |
| 264 | |
| 265 | |
| 266 | |
| 267 | |