Copyright (c) 2012-2016 Hyperion Entertainment and contributors.

PrefsObjects

From AmigaOS Documentation Wiki
Revision as of 20:51, 5 December 2017 by Steven Solie (Talk | contribs) (File Structure)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Introduction

Many applications are user-configurable so they need a way to store their settings; we traditionally use the term preferences or simply prefs in the AmigaOS context. It may come as a surprise that before version 4.x, AmigaOS had no real standard for storing preference data. Developers used various, often proprietary solutions: icon tooltypes, IFF-based formats, text files mimicking the Windows .ini format, or binary formats. Some of them are still very popular (tooltypes) or even used by the OS itself (some system preferences are stored as IFF-PREF files). While this is not a place to go into detail and discuss their particular advantages and disadvantages, let’s mention two common drawbacks that ultimately led to the development of the Application Library PrefsObjects system:

  1. All of the solutions above require you to implement your own preferences handling code (that is, routines for parsing, verification, and saving).
  2. None of the various formats used have ever provided a smart enough way to administer complex, structured settings.

Since its introduction in AmigaOS, PrefsObjects has largely been promoted as being XML-based and therefore human-readable and easy to edit. Yet using an industry standard format that is platform-independent and supports Unicode is not the biggest “selling point” of PrefsObjects. What makes it different from (and superior to) previous solutions is not the fact that it is XML-based but, rather, that it is object-oriented. Among other things, this property also helps address the two drawbacks mentioned above.

Imagine, for example, a program that uses a plug-in system. Both the main program and its plug-in modules are configurable. Instead of having ten or more individual preference files, you’ll likely want a single file containing settings for the program as well as for the plug-ins. This naturally introduces a certain hierarchy. Plus, as some plug-ins may be delivered by third parties, the particular structure of the prefs file is somewhat beyond the control of the main program: the amount, contents and sequencing of data in the file depends on which plug-ins are installed and on how (and when, if ever) they store their settings. You would have to take all of this into account when implementing your own prefs-handling routines, which means a good deal of work.

The Interface and its Functions

The Application Library contains two interfaces, named “application” and “prefsobjects”. If you want to implement your program preferences using the PrefsObjects system, you must obtain the “prefsobjects” interface: all the necessary functions and methods are contained therein. Naturally, your application must be registered before you can make use of PrefsObjects.

Starting with AmigaOS SDK 53.24, both interfaces (“application” and “prefsobjects”) must be at version 2. See the Library Opening Chores section of the Application Library documentation for more information.

All operations related to settings – retrieving, adding and changing –, as well as all operations related to the prefs file itself – creation, loading and saving – are performed solely through the library functions. Some operations can even be automated, sparing the programmer from unnecessary hassle.

Object Types

PrefsObjects tackles the task by seeing data as objects, and by providing functions and methods for object manipulation. Loading, adding, updating or removing preference objects (be they single items or entire clusters of settings) then becomes a matter of calling the respective function – “invoking the method on the object”, as we say in object-oriented programming. If you are acquainted with this philosophy (as used, for example, in Intuition’s BOOPSI framework), you will find working with PrefsObjects very easy and straightforward.

PrefsObjects distinguishes between six object types:

Object Type Description
Dictionary A container to encapsulate (embed) other objects. The prefs file usually has a Dictionary object at the top level.
Array A container to encapsulate other objects. The difference between a Dictionary and an Array is in object referencing: in Arrays encapsulated objects are referenced by an index, while in a Dictionary they are referenced by a name (key string).
Number An object to carry a long integer, double, or a boolean value.
String An object to carry a text string.
Date An object to carry date and time information.
Binary An object to carry arbitrary binary data. However, binary data should be avoided as much as possible because it's restrictive in its form and is not future-proof, as it does not enable addition or removal of data without compromising compatibility.

The Preferences File

The files generated by the Application Library's PrefsObjects system are standard XML documents. They are encoded in UTF-8 (which AmigaOS doesn't support directly) but that doesn't necessarily pose a problem. First, you rarely need to edit prefs files manually – nor are you encouraged to do so, unless the file has become corrupted or contains a setting that prevents the application from starting up or running properly. Second, characters beyond the ASCII range are rarely used in preferences, so the prefs files will likely be readable and editable even in a text editor that has no notion of UTF-8. And third, you can always use the dedicated PrefsObjectsEditor, located in the Utilities drawer on your system disk, which allows easy and safe editing of PrefsObjects files (the usage of this tool is, however, out of the scope of this page).

As already mentioned above, all data access is handled by the Application Library. On the part of the programmer (or the application user), no actual knowledge of XML is required.

File Structure

The preferences file normally starts with a Dictionary object at the top level. The advantage of using a Dictionary is that objects stored in it are identified by a name (called a “key”). Using named objects is very practical. Unlike in Arrays, where objects are indexed, the order of data is quite irrelevant as long as you deal with objects on the same level. Your application can retrieve, add, modify, re-sequence or remove individual objects in an arbitrary order without having to worry about breaking anything: a named object will always be addressed properly if it exists.

Object names (keys) must be unique at each particular level. In other words, you cannot have two settings items (objects) with the same key in the same dictionary. However, identical object names can be used in different dictionaries. The following example shows a prefs file structure that embeds two separate dictionaries named “Screen settings” and “Window settings”. As you can see, identical keys (“Name”, “Width” and “Height”) are then used without collision:

PrefsObjectsClassDiagram.png

The diagram also illustrates the hierarchical nature of the PrefsObjects system: the fact that objects can be embedded inside other objects.

Reading Preferences

In most cases, there will be just one preferences file associated with your application. If all you need is simply read settings from the file (and save them upon user request), it is recommended that you let the Application Library handle the file access. Registering your application like this

appID = IApplication->RegisterApplication("AudioMonster",
           REGAPP_URLIdentifier, "supercoders.com",
           REGAPP_Description, "A media player",
           REGAPP_LoadPrefs, TRUE,
           TAG_END);

will tell the library to access the prefs file automatically, under its default name and from the default location. The default naming scheme for prefs files uses the application name combined with the URL identifier. Also, the .xml extension is appended at the end of the name to identify the document format. So with this particular registration call, the default file name will be “AudioMonster.supercoders.com.xml” and the library will try to access it from the ENV: directory. (Don't worry, both the file name and location can be changed.)

If the file is found, the library will internally allocate a Dictionary object and read the settings into it. If the file is not found, the library will allocate an empty Dictionary for you. In either case the said Dictionary object will become the application's Main Prefs Dictionary and will be associated with the application as one of its attributes. (The application may use multiple prefs Dictionaries but at any given point in time, only one of them is main.)

After start-up and registration, the application will typically want to access the settings in order to configure itself. Having used REGAPP_LoadPrefs, TRUE in the registration call above, we should now have the application's Main Prefs Dictionary somewhere in memory, to which we need to obtain a pointer. This is done through the function GetApplicationAttrs() because as explained above, the Main Prefs Dictionary is an application attribute. After that we can use dedicated functions to access the individual objects (i.e. settings items) in the Dictionary.

The code fragment below assumes that our prefs file – already loaded by the registration routine and transformed into an object – contains three settings items:

  • a boolean value stored in the file under the key of “Switch”;
  • a numeric value of type uint32 stored under the key of “Number”;
  • a text string stored under the key of “String”.

We first obtain the pointer to the prefs Dictionary object, retrieve the three values, and store them in a dedicated data structure:

/* Default settings */
#define DEF_SWITCH   TRUE
#define DEF_NUMBER   100
#define DEF_STRING   "Play it again, Sam!"
 
struct Settings
{
  BOOL   switch;
  uint32 number;
  STRPTR string;
};
 
PrefsObject    *myPrefsDict = NULL;          /* Main Prefs Dictionary          */
struct Settings mySettings;                  /* Structure to hold the settings */
 
 
/* Obtain the Dictionary object pointer */
IApplication->GetApplicationAttrs(appID, APPATTR_MainPrefsDict, &myPrefsDict, TAG_END);
 
if ( myPrefsDict )
 {
 
   /*   Retrieve the settings values.
 
        Note: here we need to cast the type of the number and of the string value
        to avoid compiler warnings. This is because DictGetIntegerForKey()
        and DictGetStringForKey() return int32 and CONST_STRPTR, respectively,
        whereas our program declares them as uint32 and STRPTR.
    */
   mySettings.switch = IPrefsObjects->DictGetBoolForKey(myPrefsDict, "Switch", DEF_SWITCH);
   mySettings.number = (uint32) IPrefsObjects->DictGetIntegerForKey(myPrefsDict, "Number", DEF_NUMBER);
   mySettings.string = (STRPTR) IPrefsObjects->DictGetStringForKey(myPrefsDict, "String", DEF_STRING);
 
   /* Test out the string value. */
   IDOS->Printf("All I want to say is: %s\n", mySettings.string);
 }

Should any of the settings items be missing, the respective default value is used. This will be the case, for example, when a physical XML prefs file is not present and the PrefsObjects system supplies an empty Dictionary.

(to be continued)

Function Reference

The following are brief descriptions of the PrefsObjects-related functions. See the SDK/Autodocs for details on each function call.

Function Description
BeginDeserialization() Begins the deserialization of a prefs object.
DictGetBoolForKey() Obtains a boolean value for a dictionary key.
DictGetIntegerForKey() Obtains an integer value for a dictionary key.
DictGetObjectForKey() Obtains an object for a dictionary key.
DictGetOptionForKey() Obtains a string from an option table.
DictGetStringForKey() Obtains a string value for a dictionary key.
DictSetObjectForKey() Sets an object for a dictionary key.
PrefsArray() PrefsObjects array object access function.
PrefsBaseObject() PrefsObjects base object access function.
PrefsBinary() PrefsObjects binary object access function.
PrefsDate() PrefsObjects date object access function.
PrefsDictionary() PrefsObjects dictionary object access function.
PrefsNumber() PrefsObjects number object access function.
PrefsString() PrefsObjects string object access function.
ReadPrefs() Reads a prefs dictionary from a file.
WritePrefs() Writes a prefs dictionary to a file.