Table of Contents


1 Introduction

This document attempts to explain the steps that a programmer would go through to specialize the generic Aesop environment to support a specific style of design. Previous documents summarized the contents of the generic infrastructure:

Fcl: The FAM Command Language documents the language used to describe design data.
The ABLE UI:A Guide for the Bewildered describes the basics of how the user interface code is organized.
Most of the work done in creating a new style is in defining new tools and new user interface elements. A relatively small amount of effort is needed to define the new classes of design objects.

The following sections will examine the Pipe and Filter style as a running example. I won't show all the new code, but will go over most of the important pieces so its clear what has to be done and why. Here are the main steps that you take:

  1. Create new design classes.
  2. Create new visual classes.
  3. Make the "<style>.map" file.
  4. Write new palette callbacks.
  5. Write new workshops.
  6. Reconfigure main.tcl

2 Create New Design Classes

The first step in programming a new style is to specialize the standard design object classes for your environment. We also call these classes the "database" classes because they describe the objects stored in a design database, as opposed to the database used by the user interface for visualizations. Generally, you will create a specialized subclass of one of the generic classes either to model extra data that is needed that the generic class does not provide, or to override default behaviors.

In the PF style, the following new sub-classes were created:

class CFamPFFilter CFamComponent
Models filters.
class CFamPFPipe CFamConnector
Models pipes.
class CFamPFReadPort CFamPort
Models an input port on a filter.
class CFamPFWritePort CFamPort
Models an output on a filter.
class CFamPFSource CFamRole
Models a data source on a pipe.
class CFamPFSink CFamRole
Models a data sink on a pipe.
class CFamPFAggregate CFamAggregate
An aggregate for holding designs in this style.
The following methods are overridden in the PF style:

method CFamPFFilter addPort
Makes sure the user only adds input or output ports to a filter.
method CFamPFPipe addRole
Makes sure pipes never have more than 2 roles, and that they are either sources or sinks.
method CFamPFAggregate attach
Makes sure attachments are between sinks and read ports or sources and write ports.
method CFamPFAggregate bind
Does extra type checking on bindings.
There are no more modifications to default behavior in this style.

3 Create New Visual Classes

In order to modify the behavior of the user interface, the style programmer must create new visual classes for the design objects that she wants to modify. The behavior of instances of these new classes can then be changed by overriding the appropriate methods. In general, the most important methods to keep in mind are render and bindMouse which specify how an object is drawn and how it reacts to mouse events.

In the PF style, the bulk of the new code handles visualizations of the new port and role types and their corresponding binding classes. The the visual classes for pipes and filters do nothing but modify the double-click mouse binding to invoke the modified workshops. Workshops are discussed in Section 6.

The new code for source and sink roles is fairly simple. The render routine is modified so that sources are filled black and sinks are filled red. In addition, the stickTo method, which is called when a role is moved over something it can attach to, like a port or a binding, is modified to change the outline that the role is drawn in.

The new code for ports is somewhat more complicated. Because ports are now arrows, they must know what side of a component's box they live on. Therefore, we add a lot of code to change the way the arrow is drawn for each of four possible sides. In addition, the fill colors are modified to match those of sink and source roles.

Along with the new port/role classes is a pair of new binding classes to make the visualizations consistent. The new bindings just make sure to draw themselves in the right color.

Finally, aggregates are modified so that they create the right binding classes. This is important because the visualizations of bindings in a PF aggregate must also have the appropriate color.

4 Make The "<style>.map" File

The ".map" file serves three main functions. First, it provides an association between Database classes and visual classes. Second, it provides symbolic names for the various classes of design objects that are available for use. Third, it provides information to the user interface code about how the palette should act.

Most of this information is passed to the user interface via the defClass command. The syntax for the defClass command is:

defClass <symbolic name> <attribute name> <attribute value>
This command defines various attributes for symbolic "classes" that are presented to users in place of the actual classes defined in Fcl code. This is to make life easier for the user. Here is an example definition from the pf.map file. The following code defines a symbolic class called "Input." The bold keywords are the name the attributes of this symbolic class.

defClass Input dbclass CFamPFReadPort
Defines the database class of Input to be CFamPFReadPort.
defClass Input visclass CVisPFInput
Defines the visual class of Input to be CVisPFInput
defClass Input bitmap port.bit
Defines what bitmap should appear in the palette.
defClass Input callback __makePort__
Defines the procedure to call to create an Input
defClass Input activeCheck uic_compSelectedp
Declares the procedure to call to check if the palette button should be active.
This information is used by the palette to control how new objects are created in the canvas. In addition, the workshops use it in some of their menus to provide user-readable names in menus that create new objects.

You will notice that only classes representing Components, Connectors, Ports and Roles show up in the editing palette. The other classes, representing various sorts of aggregate and external representations are still needed in other situations, but since they don't show up in the main palette, some fields like the bitmap field are not relevant.

The last line of code in the pf.map file specifies what should be available in the palette of each editing window. It does this through the global variable vis_paletteArgs.

set vis_paletteArgs {
 {Components {StdFilter StderrFilter}}
 {Connectors {Pipe}}
 {Ports {Input Output}}
 {Roles {Source Sink}}
}
This says the palette should have four sections labelled Components, Connectors, Ports and Roles and it specifies a list of symbolic class names to appear in each section. When an new editor window is created, this information is used to set up the palette buttons with the right bitmaps and callbacks.

5 Write the Palette Callbacks

Having told the palette which procedures to call in the button callbacks, you must write these procedures. Each one of these creates a Database/Visual object pair and puts a rendering of the visual object into the current canvas. The file pf_palette.fcl shows an example of how this works. This file also redefines some procedures in the generic style. There are overrides of procedures for creating toplevel menus. But, most importantly, you must define a procedure for creating the toplevel objects in a design. In the PF style, this is called
__pf__MakeTopLevel. This code creates a toplevel filter and gives it an aggregate representation to hold the design. Generally, to write a new version of this code, you just have to create new objects of the appropriate type and hook them together the same way.

The rest of the code in this file just defines the palette callbacks.

6 Write New Workshops

Generally, the workshop forms must be modified to deal with new port/role types or other sorts of data that you might want the user to be able to associate with new classes of design objects. The forms code makes this pretty easy to do, but only if you understand the rather ad-hoc syntactic conventions that it uses. We'll go over the generic component form and then the form in the PF style to see what changed. The document The ABLE UI:A Guide for the Bewildered provides information about what most of these options do, including what all the magic %x substations are for. I'll just describe the special ones in more detail below so you can how things work with some context.

The form for the generic component workshop starts like this:

set __cws__CompWSForm {
 {-type group -name top -pack_opts {-anchor w -side top -expand 1}}
 {-type group -name bot -pack_opts {-anchor n -side top -pady 2m}}
 {-type group -name bot.left -pack_opts {-side left }}
 {-type group -name bot.left.list -pack_opts {-side top \
  -anchor w -padx 2m}}
 {-type group -name bot.left.buttons \
  -pack_opts {-anchor w -padx 2m -side top -pady 2m}}
 {-type group -name bot.right -pack_opts {-side left }}
 {-type group -name bot.right.list \
  -pack_opts {-side top -anchor w -padx 2m}}
 {-type group -name bot.right.buttons \
  -pack_opts {-anchor w -padx 2m -side top -pady 2m}}
This code sets up all of the frames that the form will be using to hold entry boxes and buttons. We set up a top and bottom frame, each of which contains a left and right sub-frame. The -name switches are used to define names that we can use later in pack options. The type of each of these is a "group", which just means a frame. Perhaps frame would have been a better name.

 {-type vis_ed
  -win_name top.thumbnail
  -name ThumbNail
  -mk_proc {wsc_mkThumbNail %f.topFrame.top.thumbnail %o %v}
  -widget_opts { -height 200 -width 380 -relief sunken -bd 2}
  -pack_opts {-expand 1 -fill x -side top -padx 1m -pady 1m}}
This sets up the first form element in the dialog. Its just a canvas to draw the thumbnail in. The -mk_proc option specifies a script to run to draw the initial image in the canvas. The other options specify a window path, options for the widget and options to give the packer when packing the widget.

 {-type text_entry 
  -win_name name_entry 
  -name Name 
  -get_proc {trans {%o getName}}
  -set_proc {wsc_setName %c %v %o "%t"}
  -pack_opts {-expand 1 -fill x -in %f.topFrame.top -side top}
  -entry_pack_opts {-expand 1 -fill x}}
A text box entry. These and buttons are the most common elements. The key thing here is the -get_proc and -set_proc options, which tell the form how to fetch an initial value for the entry and how to update the database object when the user edits the entry. In addition, note that the -pack_opts option applies to the frame containing the label and the entry box, whereas the -entry_pack_opts options applies to the entry itself within this frame. This is an awful hack, but what can you do.

 {-type list_box 
  -win_name portsListBox
  -name ports 
  -get_proc {trans {__cws__getPortNames %o}} 
  -select_proc {trans {__cws__editPort %o %i %X %Y %v}}
  -widget_opts {-geometry 20x5 -relief sunken -bd 2}
  -pack_opts {-in %f.topFrame.bot.left.list -anchor n -side top}}
A list box provides access to object slots that are list values, and lets the user pick objects from these lists to look at more closely. All the options here are generic except the -select_proc options, which specifies a script to run when an item receives a double-click event. This list box provides the user with a way to look at and manipulate the ports of a component. The two library routines __cws__getPortNames and __cws__editPort are provided by the UI library and can be used in other style-specific workshops as well.

 {-type menu_button 
  -name "New Port" 
  -value_expr "Port"
  -command_proc {__cws__newPort %c %v %o %t 
%f.topFrame.portsListBox.listBox} -widget_opts {-state normal} -pack_opts { -in %f.topFrame.bot.left.buttons -side left}}
A menu button takes a list of menu entries and dispatches what the user picks to the script specified in the -command_proc option. We pass along the text of the item that was picked so the dispatch procedure can tell what to do. This menu allows the user to create new ports on a component. The list of menu items is specified in the -value_expr option. In this case, we can only make generic ports. In a style, you would add any other symbolic classes that you may have defined back in the ".map" file.

 {-type button 
  -name "Delete Port"
  -command_proc {__cws__deletePort %c %v %o
%f.topFrame.portsListBox.listBox} -widget_opts {-state normal} -pack_opts { -in %f.topFrame.bot.left.buttons -side left}}
This defines a command button. Control is sent to the script specified in the -command_proc option, with all needed substitutions.

 {-type list_box 
  -win_name repsListBox
  -name reps 
  -get_proc {trans {wsc_getRepNames %o}} 
  -select_proc {wsc_editRAttrs %o %f %f.topFrame.repsListBox.listBox}
  -widget_opts {-geometry 20x5 -relief sunken -bd 2}
  -pack_opts {-in %f.topFrame.bot.right.list -side top }}
Another list box for manipulating representations.

 {-type menu_button 
  -name "New Rep" 
  -value_expr "Aggregate External" 
  -command_proc {wsc_newRep %v %o %t %f.topFrame.repsListBox.listBox}
  -pack_opts { -in %f.topFrame.bot.right.buttons -side left}}
A menu button just like the "New Port" button.

 {-type button 
  -name "Delete Rep" 
  -command_proc {trans {wsc_deleteRep %v %o
%f.topFrame.repsListBox.listBox}} -pack_opts {-in %f.topFrame.bot.right.buttons -side left}}
This works the same way as the "Delete Port" button.

 {-type button 
  -name "Edit"
  -command_proc {trans {wsc_OpenExternalEditor %v %o
%f.topFrame.repsListBox.listBox}} -pack_opts {-in %f.topFrame.bot.right.buttons -side left}} }
This buttons lets you fire up an external editor on an external rep. or a recursive edit session on an aggregate.

The code for the filter workshop in the PF style is strikingly similar to the specification above. In fact, the main difference is the following change to the two menu buttons.

 {-type menu_button 
  -name "New Port" 
  -value_expr "Input Output"
  -command_proc {__cws__newPort %c %v %o %t \
    %f.topFrame.portsListBox.listBox}
  -widget_opts {-state normal} 
  -pack_opts { -in %f.topFrame.bot.left.buttons -side left}}
 {-type menu_button 
  -name "New Rep" 
  -value_expr "Aggregate External WrightSpec" 
  -command_proc {wsc_newRep %v %o %t %f.topFrame.repsListBox.listBox}
  -pack_opts { -in %f.topFrame.bot.right.buttons -side left}}
Notice that the items in the menu have changed to reflect what is reasonable to do in the PF style. Its unfortunate that you have to do this by hand, but, well, this is a prototype. The other main change is to the behavior of the "Edit" button:

 {-type button 
  -name "Edit"
  -command_proc
   {trans {__fws__editRep %v %o %f.topFrame.repsListBox.listBox}}
  -pack_opts {-in %f.topFrame.bot.right.buttons -side left}}
Here we make sure to call the style specific routine __fws__editRep which does a few things differently for the external representations available in PF. In particular, for representations managed by the Synthesizer Generator, this routine is creates the appropriate empty Synthesizer Generator file. Actually, it does this correctly for filter language representations, but not for FDR representations, even though you can use the Synthesizer Generator tool to edit FDR specs. Future version of the style should fix this by creating a special class of representation for FDR specifications, rather than just using a semantically meaningless external representation.

Most of the other workshops make simple changes to the main form. It seems wasteful to copy all of this forms code around. On the other hand, it gives us the flexibility to make forms that look nothing like the ones we currently use, if that is appropriate.

7 Reconfigure main.tcl

This file is what gets the world moving. It loads up all the needed libraries for the database, UI and shelf. It then initializes the data needed for the palette. Finally, it invokes the FDR background process and sets up the toplevel button box.

Different styles will initialize global data differently, and may fire up more or fewer background tools. The PF style only does the former. It overrides the two global variables uiStyleMakeToplevel and uiStyleTopLevelTag so that PF designs will be created and recognized correctly in the database. It also redefines the procedure
uic__setDefaultExtTool to allow externals to be edited by the synthesizer generator by default. The rest of the file is the same as the generic style, except for the code added to load up the PF style itself.

Finally, main.tcl should also set as global variable designating the style. This is the prefix that will be used for design names in the database and in other places as an internal identifier. The variable is __globalStyle__. For the PF style the command is:

set __globalStyle__ "pf"
In order for the Aesop Design Manager to be able to recognize and work with designs in your new style, you'll also need to edit the styles.dir file in the Aesop lib directory FCL_LIBRARY . The file has the following form in the default version of Aesop:
ui "Generic Style"
pf "Pipe and Filter Style"
unix-pf "Unix Pipe and Filter Style"
msg "Real Time Style"
That is, each line designates a style where the first item on each line is the name of the unix directory that contains the style code and the the second is a quoted human readable name of the style. In order to use a newly defined style this file must contain an entry for it.

8 Integrate Tools/Checks

For some styles their may be tools/checks which you may want to integrate into the user interface. In order to do this each style defines two functions called __uic__makeToolsMenu and __uic__makeChecksMenu. These two functions are given as arguments, a handle to the menubar, the window, and the canvas. Then you just add the different tools/checks as shown in the sample code below:
 
This is for the Pf style

proc __uic__makeToolsMenu {mb w c} { set canvas "$c.c" menubutton $mb.tools -text "PF-Tools" menu $mb.tools.menu $mb.tools.menu add command -label "Build" \ -command "__pf__buildPFCommand $w" pack append $mb $mb.tools {left} $mb.tools configure -menu $mb.tools.menu $mb.checks.menu add command -label "Check invalid connectors" \ -command "checkForFiles $w $canvas" $mb.checks.menu add command -label "cycle-checker" \ -command "checkForCycles $w $canvas" }