1. Ronald Oussoren
  2. pyobjc

Commits

Just van Rossum  committed 6c261ba

New demo PythonBrowser. Compared to what has been posted to the list it is
refactored, cleaned up and commented.

  • Participants
  • Parent commits 9307cf3
  • Branches default

Comments (0)

Files changed (3)

File pyobjc/Examples/PythonBrowser/PythonBrowser.py

View file
  • Ignore whitespace
+"""PythonBrowser.py -- a module and/or demo program implementing a Python
+object browser.
+
+It can be used in two ways:
+1) as a standalone demo app that shows how to use the NSOutlineView class
+2) as a module to add an object browser to your app.
+
+For the latter usage, include PythonBrowser.nib in your app bundle,
+make sure that PythonBrowser.py and PythonBrowserModel.py can be found
+on sys.path, and call
+
+    PythonBrowser.PythonBrowserWindowController(aBrowsableObject)
+
+from your app. The object to be browsed can't be a number, a string or
+None, any other kind of object is fine.
+
+To build the demo program, run this line in Terminal.app:
+
+    $ python buildapp.py --link build
+
+This creates a directory "build" containing PythonBrowser.app. (The
+--link option causes the files to be symlinked to the .app bundle instead
+of copied. This means you don't have to rebuild the app if you edit the
+sources or nibs.)
+"""
+
+from Foundation import NSObject
+from PyObjCTools import NibClassBuilder
+import sys
+
+
+NibClassBuilder.extractClasses("PythonBrowser")
+
+
+# class defined in PythonBrowser.nib
+class PythonBrowserWindowController(NibClassBuilder.AutoBaseClass):
+    # the actual base class is NSWindowController
+    # The following outlets are added to the class:
+    # outlineView
+
+    def __new__(cls, obj):
+        # "Pythonic" constructor
+        return cls.alloc().initWithObject_(obj)
+
+    def initWithObject_(self, obj):
+        from PythonBrowserModel import PythonBrowserModel
+        self = self.initWithWindowNibName_("PythonBrowser")
+        self.setWindowTitleForObject_(obj)
+        self.model = PythonBrowserModel.alloc().initWithObject_(obj)
+        self.outlineView.setDataSource_(self.model)
+        self.outlineView.setDelegate_(self.model)
+        self.outlineView.setTarget_(self)
+        self.outlineView.setDoubleAction_("doubleClick:")
+        self.window().makeFirstResponder_(self.outlineView)
+        self.showWindow_(self)
+        # The window controller doesn't need to be retained (referenced)
+        # anywhere, so we pretend to have a reference to ourselves to avoid
+        # being garbage collected before the window is closed. The extra
+        # reference will be released in self.windowWillClose_()
+        self.retain()
+        return self
+
+    def windowWillClose_(self, notification):
+        # see comment in self.initWithObject_()
+        self.autorelease()
+
+    def setWindowTitleForObject_(self, obj):
+        if hasattr(obj, "__name__"):
+            title = "PythonBrowser -- %s: %s" % (type(obj).__name__, obj.__name__)
+        else:
+            title = "PythonBrowser -- %s" % (type(obj).__name__,)
+        self.window().setTitle_(title)
+
+    def setObject_(self, obj):
+        self.setWindowTitleForObject_(obj)
+        self.model.setObject_(obj)
+        self.outlineView.reloadData()
+
+    def doubleClick_(self, sender):
+        # Open a new browser window for each selected expandable item
+        for row in self.outlineView.selectedRowEnumerator():
+            item = self.outlineView.itemAtRow_(row)
+            if item.isExpandable():
+                PythonBrowserWindowController(item.object)
+
+    def pickRandomModule_(self, sender):
+        """Test method, hooked up from the "Pick Random Module" menu in
+        MainMenu.nib, to test changing the browsed object after the window
+        has been created."""
+        from random import choice
+        mod = None
+        while mod is None:
+            mod = sys.modules[choice(sys.modules.keys())]
+        self.setObject_(mod)
+
+
+class PythonBrowserAppDelegate(NSObject):
+
+    def applicationDidFinishLaunching_(self, notification):
+        self.newBrowser_(self)
+
+    def newBrowser_(self, sender):
+        # The PythonBrowserWindowController instance will retain itself,
+        # so we don't (have to) keep track of all instances here.
+        PythonBrowserWindowController(sys)
+
+
+if __name__ == "__main__":
+    from PyObjCTools import AppHelper
+    AppHelper.runEventLoop()

File pyobjc/Examples/PythonBrowser/PythonBrowserModel.py

View file
  • Ignore whitespace
+"""PythonBrowserModel.py -- module implementing the data model for PythonBrowser."""
+
+from Foundation import NSObject
+from AppKit import NSBeep
+from operator import getitem, setitem
+import sys
+
+
+class PythonBrowserModel(NSObject):
+
+    """This is a delegate as well as a data source for NSOutlineViews."""
+
+    def initWithObject_(self, obj):
+        self = self.init()
+        self.setObject_(obj)
+        return self
+
+    def setObject_(self, obj):
+        self.root = PythonItem("<root>", obj, None, None)
+
+    # NSOutlineViewDataSource  methods
+
+    def outlineView_numberOfChildrenOfItem_(self, view, item):
+        if item is None:
+            item = self.root
+        return len(item)
+
+    def outlineView_child_ofItem_(self, view, child, item):
+        if item is None:
+            item = self.root
+        return item.getChild(child)
+
+    def outlineView_isItemExpandable_(self, view, item):
+        if item is None:
+            item = self.root
+        return item.isExpandable()
+
+    def outlineView_objectValueForTableColumn_byItem_(self, view, col, item):
+        if item is None:
+            item = self.root
+        return getattr(item, col.identifier())
+
+    def outlineView_setObjectValue_forTableColumn_byItem_(self, view, value, col, item):
+        assert col.identifier() == "value"
+        if item.value == value:
+            return
+        try:
+            obj = eval(value, {})
+        except:
+            NSBeep()
+            print "XXX Error:", sys.exc_info()
+            print "XXX      :", repr(value)
+        else:
+            item.setValue(obj)
+
+    # delegate method
+    def outlineView_shouldEditTableColumn_item_(self, view, col, item):
+        return item.isEditable()
+
+
+# objects of these types are not eligable for expansion in the outline view
+SIMPLE_TYPES = (str, unicode, int, long, float, complex)
+
+
+def getInstanceVarNames(obj):
+    """Return a list the names of all (potential) instance variables."""
+    # Recipe from Guido
+    slots = {}
+    if hasattr(obj, "__dict__"):
+        slots.update(obj.__dict__)
+    if hasattr(obj, "__class__"):
+        slots["__class__"] = 1
+    cls = getattr(obj, "__class__", type(obj))
+    if hasattr(cls, "__mro__"):
+        for base in cls.__mro__:
+            for name, value in base.__dict__.items():
+                # XXX using callable() is a heuristic which isn't 100%
+                # foolproof.
+                if hasattr(value, "__get__") and not callable(value) and \
+                        hasattr(obj, name):
+                    slots[name] = 1
+    if "__dict__" in slots:
+        del slots["__dict__"]
+    slots = slots.keys()
+    slots.sort()
+    return slots
+
+
+class NiceError:
+
+    """Wrapper for an exception so we can display it nicely in the browser."""
+
+    def __init__(self, exc_info):
+        self.exc_info = exc_info
+
+    def __repr__(self):
+        from traceback import format_exception_only
+        lines = format_exception_only(*self.exc_info[:2])
+        assert len(lines) == 1
+        error = lines[0].strip()
+        return "*** error *** %s" %error
+
+
+class PythonItem(NSObject):
+
+    def __new__(cls, *args, **kwargs):
+        # "Pythonic" constructor
+        return cls.alloc().init()
+
+    def __init__(self, name, obj, parent, setvalue):
+        self.name = name
+        self.parent = parent
+        self._setValue = setvalue
+        self.type = type(obj).__name__
+        try:
+            self.value = repr(obj)[:256]  # XXX [:256] makes it quite a bit faster for long reprs.
+            assert isinstance(self.value, str)
+        except:
+            self.value = repr(NiceError(sys.exc_info()))
+        self.object = obj
+        self.childrenEditable = 0
+        if isinstance(obj, dict):
+            self.children = obj.keys()
+            self.children.sort()
+            self._getChild = getitem
+            self._setChild = setitem
+            self.childrenEditable = 1
+        elif obj is None or isinstance(obj, SIMPLE_TYPES):
+            self._getChild = None
+            self._setChild = None
+        elif isinstance(obj, (list, tuple)):
+            self.children = range(len(obj))
+            self._getChild = getitem
+            self._setChild = setitem
+            if isinstance(obj, list):
+                self.childrenEditable = 1
+        else:
+            self.children = getInstanceVarNames(obj)
+            self._getChild = getattr
+            self._setChild = setattr
+            self.childrenEditable = 1  # XXX we don't know that...
+        self._childRefs = {}
+
+    def setValue(self, value):
+        self._setValue(self.parent, self.name, value)
+        self.__init__(self.name, value, self.parent, self._setValue)
+
+    def isEditable(self):
+        return self._setValue is not None
+
+    def isExpandable(self):
+        return self._getChild is not None
+
+    def getChild(self, child):
+        if self._childRefs.has_key(child):
+            return self._childRefs[child]
+
+        name = self.children[child]
+        try:
+            obj = self._getChild(self.object, name)
+        except:
+            obj = NiceError(sys.exc_info())
+        if self.childrenEditable:
+            childObj = PythonItem(name, obj, self.object, self._setChild)
+        else:
+            childObj = PythonItem(name, obj, None, None)
+        self._childRefs[child] = childObj
+        return childObj
+
+    def __len__(self):
+        return len(self.children)

File pyobjc/Examples/PythonBrowser/buildapp.py

View file
  • Ignore whitespace
+from bundlebuilder import buildapp
+
+buildapp(
+	mainprogram = "PythonBrowser.py",
+	resources = ["MainMenu.nib", "PythonBrowser.nib", "PythonBrowserModel.py"],
+	nibname = "MainMenu",
+)