Chapter 31. The Resource Database

This chapter describes the use of the resource database, and how users can define buttons and menus via resource specifications. This chapter describes the option command.

Tk supports a resource database that holds specifications of widget attributes such as fonts and colors. You can control all attributes of the Tk widgets through the resource database. It can also be used as a more general database of application-specific parameter settings.

Because a Tk application can use Tcl for customization, it might not seem necessary to use the resource database. The resource database is, however, a useful tool for your Tk application. A developer can make global changes with just a few database entries. In addition, it lets users and site administrators customize applications without modifying the code.

An Introduction to Resources

When a Tk widget is created, its attributes are set by one of three sources. It is important to note that Tcl command specifications have priority over resource database specifications:

  • The most evident source of attributes are the options in Tcl commands, such as the -text quit attribute specification for a button.

  • If an attribute is not specified on the command line, then the resource database is queried as described later.

  • If there is nothing in the resource database, then a hard-coded value from the widget implementation is used.

The resource database consists of a set of keys and values. Unlike many other databases, however, the keys are patterns that are matched against the names of widgets and attributes. This makes it possible to specify attribute values for a large number of widgets with just a few database entries. In addition, the resource database can be shared by many applications, so users and administrators can define common attributes for their whole set of applications.

The resource database is maintained in main memory by the Tk toolkit. On UNIX the database is initialized from the RESOURCE_MANAGER property on the root window, or the .Xdefaults file in your home directory. On Windows and Macintosh, there are a few resources added by the tk.tcl library file. Additional files can be explicitly loaded with the option readfile command, and individual database entries are added with the option add Tcl command.

The initialization of the database is different from the Xt toolkit, which loads specifications from as many as five different files to allow per-user, per-site, per-application, per-machine, and per-user-per-application specifications. You can achieve the same effect in Tk, but you must do it yourself. Example 45-1 on page 672 gives a partial solution.

Resource Patterns

The pattern language for the keys is related to the naming convention for Tk widgets. Recall that a widget name reflects its position in the hierarchy of windows. You can think of the resource names as extending the hierarchy one more level at the bottom to account for all the attributes of each individual widget. There is also a new level of the hierarchy at the top to specify the application by name. For example, the database could contain an entry like the following in order to define a font for the quit button in a frame called .buttons:

Exmh.buttons.quit.font: fixed

The leading Exmh. matches the class name for the Tcl/Tk application. The class name of the application is set from the name of the script file, with the first character capitalized. For example, if the script is /usr/local/bin/foobar, then the class is set to Foobar. You could also specify an asterisk to match any application:

*buttons.quit.font: fixed

Resource keys can also specify classes of widgets and attributes as opposed to individual instances. The quit button, for example, is an instance of the Button class. Class names for widgets are the same as the Tcl command used to create them, except for a leading capital letter. A class-oriented specification that would set the font for all buttons in the .buttons frame would be:

Exmh.buttons.Button.font: fixed

Note

Resource Patternsresourcename patterns

Don't use widget names for script names.

The application class name becomes the class name for the main toplevel window. For example, if you use a script name like button.tcl, the class for . becomes Button. This causes it to inherit all the standard Button bindings and attribute values, which can cause problems in your application.

Patterns let you replace one or more components of the resource name with an asterisk (*). For example, to set the font for all the widgets packed into the .buttons frame, you can use the resource name *buttons*font, or you can specify the font for all buttons with the pattern *Button.font. In these examples, we have replaced the leading Tk. with an asterisk as well. It is the ability to collapse several layers of the hierarchical name with a single asterisk that makes it easy to specify attributes for many widgets with just a few database entries.

The tables in this book list attributes by their resource name. The resource names use a capital letter at the internal word boundaries. For example, if the command line switch is -offvalue, then the corresponding resource name is offValue. There are also class names for attributes, which are distinguished with a leading capital (e.g., OffValue).

Note

Resource Patternsresourcename patterns

Warning: Order is Important!

The matching between a widget name and the patterns in the database can be ambiguous, with multiple patterns matching the same widget. The order of database entries determines which pattern is used, with later entries taking precedence. (This is different from the Xt toolkit, in which longer matching patterns have precedence, and instance specifications have priority over class specifications.) Suppose the database contained just two entries, in this order:

*Text*foreground: blue
*foreground: red

Despite the more specific *Text*foreground entry, all widgets will have a red foreground, even text widgets. For this reason you should list your most general patterns early in your resource files and give the more specific patterns later.

Tk also supports different priorities among resources, as described in the next section. The ordering precedence described here applies to all resources with the same priority.

Loading Option Database Files

The option command manipulates the resource database. The first form of the command loads a file containing database entries:

option readfile filename ?priority?

The priority distinguishes different sources of resource information and gives them different priorities. Priority levels are numeric, from 0 to 100. However, symbolic names are defined for standard priorities. From lowest to highest, the standard priorities are widgetDefault (20), startupFile (40), userDefault (60), and interactive (80). These names can be abbreviated. The default priority is interactive.

Example 31-1. Reading an option database file

if [file exists $appdefaults] {
   if [catch {option readfile $appdefaults startup} err] {
      puts stderr "error in $appdefaults: $err"
   }
}

The format of the entries in the file is:

key: value

The key has the pattern format previously described. The value can be anything, and there is no need to group multiword values with any quoting characters. In fact, quotes will be picked up as part of the value.

Comment lines are introduced by the exclamation mark (!).

Example 31-2. A file containing resource specifications

!
! Grey color set
! These values match those used by the Tk widgets on UNIX
!
*background:        #d9d9d9
*foreground:        black
*activeBackground:  #ececec
*activeForeground:  black
*selectColor:       #b03060
*selectBackground:  #c3c3c3
*troughColor:       #c3c3c3
*disabledforeground:#a3a3a3

The example resource file specifies the color scheme for the Tk widget set on UNIX that is based on a family of gray levels. Color highlighting shows up well against this backdrop. These colors are applied generically to all the widgets. The hexadecimal values for the colors specify two digits (eight bits) each for red, green, and blue. Chapter 41 describes the use of color in detail.

Adding Individual Database Entries

You can enter individual database entries with the option add Tcl command. This is appropriate to handle special cases, or if you do not want to manage a separate per application resource specification file. The command syntax is:

option add pattern value ?priority?

The priority is the same as that used with option readfile. The pattern and value are the same as in the file entries, except that the key does not have a trailing colon when specified in an option add command. If value contains spaces or special characters, you will need to group it like any other argument to a Tcl command. Some of the specifications from the last example could be added as follows:

option add *foreground black
option add *selectBackground #bfdfff

You can clear the option database:

option clear

However, on UNIX the database will be initialized from your ~/.Xdefaults file, or the RESOURCE_MANAGER property on the root window, the next time the database is accessed.

Accessing the Database

Often, it is sufficient just to set up the database and let the widget implementations use the values. However, it is also possible to record application-specific information in the database. To fetch a resource value, use option get:

option get window name class

The window is a Tk widget pathname. The name is a resource name. In this case, it is not a pattern or a full name. Instead, it is the resource name as specified in the tables in this book. Similarly, the class is a simple class name. It is possible to specify a null name or class. If there is no matching database entry, option get returns the empty string.

It is not possible to enumerate the database, nor can you detect the difference between a value that is the empty string and the absence of a value. You can work around this by introducing well-known resource names that list other resources. This trick is used in the next section.

User-Defined Buttons

Suppose you want users to be able to define a set of their own buttons for frequently executed commands. Or perhaps users can augment the application with their own Tcl code. The following scheme, which is based on an idea from John LoVerso, lets them define buttons to invoke their own code or their favorite commands.

The application creates a special frame to hold the user-defined buttons and places it appropriately. Assume the frame is created like this:

frame .user -class User

The class specification for the frame means that we can name resources for the widgets inside the frame relative to *User. Users specify the buttons that go in the frame via a personal file containing resource specifications.

The first problem is that there is no means to enumerate the database, so we must create a resource that lists the names of the user-defined buttons. We use the name buttonlist and make an entry for *User.buttonlist that specifies which buttons are being defined. It is possible to use artificial resource names (e.g., buttonlist), but they must be relative to an existing Tk widget.

Example 31-3. Using resources to specify user-defined buttons

*User.buttonlist: save search justify quit
*User.save.text: Save
*User.save.command: File_Save
*User.search.text: Search
*User.search.command: Edit_Search
*User.justify.text: Justify
*User.justify.command: Edit_Justify
*user.quit.text: Quit
*User.quit.command: File_Quit
*User.quit.background: red

In Example 31-3, we have listed four buttons and specified some of the attributes for each, most importantly the text and command attributes. We are assuming, of course, that the application manual publishes a set of commands that users can invoke safely. In this simple example, the commands are all one word, but there is no problem with multiword commands. There is no interpretation done of the value, so it can include references to Tcl variables and nested command calls. Example 31-4 uses these resource specifications to define the buttons:

Example 31-4. Resource_ButtonFrame defines buttons based on resources

proc Resource_ButtonFrame { f class } {
   frame $f -class $class -borderwidth 2
   pack $f -side top -fill x
   foreach b [option get $f buttonlist {}] {
      if [catch {button $f.$b}] {
         button $f.$b -font fixed
      }
      pack $f.$b -side right
   }
}

The catch phrase is introduced to handle a common problem with fonts and widget creation. If the user's resources specify a bogus or missing font, then the widget creation command will fail. The catch phrase guards against this case by falling back to the fixed font, which is guaranteed to exist. This problem is fixed in Tk 8.0 because the font mechanism will search for alternate fonts.

Example 31-5 assumes that the resource specifications from Example 31-2 are in the file button.resources. It creates the user-defined buttons in the .users frame.

Example 31-5. Using Resource_ButtonFrame

Using Resource_ButtonFrameResource_ButtonFrame, defines buttons
option readfile button.resources
Resource_ButtonFrame .user User

User-Defined Menus

User-defined menus can be set up with a similar scheme. However, it is more complex because there are no resources for specific menu entries. We must use more artificial resources to emulate this. We use menulist to name the set of menus. Then, for each of these, we define an entrylist resource. Finally, for each entry we define a few more resources. The name of the entry has to be combined with some type information, which leads to the following convention:

  • l_entry is the label for the entry.

  • t_entry is the type of the entry.

  • c_entry is the command associated with the entry.

  • v_entry is the variable associated with the entry.

  • m_entry is the menu associated with the entry.

Example 31-6. Specifying menu entries via resources

Specifying menu entries via resourcesmenuentries via resources
*User.menulist: stuff
*User.stuff.text: My stuff
*User.stuff.m.entrylist: keep insert find
*User.stuff.m.l_keep: Keep on send
*User.stuff.m.t_keep: check
*User.stuff.m.v_keep: checkvar
*User.stuff.m.l_insert: Insert File...
*User.stuff.m.c_insert: InsertFileDialog
*User.stuff.m.l_find: Find
*User.stuff.m.t_find: cascade
*User.stuff.m.m_find: find
*User.stuff.m.find.entrylist: next prev
*User.stuff.m.find.tearoff: 0
*User.stuff.m.find.l_next: Next
*User.stuff.m.find.c_next: Find_Next
*User.stuff.m.find.l_prev: Previous
*User.stuff.m.find.c_prev: Find_Previous

In Example 31-6, .user.stuff is a Tk menubutton. It has a menu as its child, .user.stuff.m, where the menu .m is set by convention. You will see this later in the code for Resource_Menubar. The entrylist for the menu is similar in spirit to the buttonlist resource. For each entry, however, we have to be a little creative with the next level of resource names. The following does not work:

*User.stuff.m.keep.label: Keep on send

The problem is that Tk does not directly support resources for menu entries, so it assumes .stuff.m.keep is a widget pathname, but it is not. You can add the resource, but you cannot retrieve it with option get. Instead, we must combine the attribute information (i.e., label) with the name of the entry:

*User.stuff.m.l_keep: Keep on send

You must do something similar if you want to define resources for items on a canvas, too, because that is not supported directly by Tk. The code to support menu definition by resources is shown in the next example:

Example 31-7. Defining menus from resource specifications

proc Resource_Menubar { f class } {
   set f [frame $f -class $class]
   pack $f -side top
   foreach b [option get $f menulist {}] {
      set cmd [list menubutton $f.$b -menu $f.$b.m 
                   -relief raised]
      if [catch $cmd t] {
         eval $cmd {-font fixed}
      }
      if [catch {menu $f.$b.m}] {
         menu $f.$b.m -font fixed
      }
      pack $f.$b -side left
      ResourceMenu $f.$b.m
   }
}
proc ResourceMenu { menu } {
   foreach e [option get $menu entrylist {}] {
      set l [option get $menu l_$e {}]
      set c [option get $menu c_$e {}]
      set v [option get $menu v_$e {}]
      switch -- [option get $menu t_$e {}] {
         check {
            $menu add checkbutton -label $l -command $c 
               -variable $v
         }
         radio {
            $menu add radiobutton -label $l -command $c 
               -variable $v -value $l
         }
         separator {
            $menu add separator
         }
         cascade {
            set sub [option get $menu m_$e {}]
            if {[string length $sub] != 0} {
               set submenu [menu $menu.$sub]
               $menu add cascade -label $l -command $c 
                       -menu $submenu
               ResourceMenu $submenu
            }
         }
         default {
            $menu add command -label $l -command $c
         }
      }
   }
}

Application and User Resources

The examples presented here are a subset of a package I use in some large applications, exmh and webtk. The applications define nearly every button and menu via resources, so users and site administrators can redefine them. The buttonlist, menulist, and entrylist resources are generalized into user, site, and application lists. The application uses the application lists for the initial configuration. The site and user lists can add and remove widgets. For example:

  • buttonlist –. the application list of buttons

  • l-buttonlist –. the site-specific list of buttons to remove

  • lbuttonlist –. the site-specific list of buttons to add

  • u-buttonlist –. the per-user list of buttons to remove

  • ubuttonlist –. the per-user list of buttons to add

This idea and the initial implementation was contributed to exmh by Achim Bonet. The Resource_GetFamily procedure merges five sets of resources shown above. It can replace the option get commands for the buttonlist, menulist, and entrylist resources in Examples 31-4 and 31-7:

Example 31-8. Resource_GetFamily merges user and application resources

proc Resource_GetFamily { w resname } {
   set res   [option get $w $resname {}]
   set lres  [option get $w l$resname {}]
   set ures  [option get $w u$resname {}]
   set l-res [option get $w l-$resname {}]
   set u-res [option get $w u-$resname {}]
   # Site-local deletions from application resources
   set list [lsubtract $res ${l-res}]
   # Site-local additions
   set list [concat $list $lres]
   # Per-user deletions
   set list [lsubtract $list ${u-res}]
   # Per-user additions
   return [concat $list $ures]
}
proc lsubtract { orig nuke } {
   # Remove elements in $nuke from $orig
   foreach x $nuke {
      set ix [lsearch $orig $x]
      if {$ix >= 0} {
         set orig [lreplace $orig $ix $ix]
      }
   }
   return $orig
}

Expanding Variables

If the command resource contains substitution syntax like $ and [], then these are evaluated later when the command is invoked by the button or menu. This is because there is no interpretation of the command value when the widgets are created. However, it may be that you want variables substituted when the buttons and menus are defined. You can use the subst command to do this:

set cmd [$button cget -command]
$button config -command [subst $cmd]

Choosing the scope for the subst can be tricky. The previous command does the subst in the current scope. If this is the Resource_ButtonFrame procedure, then there are no interesting application-specific variables defined. The next command uses uplevel to do the subst in the scope of the caller of Resource_ButtonFrame. The list is necessary so that uplevel preserves the structure of the original subst command.

$button config -command [uplevel [list subst $cmd]]

If you do a subst in ResourceMenu, then you need to keep track of the recursion level to get back to the scope of the caller of Resource_Menubar. The next few lines show what changes in ResourceMenu:

proc ResourceMenu { menu {level 1} } {
   foreach e [option get $menu entrylist {}] {
      # code omitted
      set c [option get $menu c_$e {}]
      set c [uplevel $level [list subst $c]]
      # And the recursive call is
      ResourceMenu $submenu [expr $level+1]
      # more code omitted
   }
}

If you want the subst to occur in the global scope, use this:

$button config -command [uplevel #0 [list subst $cmd]]

However, the global scope may not be much different when you define the button than when the button is invoked. In practice, I have used subst to capture variables defined in the procedure that calls Resource_Menubar.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.111.33