| 1 | # Name: panel.py |
| 2 | # Purpose: XRC editor, Panel class and related |
| 3 | # Author: Roman Rolinsky <rolinsky@mema.ucl.ac.be> |
| 4 | # Created: 02.12.2002 |
| 5 | # RCS-ID: $Id$ |
| 6 | |
| 7 | from xxx import * # xxx imports globals and params |
| 8 | from undo import * |
| 9 | from wx.html import HtmlWindow |
| 10 | |
| 11 | # Properties panel containing notebook |
| 12 | class Panel(wx.Notebook): |
| 13 | def __init__(self, parent, id = -1): |
| 14 | if wx.Platform != '__WXMAC__': # some problems with this style on macs |
| 15 | wx.Notebook.__init__(self, parent, id, style=wx.NB_BOTTOM) |
| 16 | else: |
| 17 | wx.Notebook.__init__(self, parent, id) |
| 18 | global panel |
| 19 | g.panel = panel = self |
| 20 | self.modified = False |
| 21 | |
| 22 | # Set common sizes |
| 23 | import params |
| 24 | cTmp = wx.Button(self, -1, '') |
| 25 | params.buttonSize = (self.DLG_SZE(buttonSizeD)[0], cTmp.GetSize()[1]) |
| 26 | cTmp.Destroy() |
| 27 | cTmp = wx.TextCtrl(self, -1, '') |
| 28 | params.textSize = cTmp.GetSize() |
| 29 | cTmp.Destroy() |
| 30 | cTmp = wx.CheckBox(self, -1, 'growablerows ') # this is the longest |
| 31 | ParamPage.labelSize = cTmp.GetSize() |
| 32 | cTmp.Destroy() |
| 33 | del cTmp |
| 34 | |
| 35 | # List of child windows |
| 36 | self.pages = [] |
| 37 | # Create scrolled windows for pages |
| 38 | self.page1 = wx.ScrolledWindow(self, -1) |
| 39 | sizer = wx.BoxSizer() |
| 40 | sizer.Add(wx.BoxSizer()) # dummy sizer |
| 41 | self.page1.SetAutoLayout(True) |
| 42 | self.page1.SetSizer(sizer) |
| 43 | self.AddPage(self.page1, 'Properties') |
| 44 | # Second page |
| 45 | self.page2 = wx.ScrolledWindow(self, -1) |
| 46 | self.page2.Hide() |
| 47 | sizer = wx.BoxSizer() |
| 48 | sizer.Add(wx.BoxSizer()) # dummy sizer |
| 49 | self.page2.SetAutoLayout(True) |
| 50 | self.page2.SetSizer(sizer) |
| 51 | # Cache for already used panels |
| 52 | self.pageCache = {} # cached property panels |
| 53 | self.stylePageCache = {} # cached style panels |
| 54 | |
| 55 | # Delete child windows and recreate page sizer |
| 56 | def ResetPage(self, page): |
| 57 | topSizer = page.GetSizer() |
| 58 | sizer = topSizer.GetChildren()[0].GetSizer() |
| 59 | for w in page.GetChildren(): |
| 60 | sizer.Detach(w) |
| 61 | if isinstance(w, ParamPage): |
| 62 | if w.IsShown(): |
| 63 | w.Hide() |
| 64 | else: |
| 65 | w.Destroy() |
| 66 | topSizer.Remove(sizer) |
| 67 | # Create new windows |
| 68 | sizer = wx.BoxSizer(wx.VERTICAL) |
| 69 | # Special case - resize html window |
| 70 | if g.conf.panic: |
| 71 | topSizer.Add(sizer, 1, wx.EXPAND) |
| 72 | else: |
| 73 | topSizer.Add(sizer, 0, wx.ALL, 5) |
| 74 | return sizer |
| 75 | |
| 76 | def SetData(self, xxx): |
| 77 | self.pages = [] |
| 78 | # First page |
| 79 | # Remove current objects and sizer |
| 80 | sizer = self.ResetPage(self.page1) |
| 81 | if not xxx or (not xxx.allParams and not xxx.hasName and not xxx.hasChild): |
| 82 | if g.tree.selection: |
| 83 | sizer.Add(wx.StaticText(self.page1, -1, 'This item has no properties.')) |
| 84 | else: # nothing selected |
| 85 | # If first time, show some help |
| 86 | if g.conf.panic: |
| 87 | html = HtmlWindow(self.page1, -1, wx.DefaultPosition, |
| 88 | wx.DefaultSize, wx.SUNKEN_BORDER) |
| 89 | html.SetPage(g.helpText) |
| 90 | sizer.Add(html, 1, wx.EXPAND) |
| 91 | g.conf.panic = False |
| 92 | else: |
| 93 | sizer.Add(wx.StaticText(self.page1, -1, 'Select a tree item.')) |
| 94 | else: |
| 95 | g.currentXXX = xxx.treeObject() |
| 96 | # Normal or SizerItem page |
| 97 | isGBSizerItem = isinstance(xxx.parent, xxxGridBagSizer) |
| 98 | cacheID = (xxx.panelName(), isGBSizerItem) |
| 99 | try: |
| 100 | page = self.pageCache[cacheID] |
| 101 | page.box.SetLabel(xxx.panelName()) |
| 102 | page.Show() |
| 103 | except KeyError: |
| 104 | page = PropPage(self.page1, xxx.panelName(), xxx) |
| 105 | self.pageCache[cacheID] = page |
| 106 | page.SetValues(xxx) |
| 107 | self.pages.append(page) |
| 108 | sizer.Add(page, 1, wx.EXPAND) |
| 109 | if xxx.hasChild: |
| 110 | # Special label for child objects - they may have different GUI |
| 111 | cacheID = (xxx.child.panelName(), xxx.__class__) |
| 112 | try: |
| 113 | page = self.pageCache[cacheID] |
| 114 | page.box.SetLabel(xxx.child.panelName()) |
| 115 | page.Show() |
| 116 | except KeyError: |
| 117 | page = PropPage(self.page1, xxx.child.panelName(), xxx.child) |
| 118 | self.pageCache[cacheID] = page |
| 119 | page.SetValues(xxx.child) |
| 120 | self.pages.append(page) |
| 121 | sizer.Add(page, 0, wx.EXPAND | wx.TOP, 5) |
| 122 | self.page1.Layout() |
| 123 | size = self.page1.GetSizer().GetMinSize() |
| 124 | self.page1.SetScrollbars(1, 1, size.width, size.height, 0, 0, True) |
| 125 | |
| 126 | # Second page |
| 127 | # Create if does not exist |
| 128 | if xxx and xxx.treeObject().hasStyle: |
| 129 | xxx = xxx.treeObject() |
| 130 | # Simplest case: set data if class is the same |
| 131 | sizer = self.ResetPage(self.page2) |
| 132 | try: |
| 133 | page = self.stylePageCache[xxx.__class__] |
| 134 | page.Show() |
| 135 | except KeyError: |
| 136 | page = StylePage(self.page2, xxx.className + ' style', xxx) |
| 137 | self.stylePageCache[xxx.__class__] = page |
| 138 | page.SetValues(xxx) |
| 139 | self.pages.append(page) |
| 140 | sizer.Add(page, 0, wx.EXPAND) |
| 141 | # Add page if not exists |
| 142 | if not self.GetPageCount() == 2: |
| 143 | self.AddPage(self.page2, 'Style') |
| 144 | self.page2.Layout() |
| 145 | if 'wxGTK' in wx.PlatformInfo: |
| 146 | self.page2.Show(True) |
| 147 | size = self.page2.GetSizer().GetMinSize() |
| 148 | self.page2.SetScrollbars(1, 1, size.width, size.height, 0, 0, True) |
| 149 | else: |
| 150 | # Remove page if exists |
| 151 | if self.GetPageCount() == 2: |
| 152 | self.SetSelection(0) |
| 153 | self.page1.Refresh() |
| 154 | self.RemovePage(1) |
| 155 | self.modified = False |
| 156 | |
| 157 | def Clear(self): |
| 158 | self.SetData(None) |
| 159 | self.modified = False |
| 160 | |
| 161 | # If some parameter has changed |
| 162 | def IsModified(self): |
| 163 | return self.modified |
| 164 | |
| 165 | def SetModified(self, value): |
| 166 | # Register undo object when modifying first time |
| 167 | if not self.modified and value: |
| 168 | g.undoMan.RegisterUndo(UndoEdit()) |
| 169 | g.frame.SetModified() |
| 170 | self.modified = value |
| 171 | |
| 172 | def Apply(self): |
| 173 | for p in self.pages: p.Apply() |
| 174 | |
| 175 | ################################################################################ |
| 176 | |
| 177 | # General class for notebook pages |
| 178 | class ParamPage(wx.Panel): |
| 179 | labelSize = None |
| 180 | def __init__(self, parent, xxx): |
| 181 | wx.Panel.__init__(self, parent, -1) |
| 182 | self.xxx = xxx |
| 183 | # Register event handlers |
| 184 | for id in paramIDs.values(): |
| 185 | wx.EVT_CHECKBOX(self, id, self.OnCheckParams) |
| 186 | self.checks = {} |
| 187 | self.controls = {} # save python objects |
| 188 | self.controlName = None |
| 189 | |
| 190 | def OnCheckParams(self, evt): |
| 191 | xxx = self.xxx |
| 192 | param = evt.GetEventObject().GetName() |
| 193 | w = self.controls[param] |
| 194 | w.Enable(True) |
| 195 | objElem = xxx.node |
| 196 | if evt.IsChecked(): |
| 197 | # Ad new text node in order of allParams |
| 198 | w.SetValue('') # set empty (default) value |
| 199 | w.SetModified() # mark as changed |
| 200 | elem = g.tree.dom.createElement(param) |
| 201 | # Some classes are special |
| 202 | if param == 'font': |
| 203 | xxx.params[param] = xxxParamFont(xxx.node, elem) |
| 204 | elif param in xxxObject.bitmapTags: |
| 205 | xxx.params[param] = xxxParamBitmap(elem) |
| 206 | else: |
| 207 | xxx.params[param] = xxxParam(elem) |
| 208 | # Find place to put new element: first present element after param |
| 209 | found = False |
| 210 | if xxx.hasStyle: |
| 211 | paramStyles = xxx.allParams + xxx.styles |
| 212 | else: |
| 213 | paramStyles = xxx.allParams |
| 214 | for p in paramStyles[paramStyles.index(param) + 1:]: |
| 215 | # Content params don't have same type |
| 216 | if xxx.params.has_key(p) and p != 'content': |
| 217 | found = True |
| 218 | break |
| 219 | if found: |
| 220 | nextTextElem = xxx.params[p].node |
| 221 | objElem.insertBefore(elem, nextTextElem) |
| 222 | else: |
| 223 | objElem.appendChild(elem) |
| 224 | else: |
| 225 | # Remove parameter |
| 226 | xxx.params[param].remove() |
| 227 | del xxx.params[param] |
| 228 | w.SetValue('') |
| 229 | w.SetModified(False) # mark as not changed |
| 230 | w.Enable(False) |
| 231 | # Set modified flag (provokes undo storing is necessary) |
| 232 | panel.SetModified(True) |
| 233 | def Apply(self): |
| 234 | xxx = self.xxx |
| 235 | if self.controlName: |
| 236 | name = self.controlName.GetValue() |
| 237 | if xxx.name != name: |
| 238 | xxx.name = name |
| 239 | xxx.node.setAttribute('name', name) |
| 240 | for param, w in self.controls.items(): |
| 241 | if w.modified: |
| 242 | paramObj = xxx.params[param] |
| 243 | value = w.GetValue() |
| 244 | if param in xxx.specials: |
| 245 | xxx.setSpecial(param, value) |
| 246 | else: |
| 247 | paramObj.update(value) |
| 248 | |
| 249 | # Save current state |
| 250 | def SaveState(self): |
| 251 | self.origChecks = map(lambda i: (i[0], i[1].GetValue()), self.checks.items()) |
| 252 | self.origControls = map(lambda i: (i[0], i[1].GetValue(), i[1].enabled), |
| 253 | self.controls.items()) |
| 254 | if self.controlName: |
| 255 | self.origName = self.controlName.GetValue() |
| 256 | # Return original values |
| 257 | def GetState(self): |
| 258 | if self.controlName: |
| 259 | return (self.origChecks, self.origControls, self.origName) |
| 260 | else: |
| 261 | return (self.origChecks, self.origControls) |
| 262 | # Set values from undo data |
| 263 | def SetState(self, state): |
| 264 | for k,v in state[0]: |
| 265 | self.checks[k].SetValue(v) |
| 266 | for k,v,e in state[1]: |
| 267 | self.controls[k].SetValue(v) |
| 268 | self.controls[k].Enable(e) |
| 269 | # Set all states to modified |
| 270 | if e and k in self.xxx.params: self.controls[k].modified = True |
| 271 | if self.controlName: |
| 272 | self.controlName.SetValue(state[2]) |
| 273 | |
| 274 | ################################################################################ |
| 275 | |
| 276 | # Panel for displaying properties |
| 277 | class PropPage(ParamPage): |
| 278 | renameDict = {'orient':'orientation', 'option':'proportion', |
| 279 | 'usenotebooksizer':'usesizer', 'dontattachtoframe':'dontattach', |
| 280 | } |
| 281 | def __init__(self, parent, label, xxx): |
| 282 | ParamPage.__init__(self, parent, xxx) |
| 283 | self.box = wx.StaticBox(self, -1, label) |
| 284 | self.box.SetFont(g.labelFont()) |
| 285 | topSizer = wx.StaticBoxSizer(self.box, wx.VERTICAL) |
| 286 | sizer = wx.FlexGridSizer(len(xxx.allParams), 2, 1, 5) |
| 287 | sizer.AddGrowableCol(1) |
| 288 | if xxx.hasName: |
| 289 | label = wx.StaticText(self, -1, 'XML ID:', size=self.labelSize) |
| 290 | control = ParamText(self, 'XML_name', 200) |
| 291 | sizer.AddMany([ (label, 0, wx.ALIGN_CENTER_VERTICAL), |
| 292 | (control, 0, wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.GROW, 10) ]) |
| 293 | self.controlName = control |
| 294 | for param in xxx.allParams: |
| 295 | present = xxx.params.has_key(param) |
| 296 | sParam = self.renameDict.get(param, param) |
| 297 | if param in xxx.required: |
| 298 | if isinstance(xxx, xxxComment): |
| 299 | label = None |
| 300 | else: |
| 301 | label = wx.StaticText(self, paramIDs[param], sParam, |
| 302 | size = self.labelSize, name = param) |
| 303 | else: |
| 304 | label = wx.CheckBox(self, paramIDs[param], sParam, |
| 305 | size = self.labelSize, name = param) |
| 306 | self.checks[param] = label |
| 307 | try: |
| 308 | typeClass = xxx.paramDict[param] |
| 309 | except KeyError: |
| 310 | try: |
| 311 | # Standart type |
| 312 | typeClass = paramDict[param] |
| 313 | except KeyError: |
| 314 | # Default |
| 315 | typeClass = ParamText |
| 316 | control = typeClass(self, param) |
| 317 | control.Enable(present) |
| 318 | # Comment has only one parameter |
| 319 | if isinstance(xxx, xxxComment): |
| 320 | # Bind char event to check Enter key |
| 321 | control.text.Bind(wx.EVT_CHAR, self.OnEnter) |
| 322 | sizer.Add(control, 0, wx.ALIGN_CENTER_VERTICAL | wx.GROW) |
| 323 | else: |
| 324 | sizer.AddMany([ (label, 0, wx.ALIGN_CENTER_VERTICAL), |
| 325 | (control, 0, wx.ALIGN_CENTER_VERTICAL | wx.GROW) ]) |
| 326 | self.controls[param] = control |
| 327 | topSizer.Add(sizer, 1, wx.ALL | wx.EXPAND, 3) |
| 328 | self.SetSizer(topSizer) |
| 329 | topSizer.Fit(self) |
| 330 | |
| 331 | def SetValues(self, xxx): |
| 332 | self.xxx = xxx |
| 333 | self.origChecks = [] |
| 334 | self.origControls = [] |
| 335 | # Set values, checkboxes to False, disable defaults |
| 336 | if xxx.hasName: |
| 337 | self.controlName.SetValue(xxx.name) |
| 338 | self.origName = xxx.name |
| 339 | for param in xxx.allParams: |
| 340 | w = self.controls[param] |
| 341 | w.modified = False |
| 342 | try: |
| 343 | value = xxx.params[param].value() |
| 344 | w.Enable(True) |
| 345 | w.SetValue(value) |
| 346 | if not param in xxx.required: |
| 347 | self.checks[param].SetValue(True) |
| 348 | self.origChecks.append((param, True)) |
| 349 | self.origControls.append((param, value, True)) |
| 350 | except KeyError: |
| 351 | # Optional param not present in xxx - set empty value |
| 352 | self.checks[param].SetValue(False) |
| 353 | w.SetValue('') |
| 354 | w.Enable(False) |
| 355 | self.origChecks.append((param, False)) |
| 356 | self.origControls.append((param, '', False)) |
| 357 | |
| 358 | # This is called only for comment now |
| 359 | def OnEnter(self, evt): |
| 360 | if evt.GetKeyCode() == 13: |
| 361 | g.tree.Apply(self.xxx, g.tree.selection) |
| 362 | else: |
| 363 | evt.Skip() |
| 364 | |
| 365 | ################################################################################ |
| 366 | |
| 367 | # Style notebook page |
| 368 | class StylePage(ParamPage): |
| 369 | renameDict = {'fg':'foreground', 'bg':'background'} |
| 370 | def __init__(self, parent, label, xxx): |
| 371 | ParamPage.__init__(self, parent, xxx) |
| 372 | box = wx.StaticBox(self, -1, label) |
| 373 | box.SetFont(g.labelFont()) |
| 374 | topSizer = wx.StaticBoxSizer(box, wx.VERTICAL) |
| 375 | sizer = wx.FlexGridSizer(len(xxx.styles), 2, 1, 5) |
| 376 | sizer.AddGrowableCol(1) |
| 377 | for param in xxx.styles: |
| 378 | present = xxx.params.has_key(param) |
| 379 | sParam = self.renameDict.get(param, param) |
| 380 | check = wx.CheckBox(self, paramIDs[param], |
| 381 | sParam, size = self.labelSize, name = param) |
| 382 | check.SetValue(present) |
| 383 | control = paramDict[param](self, name = param) |
| 384 | control.Enable(present) |
| 385 | sizer.AddMany([ (check, 0, wx.ALIGN_CENTER_VERTICAL), |
| 386 | (control, 0, wx.ALIGN_CENTER_VERTICAL | wx.GROW) ]) |
| 387 | self.checks[param] = check |
| 388 | self.controls[param] = control |
| 389 | topSizer.Add(sizer, 1, wx.ALL | wx.EXPAND, 3) |
| 390 | self.SetAutoLayout(True) |
| 391 | self.SetSizer(topSizer) |
| 392 | topSizer.Fit(self) |
| 393 | |
| 394 | # Set data for a cahced page |
| 395 | def SetValues(self, xxx): |
| 396 | self.xxx = xxx |
| 397 | self.origChecks = [] |
| 398 | self.origControls = [] |
| 399 | for param in xxx.styles: |
| 400 | present = xxx.params.has_key(param) |
| 401 | check = self.checks[param] |
| 402 | check.SetValue(present) |
| 403 | w = self.controls[param] |
| 404 | w.modified = False |
| 405 | if present: |
| 406 | value = xxx.params[param].value() |
| 407 | else: |
| 408 | value = '' |
| 409 | w.SetValue(value) |
| 410 | w.Enable(present) |
| 411 | self.origChecks.append((param, present)) |
| 412 | self.origControls.append((param, value, present)) |
| 413 | |