Notes about the changes caused by the fix for bug 381323.
Summary
Previously, Setting objects were brought into existence only when the settings dialog was shown to wrap standard Python attributes on the AEState class for configuration. Now the Setting objects exist for the lifetime of the AEState object and replace the use of raw Python attributes for user configurable settings. This change lets us:
- Support the observer pattern for Setting changes.
- Access setting metadata (e.g. min/max on range settings) at any time from the Setting object.
- Remove the need for separate flyweight and default style objects in devices.
- Say which settings should be persisted in the constructor to the Setting object rather than using a strange naming convention.
Support enabled/disabled settings in the configuration dialog. (It's not implemented yet, but I think it can be using the new observer system. See 376101.)
Changes to Perks
The attributes on PerkState objects should no longer be initialized as class variables. Instead, there is now an init() method that you can populate with calls to new* to create setting objects. The getSettings() method is replaced with getGroups() which just returns a group constructed using newGroup() containing other groups and the names of configurable settings.
1 class MyPerkState(Perk.PerkState):
2 def init(self):
3 self.newBool('IsCool', True, 'Is LSR cool?', 'Why would you ever unset this?')
4 self.newRange('HowCool', 100, 'How cool?', 0, 100, 0, 'How cool is LSR?')
5
6 def getGroups(self):
7 root = self.newGroup()
8 g = root.newGroup('LSR Coolness')
9 g.append('IsCool')
10 g.append('HowCool') # can also do g.extend(['IsCool', 'HowCool'])
11 return root
All new* methods now take a final optional parameter named 'persist' (defaults to True). If set to False, the setting value will never be written to disk.
Access to the values of these settings can still be accomplished like so:
1 self.perk_state.HowCool += 1
You can also gain access with the following methods:
Since the setting objects are alive "forever" now, you can also fetch the Setting instances:
For range settings, bounds checking is performed automatically when the 'value' property of a setting is assigned. If the value is outside the allowed bounds, it is snapped to the closest bound automatically without error. You will still get a TypeError in most cases if you try to assign a value that cannot be converted to a proper type for the setting (e.g. None).
Changes to Devices
All the information about states/settings in Perks apply to devices, with the following additional notes.
The init method on a style takes one additional parameter: a reference to the corresponding device after it is initialized. This reference can be used to dynamically detect settings at runtime if needed. The reference should not be stored in an instance variable as it will prevent proper garbage collection when the device is unloaded.
All devices now must define a STYLE class variable pointing to the subclass of AEOutput.Style that will serve as the state object for the device. FLYWEIGHT_STYLE is no longer needed. The trick is to make sure flyweights override certain settings when they might differ per semantic tag and do not override others when the default is all that is needed. This is easily accomplished with logic like the following:
In this case, Invert is available on all style objects (possibly one per zoom region) while Kinematics is only available on the default (affecting all zoom regions).
The AEOutput.Style class defines three additional new* methods for creating relative ranges: newRelPercent, newRelNumeric, and newRelRange. When these settings are in a Style instance that is not the default (i.e. any style with a reference to the default), the value of the current setting adds to the value in the default. This is used for speech pitch, rate, and volume such that changing the value in the default causes a relative change in all voices. It may or may not find use for other devices.
Finally, when implementing a createDistinctStyles method and constructing new Style objects, be sure to call init() on any new styles. Otherwise, settings for these styles will not be created.
See IBMSpeech for the shining example of a device that supports the new AEState system. You may also look at GSpeech, but realize it is far more complex because it is a generic interface to any number of other speech engines (i.e. it's settings must be built dynamically).
The CliqueAudio and SpeechDispatcher devices have not yet been updated for new AEState. See bug #383815.