Coverage for /Users/Newville/Codes/xraylarch/larch/wxlib/larchfilling.py: 22%

375 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-09 10:08 -0600

1#!/usr/bin/env python 

2""" 

3Larch Filling: stolen and hacked from PyCrust's Filling module 

4 

5Filling is the gui tree control through which a user can navigate 

6the local namespace or any object.""" 

7 

8__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>" 

9__cvsid__ = "$Id: filling.py 37633 2006-02-18 21:40:57Z RD $" 

10__revision__ = "$Revision: 37633 $" 

11 

12import sys 

13import wx 

14import numpy 

15import wx.html as html 

16import types 

17import time 

18 

19from wx.py import editwindow 

20 

21import inspect 

22from functools import partial 

23 

24from wx.py import introspect 

25from larch.symboltable import SymbolTable, Group 

26from larch.larchlib import Procedure 

27from wxutils import Button, pack 

28from . import FONTSIZE 

29 

30VERSION = '0.9.5(Larch)' 

31 

32COMMONTYPES = (int, float, complex, str, bool, dict, list, tuple, numpy.ndarray) 

33 

34H5TYPES = () 

35try: 

36 import h5py 

37 H5TYPES = (h5py.File, h5py.Group, h5py.Dataset) 

38except ImportError: 

39 pass 

40 

41TYPE_HELPS = {} 

42for t in COMMONTYPES: 

43 TYPE_HELPS[t] = 'help on %s' % t 

44 

45IGNORE_TYPE = [] 

46DOCTYPES = ('BuiltinFunctionType', 'BuiltinMethodType', 'ClassType', 

47 'FunctionType', 'GeneratorType', 'InstanceType', 

48 'LambdaType', 'MethodType', 'ModuleType', 

49 'UnboundMethodType', 'method-wrapper') 

50 

51def rst2html(text): 

52 return "<br>".join(text.split('\n')) 

53 

54 

55def call_signature(obj): 

56 """try to get call signature for callable object""" 

57 fname = obj.__name__ 

58 

59 

60 if isinstance(obj, partial): 

61 obj = obj.func 

62 

63 argspec = None 

64 if hasattr(obj, '_larchfunc_'): 

65 obj = obj._larchfunc_ 

66 

67 argspec = inspect.getfullargspec(obj) 

68 keywords = argspec.varkw 

69 

70 fargs = [] 

71 ioff = len(argspec.args) - len(argspec.defaults) 

72 for iarg, arg in enumerate(argspec.args): 

73 if arg == '_larch': 

74 continue 

75 if iarg < ioff: 

76 fargs.append(arg) 

77 else: 

78 fargs.append("%s=%s" % (arg, repr(argspec.defaults[iarg-ioff]))) 

79 if keywords is not None: 

80 fargs.append("**%s" % keywords) 

81 

82 out = "%s(%s)" % (fname, ', '.join(fargs)) 

83 maxlen = 71 

84 if len(out) > maxlen: 

85 o = [] 

86 while len(out) > maxlen: 

87 ecomm = maxlen - out[maxlen-1::-1].find(',') 

88 o.append(out[:ecomm]) 

89 out = " "*(len(fname)+1) + out[ecomm:].strip() 

90 if len(out) > 0: 

91 o.append(out) 

92 out = '\n'.join(o) 

93 return out 

94 

95class FillingTree(wx.TreeCtrl): 

96 """FillingTree based on TreeCtrl.""" 

97 

98 name = 'Larch Filling Tree' 

99 revision = __revision__ 

100 

101 def __init__(self, parent, id=-1, pos=wx.DefaultPosition, 

102 size=wx.DefaultSize, style=wx.TR_DEFAULT_STYLE, 

103 rootObject=None, rootLabel=None, rootIsNamespace=False): 

104 

105 """Create FillingTree instance.""" 

106 wx.TreeCtrl.__init__(self, parent, id, pos, size, style) 

107 self.rootIsNamespace = rootIsNamespace 

108 self.rootLabel = rootLabel 

109 self.item = None 

110 self.root = None 

111 self.setRootObject(rootObject) 

112 

113 def setRootObject(self, rootObject=None): 

114 self.rootObject = rootObject 

115 if self.rootObject is None: 

116 return 

117 if not self.rootLabel: 

118 self.rootLabel = 'Larch Data' 

119 

120 self.item = self.root = self.AddRoot(self.rootLabel, -1, -1, self.rootObject) 

121 

122 self.SetItemHasChildren(self.root, self.objHasChildren(self.rootObject)) 

123 self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged, id=self.GetId()) 

124 self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated, id=self.GetId()) 

125 self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.OnItemExpanding, id=self.GetId()) 

126 self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnItemCollapsed, id=self.GetId()) 

127 self.Bind(wx.EVT_RIGHT_DOWN, self.OnSelChanged, id=self.GetId() ) 

128 

129 

130 def push(self, command=None, more=None): 

131 """Receiver for Interpreter.push signal.""" 

132 self.display() 

133 

134 def OnItemExpanding(self, event=None): 

135 """Add children to the item.""" 

136 try: 

137 item = event.GetItem() 

138 except: 

139 item = self.item 

140 if self.IsExpanded(item): 

141 return 

142 self.addChildren(item) 

143 self.SelectItem(item) 

144 

145 

146 def OnItemCollapsed(self, event): 

147 """Remove all children from the item.""" 

148 item = event.GetItem() 

149 

150 def OnSelChanged(self, event): 

151 """Display information about the item.""" 

152 if hasattr(event, 'GetItem'): 

153 self.item = event.GetItem() 

154 self.display() 

155 

156 def OnItemActivated(self, event): 

157 """Launch a DirFrame.""" 

158 item = event.GetItem() 

159 text = self.getFullName(item) 

160 obj = self.GetItemData(item) 

161 frame = FillingFrame(parent=self, size=(500, 500), 

162 rootObject=obj, 

163 rootLabel=text, rootIsNamespace=False) 

164 frame.Show() 

165 

166 def objHasChildren(self, obj): 

167 """Return true if object has children.""" 

168 children = self.objGetChildren(obj) 

169 if isinstance(children, dict): 

170 return len(children) > 0 

171 else: 

172 return False 

173 

174 def objGetChildren(self, obj): 

175 """Return dictionary with attributes or contents of object.""" 

176 otype = type(obj) 

177 d = {} 

178 if (obj is None or obj is False or obj is True): 

179 return d 

180 self.ntop = 0 

181 if isinstance(obj, SymbolTable) or isinstance(obj, Group): 

182 d = obj._members() 

183 if isinstance(obj, COMMONTYPES): 

184 d = obj 

185 elif isinstance(obj, h5py.Group): 

186 try: 

187 for key, val in obj.items(): 

188 d[key] = val 

189 except (AttributeError, ValueError): 

190 pass 

191 elif isinstance(obj, h5py.Dataset): 

192 d = obj 

193 elif isinstance(obj, (list, tuple)): 

194 for n in range(len(obj)): 

195 key = '[' + str(n) + ']' 

196 d[key] = obj[n] 

197 elif (not isinstance(obj, wx.Object) 

198 and not hasattr(obj, '__call__')): 

199 d = self.GetAttr(obj) 

200 return d 

201 

202 def GetAttr(self, obj): 

203 out = {} 

204 for key in dir(obj): 

205 if not ((key.startswith('__') and key.endswith('__')) or 

206 key.startswith('_SymbolTable') or 

207 key == '_main'): 

208 try: 

209 out[key] = getattr(obj, key) 

210 except: 

211 out[key] = key 

212 return out 

213 

214 def addChildren(self, item): 

215 self.DeleteChildren(item) 

216 obj = self.GetItemData(item) 

217 children = self.objGetChildren(obj) 

218 if not children: 

219 return 

220 try: 

221 keys = children.keys() 

222 except: 

223 return 

224 # keys.sort(lambda x, y: cmp(str(x).lower(), str(y).lower())) 

225 # print("add Children ", obj, keys) 

226 for key in sorted(keys): 

227 itemtext = str(key) 

228 # Show string dictionary items with single quotes, except 

229 # for the first level of items, if they represent a 

230 # namespace. 

231 if (isinstance(obj, dict) and isinstance(key, str) and 

232 (item != self.root or 

233 (item == self.root and not self.rootIsNamespace))): 

234 itemtext = repr(key) 

235 child = children[key] 

236 branch = self.AppendItem(parent=item, text=itemtext, data=child) 

237 self.SetItemHasChildren(branch, self.objHasChildren(child)) 

238 

239 def display(self): 

240 item = self.item 

241 if not item: 

242 return 

243 obj = self.GetItemData(item) 

244 if self.IsExpanded(item): 

245 self.addChildren(item) 

246 self.setText('') 

247 

248 if wx.Platform == '__WXMSW__': 

249 if obj is None: # Windows bug fix. 

250 return 

251 self.SetItemHasChildren(item, self.objHasChildren(obj)) 

252 otype = type(obj) 

253 text = [] 

254 fullname = self.getFullName(item) 

255 if fullname is not None: 

256 text.append("%s\n" % fullname) 

257 

258 needs_doc = False 

259 if isinstance(obj, COMMONTYPES): 

260 text.append('Type: %s' % otype.__name__) 

261 text.append('Value = %s' % repr(obj)) 

262 

263 elif isinstance(obj, Group): 

264 text.append('Group: %s ' % obj.__name__) 

265 gdoc = getattr(obj, '__doc__', None) 

266 if gdoc is None: gdoc = Group.__doc__ 

267 text.append(gdoc) 

268 elif hasattr(obj, '__call__'): 

269 text.append('Function: %s' % obj) 

270 try: 

271 text.append("\n%s" % call_signature(obj)) 

272 except: 

273 pass 

274 needs_doc = True 

275 else: 

276 text.append('Type: %s' % str(otype)) 

277 text.append('Value = %s' % repr(obj)) 

278 text.append('\n') 

279 if needs_doc: 

280 try: 

281 doclines = obj.__doc__.strip().split('\n') 

282 except: 

283 doclines = ['No documentation found'] 

284 indent = 0 

285 for dline in doclines: 

286 if len(dline.strip()) > 0: 

287 indent = dline.index(dline.strip()) 

288 break 

289 for d in doclines: 

290 text.append(d[indent:]) 

291 text.append('\n') 

292 self.setText('\n'.join(text)) 

293 

294 def getFullName(self, item, part=''): 

295 """Return a syntactically proper name for item.""" 

296 try: 

297 name = self.GetItemText(item) 

298 except: 

299 return None 

300 

301 # return 'Could not get item name: %s' % repr(item) 

302 parent = None 

303 obj = None 

304 if item != self.root: 

305 parent = self.GetItemParent(item) 

306 obj = self.GetItemData(item) 

307 # Apply dictionary syntax to dictionary items, except the root 

308 # and first level children of a namepace. 

309 if ((isinstance(obj, dict) or hasattr(obj, 'keys')) and 

310 ((item != self.root and parent != self.root) or 

311 (parent == self.root and not self.rootIsNamespace))): 

312 name = '[' + name + ']' 

313 # Apply dot syntax to multipart names. 

314 if part: 

315 if part[0] == '[': 

316 name += part 

317 else: 

318 name += '.' + part 

319 # Repeat for everything but the root item 

320 # and first level children of a namespace. 

321 if (item != self.root and parent != self.root) \ 

322 or (parent == self.root and not self.rootIsNamespace): 

323 name = self.getFullName(parent, part=name) 

324 return name 

325 

326 def setText(self, text): 

327 """Display information about the current selection.""" 

328 

329 # This method will likely be replaced by the enclosing app to 

330 # do something more interesting, like write to a text control. 

331 print( text) 

332 

333 def setStatusText(self, text): 

334 """Display status information.""" 

335 

336 # This method will likely be replaced by the enclosing app to 

337 # do something more interesting, like write to a status bar. 

338 print( text) 

339 

340 

341class FillingTextE(editwindow.EditWindow): 

342 """FillingText based on StyledTextCtrl.""" 

343 

344 name = 'Filling Text' 

345 revision = __revision__ 

346 

347 def __init__(self, parent, id=-1, pos=wx.DefaultPosition, 

348 size=wx.DefaultSize, style=wx.CLIP_CHILDREN, bgcol=None): 

349 """Create FillingText instance.""" 

350 if bgcol is not None: 

351 editwindow.FACES['backcol'] = bgcol 

352 

353 

354 editwindow.EditWindow.__init__(self, parent, id, pos, size, style) 

355 # Configure various defaults and user preferences. 

356 self.SetReadOnly(False) # True) 

357 self.SetWrapMode(True) 

358 self.SetMarginWidth(1, 0) 

359 

360 def push(self, command, more): 

361 """Receiver for Interpreter.push signal.""" 

362 self.Refresh() 

363 

364 def SetText(self, *args, **kwds): 

365 self.SetReadOnly(False) 

366 editwindow.EditWindow.SetText(self, *args, **kwds) 

367 

368class FillingText(wx.TextCtrl): 

369 """FillingText based on StyledTextCtrl.""" 

370 

371 name = 'Filling Text' 

372 revision = __revision__ 

373 

374 def __init__(self, parent, id=-1, pos=wx.DefaultPosition, 

375 size=wx.DefaultSize, bcol=None, 

376 style=wx.TE_MULTILINE|wx.TE_RICH|wx.TE_READONLY): 

377 """Create FillingText instance.""" 

378 

379 wx.TextCtrl.__init__(self, parent, id, style=style) 

380 self.CanCopy() 

381 self.fontsize = FONTSIZE 

382 fixfont = wx.Font(FONTSIZE, wx.MODERN, wx.NORMAL, wx.BOLD, 0, "") 

383 self.SetFont(fixfont) 

384 

385 def push(self, command, more): 

386 """Receiver for Interpreter.push signal.""" 

387 self.Refresh() 

388 

389 def SetText(self, *args, **kwds): 

390 # self.SetReadOnly(False) 

391 self.Clear() 

392 self.SetInsertionPoint(0) 

393 self.WriteText(*args) 

394 self.ShowPosition(0) 

395 

396 

397class FillingRST(html.HtmlWindow): 

398 """FillingText based on Rest doc string!""" 

399 

400 name = 'Filling Restructured Text' 

401 

402 def __init__(self, parent, id=-1, pos=wx.DefaultPosition, 

403 size=wx.DefaultSize, style=wx.NO_FULL_REPAINT_ON_RESIZE, **kws): 

404 """Create FillingRST instance.""" 

405 html.HtmlWindow.__init__(self, parent, id, style=wx.NO_FULL_REPAINT_ON_RESIZE) 

406 

407 

408 def push(self, command, more): 

409 """Receiver for Interpreter.push signal.""" 

410 pass 

411 

412 def SetText(self, text='', **kwds): 

413 # html = ['<html><body>',rst2html(text), '</body></html>'] 

414 self.SetPage(rst2html(text)) 

415 

416 

417 

418class Filling(wx.SplitterWindow): 

419 """Filling based on wxSplitterWindow.""" 

420 

421 name = 'Filling' 

422 revision = __revision__ 

423 

424 def __init__(self, parent, pos=wx.DefaultPosition, 

425 size=wx.DefaultSize, style=wx.SP_3D|wx.SP_LIVE_UPDATE, 

426 name='Filling Window', rootObject=None, 

427 rootLabel=None, rootIsNamespace=False, bgcol=None, 

428 fgcol=None): 

429 """Create a Filling instance.""" 

430 

431 wx.SplitterWindow.__init__(self, parent, -1, pos, size, style, name) 

432 self.tree = FillingTree(parent=self, rootObject=rootObject, 

433 rootLabel=rootLabel, 

434 rootIsNamespace=rootIsNamespace) 

435 self.text = FillingText(parent=self) 

436 self.tree.SetBackgroundColour(bgcol) 

437 self.tree.SetForegroundColour(fgcol) 

438 self.text.SetBackgroundColour(bgcol) 

439 self.text.SetForegroundColour(fgcol) 

440 

441 self.SplitVertically(self.tree, self.text, 200) 

442 self.SetMinimumPaneSize(100) 

443 

444 # Override the filling so that descriptions go to FillingText. 

445 self.tree.setText = self.text.SetText 

446 

447 # Display the root item. 

448 if self.tree.root is not None: 

449 self.tree.SelectItem(self.tree.root) 

450 self.tree.display() 

451 

452 def SetRootObject(self, rootObject=None): 

453 self.tree.setRootObject(rootObject) 

454 if self.tree.root is not None: 

455 self.tree.SelectItem(self.tree.root) 

456 self.tree.display() 

457 

458 

459 def OnChanged(self, event): 

460 pass 

461 

462 def onRefresh(self, evt=None): 

463 """ refesh data tree, preserving current selection""" 

464 root = self.tree.GetRootItem() 

465 this = self.tree.GetFocusedItem() 

466 parents = [self.tree.GetItemText(this)] 

467 while True: 

468 try: 

469 this = self.tree.GetItemParent(this) 

470 if this == root: 

471 break 

472 parents.append(self.tree.GetItemText(this)) 

473 except: 

474 break 

475 self.tree.Collapse(root) 

476 self.tree.Expand(root) 

477 node = root 

478 

479 while len(parents) > 0: 

480 name = parents.pop() 

481 node = self.get_node_by_name(node, name) 

482 if node is not None: 

483 self.tree.Expand(node) 

484 

485 try: 

486 self.tree.Expand(node) 

487 self.tree.SelectItem(node) 

488 except: 

489 pass 

490 

491 def get_node_by_name(self, node, name): 

492 if node is None: 

493 node = self.tree.GetRootItem() 

494 item, cookie = self.tree.GetFirstChild(node) 

495 if item.IsOk() and self.tree.GetItemText(item) == name: 

496 return item 

497 

498 nodecount = self.tree.GetChildrenCount(node) 

499 while nodecount > 1: 

500 nodecount -= 1 

501 item, cookie = self.tree.GetNextChild(node, cookie) 

502 if not item.IsOk() or self.tree.GetItemText(item) == name: 

503 return item 

504 

505 def ShowNode(self, name): 

506 """show node by name""" 

507 root = self.tree.GetRootItem() 

508 self.tree.Collapse(root) 

509 self.tree.Expand(root) 

510 node = root 

511 parts = name.split('.') 

512 parts.reverse() 

513 while len(parts) > 0: 

514 name = parts.pop() 

515 node = self.get_node_by_name(node, name) 

516 if node is not None: 

517 self.tree.Expand(node) 

518 

519 try: 

520 self.tree.Expand(node) 

521 self.tree.SelectItem(node) 

522 except: 

523 pass 

524 

525 def LoadSettings(self, config): 

526 pos = config.ReadInt('Sash/FillingPos', 200) 

527 wx.FutureCall(250, self.SetSashPosition, pos) 

528 zoom = config.ReadInt('View/Zoom/Filling', -99) 

529 if zoom != -99: 

530 self.text.SetZoom(zoom) 

531 

532 def SaveSettings(self, config): 

533 config.WriteInt('Sash/FillingPos', self.GetSashPosition()) 

534 config.WriteInt('View/Zoom/Filling', self.text.GetZoom()) 

535 

536 

537class FillingFrame(wx.Frame): 

538 """Frame containing the namespace tree component.""" 

539 name = 'Filling Frame' 

540 revision = __revision__ 

541 def __init__(self, parent=None, id=-1, title='Larch Data Tree', 

542 pos=wx.DefaultPosition, size=(600, 400), 

543 style=wx.DEFAULT_FRAME_STYLE, rootObject=None, 

544 rootLabel=None, rootIsNamespace=False): 

545 """Create FillingFrame instance.""" 

546 wx.Frame.__init__(self, parent, id, title, pos, size, style) 

547 intro = 'Larch Data Tree' 

548 self.CreateStatusBar() 

549 self.SetStatusText(intro) 

550 self.filling = Filling(parent=self, 

551 rootObject=rootObject, 

552 rootLabel=rootLabel, 

553 rootIsNamespace=rootIsNamespace) 

554 

555 # Override so that status messages go to the status bar. 

556 self.filling.tree.setStatusText = self.SetStatusText