INIXApp Resources

Terminology and Overview

Resources are the building blocks of INIXApp applications. Every item in the application menu on the left side of the application window represents a resource. A set of standard resources is provided in $(INDEL_ROOT)/share/resource, and you can build your own as explained in this document.

A resource can, but need not, have a page that presents its graphical user interface. A page is an INIX plugin or an INIX plugin hierarchy (IGD hierarchy).

A resource can, but need not, contain other resources (subresources) that appear as sub-items of its menu item in the application menu. Any number of these subresources can be displayed at a time next to the resource's page (if any). The author can specify how many subresources are displayed at a time by default, but the user can change that number at will.

Most resources have either a page or a set of subresources, some have both. Resources that have neither a page nor subresources are possible too, but are of limited usefulness.

The main area of the application window contains rectangular regions named work places, identified by a narrow blue or yellow bar at the top or left that expands when touched by the mouse to display a number of controls. Work places are where the contents of resources (pages and subresources) are displayed, every work place always contains exactly one resource. Work places can be nested in a way that corresponds to the resource hierarchy shown in the application menu: A work place that displays a certain resource can (adjacent to the resource's page, if any) contain sub-work-places that each display one of the resource's subresources.

When a resource is selected in the menu, different things can happen, depending on the state of the switcher button at the top of the window that determines whether to use an existing work place or to create a new one. Pressing the shift key while clicking on the menu item reverses that choice.

The page and sub-work-places in a work place can be arranged either horizontally or vertically, the leftmost/topmost button in the header bar switches between the two orientations. The relative size of stretchable sub-work-places can be changed by dragging the divider between work places. Dragging the divider to the side edge of the frame is another way of switching between horizontal and vertical orientation. Whether a work place is stretchable or fixed-size is determined by the contained resource (using the WorkPlaceDefaults property). The size and resizing behavior of a page is set by its author (in IGD files or whichever other means of setting the relevant INCO variables).

Anatomy of a Resource

A resource is stored as a folder in the file system. The only mandatory requirement to make a folder a resource is that it contain a text file named resource.prp. It can also contain other files and folders as discussed further on, e.g.:

The resource.prp File

resource.prp is a text file that describes the properties of the resource. It is formatted as a Python dictionary that maps string keys to values of various types (strings, numbers, lists, dictionaries, tuples etc.) as shown in the examples below. Since it is interpreted by the eval() Python function, it may consist of arbitrary Python expressions (as long as they evaluate to legal values). For more information about the syntax of such expressions, see the corresponding sections in the Python tutorial (e.g. strings, numbers, lists, dictionaries) or the Python language reference (e.g. strings, numbers, lists, dictionaries).

Examples
The minimal legal resource.prp file consists of an empty dictionary:
{}
A slightly more elaborate resource.prp file for a resource whose only purpose is to contain two standard resources as subresources might look like this:
{
        "Title": "Plot & Memory Dump",

        "Resources": [
                "misc/plot",
                "debug/memdump"
        ]
}
For a resource that only encapsulates a page (an INIX plugin), resource.prp might look like this:
{
        "Title": "Memory Dump",
        
        "Commands": [
                ("Find…", "OnMenuFind"),
                ("Clear…", "OnMenuClear"),
                ("Copy…", "OnMenuCopy"),
                ("Save…", "OnMenuSave"),
                ("Load…", "OnMenuLoad")
        ],
        
        "PageDefaults": {
                "Prop.Address": 0xDEADBEEF,
        }
}

Specifically, the following keys are allowed in a resource.prp file. All of them are optional and most have a default value that is used when the key is not specified.

The resource.py Script

Every resource that is present in the application menu is represented in Python by an object of class INIXApp.ResourceInstance (or a subclass thereof). Given the index path of a resource, this object can be retrieved by INIXApp.GetResourceInstance(). Its attributes contain the main properties of the resource, including those set by ResourceDefaults, and it provides some methods that could be of use to scripts used in the resource (documented in the class documentation). New attributes can be created on this object to store additional resource state. Functionality can be added to this object by defining a custom subclass as detailed below, in particular methods OnDidEnter, OnWillLeave, OnDidCreatePage, OnWillDestroyPage, OnDidLoad, OnWillUnload, OnDidPreload can be overridden to perform custom actions at specific points in the life cycle of the resource (see Resource Life Cycle), method PopulateCommandsMenu can be overridden to create a dynamic commands menu (see Dynamic Menus by Overriding PopulateCommandsMenu), and methods to be called by command menu items (see Commands Menu Methods) or by other resources can be added.

To make a custom class for the ResourceInstance object of a resource, a Python file named resource.py is added to the resource folder. This file is expected to define exactly one subclass of ResourceInstanceBase (which is a global variable that usually refers to INIXApp.ResourceInstance, or to a subclass of it if the resource has a Base resource that defines a custom subclass). The name of this class is irrelevant. The file may define other classes, functions, or variables for its own use, but these will not be accessible from outside unless specifically made so through the resource instance.

Example
resource.py
from inco_32 import *
class MyResourceInstance(ResourceInstanceBase):
# methods overridden from the superclass, called automatically
def OnDidLoad(self):
# must include a call to the superclass
ResourceInstanceBase.OnDidLoad(self)
# create an INCO variable with a property of the resource (from
# ResourceDefaults), e.g. for display in a variable plugin
varPath = self.PutOrCreateIncoVariable("MyVariable", self.MyVariable))
# add a trigger to it to be notified when it changes
PutVariable(".", varPath + "!Triggers", "Add;!=;" + self.GetMethodCallIncoAction("VariableChanged"))
def OnWillUnload(self):
# must include a call to the superclass
ResourceInstanceBase.OnWillUnload(self)
# remove the trigger on the variable
PutVariable(".", "App.Menu.Items." + self.indexPath + ".MyVariable!Triggers",
"Remove;!=;" + self.GetMethodCallIncoAction("VariableChanged"))
# put the variable value back into the object attribute so that it will
# be saved with the persistent properties
self.MyVariable = GetVariable(".", "App.Menu.Items." + self.indexPath + ".MyVariable")
# a custom method, called by the trigger
def VariableChanged(self):
if GetVariable(".", "App.Menu.Items." + self.indexPath + ".MyVariable") > 0:
print "Positive!"
else:
print "Negative!"

Commands Menu Methods

Methods named in the Commands property as handlers for menu items receive one argument (two if you count self), the index path in the commands menu of the item that invoked them.

Methods specified as dynamic menu creators in Commands receive two arguments: the index path in the commands menu of the item that is being expanded and into which items should be inserted, and an integer specifying the position at which items are to be inserted (derived from the location of the placeholder item in the commands list). To do so, the method should directly use the INCO interface of the menu plugin. The position argument can be directly passed to Cmd.AddMenu(), it is negative (counted from the end), hence using it multiple times will cause the new items to appear in the order they are created. Because the method is called every time the menu is expanded, it should not blindly add new items, but check if they are already there and either remove them first or only add new ones if they aren't, e.g. by keeping track of them in a custom attribute of the self object (make sure to invalidate this attribute in OnWillLeave, when the menu is going to be destroyed). Tools the method has at its disposal for creating menu items include GetMethodCallIncoAction to generate actions and INIXApp.AddKeyboardEquivalent to add keyboard equivalents.

Example
A resource.py script corresponding to the example from the Commands section might look like this:
from inco_32 import *
from inco import CallProcedurePy
import INIXApp
class MyResourceInstance(ResourceInstanceBase):
def OnWillLeave(self):
ResourceInstanceBase.OnWillLeave(self)
# menu will be destroyed, make sure it's recreated next time
self.dynamicMenus = []
def OnMenuA(self, aCommandIndexPath):
CallProcedure(".", self.pageIndexPath + ".Cmd.DoSomething(17:l)")
def OnMenuSub1(self, aCommandIndexPath):
PutVariable(".", "App.Command.Items." + aCommandIndexPath + ".Title", "A new Title")
def OnMenuSub2(self, aCommandIndexPath):
print self.SomeProperty
def OnMenuB(self, aCommandIndexPath):
self.SomeProperty = "B"
def OnMenuC(self, aCommandIndexPath):
self.SomeProperty = "C"
def CreateDynamicMenu(self, aCommandIndexPath, aPosition):
if not hasattr(self, "dynamicMenus"):
self.dynamicMenus = []
# add some items
if self.dynamicMenus == []:
i = int(CallProcedurePy(".", "App.Command.Cmd.AddMenu", "A", aCommandIndexPath, self.GetMethodCallIncoAction("ToggleMenu", "#{PLUGIN_NAME}"), aPosition, 0))
self.dynamicMenus.append(aCommandIndexPath + ("_%d" % i))
i = int(CallProcedurePy(".", "App.Command.Cmd.AddMenu", "B", aCommandIndexPath, self.GetMethodCallIncoAction("OnMenuSub2", "#{PLUGIN_NAME}"), aPosition, 0))
self.dynamicMenus.append(aCommandIndexPath + ("_%d" % i))
i = int(CallProcedurePy(".", "App.Command.Cmd.AddMenu", "C", aCommandIndexPath, self.GetMethodCallIncoAction("SetProperty", "C"), aPosition, 0))
self.dynamicMenus.append(aCommandIndexPath + ("_%d" % i))
i = int(CallProcedurePy(".", "App.Command.Cmd.AddMenu", "D", aCommandIndexPath, self.GetMethodCallIncoAction("SetProperty", "D"), aPosition, 0))
self.dynamicMenus.append(aCommandIndexPath + ("_%d" % i))
# check item A
PutVariable(".", "App.Command.Items." + self.dynamicMenus[0] + ".Checked", 1)
# add a keyboard equivalent to item A
INIXApp.AddKeyboardEquivalent("global Ctrl-F3", self.dynamicMenus[0], self.indexPath)
def ToggleMenu(self, aCommandIndexPath):
checked = int(GetVariable(".", "App.Command.Items." + aCommandIndexPath + ".Checked"))
PutVariable(".", "App.Command.Items." + aCommandIndexPath + ".Checked", 1-checked)
def SetProperty(self, aValue):
self.SomeProperty = aValue
Notes
  • To make dynamic menu items that call methods with the same signature as those for static menu items, i.e. with one argument specifying the menu item index path, use the misleadingly named #{PLUGIN_NAME} constant (see Constants on the Syntax Definitions page) in the second argument to GetMethodCallIncoAction, as in dynamic items A and B in the example above. In the context of the menu item action, it expands not to a plugin name, but to the name of the INCO folder in which the action lives, which is the index path of its item (see Action in the Menu Plugin User Documentation).
  • Sometimes it can be useful to define even de-facto static menu items using the dynamic mechanism because it gives you control over the actions, e.g. to handle several menu items using the same method, but with different parameters, as for dynamic items C and D in the example.

Dynamic Menus by Overriding PopulateCommandsMenu

One mechanism of creating a dynamic commands menu is to use "DYNAMIC" items in the Commands property, as described above. There is a second mechanism that bypasses the Commands property altogether: Your ResourceInstance subclass can override the PopulateCommandsMenu method that implements interpretation of the Commands property. This approach is particularly suitable if your resource can have no commands at all depending on circumstances because it allows you to hide an empty menu. By calling the superclass in the overriding implementation, parts of the menu can still be built from a Commands property. See the PopulateCommandsMenu description for details.

Page Contents

To attach a page, i.e. an IGD hierarchy, to a resource, treat the resource folder itself as the root of the IGD hierarchy. In other words, the resource folder should contain a _Plugin folder with a Type.igd file, and possibly other items such as _Prop, _Streams_, or _Scripts_ folders.

Resource Life Cycle

The following diagram describes the possible states of a ResourceInstance (see GetState()) and details some of the actions that happen on state transitions.

resourcestates.png

Generated by doxygen 1.8.8