Menu templates are a technique (some might say a hack) for organizing an STV file such that common elements of menu structure and layout can be factored out from specific menu content. This is more or less what themes are meant to do, but I was having trouble figuring out how to do everything I wanted to do with themes, so I came up with this idea as an alternative.
For instance, in the standard STV there are several menus (e.g. SageTV Recordings, Recording Schedule, Recordings of X) that combine a detail panel at the top with a scrollable vertical table of media files below. While some of the stylistic elements of these menus are factored out into common themes, several key pieces of the menu structure -- tables, table components, items and so on -- are repeated in each menu. It seems like there ought to be a way to define a generic vertical-table-with-detail-panel menu class in which all of the structure and layout is defined in one place, and then create instances or subclasses of that menu class by plugging in specific content elements as needed. Menu templates let you do this in a fairly straightforward and natural fashion.
A menu template contains the bones of a reusable menu structure -- all the widgets needed to position and format UI elements on screen -- but without any specific content. The content is defined by a second menu that calls the menu template with widget-chain arguments that get plugged into placeholder widgets in the template (much like assembly-language macro expansion). The combination of template plus caller yields a complete menu definition that can be rendered on screen, with the structure coming from one menu (the template) and the content coming from another (the caller).
The actual mechanics of combining templates with callers are embodied in a Java class called SageWorks.Template, which you can call from Action widgets in your STV. A complete listing of the functions in this class can be found below, but before we get into that, let's take a look at a working example.
This discussion might be easier to follow if you download and install the template package and bring up the sample STV in Studio. But if you aren't prepared to do that just yet, you can skip ahead and follow along using the in-line screenshots.
If you haven't already done so, you can download the template package from here: Download
There is no installer; just unzip the downloaded package into your \Program Files\SageTV folder (or possibly \Program Files\Frey Technologies, depending on your setup). Be sure to use file paths when unzipping and everything should land where it belongs; if not, here's where the various files should go:
| JWin32.dll | \Program Files\SageTV\SageTV |
| SageWorks.jar | \Program Files\SageTV\SageTV\JARs |
| TemplateDemo.xml | \Program Files\SageTV\SageTV\STVs\SageTV3 |
| Templates.html | \Program Files\SageTV\Template\Docs |
| *.png | \Program Files\SageTV\Template\Docs |
| *.java | \Program Files\SageTV\Template\Src |
| JWin32.cpp | \Program Files\SageTV\Template\Src |
| SageWorks_Win32.h | \Program Files\SageTV\Template\Src |
Note that SageWorks.jar was compiled with Java 1.5.0, so you'll need that version or better to run it.
The package includes a sample STV called TemplateDemo.xml. This is a rather stripped-down toy menu system consisting of just two rather Spartan menus, without all the bells and whistles of the standard STV. Nevertheless it's a working menu system based entirely on templates.
Here's the Main Menu from that example:

As you can see, there's not a lot of structure here. In fact there's not really a menu here at all; all the logic is in the BeforeMenuLoad hook, which consists of a call to SageWorks_Template_BindArgs, wrapped in a complicated conditional.
BindArgs is the workhorse of the Template class. Its single direct argument, a string literal, doesn't really mean anything and is just an artifact of the implementation. We'll ignore it for now and turn to the real arguments, found in the widget tree below the call.
The first branch, called "Template", points to the template menu to be used: in this case, a template called Simple vertical list of menu items. We'll get to the details of that template in a moment. For now, it's enough to know that this is the template we'll be using.
The second branch of the conditional, called "Args", specifies the widget chains to be passed as arguments to that template. In this case there are two arguments. The first, called "@MenuTitle@", is a widget chain yielding the text to be displayed as the menu title. The second, called "@Items@", is a list of menu items and their associated actions. (Actual parameters to menu templates always begin with an Action widget of the form "@ArgName@".)
The third branch of the conditional, the else branch, is optional, and can be used for handling any errors that may arise during the execution of BindArgs. More on that later.
That's it. There's nothing here about the layout or style of the menu; that's all taken care of by the template. All we need here is the boilerplate structure of the BindArgs call, plus the actual widget chains that supply the specific content of this particular menu.
Now let's take a look at the template menu:

Here's another called to BindArgs. You're not limited to one level of template; templates can themselves be implemented in terms of other templates. This particular template is a subclass of the more general template Standard menu header & background. That other template takes two arguments: "@MenuTitle@" and "@MenuBody@".
"@MenuTitle@" we're simply passing through from our caller; that's what the "?MenuTitle?" widget means. That's the placeholder in this template that will be filled in by BindArgs with the actual menu title provided by Main Menu. (Formal parameters in menu templates are always Action widgets of the form "?ArgName?".)
"@MenuBody@" contains a bit of structure for laying out a vertical list of menu items on screen. There's a Panel to contain the item list, a Shape defining a border around that panel, a couple of themes for setting fonts and focus highlights, and so forth. There's also another formal placeholder, "?Items?", which is where the menu items supplied by Main Menu get plugged in.
Let's go one level deeper:

Here's the bottommost template, the one that actually makes things happen. It's all structure, with panels, themes, and whatnot, with a couple of placeholders for "?MenuTitle?" and "?MenuBody?". Fully bound via the call chain we just traversed, the template looks like this:

We now have a complete menu with no unbound placeholders, ready to display. There are a few extra Action widgets here and there (notably in the "?MenuTitle?" chain, where the actual argument passed through two levels of template expansion), but they're harmless.
I want to emphasize that no widgets are copied in the process of this expansion. It's all done with references. To make this screenshot, I simply pulled all the actual arguments in line using Set as Primary Reference.
Hopefully at this point it should be fairly clear how to use the Simple vertical list of menu items template to quickly generate more menu-item lists with the same look-and-feel as Main Menu. All the work of creating that look-and-feel has already been encapsulated in the template.
Similarly, at a deeper level it should be straightforward to define new menu types (new templates) that share the same background and header as Main Menu by further subclassing the Standard menu header & background template. In fact the sample STV contains an example of this in the form of a second, parallel template chain beginning with Scheduled Recordings and running through Vertical table with detail panel to the same Standard menu header used above. This second chain is somewhat more complex and won't be analyzed in detail here. I included it in the sample mainly to show that fairly complex menus can be built using templates, and that the result is arguably a cleaner separation of structure from content than you can get using themes alone.
By the way, templates names don't have to begin with the word "Template". What makes them templates is that fact that they contain formal placeholder widgets and are referenced in calls to BindArgs. I just find it convenient to name them "Template: this" and "Template: that" so they all sort out together in one place in Studio's menu list.
One thing to notice about this sample menu system is that all the layout and formatting widgets -- the panels, themes, and shapes -- are hidden inside the templates. Main Menu and Scheduled Recordings contain only content elements -- items and text widgets, and the Action chains that give them their values. There is no global theme organizer, no spaghetti snarl of shared-code references. The only permanent references are those connecting callers to templates. BindArgs creates additional temporary references connecting formals to actuals. So the problem of indecipherable reference tangles has been brought somewhat under control by doing all code-sharing through templates, and quarantining the use of references to the BindArgs machinery.
How exactly does BindArgs do its job of binding formals to actuals? It begins by locating the Conditional widget that called it. This is where the string argument to BindArgs comes in. As mentioned above, this argument has no real meaning, and can be any text at all, so long as it's unique within the STV. Its only purpose is so that BindArgs can find the correct calling widget by comparing the passed-in string to the strings it sees in the text of Conditional widgets it inspects.
Having found the right call, BindArgs then walks the widget tree of the specified template menu, looking for placeholder widgets with names of the form "?ArgName?". These placeholder widgets are the formal parameters of the template. When it finds one, BindArgs searches the caller's "Args" list for a matching actual parameter labeled "@ArgName@", and binds formal to actual with a widget reference. Any formal for which no actual argument can be found is left unbound. The parameter names can be any alphanumeric text you like, so long as the formals are surrounded by "?...?" and the actuals by "@...@".
Note that this is not macro expansion in the traditional sense. No widgets are copied in the process, and no new menu is created from the combination of template and caller. Instead, everything is done with references linking template back to caller, and the bound template functions as its own expansion. (This means, among other things, that you can use the property sliders to tweak widget layout interactively in both template and caller, without having to hit Refresh all the time.)
Once the template is bound, BindArgs performs some additional housekeeping functions related to the PushMenu, PopMenu, and AddContext functions described in the Function reference section below.
If successful, BindArgs returns the string "Template", so that execution continues with the "Template" branch of the calling conditional, which then passes control to the bound template menu. As the template menu renders, execution flows from template to caller and back again through the bound formal-to-actual references.
If BindArgs fails for any reason, it returns an error string. You can detect this case if you like by putting an "else" branch on your conditional, as shown in the example. You can retrieve a textual description of the error from the global variable BindArgsError.
BindArgs never returns the value "Args", so execution cannot flow directly from BindArgs into the actual argument widgets. The only entry points to these widgets are from the bound formals in the template menu.As currently implemented, BindArgs tries to optimize the reuse of templates by using backlinks to remember which caller a template is bound to. If the template is called from the same place twice in a row, BindArgs skips the search for formals the second time around, on the assumption that they're already correctly bound from the first call. These backlinks take the form of Conditional widgets with the expression false && "@@backlink@@", so they never execute. If these backlinks get in your way, feel free to delete them; the worst that can happen is that BindArgs will do an unnecessary binding pass next time it enters that template.
I'm not completely convinced this backlink optimization is necessary. There's not much evidence that searching the widget tree for formals is a serious performance hit. But it seemed like a good idea to try to minimize unnecessary reference-binding churn if possible. There's a static boolean called fUseBacklinks in Template.java if you want to experiment with turning this optimization off.
The BindArgs machinery is largely agnostic about the kinds of widgets it manipulates. There's some special processing that happens for menu templates to support the AddContext and PushMenu/PopMenu functions. But if the specified target template happens to be some other type of widget, BindArgs still goes ahead and binds it, minus the extra menu-specific processing.
So in principle it should be possible to use templates for other UI purposes as well, such as commonly-used Panel and OptionsMenu layouts. I have not yet done any experimentation along these lines, but the Template code as written should support it. (I would, however, be cautious about trying to display more than one instance of the same Panel template at the same time.)
This template scheme is not without its drawbacks. Since SageTV and Studio were never designed with this sort of thing in mind, there are a few things that don't work quite the way you'd expect when using templates. Here's a list of some of the issues I've run into so far, and what (if anything) I've done to work around them.
Problem: You can't pass a literal text widget as the only child of an actual argument chain. If you try, the text of the argument name ("@ArgName@") will override the widget's own text.
Workaround: Insert into the chain an extra Action widget containing the literal text you want, with an untitled text widget below that, as in the "@MenuTitle@" example above.
Problem: BindArgs treats any Action widget of the form "?text?" as a formal placeholder and tries to bind it. What if you want to have an actual string literal that just happens to look like that, and you don't want it treated as a formal?
Workaround: Write your Action widget like so: "?"+"text"+"?" and BindArgs won't mistake it for a formal.
Problem: You add a new formal to a template, but BindArgs doesn't bind it. This is most likely a consequence of the backlink optimization discussed under Detailed operation of BindArgs. If BindArgs thinks the template is already correctly bound, it will skip the search for formals without noticing that you've added some new ones.
Workaround: Either delete the template's backlink manually, or call SageWorks_Template_UnbindAll() from Studio's Expression Evaluator to remove backlinks automatically and force everything to be rebound.
Problem: String arguments to BindArgs must be unique.
Ideal solution: A new GetCurrentWidget() function in SageTV's WidgetAPI would enable BindArgs to get a handle directly to the calling widget, without any searching and without requiring unique string arguments to differentiate calls.
Workaround: Use UniquefyCalls periodically during STV development to ensure that your BindArgs calls are in fact unique.
Problem: Set as Primary Reference doesn't stick. If you cut and paste widgets, or save and reopen the STV file, you might find that the actual widget chains you provided as arguments to some BindArgs call have migrated into the template, leaving italicized references behind at the call site. This tends to undermine the clean separation of code that is whole point of using templates in the first place.
Ideal solution: I'd like to see Studio fixed so that primary references stay where you put them until you use Set as Primary Reference again to put them somewhere else.
Workaround: Use Expression Evaluator to call UnbindAll whenever you get confused. This will break the binding references from formals to actuals and put things back where they belong.
Problem: AddStaticContext doesn't work across multiple levels of template expansion. Since BindArgs finishes by transferring control to the template menu, each level of template expansion blows away the previous static context.
Solution: I've implemented an AddContext function in the Template class to do what AddStaticContext does, but in a template-aware fashion. Use this instead of AddStaticContext to pass variables from one calling menu to another through arbitrarily many levels of template expansion. Since there seems to be some difficulty passing ints and booleans to AddContext, I've also provided type-specific AddIntContext and AddBoolContext functions as well.
Problem: SageTV's Back command doesn't work the way you think it should. Because it's the bottommost template menu that ultimately gets control, Sage thinks of this template as the current menu for forward-and-backward navigation purposes. But from the user's point of view, it's the topmost calling menu that should be remembered and returned to.
Solution: I've implemented a template-aware menu stacking facility in the form of PushMenu and PopMenu functions. Use these in place of the built-in Back command for more sensible menu navigation.
Problem: Studio doesn't always update its display properly when widgets are edited from Java code. Template binding (or unbinding) can therefore produce some minor display artifacts.
Ideal solution: This strikes me as a pure-and-simple Studio bug that ought to be fixed.
Workaround: Force a display update manually by selecting the first child of some widget and hitting Ctrl+U (Move Up). The widget won't move (since it's already at the top), but the display will be refreshed and any artifacts cleaned up.
Problem: The widget manipulations done by BindArgs count as edits as far as SageTV is concerned. Simply displaying a template-based menu therefore marks the STV dirty and causes Sage to prompt you with the "Save changes?" dialog when you quit.
Ideal solution: A pair of WidgetAPI functions for getting/setting the STV dirty status would let me wrap my BindArgs code with save/restore logic, rendering it transparent to the dirtiness check.
Workaround: Unfortunately I don't have a good workaround, other than clicking Yes to save changes every time I quit. This is perhaps the most serious of these template-related issues and I'd really like to see an official Sage fix for it if templates are going to be accepted as a legitimate way of organizing STV code.
Here's the complete list of functions implemented by SageWorks.Template. All methods of this class are static, so you don't need to create an instance of SageWorks.Template to call any of these functions.
Binds templates to calling menus as described in detail above. The string parameter szArg is meaningless and is used only to locate the calling widget in the widget tree.
Walks the widget tree of the entire STV, removing backlinks and formal-to-actual binding references. Call this from Studio's Expression Evaluator when you add or delete template formals, or when you just get tired of the way primary references drift around in Studio and want to bring everything back home where it belongs.
Walks the widget tree of the entire STV, looking for calls to BindArgs and assigning unique string arguments as needed.
void AddContext(String szVar, Object oValue)
Adds a name/value pair to the current variable context. This is similar to SageTV's built-in AddStaticContext function, except that variables defined by Template.AddContext are propagated automatically across multiple levels of template expansion, whereas AddStaticContext variables are not.
void AddIntContext(String szVar, int iValue)
Like AddContext, but accepts an integer value instead of an Object.
void AddBoolContext(String szVar, boolean bValue)
Like AddContext, but accepts an boolean value instead of an Object.
Pushes the current topmost calling menu (i.e. the non-template menu that kicked off the current chain of nested template expansions) onto a stack of recently displayed menus. You would call this typically from the BeforeMenuUnload hook of your bottommost template menu (i.e. the one Sage thinks of as the current menu).
The menu stack has a fixed maximum depth of 10, after which older menus are thrown away. You can change this maximum by changing the static int cmenuMaxStack in Template.java.
Pops a menu off the stack of recently displayed menus and displays it. You might call this typically from a global Left listener in your bottommost template menu.
void SetMenuVar(String szVar, Object oValue)
Sets a variable value, similar to the built-in AddGlobalContext, except that the scope is not global, but specific to the current topmost calling menu. You might use this inside a template menu to keep track of persistent menu state on a per-caller basis.
Object GetMenuVar(String szVar)
Retrieves a variable value set by SetMenuVar.
void SetMenuInt(String szVar, int iValue)
int GetMenuInt(String szVar)
Similar to SetMenuVar/GetMenuVar, but with integer values instead of Objects.
void SetMenuBool(String szVar, boolean bValue)
boolean GetMenuBool(String szVar)
Similar to SetMenuVar/GetMenuVar, but with boolean values instead of Objects.
Enables or disables debug logging of internal Template class operations. Logging is disabled by default. Calling Log with a valid java.io.Writer directs debug logging to that sink. For your convenience, the SageWorks package includes a DebugLogWriter class (for sending output to Sage's DebugLog console) and an ODSWriter class (for sending output to the Windows OutputDebugString API, where you can view it with DebugView). Calling Log with null as an argument disables debug logging.
You can call Log from Studio's Expression Evaluator, of course, to enable logging manually, or you can set up an ApplicationStarted hook to enable logging at startup. This can be useful during STV development to make sure your templates are doing what you think they're doing. Then, when you're ready to release your STV, you can remove the hook and leave logging disabled.