1"""PythonBrowserModel.py -- module implementing the data model for PythonBrowser.""" 2 3from Foundation import NSObject 4from AppKit import NSBeep 5from operator import getitem, setitem 6import sys 7 8 9class PythonBrowserModel(NSObject): 10 11 """This is a delegate as well as a data source for NSOutlineViews.""" 12 13 def initWithObject_(self, obj): 14 self = self.init() 15 self.setObject_(obj) 16 return self 17 18 def setObject_(self, obj): 19 self.root = PythonItem("<root>", obj, None, None) 20 21 # NSOutlineViewDataSource methods 22 23 def outlineView_numberOfChildrenOfItem_(self, view, item): 24 if item is None: 25 item = self.root 26 return len(item) 27 28 def outlineView_child_ofItem_(self, view, child, item): 29 if item is None: 30 item = self.root 31 return item.getChild(child) 32 33 def outlineView_isItemExpandable_(self, view, item): 34 if item is None: 35 item = self.root 36 return item.isExpandable() 37 38 def outlineView_objectValueForTableColumn_byItem_(self, view, col, item): 39 if item is None: 40 item = self.root 41 return getattr(item, col.identifier()) 42 43 def outlineView_setObjectValue_forTableColumn_byItem_(self, view, value, col, item): 44 assert col.identifier() == "value" 45 if item.value == value: 46 return 47 try: 48 obj = eval(value, {}) 49 except: 50 NSBeep() 51 print "XXX Error:", sys.exc_info() 52 print "XXX :", repr(value) 53 else: 54 item.setValue(obj) 55 56 # delegate method 57 def outlineView_shouldEditTableColumn_item_(self, view, col, item): 58 return item.isEditable() 59 60 61# objects of these types are not eligable for expansion in the outline view 62SIMPLE_TYPES = (str, unicode, int, long, float, complex) 63 64 65def getInstanceVarNames(obj): 66 """Return a list the names of all (potential) instance variables.""" 67 # Recipe from Guido 68 slots = {} 69 if hasattr(obj, "__dict__"): 70 slots.update(obj.__dict__) 71 if hasattr(obj, "__class__"): 72 slots["__class__"] = 1 73 cls = getattr(obj, "__class__", type(obj)) 74 if hasattr(cls, "__mro__"): 75 for base in cls.__mro__: 76 for name, value in base.__dict__.items(): 77 # XXX using callable() is a heuristic which isn't 100% 78 # foolproof. 79 if hasattr(value, "__get__") and not callable(value) and \ 80 hasattr(obj, name): 81 slots[name] = 1 82 if "__dict__" in slots: 83 del slots["__dict__"] 84 slots = slots.keys() 85 slots.sort() 86 return slots 87 88 89class NiceError: 90 91 """Wrapper for an exception so we can display it nicely in the browser.""" 92 93 def __init__(self, exc_info): 94 self.exc_info = exc_info 95 96 def __repr__(self): 97 from traceback import format_exception_only 98 lines = format_exception_only(*self.exc_info[:2]) 99 assert len(lines) == 1 100 error = lines[0].strip() 101 return "*** error *** %s" %error 102 103 104class PythonItem(NSObject): 105 106 """Wrapper class for items to be displayed in the outline view.""" 107 108 # We keep references to all child items (once created). This is 109 # neccesary because NSOutlineView holds on to PythonItem instances 110 # without retaining them. If we don't make sure they don't get 111 # garbage collected, the app will crash. For the same reason this 112 # class _must_ derive from NSObject, since otherwise autoreleased 113 # proxies will be fed to NSOutlineView, which will go away too soon. 114 115 def __new__(cls, *args, **kwargs): 116 # "Pythonic" constructor 117 return cls.alloc().init() 118 119 def __init__(self, name, obj, parent, setvalue): 120 self.realName = name 121 self.name = str(name) 122 self.parent = parent 123 self._setValue = setvalue 124 self.type = type(obj).__name__ 125 try: 126 self.value = repr(obj)[:256] # XXX [:256] makes it quite a bit faster for long reprs. 127 assert isinstance(self.value, str) 128 except: 129 self.value = repr(NiceError(sys.exc_info())) 130 self.object = obj 131 self.childrenEditable = 0 132 if isinstance(obj, dict): 133 self.children = obj.keys() 134 self.children.sort() 135 self._getChild = getitem 136 self._setChild = setitem 137 self.childrenEditable = 1 138 elif obj is None or isinstance(obj, SIMPLE_TYPES): 139 self._getChild = None 140 self._setChild = None 141 elif isinstance(obj, (list, tuple)): 142 self.children = range(len(obj)) 143 self._getChild = getitem 144 self._setChild = setitem 145 if isinstance(obj, list): 146 self.childrenEditable = 1 147 else: 148 self.children = getInstanceVarNames(obj) 149 self._getChild = getattr 150 self._setChild = setattr 151 self.childrenEditable = 1 # XXX we don't know that... 152 self._childRefs = {} 153 154 def setValue(self, value): 155 self._setValue(self.parent, self.realName, value) 156 self.__init__(self.realName, value, self.parent, self._setValue) 157 158 def isEditable(self): 159 return self._setValue is not None 160 161 def isExpandable(self): 162 return self._getChild is not None 163 164 def getChild(self, child): 165 if self._childRefs.has_key(child): 166 return self._childRefs[child] 167 168 name = self.children[child] 169 try: 170 obj = self._getChild(self.object, name) 171 except: 172 obj = NiceError(sys.exc_info()) 173 if self.childrenEditable: 174 childObj = PythonItem(name, obj, self.object, self._setChild) 175 else: 176 childObj = PythonItem(name, obj, None, None) 177 self._childRefs[child] = childObj 178 return childObj 179 180 def __len__(self): 181 return len(self.children) 182