Coverage for larch/wxlib/larchfilling.py: 21%
374 statements
« prev ^ index » next coverage.py v7.6.0, created at 2024-10-16 21:04 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2024-10-16 21:04 +0000
1#!/usr/bin/env python
2"""
3Larch Filling: stolen and hacked from PyCrust's Filling module
5Filling is the gui tree control through which a user can navigate
6the local namespace or any object."""
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 $"
12import sys
13import wx
14import numpy
15import wx.html as html
16import types
17import time
19from wx.py import editwindow
21import inspect
22from functools import partial
24from wx.py import introspect
25from larch.symboltable import SymbolTable, Group
26from larch.larchlib import Procedure
27from wxutils import Button, pack
28from . import FONTSIZE
30VERSION = '0.9.5(Larch)'
32COMMONTYPES = (int, float, complex, str, bool, dict, list, tuple, numpy.ndarray)
34H5TYPES = ()
35try:
36 import h5py
37 H5TYPES = (h5py.File, h5py.Group, h5py.Dataset)
38except ImportError:
39 pass
41TYPE_HELPS = {}
42for t in COMMONTYPES:
43 TYPE_HELPS[t] = 'help on %s' % t
45IGNORE_TYPE = []
46DOCTYPES = ('BuiltinFunctionType', 'BuiltinMethodType', 'ClassType',
47 'FunctionType', 'GeneratorType', 'InstanceType',
48 'LambdaType', 'MethodType', 'ModuleType',
49 'UnboundMethodType', 'method-wrapper')
51def rst2html(text):
52 return "<br>".join(text.split('\n'))
55def call_signature(obj):
56 """try to get call signature for callable object"""
57 fname = obj.__name__
60 if isinstance(obj, partial):
61 obj = obj.func
63 argspec = None
64 if hasattr(obj, '_larchfunc_'):
65 obj = obj._larchfunc_
67 argspec = inspect.getfullargspec(obj)
68 keywords = argspec.varkw
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)
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
95class FillingTree(wx.TreeCtrl):
96 """FillingTree based on TreeCtrl."""
98 name = 'Larch Filling Tree'
99 revision = __revision__
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):
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)
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'
120 self.item = self.root = self.AddRoot(self.rootLabel, -1, -1, self.rootObject)
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() )
130 def push(self, command=None, more=None):
131 """Receiver for Interpreter.push signal."""
132 self.display()
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)
146 def OnItemCollapsed(self, event):
147 """Remove all children from the item."""
148 item = event.GetItem()
150 def OnSelChanged(self, event):
151 """Display information about the item."""
152 if hasattr(event, 'GetItem'):
153 self.item = event.GetItem()
154 self.display()
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()
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
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
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
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))
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('')
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)
258 needs_doc = False
259 if isinstance(obj, COMMONTYPES):
260 text.append('Type: %s' % otype.__name__)
261 text.append('Value = %s' % repr(obj))
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))
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
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
326 def setText(self, text):
327 """Display information about the current selection."""
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)
333 def setStatusText(self, text):
334 """Display status information."""
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)
341class FillingTextE(editwindow.EditWindow):
342 """FillingText based on StyledTextCtrl."""
344 name = 'Filling Text'
345 revision = __revision__
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
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)
360 def push(self, command, more):
361 """Receiver for Interpreter.push signal."""
362 self.Refresh()
364 def SetText(self, *args, **kwds):
365 self.SetReadOnly(False)
366 editwindow.EditWindow.SetText(self, *args, **kwds)
368class FillingText(wx.TextCtrl):
369 """FillingText based on StyledTextCtrl."""
371 name = 'Filling Text'
372 revision = __revision__
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."""
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)
385 def push(self, command, more):
386 """Receiver for Interpreter.push signal."""
387 self.Refresh()
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)
397class FillingRST(html.HtmlWindow):
398 """FillingText based on Rest doc string!"""
400 name = 'Filling Restructured Text'
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)
408 def push(self, command, more):
409 """Receiver for Interpreter.push signal."""
410 pass
412 def SetText(self, text='', **kwds):
413 # html = ['<html><body>',rst2html(text), '</body></html>']
414 self.SetPage(rst2html(text))
418class Filling(wx.SplitterWindow):
419 """Filling based on wxSplitterWindow."""
421 name = 'Filling'
422 revision = __revision__
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."""
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)
441 self.SplitVertically(self.tree, self.text, 200)
442 self.SetMinimumPaneSize(100)
444 # Override the filling so that descriptions go to FillingText.
445 self.tree.setText = self.text.SetText
447 # Display the root item.
448 if self.tree.root is not None:
449 self.tree.SelectItem(self.tree.root)
450 self.tree.display()
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()
459 def OnChanged(self, event):
460 pass
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
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)
485 try:
486 self.tree.Expand(node)
487 self.tree.SelectItem(node)
488 except:
489 pass
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
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
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)
519 try:
520 self.tree.Expand(node)
521 self.tree.SelectItem(node)
522 except:
523 pass
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)
532 def SaveSettings(self, config):
533 config.WriteInt('Sash/FillingPos', self.GetSashPosition())
534 config.WriteInt('View/Zoom/Filling', self.text.GetZoom())
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)
555 # Override so that status messages go to the status bar.
556 self.filling.tree.setStatusText = self.SetStatusText