1from Foundation import NSObject, NSBundle
2from objc import selector, getClassList, objc_object, IBOutlet, IBAction
3
4import objc
5import AppKit
6objc.setVerbose(1)
7
8try:
9    import AddressBook
10except ImportError:
11    pass
12
13try:
14    import PreferencePanes
15except ImportError:
16    pass
17
18try:
19    import InterfaceBuilder
20except ImportError:
21    pass
22
23WRAPPED={}
24class Wrapper (NSObject):
25    """
26    NSOutlineView doesn't retain values, which means we cannot use normal
27    python values as values in an outline view.
28    """
29    def init_(self, value):
30        self.value = value
31        return self
32
33    def __str__(self):
34        return '<Wrapper for %s>'%self.value
35
36    def description(self):
37        return str(self)
38
39def wrap_object(obj):
40    if WRAPPED.has_key(obj):
41        return WRAPPED[obj]
42    else:
43        WRAPPED[obj] = Wrapper.alloc().init_(obj)
44        return WRAPPED[obj]
45
46def unwrap_object(obj):
47    if obj is None:
48        return obj
49    return obj.value
50
51methodIdentifier = {
52    'objc_method':0,
53    'python_method':1,
54    'signature':2
55}
56
57def classPath(cls):
58    if cls == objc_object:
59        return ''
60    elif cls.__bases__[0] == objc_object:
61        return cls.__name__
62    else:
63        return '%s : %s'%(classPath(cls.__bases__[0]), cls.__name__)
64
65SYSFRAMEWORKS_DIR='/System/Library/Frameworks/'
66def classBundle(cls):
67    framework = NSBundle.bundleForClass_(cls).bundlePath()
68    if framework.startswith(SYSFRAMEWORKS_DIR):
69        framework = framework[len(SYSFRAMEWORKS_DIR):]
70        if framework.endswith('.framework'):
71            framework = framework[:-len('.framework')]
72    return framework
73
74class ClassesDataSource (NSObject):
75    __slots__ = ('_classList', '_classTree', '_methodInfo', '_classInfo')
76
77    classLabel = IBOutlet()
78    classTable = IBOutlet()
79    frameworkLabel = IBOutlet()
80    methodTable = IBOutlet()
81    searchBox = IBOutlet()
82    window = IBOutlet()
83
84    def clearClassInfo(self):
85        self._methodInfo = []
86        self.methodTable.reloadData()
87        self.window.setTitle_('iClass')
88
89
90    def setClassInfo(self, cls):
91        self.window.setTitle_('iClass: %s'%cls.__name__)
92        self.classLabel.setStringValue_(classPath(cls))
93        self.frameworkLabel.setStringValue_(classBundle(cls))
94        self._methodInfo = []
95        for nm, meth in cls.pyobjc_instanceMethods.__dict__.items():
96            if not isinstance(meth, selector):
97                continue
98            self._methodInfo.append(
99                ('-'+meth.selector, nm, meth.signature))
100
101        for nm, meth in cls.pyobjc_classMethods.__dict__.items():
102            if not isinstance(meth, selector):
103                continue
104            self._methodInfo.append(
105                ('+'+meth.selector, nm, meth.signature))
106        self._methodInfo.sort()
107        self.methodTable.reloadData()
108
109    def outlineViewSelectionDidChange_(self, notification):
110        rowNr = self.classTable.selectedRow()
111        if rowNr == -1:
112            self.clearClassInfo()
113        else:
114            item = self.classTable.itemAtRow_(rowNr)
115            self.setClassInfo(unwrap_object(item))
116
117    def showClass(self, cls):
118        # First expand the tree (to make item visible)
119        super = cls.__bases__[0]
120        if  super == objc_object:
121            return
122
123        self.showClass(super)
124        item = wrap_object(super)
125        rowNr = self.classTable.rowForItem_(item)
126        self.classTable.expandItem_(item)
127
128    def selectClass(self, cls):
129        self.showClass(cls)
130
131        item = wrap_object(cls)
132        rowNr = self.classTable.rowForItem_(item)
133
134        self.classTable.scrollRowToVisible_(rowNr)
135        self.classTable.selectRow_byExtendingSelection_(rowNr, False)
136
137    @IBAction
138    def searchClass_(self, event):
139        val = self.searchBox.stringValue()
140        if not val:
141            return
142
143        found = None
144        for cls in self._classTree.keys():
145            if not cls: continue
146            if cls.__name__ == val:
147                self.setClassInfo(cls)
148                self.selectClass(cls)
149                return
150            elif cls.__name__.startswith(val):
151                if not found:
152                    found = cls
153                elif len(cls.__name__) > len(found.__name__):
154                    found = cls
155
156        # mvl 2009-03-02: fix case where no match is found caused exception:
157        if (found is None): return
158
159        self.setClassInfo(found)
160        self.selectClass(found)
161
162
163    def refreshClasses(self):
164        self._classList = getClassList()
165        self._classTree = {}
166        self._methodInfo = []
167
168        for cls in self._classList:
169            super = cls.__bases__[0]
170            if super == objc_object:
171                super = None
172            else:
173                super = super
174            if not self._classTree.has_key(cls):
175                self._classTree[cls] = []
176
177            if self._classTree.has_key(super):
178                self._classTree[super].append(cls)
179            else:
180                self._classTree[super] = [ cls ]
181
182        for lst in self._classTree.values():
183            lst.sort()
184
185    def init(self):
186        self._classInfo = getClassList()
187        self.refreshClasses()
188        return self
189
190    def awakeFromNib(self):
191        self._classInfo = getClassList()
192        self.refreshClasses()
193
194
195    def outlineView_child_ofItem_(self, outlineview, index, item):
196        return wrap_object(self._classTree[unwrap_object(item)][index])
197
198    def outlineView_isItemExpandable_(self, outlineview, item):
199        return len(self._classTree[unwrap_object(item)]) != 0
200
201
202    def outlineView_numberOfChildrenOfItem_(self, outlineview, item):
203        return len(self._classTree[unwrap_object(item)])
204
205    def outlineView_objectValueForTableColumn_byItem_(self, outlineview, column, item):
206        if item is None:
207            return '<None>'
208        else:
209            v = item.value
210            return v.__name__
211
212
213    def numberOfRowsInTableView_(self, aTableView):
214        return len(self._methodInfo)
215
216    def tableView_objectValueForTableColumn_row_(self,
217                             aTableView, aTableColumn, rowIndex):
218        return self._methodInfo[rowIndex][methodIdentifier[aTableColumn.identifier()]]
219