ControlledPreferences

Demonstrates how to use Cocoa Bindings to simplify storing and retrieving user preferences. Also demonstrates how to use an NSValueTransformer to archive/unarchive a non-property-list type automatically (NSColor, in this case).

Originally from “Cocoa Bindings Examples and Hints”, converted to PyObjC by u.fiedler.

Sources

ControlledPreferences.py

#
#  ControlledPreferences
#
#

from PyObjCTools import AppHelper

# import classes required to start application
import PreferencesPanelController
import FontNameToDisplayNameTransformer
import FontSampleDisplayView

# start the event loop
AppHelper.runEventLoop()

FontNameToDisplayNameTransformer.py

#
#  FontNameToDisplayNameTransformer.py
#  ControlledPreferences
#
#  Converted by u.fiedler on 04.02.05.
#  with great help from Bob Ippolito - Thank you Bob!
#
#  The original version was written in Objective-C by Malcolm Crawford
#  at http://homepage.mac.com/mmalc/CocoaExamples/controllers.html

from Cocoa import NSValueTransformer, NSString, NSFont

class FontNameToDisplayNameTransformer(NSValueTransformer):
    """
    Takes as input the fontName of a font as stored in user defaults,
    returns the displayed font name of the font to show to the user.
    """
    @classmethod
    def transformedValueClass(cls):
        return NSString

    @classmethod
    def allowsReverseTransformation(cls):
        return False

    def transformedValue_(self, aValue):
        font = NSFont.fontWithName_size_(aValue, 12)
        return font.displayName()

FontSampleDisplayView.py

#
#  FontSampleDisplayView.py
#  ControlledPreferences
#
#  Converted by u.fiedler on 04.02.05.
#  with great help from Bob Ippolito - Thank you Bob!
#
#  The original version was written in Objective-C by Malcolm Crawford
#  at http://homepage.mac.com/mmalc/CocoaExamples/controllers.html

from objc import super
from Cocoa import NSView, NSUserDefaultsController, NSUnarchiver
from Cocoa import NSDrawLightBezel, NSRectFill, NSInsetRect
from Cocoa import NSFontAttributeName, NSAttributedString, NSFont
from Cocoa import NSMakePoint, NSNotificationCenter, NSUserDefaultsDidChangeNotification
from PyObjCTools.KeyValueCoding import getKey

class FontSampleDisplayView(NSView):
    """
    Display WordOfTheDay with the preferred font and color
    """
    def drawRect_(self, rect):
        defaults = NSUserDefaultsController.sharedUserDefaultsController().values()
        favoriteColor = NSUnarchiver.unarchiveObjectWithData_(getKey(defaults, 'FavoriteColor'))
        fontName = getKey(defaults, 'FontName')
        fontSize = getKey(defaults, 'FontSize')
        favoriteFont = NSFont.fontWithName_size_(fontName, fontSize)
        wordOfTheDay = getKey(defaults, 'WordOfTheDay')

        # Do the actual drawing
        myBounds = self.bounds() # = (x, y), (bw, bh)
        NSDrawLightBezel(myBounds, myBounds)
        favoriteColor.set()
        NSRectFill(NSInsetRect(myBounds, 2, 2))
        attrsDictionary = {NSFontAttributeName: favoriteFont}
        attrString = NSAttributedString.alloc().initWithString_attributes_(wordOfTheDay, attrsDictionary)
        attrSize = attrString.size() # = (bw, bh)
        attrString.drawAtPoint_(
            NSMakePoint(
                (attrSize.width / -2) + (myBounds.size.width / 2),
                (attrSize.height / -2) + (myBounds.size.height / 2),
            ),
        )

    def initWithFrame_(self, frameRect):
        self = super(FontSampleDisplayView, self).initWithFrame_(frameRect)
        dnc = NSNotificationCenter.defaultCenter()
        dnc.addObserver_selector_name_object_(self, "redisplay:", NSUserDefaultsDidChangeNotification, None)
        return self

    def redisplay_(self, sender):
        self.setNeedsDisplay_(True)

PreferencesPanelController.py

#
#  PreferencesPanelController.py
#  ControlledPreferences
#
#  Converted by u.fiedler on 04.02.05.
#  with great help from Bob Ippolito - Thank you Bob!
#
#  The original version was written in Objective-C by Malcolm Crawford
#  at http://homepage.mac.com/mmalc/CocoaExamples/controllers.html

import objc
from FontNameToDisplayNameTransformer import FontNameToDisplayNameTransformer
from Cocoa import NSWindowController, NSUserDefaultsController, NSFont
from Cocoa import NSFontManager, NSValueTransformer, NSArchiver, NSColor
from PyObjCTools.KeyValueCoding import getKey

class PreferencesPanelController (NSWindowController):

    @objc.IBAction
    def changeTextFont_(self, sender):
        "The user changed the current font selection, so update the default font"

        # Get font name and size from user defaults
        defaults = NSUserDefaultsController.sharedUserDefaultsController().values()
        fontName = getKey(defaults, 'FontName')
        fontSize = getKey(defaults, 'FontSize')

        # Create font from name and size; initialize font panel
        font = NSFont.fontWithName_size_(fontName, fontSize)
        if font is None:
            font = NSFont.systemFontOfSize_(NSFont.systemFontSize())
        NSFontManager.sharedFontManager().setSelectedFont_isMultiple_(font, False)
        NSFontManager.sharedFontManager().orderFrontFontPanel_(self)

        # Set window as firstResponder so we get changeFont: messages
        self.window().makeFirstResponder_(self.window())


    @objc.IBAction
    def changeFont_(self, sender):
        "This is the message the font panel sends when a new font is selected"
        # Get selected font
        fontManager = NSFontManager.sharedFontManager()
        selectedFont = fontManager.selectedFont()
        if selectedFont is None:
            selectedFont = NSFont.systemFontOfSize_(NSFont.systemFontSize())
        panelFont = fontManager.convertFont_(selectedFont)

        # Get and store details of selected font
        # Note: use fontName, not displayName.  The font name identifies the font to
        # the system, we use a value transformer to show the user the display name
        fontSize = panelFont.pointSize()

        defaults = NSUserDefaultsController.sharedUserDefaultsController().values()
        defaults.setValue_forKey_(panelFont.fontName(), "FontName")
        defaults.setValue_forKey_(fontSize, "FontSize")


# Set up initial values for defaults:
# Create dictionary with keys and values for WordOfTheDay, FontName,
# FontSize, and FavoriteColor.  Mostly straightforward, but:
#
# Store the fontName of the font as the default; the textfield displays
# the font's displayName using a value transformer.
#
# The color must be archived -- you can't store NSColors directly in NSUserDefaults.
dictionary = {}
dictionary['WordOfTheDay'] = 'Today'
systemFont = NSFont.systemFontOfSize_(NSFont.systemFontSize())
dictionary["FontName"] = systemFont.fontName()
dictionary["FontSize"] = systemFont.pointSize()
archivedColor = NSArchiver.archivedDataWithRootObject_(NSColor.greenColor())
dictionary['FavoriteColor'] = archivedColor
NSUserDefaultsController.sharedUserDefaultsController().setInitialValues_(dictionary)

# Create and register font name value transformer
transformer = FontNameToDisplayNameTransformer.alloc().init()
NSValueTransformer.setValueTransformer_forName_(transformer, 'FontNameToDisplayNameTransformer')

setup.py

"""
Script for building the example:

Usage:
    python3 setup.py py2app
"""
from setuptools import setup

setup(
    name="ControlledPreferences",
    app=["ControlledPreferences.py"],
    data_files=["English.lproj"],
    setup_requires=[
        "py2app",
        "pyobjc-framework-Cocoa",
    ]
)

Resources