Hi guys

I was reading the comment of the SettingManager (may be should be  
called a preference manager). But this is impressive.
We should start in 1.1 to use setting.

Stef


System settings are used to manage useful global parameters. A system  
setting is an instance of SystemSettingNode or of one of its  
subclasses. SettingManager is a facade for system setting instanciation.

A setting is declared by a class method with a simple pragma. The  
pragma allows the SystemSettingBrowser to dynamically discover current  
settings. The default system setting pragma is <setting>.

The important point is that a setting must be package specific. It  
means that each package is responsible for the declaring of its own  
settings. For a particular package, specific settings are declared by  
one or several of its classes. The consequence is that when the  
package is loaded, then its settings are automatically loaded and that  
when a package is unloaded, then its settings are automatically  
unloaded.

Basically, settings are of two main kinds: setting nodes and settings  
with value.
- A setting node don't hold any value. Such a setting is declared by a  
method which simply returns a SystemSettingNode instance. Any setting  
can declare any setting node as its parent.
- A setting with value is an instance of SettingValue or of one of its  
subclasses. Such a setting holds a useful global system parameter and  
is made persistent: it is stored in a class variable which is  
initialized by the method that declares the setting.

1 DECLARING A SETTING NODE

All System settings are organized in trees. Each tree root is a  
setting with no parent (parent set to nil). Whatever setting can be a  
root. If a root doesn't need to keep any value, then it can be an  
instance of SystemSettingNode. Such a setting is instanciated with:  
#SettingManager class>>newNode:. The argument is the name of the node.  
A setting node is mostly useful for the grouping of several related  
settings. The dialog instance variable can be set to the name of a  
dialog class. Then, the SystemSettingBrowser shows a button for this  
dialog: when pressed, the class which name is the dialog instance  
variable value receives the #open message. It can be useful in order  
to open a complex or handy dialog for the input of some settings.

The example below shows a root node named 'System' declared in a  
SystemSettings class.
------------------
SystemSettings class>>systemSettingNode
        <setting>
        ^ (SettingManager newNode: 'System') description: 'All standard  
system settings'
------------------

The example below shows a the declaration of a subnode of  
systemSettingNode, it is declared in a UISettings class:
------------------
UISettings class>>UINode
        <setting>
        ^ (SettingManager newNode: 'User interface')
                        description: 'Paramaters for the user interface';
                        parent: #systemSettingNode
------------------
                
2 DECLARING SIMPLE VALUE SETTINGS

2.1 PRESENTATION
A simple value setting is a setting which value is a Boolean, a  
String, a Symbol, a Number, a Font or a Color. Such a setting is hold  
by a SettingValue which is instanciated with #SettingManager  
class>>newSetting:. The argument is the name of the setting.
If the setting has a default value, then, the setting is declared with  
the default value as below:
------------------
MyPackageSettings class>>mySetting
        <setting>
        ^ MySetting
                ifNil: [MySetting := (SettingManager newSetting: 'My setting 
name')
                                                                                
        description: 'A useful description';
                                                                                
        default: aDefaultValue]
------------------
where:
- MyPackageSettings is a class of MyPackage package (a system setting  
is declared class side)
- mySetting is the getter for the setting
- MySetting is the name of the class variable which value is the  
setting (instance of SettingValue)
- 'My setting label' and 'A useful description' are respectively, the  
name and the description shown by the SystemSettingBrowser.
- aDefaultValue is the default value for the setting

If the setting doesn't have any default value (or if its default value  
is nil):
------------------
MyPackageSettings class>>mySetting
        <setting>
        ^ MySetting
                ifNil: [MySetting := (SettingManager newSetting: 'My setting 
name')
                                                                                
        description: 'A useful description';
                                                                                
        type: aTypeSymbol]
------------------

aTypeSymbol is a symbol used by the setting browser in order to know  
how to build the input widget. By default, possible values are  
#Boolean, #String, #Symbol, #Integer, #Float, #Font or #Color. It can  
be of another value but in that case, the class which name is the type  
value should understand the #settingInputWidgetForNode: message.

Classically, a SettingValue is not a root: it is declared as a child  
of one or several nodes in order to ease its managing.
------------------
MyPackageSettings class>>mySetting
        <setting>
        ^ MySetting
                ifNil: [MySetting := (SettingManager newSetting: 'My setting 
name')
                                                                                
        description: 'A useful description';
                                                                                
        parent: aParentNodeSelector;
                                                                                
        type: aTypeSymbol]
------------------

aParentNodeSelector is a symbol which is the parent node method  
selector.

A setting node may have several parents:
------------------
MyPackageSettings class>>mySetting
        <setting>
        ^ MySetting
                ifNil: [MySetting := (SettingManager newSetting: 'My setting 
name')
                                                                                
        description: 'A useful description';
                                                                                
        parent: firstParentNodeSelector;
                                                                                
        parent: secondParentNodeSelector;
                                                                                
        type: aTypeSymbol]
------------------
or
------------------
MyPackageSettings class>>mySetting
        <setting>
        ^ MySetting
                ifNil: [MySetting := (SettingManager newSetting: 'My setting 
name')
                                                                                
        description: 'A useful description';
                                                                                
        parents: #(firstParentNodeSelector secondParentNodeSelector);
                                                                                
        type: aTypeSymbol]
------------------


2.2 SIMPLE VALUE SETTING EXAMPLES

2.2.1 Boolean settings
Definition of the boolean setting "newbyMode" with only a label and a  
default value:
------------------
MyClass class>>newbyMode
        <setting>
        ^ NewbyMode ifNil: [NewbyMode := (SettingManager newSetting: 'Newby  
mode') default: false].
------------------
                                                
A description could be very helpful when using the  
SystemSettingBrowser:                   
------------------
MyClass class>>newbyMode
        <setting>
        ^ NewbyMode ifNil: [NewbyMode := (SettingManager newSetting: 'Newby  
mode')
                        default: false;
                        description: 'the useful description goes here...'].
------------------

The default value is optional. If not present, the declaration must  
explicitly initialize the setting type:
------------------
MyClass class>>newbyMode
        <setting>
        ^ NewbyMode ifNil: [NewbyMode := (SettingManager newSetting: 'Newby  
mode')
                        type: #Boolean;
                        description: 'the useful description goes here...'].
------------------

2.2.4 Color setting
Note that the default can be a valuable. Here, the default is given by  
a MessageSend instance:
------------------
MyClass class>>desktopColor
        <setting>
        ^ DesktopColor
                ifNil: [DesktopColor := (SettingManager newSetting: 'Desktop 
color')
                                        parent: #uiColors;
                                        description: 'The color used for the 
desktop background';
                                        default: (MessageSend receiver: Color 
selector: #green)]
------------------

You can also use a block for the default:
------------------
MyClass class>>desktopColor
        <setting>
        ^ DesktopColor
                ifNil: [DesktopColor := (SettingManager newSetting: 'Desktop 
color')
                                        parent: #uiColors;
                                        description: 'The color used for the 
desktop background';
                                        default: [Color green]]
------------------

2.2.5 Font setting
Here the default is an instance of LogicalFont:
------------------
MyClass class>>codeFont
        <setting>
        ^ CodeFont
                ifNil: [CodeFont := (SettingManager newSetting: 'Code') 
description:  
'The code font';
                                                 parent: #fontSettingNode;
                                                 default: (LogicalFont 
familyName: 'DejaVu Sans'
                                                                                
                fallbackFamilyNames: nil
                                                                                
                pointSize: 12
                                                                                
                stretchValue: 5
                                                                                
                weightValue: 400
                                                                                
                slantValue: 0)]
------------------

The default can also be set with a MessageSend instance:
------------------
MyClass class>>codeFont
        <setting>
        ^ CodeFont
                ifNil: [CodeFont := (SettingManager newSetting: 'Code') 
description:  
'The code font';
                                                 parent: #fontSettingNode;
                                 default: (MessageSend receiver: self selector: 
 
#defaultStandardFont)]

MyClass class>>defaultStandardFont
        ^ LogicalFont
                familyName: 'DejaVu Sans'
                fallbackFamilyNames: nil
                pointSize: 12
                stretchValue: 5
                weightValue: 400
                slantValue: 0
------------------

3 DECLARING A SETTING WITH FINITE SET OF VALUES

3.1 PRESENTATION
A setting can be defined so that its value is get from a restricted  
set of values. The setting is an instance of MultipleSettingValue. It  
is instanciated with #SettingManager class>>newMultiple:. The argument  
is the name of the setting. The collection of values from which a  
multiple setting takes its value is always a set of FixedSettingValue.  
A FixedSettingValue is created with #SettingManager class>>newFixed:.

Such a setting can be of two different kinds :
- if the valid value list is known at declaration time and is  
constant, then the list of values is set by sending the #domainValues:  
message to the setting. The argument is an array of fixed values.

Such a setting can be declared as below:
------------------
MyPackageSettings class>>mySetting
        <setting>
        ^ MySetting
                ifNil: [MySetting := (SettingManager newMultiple: 'My setting 
name')
                                                description: 'A useful 
description';
                                                default: aDefaultValue;
                                                domainValues: 
aListOfFixedSettingValues]
------------------
                                        
The default should be one of the valid values: a real value which is  
token from the list of possible values.

- if the valid value list is not known at declaration time, then the  
list of possible values is dynamically computed by the  
SystemSettingBrowser by evaluating the block or the MessageSend stored  
as the getter instance variable value. The getter instance variable is  
set by sending the #domainValuesGetter: message to the setting.

Such a setting can be declared as below:
------------------
MyPackageSettings class>>mySetting
        <setting>
        ^ MySetting
                ifNil: [MySetting := (SettingManager newMultiple:'My setting 
name')
                                                description: 'A useful 
description';
                                                default: aDefaultValue;
                                                domainValuesGetter: 
aBlockOrAMessageSend]
------------------
The argument of #domainValuesGetter: can be a block or a instance of  
MessageSend. This valuable takes no argument.

3.2 EXAMPLES

The declaration gives all possibles values for the setting. Each  
possible domain value is a fixed setting (read-only setting). Here is  
the example of #hintingSetting which value can be #Light, #Normal,  
#Full or #None:
------------------
MyClass class>>hintingSetting
        <setting>
        ^ HintingSetting
                ifNil: [HintingSetting := (SettingManager newMultiple: 
'Hinting')
                                                 description: 'Defines the 
glyph shapes' translated;
                                                 parent: #freeTypeSettingNode;
                                                 domainValues: {
                                                        SettingManager newFixed 
value: #Light.
                                                        SettingManager newFixed 
value: #Normal.
                                                        SettingManager newFixed 
value: #Full.
                                                        SettingManager newFixed 
value: #None
                                                }].
------------------

In the case where domain values are of simple type, as Symbol for  
example, the shorter declaration given below can be used:               
------------------
MyClass class>>hintingSetting
        <setting>
        ^ HintingSetting
                ifNil: [HintingSetting := (SettingManager newMultiple: 
'Hinting')
                                                 description: 'Defines the 
glyph shapes' translated;
                                                 parent: #freeTypeSettingNode;
                                                 domainValues: {#Light. 
#Normal. #Full. #None}]
------------------

The value domain can also be made of a set of complex objects. The  
example below shows #themeSetting setting. The value domain is an  
array with two fixed settings, for UIThemeStandardSqueak and for  
UIThemeWatery2:
------------------
MyClass class>>themeSetting
        <setting>
        ^ ThemeSetting
                ifNil: [ThemeSetting := (SettingManager newMultiple: 'UITheme')
                                                description: 'The theme to use 
for UI look and feel';
                                                parent: #uiSettingNode;
                                                type: #UITheme;
                                                default: UIThemeWatery2;
                                                domainValues: {
                                                        (SettingManager 
newFixed: 'Standard Squeak') value:  
UIThemeStandardSqueak.
                                                        (SettingManager 
newFixed: 'Watery 2') description: 'Similar to  
a nice OS'; value: UIThemeWatery2}]
------------------

The shorter declaration given below can also be used. The domain  
values is a list of association. Each association key is used as the  
name of the fixed setting, and the corresponding association value is  
used as the value of the fixed setting:
------------------
MyClass class>>themeSetting
        <setting>
        ^ ThemeSetting
                ifNil: [ThemeSetting := (SettingManager newMultiple: 'UI theme')
                                                 description: 'The theme to use 
for UI look and feel';
                                                 parent: #uiSettingNode;
                                                 type: #UITheme;
                                                 default: UIThemeWatery2;
                                                 domainValues: {'Standard' -> 
UIThemeStandardSqueak. 'Watery 2' - 
 > UIThemeWatery2}]
------------------

Previous version has a serious drawback: the list of possible values  
is fixed, then the first time the setting is read, the list is  
definitively built. After its building, there is no way to update the  
list except by the reinitializing of the corresponding class variable  
(hard reset).
Regarding the themeSetting example, the problem is that new instances  
of UITheme can be added in the system after the setting has been  
created and initialized. In order be able to take into account system  
dependent domain values, you can makes the list dynamically computed.  
A getter instance variable can be set to a block or to an instance of  
a MessageSend. Such a valuable is dynamically evaluated by the  
SystemSettingBrowser in order to get current and up-to-date list of  
valid values. Here is the themeSetting example revisited:

------------------
MyClass class>>themeSetting
        <setting>
        ^ ThemeSetting
                ifNil: [ThemeSetting := (SettingManager newMultiple:'UI theme')
                                                parent: #uiSettingNode;
                                                default: UIThemeWatery2;
                                                domainValuesGetter: [UITheme 
allSubclasses collect: [:cls |  
SettingManager newFixed value: cls]]]
------------------

or with a much simpler version
------------------
MyClass class>>themeSetting
        <setting>
        ^ ThemeSetting
                ifNil: [ThemeSetting := (SettingManager newMultiple:'UI theme')
                                                parent: #uiSettingNode;
                                                default: UIThemeWatery2;
                                                domainValuesGetter: [UITheme 
allSubclasses]]
------------------

4 DECLARING RANGE SETTING

4.1 PRESENTATION
A setting for which all possible values are given by a range is an  
instance of RangeSettingValue. Such a setting is instanciated with  
#SettingManager class>>newRange:. The argument is the name of the  
setting. The range is set by sending a #range: message to a setting.  
The argument is an Interval instance.

Such a setting can be declared as below:
------------------
MyPackageSettings class>>mySetting
        <setting>
        ^ MySetting
                ifNil: [MySetting := (SettingManager newRange: 'My setting 
name')
                                                description: 'A useful 
description';
                                                default: aDefaultValue;
                                                range: anInterval]
------------------
The range is an instance of Interval. It is set by the #range: message  
and is evaluated by the SystemSettingBrowser when choosing a new value.
Note that the SystemSettingBrowser presents an input field and a slider.

4.2 EXAMPLE

------------------
FontSettings class>>glyphContrast
        <setting>
        ^ GlyphContrast
                ifNil: [GlyphContrast := (SettingManager newRange: 'Glyph 
contrast')
                                                description: 'Change the 
contrast level for glyphs. This is an  
integer between 1 and 100';
                                                parent: #freeTypeSettingNode;
                                                default: 50;
                                                range: (1 to: 100)]
------------------

5 USING SPECIFIC DIALOG
For setting browsing and editing, it could be useful to use a specific  
dialog instead of SystemSettingBrowser simple input widgets. For that  
purpose, the dialog attribute can be set with the name of a dialog  
class. While browsing a setting with a dialog, the  
SystemSettingBrowser presents a button for the dialog opening. The  
only constraint is that the dialog class must understand the #open  
message.

For a node with children, it can be very useful in order to be able to  
use a cool dialog for the input of all children instead of being  
forced to input them one by one as it is the case with the  
SystemSettingrowser.

Here is an example of a setting node with its dialog instance variable  
set to the value #FontSettingDialog. In the case of fonts, it could be  
useful in order to allow the using of a specific cool dialog for font  
preferences settings.
------------------
FontSettings class>>fontSettingNode
        <setting>
        ^ (SettingManager newNode: 'Fonts')
                description: 'System fonts settings';
                dialog: #FontSettingDialog;
                parent: #uiSettingNode
------------------

The dialog instance variable can be set for a setting value too. Then,  
neither the default value nor the type is needed:
------------------
UISettings class>>uiStyle
        <setting>
        ^ UIStyle ifNil: [UIStyle := (SettingManager newSetting: 'UI style')
                        dialog: #UIStyleChooserDialog;
                        description: 'System style'].
------------------

6 SIMPLE SETTING WITH CHILDREN
A setting value can be a parent for another setting value. As an  
example, #gradientButtonLook can be a child of a boolean setting  
#advancedGUISetting:
------------------
MyClass class>>advancedGUISetting
        <setting>
        ^ AdvancedGUISetting
                ifNil: [AdvancedGUISetting :=
                                                (SettingManager newSetting: 
'Advanced GUI settings')
                                                parent: #uiSettingNode;
                                                default: true]
                                        
MyClass class>>gradientButtonLook
        <setting>
        ^ GradientButtonLook
                ifNil: [GradientButtonLook :=
                                                (SettingManager newSetting: 
'Gradient look for buttons')
                                                parent: #advancedGUISetting;
                                                default: true]
------------------

7 MANAGING SETTING VALUES

7.1 GETTING AND CHANGING A SETTING VALUE

Let's use the following example of a boolean setting declaration:
------------------
MyPackageSettings class>>mySetting
        <setting>
        ^ MySetting ifNil: [MySetting := (SettingManager newSetting: 'My  
setting label') default: true]
------------------
                                                                                
The following two expressions are used in order to, respectively, get  
and set the value of the setting:
------------------
MyPackageSettings mySetting value. "get the value of the setting"
MyPackageSettings mySetting value: false. "change the value of the  
setting"
------------------

7.2 SETTING VALUE CHANGE NOTIFICATION

7.2.1 REGISTERING A LISTENER

Whatever object can be declared as a change listener for a particular  
setting: each time the setting value is changed, each listener is  
aware of the change by receiving a particular message. In order to  
declare a listener, a client object must send the #whenChangedSend:to:  
message to a setting. The first argument is a message selector and the  
second argument it a receiver for the message. Then, the receiver  
becomes a listener for the setting. The message selector can take zero  
or one argument. In the case of one argument, the changed setting  
value is passed as argument to the message send. Note that an object  
can be declared as a listener several times, each time with a  
different message selector.

As an example, lets take the example of the desktopColor setting:
------------------
DesktopSettings class>>desktopColor
        <setting>
        ^ DesktopColor
                ifNil: [DesktopColor := (SettingManager newSetting: 'Desktop 
color')
                                        parent: #uiColors;
                                        description: 'The color used for the 
desktop background';
                                        default: (MessageSend receiver: Color 
selector: #white)]
------------------

Each time the desktop color setting is changed, the World color should  
be changed according to the new setting value. A solution could be to  
declare the World as a listener for the desktopColor setting changes  
and make it change its color according to the desktopColor setting  
each time the desktopColor setting value is changed. This can be  
achieved from the class initialize method as below:

------------------
DesktopSettings class>>initialize
        self desktopColor whenChangedSend: #color: to: World.
------------------

7.2.2 UNREGISTERING A LISTENER

There is two ways to unregister a listener for a particular setting  
changes.
- The first way is to globally unregister a listener whatever the  
message selector. After the listener is unregistered, it is totally  
ignored when the concerned setting is changed. This is achieved by  
sending a #forget: message to the setting with the listener as argument.

Concerning the desktopColor setting example, the World can be  
unregistered as shown below:
------------------
DesktopSettings desktopColor forget: World. "no more notification at  
all"
------------------

- The second way is to unregister a listener for only a specific  
selector. This is achieved by sending a #forget:selector: message to  
the setting. The first argument is the listener and the second is a  
selector.

Concerning the desktopColor setting example, the World can be  
unregistered for only the #color: selector as shown below:
------------------
DesktopSettings desktopColor
        forget: World
        selector: #color:. "The World can still be notified using another  
selector"
------------------





_______________________________________________
Pharo-project mailing list
[email protected]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project

Reply via email to