Skip to main content
Engineering LibreTexts

4.3: Declaring a Setting

  • Page ID
    43808
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    All global preferences of Pharo can be viewed or changed using the Settings Browser. A preference is typically a class variable or an instance variable of a singleton. If one wants to be able to change a value from the SettingsBrowser, then a setting must be declared for it. A setting is declared by a particular class method that should be implemented as follows: it takes a builder as argument and it is tagged with the <systemsettings> pragma.

    The argument, aBuilder, serves as an API or facade for building setting declarations. The pragma allows the Settings Browser to dynamically discover current setting declarations.

    The important point is that a setting declaration should 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 or a companion package. There is no global setting defining class or package (as was the case in Pharo1.0). The direct benefit is that when the package is loaded, then its settings are automatically loaded. When a package is unloaded, then its settings are automatically unloaded. In addition, a Setting declaration should not refer to any Setting class but to the builder argument. This assures that your application is not dependent on Settings and that you will be able to remove Setting if you want to define extremely small footprint applications.

    Let’s take the example of the caseSensitiveFinds preference. It is a boolean preference which is used for text searching. If it is true, then text finding is case sensitive. This preference is stored in the CaseSensitiveFinds class variable of the class TextEditor. Its value can be queried and changed by, respectively, TextEditor class>>caseSensitiveFinds and TextEditor class>>caseSensitiveFinds: given below:

    TextEditor class>>caseSensitiveFinds
        ^ CaseSensitiveFinds ifNil: [CaseSensitiveFinds := false]
    
    TextEditor class>>caseSensitiveFinds: aBoolean
        CaseSensitiveFinds := aBoolean
    

    To define a setting for this preference (i.e., for the CaseSensitiveFinds class variable) and be able to see it and change it from the Settings Browser, the method below is implemented. The result is shown in the screenshot of the Figure \(\PageIndex{1}\).

    CodeHolderSystemSettings class>>caseSensitiveFindsSettingsOn: aBuilder
        <systemsettings>
        (aBuilder setting: #caseSensitiveFinds)
            target: TextEditor;
            label: 'Case sensitive search' translated;
            description: 'If true, then the "find" command in text will always make its searches in
            a case-sensitive fashion' translated;
            parent: #codeEditing.
    

    caseSensitiveFinds setting.
    Figure \(\PageIndex{1}\): The caseSensitiveFinds setting.

    Now, let’s study this setting declaration in details.

    The header

    CodeHolderSystemSettings class>>caseSensitiveFindsSettingsOn: aBuilder
        ...
    

    This class method is declared in the class CodeHolderSystemSettings. This class is dedicated to settings and contains nothing but setting declarations. Defining such a class is not mandatory; in fact, any class can define setting declarations. We define it that way to make sure that the setting declaration is packaged in a different package than the one of the preference definition – for layering purposes.

    This method takes a builder as argument. This object serves as a facade API for setting buildings: the contents of the method essentially consists in sending messages to the builder to declare and organize a sub-tree of settings.

    The pragma

    A setting declaration is tagged with the <systemsettings> pragma.

    CodeHolderSystemSettings class>>caseSensitiveFindsSettingsOn: aBuilder
        <systemsettings>
        ...
    

    In fact, when the settings browser is opened, it first collects all settings declarations by searching all methods with the <systemsettings> pragma. In addition, if you compile a setting declaration method while a Settings Browser is opened then it is automatically updated with the new setting.

    The setting configuration

    A setting is declared by sending the message setting: to the builder with an identifier passed as argument. Here is an example where the identifier is #caseSensitiveFinds:

    CodeHolderSystemSettings class>>caseSensitiveFindsSettingsOn: aBuilder
        <systemsettings>
        (aBuilder setting: #caseSensitiveFinds)
        ...
    

    Sending the message setting: to a builder creates a setting node builder which itself is a wrapper for a setting node. By default, the symbol passed as argument is considered as the selector used by the Settings Browser to get the preference value. The selector for changing the preference value is by default built by adding a colon to the getter selector (i.e., it is caseSensitiveFinds: here). These selectors are sent to a target which is by default the class in which the method is implemented (i.e., CodeHolderSystemSettings). Thus, this one line setting declaration is sufficient if caseSensitiveFinds and caseSensitiveFinds: accessors are implemented in CodeHolderSystemSettings.

    In fact, very often, these default initializations will not fit your need. Of course you can adapt the setting node configuration to take into account your specific situation. For example, the corresponding getter and setter accessors for the caseSensitiveFinds setting are implemented in the class TextEditor. Then, we should explicitly set that the target is TextEditor. This is done by sending the message target: to the setting node with the target class TextEditor passed as argument as shown by the updated definition:

    CodeHolderSystemSettings class>>caseSensitiveFindsSettingsOn: aBuilder
        <systemsettings>
        (aBuilder setting: #caseSensitiveFinds)
            target: TextEditor
    

    This very short version is fully functional and enough to be compiled and taken into account by the Settings Browser as shown by Figure \(\PageIndex{2}\).

    A first simple version of the caseSensitiveFinds setting.
    Figure \(\PageIndex{2}\): A first simple version of the caseSensitiveFinds setting.

    Unfortunately, the presentation is not really user-friendly because:

    • the label shown in the settings browser is the identifier (the symbol used to build accessors to access it),
    • there is no description or explanation available for this setting, and
    • the new setting is simply added at the root of the setting tree.

    To address such shortcomings, you can better configure your setting node with a label and a description respectively with the label: and description: messages which take a string as argument.

    CodeHolderSystemSettings class>>caseSensitiveFindsSettingsOn: aBuilder
        <systemsettings>
        (aBuilder setting: #caseSensitiveFinds)
            target: TextEditor;
            label: 'Case sensitive search' translated;
            description: 'If true, then the "find" command in text will always make its searches
            in a case-sensitive fashion' translated;
            parent: #codeEditing.
    

    Don’t forget to send translated to the label and the description strings, it will greatly facilitate the translation into other languages.

    Concerning the classification and the settings tree organization, there are several ways to improve it. This point is fully detailed in the next section.

    More about the target

    The target of a setting is the receiver for getting and changing the preference value. Most of the time it is a class. Typically, a preference value is stored in a class variable. Thus, class side methods are used as accessors for accessing the setting.

    But the receiver can also be a singleton object. This is currently the case for many preferences. As an example, the Free Type fonts preferences, they are all stored in the instance variables of a FreeTypeSettings singleton. Thus, here, the receiver is the FreeTypeSettings instance that you can get by evaluating the following expression:

    FreeTypeSettings current
    

    One can use this expression to configure the target of a corresponding setting. As an example the #glyphContrast preference could be declared as follow:

    (aBuilder setting: #glyphContrast)
        target: FreeTypeSettings current;
        label: 'Glyph contrast' translated;
        ...
    

    This is simple, but unfortunately, declaring such a singleton target like this is not a good idea. This declaration is not compatible with the Setting style functionalities. In such a case, one would have to separately indicate the target class and the message selector to send to the target class to get the singleton. Thus, as shown in the example below, you should use the targetSelector: message:

    (aBuilder setting: #glyphContrast)
        target: FreeTypeSettings;
        targetSelector: #current;
        label: 'Glyph contrast' translated;
        ...
    

    More about default values

    The way the Settings Browser builds a setting input widget depends on the actual value type of a preference. Having nil as a value for a preference is a problem for the Settings Browser because it can’t figure out which input widget to use. So basically, to be properly shown with the good input widget, a preference must always be set with a non nil value. You can set a default value to a preference by initializing it as usual, with a #initialize method or with a lazy initialization programed in the accessor method of the preference.

    Regarding the Settings Browser, the best way is the lazy initialization (see the example of the #caseSensitiveFinds preference given in Section 5.3). Indeed, as explained in Section 5.2, from the Settings Browser contextual menu, you can reset a preference value to its default or globally reset all preference values. In fact, it is done by setting the preference value to reset to nil. As a consequence, the preference is automatically set to its default value as soon as it is get by using its dedicated accessor.

    It is not always possible to change the way an accessor is implemented. A reason for that could be that the preference accessor is maintained within another package which you are not allowed to change. As shown in the example below, as a workaround, you can indicate a default value from the declaration of the setting by sending the message default: to the setting node:

    CodeHolderSystemSettings class>>caseSensitiveFindsSettingsOn: aBuilder
        <systemsettings>
        (aBuilder setting: #caseSensitiveFinds)
            default: true;
        ...
    

    This page titled 4.3: Declaring a Setting is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by Alexandre Bergel, Damien Cassou, Stéphane Ducasse, Jannik Laval (Square Bracket Associates) via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.