1. Ronald Oussoren
  2. pyobjc

Commits

Jack Jansen  committed 3f0625d

Adding the step-by-step tutorial.

  • Participants
  • Parent commits 975ba33
  • Branches default

Comments (0)

Files changed (11)

File pyobjc/Doc/tutorial/step12-src/CurrencyConverter.py

View file
  • Ignore whitespace
+from Foundation import NSObject, NSObject
+from AppKit import NibClassBuilder
+from AppKit.NibClassBuilder import AutoBaseClass
+
+NibClassBuilder.extractClasses("MainMenu")
+
+
+# class defined in MainMenu.nib
+class Converter(AutoBaseClass):
+    # the actual base class is NSObject
+    
+    def convertAmount(self, amt, rate):
+        return amt*rate
+
+# class defined in MainMenu.nib
+class ConverterController(AutoBaseClass):
+    # the actual base class is NSObject
+    # The following outlets are added to the class:
+    # converter
+    # dollarField
+    # rateField
+    # totalField
+
+    def convert_(self, sender):
+        amt = self.dollarField.floatValue()
+        rate = self.rateField.floatValue()
+
+        total = self.converter.convertAmount(rate, amt)
+        self.totalField.setFloatValue_(total)
+        self.rateField.selectText_(self)
+
+    def awakeFromNib(self):
+        self.rateField.window().makeKeyAndOrderFront_(self)
+        self.rateField.selectText_(self)
+        
+    def invertRate_(self, sender):
+        rate = self.rateField.floatValue()
+        if rate != 0:
+            rate = 1/rate
+        self.rateField.setFloatValue_(rate)
+        
+def main():
+    from AppKit import NSApplicationMain, NSRunAlertPanel, NSApp
+    import traceback
+    import sys
+    def unexpectedErrorAlert():
+        exceptionInfo = traceback.format_exception_only(*sys.exc_info()[:2])[0].strip()
+        return NSRunAlertPanel("An unexpected error has occurred",
+                "(%s)" % exceptionInfo,
+                "Continue", "Quit", None)
+    
+    
+    mainFunc = NSApplicationMain
+    args = (sys.argv,)
+    while 1:
+        try:
+            mainFunc(*args)
+        except:
+            if not unexpectedErrorAlert():
+                raise
+            mainFunc = NSApp().run
+            args = ()
+        else:
+            break
+
+if __name__ == '__main__':
+    main()
+    

File pyobjc/Doc/tutorial/step12-src/MainMenu.nib/classes.nib

View file
  • Ignore whitespace
+{
+    IBClasses = (
+        {CLASS = Converter; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, 
+        {
+            ACTIONS = {convert = id; invertRate = id; }; 
+            CLASS = ConverterController; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {
+                converter = id; 
+                dollarField = NSTextField; 
+                rateField = NSTextField; 
+                totalField = NSTextField; 
+            }; 
+            SUPERCLASS = NSObject; 
+        }, 
+        {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }
+    ); 
+    IBVersion = 1; 
+}

File pyobjc/Doc/tutorial/step12-src/MainMenu.nib/info.nib

View file
  • Ignore whitespace
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBDocumentLocation</key>
+	<string>485 378 356 240 0 0 1280 1002 </string>
+	<key>IBEditorPositions</key>
+	<dict>
+		<key>29</key>
+		<string>52 213 318 44 0 0 1280 1002 </string>
+	</dict>
+	<key>IBFramework Version</key>
+	<string>286.0</string>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>29</integer>
+		<integer>21</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>6I32</string>
+</dict>
+</plist>

File pyobjc/Doc/tutorial/step12-src/MainMenu.nib/objects.nib

  • Ignore whitespace
Binary file added.

File pyobjc/Doc/tutorial/step3-MainMenu.nib/classes.nib

View file
  • Ignore whitespace
+{
+    IBClasses = (
+        {CLASS = Converter; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, 
+        {
+            ACTIONS = {convert = id; }; 
+            CLASS = ConverterController; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {
+                converter = id; 
+                dollarField = NSTextField; 
+                rateField = NSTextField; 
+                totalField = NSTextField; 
+            }; 
+            SUPERCLASS = NSObject; 
+        }, 
+        {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }
+    ); 
+    IBVersion = 1; 
+}

File pyobjc/Doc/tutorial/step3-MainMenu.nib/info.nib

View file
  • Ignore whitespace
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBDocumentLocation</key>
+	<string>253 287 356 240 0 0 1280 1002 </string>
+	<key>IBEditorPositions</key>
+	<dict>
+		<key>29</key>
+		<string>52 213 318 44 0 0 1280 1002 </string>
+	</dict>
+	<key>IBFramework Version</key>
+	<string>286.0</string>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>29</integer>
+		<integer>21</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>6I32</string>
+</dict>
+</plist>

File pyobjc/Doc/tutorial/step3-MainMenu.nib/objects.nib

  • Ignore whitespace
Binary file added.

File pyobjc/Doc/tutorial/step4-CurrencyConverter.py

View file
  • Ignore whitespace
+from Foundation import NSObject, NSObject
+from AppKit import NibClassBuilder
+from AppKit.NibClassBuilder import AutoBaseClass
+
+NibClassBuilder.extractClasses("MainMenu")
+
+
+# class defined in MainMenu.nib
+class Converter(AutoBaseClass):
+    # the actual base class is NSObject
+    pass
+
+
+# class defined in MainMenu.nib
+class ConverterController(AutoBaseClass):
+    # the actual base class is NSObject
+    # The following outlets are added to the class:
+    # converter
+    # dollarField
+    # rateField
+    # totalField
+
+    def convert_(self, sender):
+        pass
+
+

File pyobjc/Doc/tutorial/step5-CurrencyConverter.py

View file
  • Ignore whitespace
+from Foundation import NSObject, NSObject
+from AppKit import NibClassBuilder
+from AppKit.NibClassBuilder import AutoBaseClass
+
+NibClassBuilder.extractClasses("MainMenu")
+
+
+# class defined in MainMenu.nib
+class Converter(AutoBaseClass):
+    # the actual base class is NSObject
+    pass
+
+
+# class defined in MainMenu.nib
+class ConverterController(AutoBaseClass):
+    # the actual base class is NSObject
+    # The following outlets are added to the class:
+    # converter
+    # dollarField
+    # rateField
+    # totalField
+
+    def convert_(self, sender):
+        pass
+
+def main():
+    from AppKit import NSApplicationMain, NSRunAlertPanel, NSApp
+    import traceback
+    import sys
+    def unexpectedErrorAlert():
+        exceptionInfo = traceback.format_exception_only(*sys.exc_info()[:2])[0].strip()
+        return NSRunAlertPanel("An unexpected error has occurred",
+                "(%s)" % exceptionInfo,
+                "Continue", "Quit", None)
+    
+    
+    mainFunc = NSApplicationMain
+    args = (sys.argv,)
+    while 1:
+        try:
+            mainFunc(*args)
+        except:
+            if not unexpectedErrorAlert():
+                raise
+            mainFunc = NSApp().run
+            args = ()
+        else:
+            break
+
+if __name__ == '__main__':
+    main()

File pyobjc/Doc/tutorial/step8-CurrencyConverter.py

View file
  • Ignore whitespace
+from Foundation import NSObject, NSObject
+from AppKit import NibClassBuilder
+from AppKit.NibClassBuilder import AutoBaseClass
+
+NibClassBuilder.extractClasses("MainMenu")
+
+
+# class defined in MainMenu.nib
+class Converter(AutoBaseClass):
+    # the actual base class is NSObject
+    
+    def convertAmount(self, amt, rate):
+        return amt*rate
+
+# class defined in MainMenu.nib
+class ConverterController(AutoBaseClass):
+    # the actual base class is NSObject
+    # The following outlets are added to the class:
+    # converter
+    # dollarField
+    # rateField
+    # totalField
+
+    def convert_(self, sender):
+        amt = self.dollarField.floatValue()
+        rate = self.rateField.floatValue()
+
+        total = self.converter.convertAmount(rate, amt)
+        self.totalField.setFloatValue_(total)
+        self.rateField.selectText_(self)
+
+    def awakeFromNib(self):
+        self.rateField.window().makeKeyAndOrderFront_(self)
+        self.rateField.selectText_(self)
+        
+def main():
+    from AppKit import NSApplicationMain, NSRunAlertPanel, NSApp
+    import traceback
+    import sys
+    def unexpectedErrorAlert():
+        exceptionInfo = traceback.format_exception_only(*sys.exc_info()[:2])[0].strip()
+        return NSRunAlertPanel("An unexpected error has occurred",
+                "(%s)" % exceptionInfo,
+                "Continue", "Quit", None)
+    
+    
+    mainFunc = NSApplicationMain
+    args = (sys.argv,)
+    while 1:
+        try:
+            mainFunc(*args)
+        except:
+            if not unexpectedErrorAlert():
+                raise
+            mainFunc = NSApp().run
+            args = ()
+        else:
+            break
+
+if __name__ == '__main__':
+    main()

File pyobjc/Doc/tutorial/tutorial.txt

View file
  • Ignore whitespace
+=======================================
---------------
   to the Python Lib directory::
   
       $ setenv PYLIB /Library/Frameworks/Python.framework/Versions/Current/lib/python2.3
       
   or if you use bash as your shell::
   
       $ export PYLIB=/Library/Frameworks/Python.framework/Versions/Current/lib/python2.3
       
   *XXXX Editing Note:* This step is different if you use PyObjC on top of Apple's Python 2.2.
   Moreover, the bundlebuilder step is going to be different too.
    
   in the new file dialog, save this file as ``src/MainMenu.nib``.
   
   "*Developing Cocoa Objective-C Applications: a Tutorial*", chapter 3,
   just after the section "*Open the Main Nib File*". Stop when you get
   to the section "*Implementing the Classes of Currency Converter*",
   as we are going to do this in Python, not Objective-C. Your nib file
   should now be the same as step3-MainMenu.nib_
   
   
   When invoked as a main program from the command line ``NibClassBuilder`` will
   parse the NIB file and create a skeleton module for you. Invoke
   it as follows (from the ``src`` directory)::
   
       $ python $PYLIB/site-packages/PyObjC/AppKit/NibClassBuilder.py \
               MainMenu.nib > CurrencyConverter.py
               
   The result of this can be seen in step4-CurrencyConverter.py_
   
               
   in the future this will become simpler, but for now we need to add a main program
   that jumps through various hoops to get the application started.
   The results of this can be seen in step5-CurrencyConverter.py_.
   
   
   *XXXX Implementation Note:* NibClassBuilder should do this for you, at least
   optionally.
   
--------------------------
   in the future it could well be that this step is not needed during development,
   or that it becomes simpler, but for now we do the following::
   
       $ python $PYLIB/plat-mac/bundlebuilder.py --link --nib=MainMenu \\
               --mainprogram=CurrencyConverter.py --resource=MainMenu.nib build
               
   There are a few things to note:
   
   - We use the ``--link`` argument. This creates a ``.app`` bundle which has symlinks
     to our source files (``CurrencyConverter.py`` and ``MainMenu.nib``) in stead of copies.
     This allows us to keep working on the sources without having to re-run bundlebuilder
     after every edit.
   - You have to specify MainMenu twice: once (with ``--resource``) to get it linked/copied 
     into the bundle and once (with ``--nib``) to get it listed in the ``.plist`` file.
     
   - double-click ``build/CurrencyConverter.app`` from the Finder (where you won't see the
     .app extension)
   - similarly, open it from the terminal with::
   
       $ open build/CurrencyConverter.app
       
   - run it directly from the Terminal, as::
   
       $ ./build/CurrencyConverter.app/Contents/MacOS/CurrencyConverter
       
   The last method is actually the best to use: it leaves stdout and stderr connected
   to your terminal session so you can see what is going on if there are errors. When
   running with the other two methods stdout and stderr go to the console.
   
   When you run your script as it is now it should behave identically as when you
   tested your interface in Interface Builder in step 3, only now the skeleton is
   in Python, not Objective-C.
   
----------------
   some. Follow Apple's documentation again, chapter 3, section "Implementing
   Currency Converter's Classes". We need to do some name mangling on ObjC
   names to get the corresponding Python names, see *XXXX Editing Note: need reference*
   for the full details, but
   in short if the ObjC name of a method is ``modifyArg:andAnother:``, in
   other words, if an ObjC call would be::
   
   	[object modifyArg: arg1 andAnother: arg2]
   	
   the Python name will be ``modifyArg_andAnother_`` and you invoke it as::
   
   	object.modifyArg_andAnother(arg1, arg2)
   
   Note that we don't do this mangling for ``Converter.ConvertAmount()``: this method is
   only called by other Python code, so there is no need to go through the name mangling.
   Also, if we would want to make this method callable from ObjC code we would have
   to tell the PyObjC runtime system about the types of the arguments, so it could
   do the conversion. This is beyond the scope of this first tutorial, see 
   *XXXX Editing Note: need reference* for more information.
   
   The application should now be fully functional, try it. The results of what we have
   up to now can be seen in step8-CurrencyConverter.py_.
   
   
---------------------------
    The main problem, which may be obvious, is that we cannot run NibClassBuilder again
    because we would destroy all the code we wrote in steps 5 and 8, so we do this by
    hand.
   
    What we are going to do is add an "invert rate" command, because I always get this
    wrong: in stead of typing in the exchange rate from dollars to euros I type in the
    rate to convert from euros to dollars.
   
    Open ``MainMenu.nib`` in Interface Builder. Select the *Classes* view and there select the
    ``ConverterController`` class. In the info panel select the *Attributes* from the popup.
    Select the *Actions* tab, and add an action ``invertRate:``. You have now told Interface Builder
    that instances of the ``ConverterController`` class have grown a new method ``invertRate_()``.
   
    In the ``MainMenu.nib main`` window open the *MainMenu* menubar. Select the ``Edit``
    menu. Make sure the *Menus* palette is open and selected, drag a separator to the 
    ``Edit`` menu and then drag an ``Item`` there. Double click the item and set the text to
    ``Invert Exchange Rate``.
   
    Make the connection by control-dragging from the new ``Invert Exchange Rate`` menu item to
    the ``ConverterController`` instance in the Instances tab in the ``MainMenu.nib`` main window.
    *NOTE:* you drag to the *instance* of ``ConverterController``, not to the class. This is logical
    if you think about it, but I keep forgetting it myself all the time too.
    In the *Info* panel, *Connections* section, select ``invertRate:`` and press *Connect*. 
    *NOTE:* that is another thing I always forget: pressing *Connect* after selecting the action:-)
   
    to do it, but we are going to try it anyway, just to see what sort of spectacular
    crash we get. Alas, nothing spectacular about it: when the NIB is loaded the Cocoa runtime
    system tries to make the conection, notices that we have no ``invertRate_()`` method in
    our ``ConverterController`` class and it gives an error message::
       
       $ ./build/CurrencyConverter.app/Contents/MacOS/CurrencyConverter 
       2003-03-24 16:22:43.037 CurrencyConverter[16163] Could not connect the action 
       invertRate: to target of class ConverterController
   
    Moreover, it has disabled the ``Invert Exchange Rate`` menu command and continues, so the 
    program really works as it did before, only with one more (disabled) menu item.
   
---------
    value of ``rateField``, inverts it and puts it back. We deliberately forget to test for
    divide by zero. We run the program again, and now the menu entry is enabled. After
    trying it with a couple of non-zero exchange rates we try it with an exchange rate of
    zero (or empty, which is the same). We get a dialog box giving the Python exception, and
    offering the choice of continuing or quitting. If we select *Quit* then we get a normal
    Python exception traceback in the Terminal window. The exception is actually re-raised,
    so we can use the standard Python trick to debug this: set shell variable
    ``PYTHONINSPECT`` to ``1``, run our program, try to invert an exchange rate of ``0``, press quit.
    At the ``>>>`` prompt, type ``import pdb ; pdb.pm()`` and we can inspect all local variables,
    etc.
      
    step12-src_ directory.
    
   
--------------------------------
----------------------------------------------