|
Writing extensions requires, first of all, knowing the required structure of the tree- that is, what the core expects the tree to look like, and how it draws it. This includes knowing what kinds of nodes and attributes have special meaning. Further, adding to or modifying an existing application requires knowing the tree structure of that application. What remains is understanding the underlying programming model: knowing how scripts are invoked and the tree operations (and non-tree operations) that are available. That is what is provided on this page.
A basic principle of this architecture is that the tree is always right. The tree and the display should always correspond exactly. This rule is intended to simplify the extender's view of the extension process. If extenders write scripts to change the tree - as they will, frequently - then when those scripts are invoked, the display must change accordingly. Logically speaking, the screen is redrawn every time the tree changes.
The central operation in Slice is "drawing" - the operation of traversing the tree and displaying the nodes that are encountered.
Traversal starts at the root, which must be labeled "Slice". The root must have a child node labeled Frames, which has children labeled Frame. Each Frame node has several essential attributes: DisplayId contains a value which is used to find the subtree displayed in this node; X, Y attributes give the location, and W, H attributes the size, of this frame, in pixels; Visible has value True or False. For those Frame nodes with Visible = True, a node is sought in the tree with an Id attribute whose value equals that of the Frame's DisplayId Attribute. That node must be a "container" node, meaning that its name or Type attribute is Panel or InkPanel. The traversal continues at that node.
On the whole, the traversal is simply this: If the node has attribute Visible and its value is False, do nothing. Otherwise, if the node is one of the built-in node types, first perform whatever is called for by that node (described below). Whether or not the node is built-in, if it has children, these are traversed recursively. A child may have its own X, Y, W, and H attributes, or it may inherit their values from the node's container (not from the parent, but from the container, which may be different). X and Y default to zero, while W and H default to the width and height of the container.
The running system has a single tree that it displays, which is called the state tree. The constraints named above apply to that tree. A script may create other trees using the operations listed later in this page. None of the constraints we have stated apply to such trees, unless and until they are added to the state tree (i.e. added as a child of a node in the state tree).
Events cause scripts to be invoked. Special attributes are used to associate scripts with events; the node containing one of these attributes is called the script source for that script. A script attribute can contain the name of more than one script; the names are separated by a vertical bar, plus any amount of whitespace. At any given node, there is a set of scripts associated with various events; these are all the scripts that appear in script sources on the path from the root, through the frame node, to that node. This is the node's script map.
Every event is associated with a node in the tree, called the responsible node for that event. The association of events to nodes is as follows: A button click is associated with the Button node that created the button. A mouse click or key press is associated with the smallest container containing the area in which the mouse click or key press occurred. An ink stroke is associated with the smallest InkPanel node containing the area of the stroke (Panels do not accept ink strokes). Frame events like resizing are associated with the Frame node.
Given an event e and its responsible node n, the scripts executed are all the scripts for e in n's node map. They are executed in order from lowest in the tree to highest (and, within nodes, from left to right). Scripts have access to certain global variables, such as one that points to the responsible node. Details are given below.
Note that scripts are strongly discouraged from using global Python variables to communicate between scripts (either for the same or different events). Any communication that is required should be accomplished by adding nodes or setting attributes in the tree.
The type of a node is given either by its name or by the value of its Type attribute (using the Name if these two disagree). The following node types have a special meaning to the underlying system. The user may introduce other nodes, known as user nodes, which have no meaning to the system; they are used frequently, to organize the tree and store information. User nodes are not drawn (although their children are traversed and may be drawn); only the special nodes listed here give rise to actual items on the screen.
| Slice | Always the name of the root node - should not be used for any other node. |
| Frames | Must be child of root node. Children are Frame nodes. |
| Frame | Contains DisplayId attribute, whose value is the (unique) id (meaning, value of the Id attribute) of a node in the tree; that node contains the items to be displayed in that frame. This node also contains attributes W, H, X, and Y, giving the size and location of the frame, in pixels. The displayed node for a frame must be a container - Panel or InkPanel - node. |
| InkPanel, Panel | Establishes an area that is inkable or non-inkable. These can establish an inkable area over a non-inkable one and vice versa. An InkPanel cannot be placed on another InkPanel. These two node types are collectively known as container nodes. |
| Button, Label, TextBox | These nodes represent the corresponding screen widgets. Each should have size coordinates (X, Y, W, H, except where default values suffice) and attributes describing their appearance, such as ImageFile. These nodes are collectively referred to as leaf control nodes, because they occur at the leaves of the tree (and represent "controls" in the .NET sense; Stroke nodes are also leaves, but are not controls). Because a Frame node must point to a container node, a leaf control node is always a descendant of a container node; its closest container node is referred as "its container." |
| Stroke | A Stroke node contains X, Y, W, and H coordinates giving the bounding box of the stroke, and pen attributes describing the appearance of the pen stroke (e.g. PenColor). It then contains a list of floating-point numbers in the range 0-1, giving the x,y coordinates of each point in the stroke, relative to the bounding box. In addition, it contains two lists of integers, each of the same length as the number of coordinates; one gives the pressure reading at each point, and one gives the "time tick" at each point. Scripts can move and resize strokes by adjusting the location and dimension attributes; there is rarely, if ever, any need to access the coordinates of the stroke, except when creating a new Stroke node. Like leaf control nodes, a stroke node always has a container, but in this case, the container must be an InkPanel. |
| Floats, Pressure, TimerTicks | These names should never be used. In the external, XML representation of trees, nodes with these names are children of Stroke nodes; the Floats node gives the (x,y) coordinates of the stroke, the Pressure node gives the pressure readings at each point, and TimerTicks gives the time tick within the stroke (generally, at 7.5 millisecond intervals). If another node uses this name, the routine that reads XML files will get confused. However, internally, Stroke nodes have no children. Note that the Floats node will always be present, but Pressure and TimerTicks may be absent, depending upon the digitizer (and certainly will be absent when a stroke is drawn with the mouse instead of the pen). |
Here we list all attributes except "script attributes," which are used to associate scripts with nodes; those are listed in the next section. Most attributes are meaningful only for specific types of nodes. (It is still legal to give other types of nodes these attributes, but their values will not be considered by the system.) Some of these attributes have default values, which are given in the description. This does not mean that every node has the attribute - the HasAttribute method will return false unless a script (or in some cases noted below, the system) has specifically put that attribute in the node - but just that the drawing code will assume a particular value in its absence.
| Id | All nodes. Id can be any string, but must be unique within the state tree. Id's are placed in a hash table for quick access, so should be used if a particular node needs to be accessed frequently. See operation FindNodeById. In many (if not most) cases, nodes can be found quickly enough using operations like GetChild and FindChildByAttribute. |
| X, Y, W, H | These attributes have the meaning previously described. If either location (X or Y) is missing, it defaults to 0, and if either size (W or H) is missing, it defaults to the corresponding value of its container. Exception: If the container is an InkPanel, and it has InkW or InkH attribute, then the W and H attributes of this node inherit the InkW or InkH value, rather than the W or H value. |
| InkW, InkH | For InkPanels only, these give the size of the underlying ink overlay. The InkPanel will scroll if either of these values is greater than the size (W, H) of the node, and AutoScroll is true. InkPanels, like Panels, also scroll, regardless of the values of these attributes, if the contents of the container exceed the size of the node, and AutoScroll is true. |
| ZoomLevel | For containers, indicates the percentage by which all objects in the container should automatically be magnified. Given as a floating-point number. Note that this does not increase the size of the container, but only of its contents. |
| Maximized | For Frame nodes. Boolean. If true, Frame is maximized. |
| Visible | If False, the node and its children will not be displayed. Default is True. |
| PenColor, PenWidth, PenHeight, PenTip, Transparency, Highlight | Any node, but applies mainly to InkPanel and Stroke nodes. These attributes are inherited (as described here), so may be defined in Root or any other node. For an InkPanel, the values of these attributes (inherited or given in the node itself) determine how subsequent strokes will appear; for a StrokeNode, they determine how this stroke will appear. For a StrokeNode: If the stroke comes from a user writing on the tablet, all attributes are determined at that time; if the stroke node is created by a script, and does not specify all of these attributes, then they are taken from the inherited values at the time the node is added to the tree. In either case, the attribute values are all given explicitly in the node, and can be changed only by explicitly changing them in the Stroke node itself; thus, changing any of these attributes in an InkPanel will not change the properties of any existing strokes. For the possible values for PenColor, see BackColor below; default is Black. PenWidth and PenHeight values are integers in ink coordinates (much finer than pixel coordinates); default is ?. PenTip is either Rectangle or Ball; default is Rectangle. Transparency is an integer between 0 and 255, where a higher number represents greater transparency; default is 0. Highlight is boolean; if value is "True", the pen becomes a highlighter. |
| ScreenHeight, ScreenWidth | These attributes are set in the root node when Slice starts, based on the size of the screen. (They should be altered when the screen orientation changes, but they are not. However, if changing the screen orientation results in a frame changing size, this will trigger an event; see below.) |
| BackColor, ForeColor | For leaf controls and containers. ForeColor is the color of the text in a label or button. Allowable values are: the name of a color as listed in the KnownColor class in .NET - including Black, Blue, Brown, Gray, Green, Orange, Pink, Purple, Red, Yellow, and many others; an RGB value consisting of a # character followed by three hexadecimal digits; or an ARGB value consisting of a # followed by four hexadecimal digits. |
| Text, Title | For leaf controls and Frame nodes. The text will be placed in the leaf control node, and will appear as the title of the frame. Text and Title are synonyms. |
| TopMost | For Frames. Boolean. Indicates that this frame always appears on top of other frames. (Default: False) |
| Image, ImageFile | For leaf controls and containers. At most one of these should be defined. The ImageFile attribute should give the path name of an image file (in any format supported by GDI+, currently GIF, JPEG, BMP, TIFF, PNG, and EXIF), relative to the starting directory. The Image attribute should contain an image in base 64 encoding. |
| HoverImageFile | For leaf controls and containers. Gives image that is shown when mouse is over the control. (Should have HoverImage attribute, but do not.) |
| HoverColor | For Buttons. Gives background color when mouse is over the control. |
| Font | Leaf controls. Sets font family for this control. Common font families are Arial, Courier New, and Times New Roman. Default is Microsoft Sans Serif. |
| FontSize | For leaf controls. Defaults to 10. |
| ImageLayout | Leaf controls and containers. Value indicates how an image given as an Image or ImageFile attribute is laid out in the control or container. Allowable values are None (place image at (0,0) in its natural size), Center (place image in the center of the control at its natural size), Stretch (stretch image to fill control), Tile (tile image to fill control), and Zoom (increase size of image as much as possible, while maintaining aspect ratio - compare with Stretch). Default is Stretch. |
| Center | For Labels and Buttons. Boolean. If True, center text in the label. |
| Decorations | For Buttons. Determines whether buttons appear flat or three-dimensional, or three-dimensional only when mouse is hovering. Values are Flat, Standard, and Popup. |
| Border | Size of border around control, in pixels. Default is zero. |
| AutoScroll | For containers. If true, container will resize to fit its contents, adding scroll bars if necessary. Default is false. |
| ScrollPosVert, ScrollPosHoriz | Current position of scroll window. If window has scroll bars, these values will be set whenever the scroll bars are moved. Scripts may also set them, which will cause the control to scroll to the given position. |
| AutoSize | For Labels only. If true, label will get size determined by its contents. Default is false. |
| Selectable | For leaf controls. Boolean. If true, leaf control can be selected, and then, in turn, deleted, resized, or moved. If not true, it cannot be selected. Default is True. (Note: Strokes are always selectable.) |
| Enabled | Leaf controls and containers. If False, control cannot respond to user interaction. |
A script attribute contains the name of one or more Python files or Python functions (separated by a vertical bar and any amount of whitespace). These attributes are named for the events for which scripts can be given. When the event occurs, the files are loaded and the functions are called (with zero arguments). If a node is responsible for an action, then all the scripts for that action that appear along the path from the root node, through the Frame node, to the responsible node are executed, in order from lowest in the tree (i.e. closest to the responsible node) to highest; within a node, they are executed from left to right.
The values of these attributes will normally be function names rather than file names (with the exception of the PythonDefs attribute). The allowance of filenames is mainly for compatibility with earlier versions of Slice.
For each event, we say what the responsible node is; when a script executes, this node will be the value of the Python variable Source. We also mention what global Python variables are defined; later, in the Scripts section, we discuss more about writing scripts, and give more details about these global variables.
| PythonDefs | This is a special case. This attribute appears only in the root node. It can contain only file names. These Python files are loaded as soon as the XML tree is loaded, providing definitions that can be used in other scripts. Normally, all function names that appear in other script attributes will be defined in these files (although any file associated with a script attribute can provide definitions which can be used after that file has been loaded). |
| Init | This is a also special case. This attribute appears only in the root node. Its action is identical to the PythonDefs attribute: listed functions are executed once, when the application first starts. However, its intent is different, namely to perform actions, not just load definitions. These functions are executed immediately after the state tree is read in from the XML file, and before anything has been displayed on the screen. Python variable Root points to root of tree; attributes ScreenWidth and ScreenHeight (see above) have been set. (Technical note: When a state tree has been saved and is reloaded, care may be needed to ensure that these initialization scripts work correctly, which may mean not executing some of them. When an application is started, it may, for example, set a new attribute in the root; whenever the init scripts run they can check if this attribute is set or not; if it is, it means this tree is being re-loaded. An application can also reassign the Init attribute of the root, removing some init scripts or adding new ones; when the tree is reloaded, those init scripts will be executed instead of the original ones.) |
| InkStrokeHandler | Invoked when user enters an ink stroke. Responsible node is the InkPanel into which the stroke was entered. Stroke node has already been added as child of the InkPanel node. Python variable StrokeNode points to that node. |
| InkStrokeDeleting-Handler | Invoked when user deletes stroke or leaf control by using the stroke eraser. (Not invoked when user deletes strokes by selecting them and clicking a button; any processing in that case needs to be done in the script associated with that button.) Responsible node is InkPanel containing the stroke. Python variable NodesToDelete contains a list with the deleted node, and ParentOfDeletedNodes points to the parent of the deleted node; StrokeNodes is a synonym for NodesToDelete. (I don't believe NodesToDelete can ever have more than one stroke - need to check.) |
| PointDeletingHandler | Called when point-deleting stroke has been entered. Responsible node is the InkPanel. A point-deleting stroke can break up a stroke into several strokes, and can delete entire strokes. Variable AddedNodes gives a list of the new Stroke nodes created, and DeletedNodes a list of the stroke or leaf control nodes deleted. (Unlike regular stroke eraser, the point eraser can delete more than one stroke. The reason is that the regular stroke eraser handler is called for each deleted stroke, even if it is in the middle of the eraser stroke; the point eraser handler is called only after the erasing stroke is completed.) |
| InkStrokeSelecting-Handler | Called when user selects a collection of strokes and leaf controls; responsible node is InkPanel containing the strokes. Each selected node has attribute Selected set to True (and any previously-selected nodes have that attribute removed). Variable SelectedNodes contains a list of the selected nodes. |
| InkStrokeMovedHandler | Called when selection is moved; responsible node is the InkPanel. Variable MoveDelta is set to a pair (dx,dy) giving the movement of the selection, in pixels. |
| InkStrokeSelection-ResizedHandler | Called when selection is resized; responsible node is the InkPanel. Note that resizing a stroke can also move it, if it resized from its upper-left corner. Variable MoveDelta is set as for InkStrokeMovedHandler, and Stretch is set to a pair of floating-point numbers indicating by what percentage the size of the selection was changed in the horizontal and vertical directions. |
| OnClick | Called when left mouse button is clicked. Responsible node is the leaf control or container on which it is clicked. Variable MouseLoc is set to a Point giving the location of the mouse; MouseLoc.X and MouseLoc.Y give x and y coordinates. |
| OnRightClick, OnMiddleClick, OnForwardClick, OnBackClick, OnDoubleClick | Just as for OnClick, but for other buttons: right button, middle button, the "x" buttons (these are the side buttons on some newer mice, which act as browser forward and backward navigation buttons), and for double click. |
| OnKeyPress | Called whenever a keyboard key is pressed. Responsible node is leaf control or container that had the focus. Variable KeyEventArgs gives the argument to the handler, which is of type KeyEventArgs. As can be seen in the Microsoft doc page for this class, KeyEventArgs.KeyValue returns an integer value representing the key pressed (e.g. A-Z are 65-90), and KeyEventArgs.Shift says whether the shift key was being held. |
| OnTextChange | Called when text is entered in a textbox. Responsible node is the TextBox node. No Python variable is set, but the Text attribute of the TextBox node is set to the new text. (If it is important to remember the previous value, this needs to be saved in another attribute.) |
| OnExit | Called when application exits. No responsible node. This attribute should be given in the Root node. |
| WindowClosingHandler | Called when a window closes. Responsible node is the Frame node. |
| FrameResizeHandler | Called when user resizes a frame. Responsible node is the Frame node. X, Y, W, and H attributes have already been set to new size before the script is invoked. |
| OnMouseEnter, OnMouseLeave | Called when mouse enters or leaves the focus of a control. The responsible node is the control being entered or left. When these events occur on a Button, the HoverImageFile image and the regular image are swapped (without the script being involved). |
| ScrollHandler | Called after a scrollbar has been moved. The responsible node is the Panel or InkPanel that has the scrollbar. The variable ScrollDir contains the string "Vertical" or "Horizontal". The corresponding attribute in the responsible node - ScrollPosVert or ScrollPosHoriz - has had its value changed to reflect the new position. |
| OnPenInvert | Called when the opposite side of the pen touches the screen (for styluses that have an "eraser" on the opposite end). The variable PenInverted is True if the pen is now on the "eraser" side, and False if it is on the pen side. |
Scripts are associated with events via the script attributes listed in the previous section. For each event, a node can have the corresponding attribute, containing one or more scripts; if there is more than one, they are executed in left-to-right order. A node also inherits scripts from its ancestors (on the path from the root, through the Frames node, to this node); these are executed after the scripts given in this node itself, starting with the scripts closer to this node.
Scripts for some events have certain Python variables defined; these were noted in the previous section and are listed again below. In addition, every script has access to several Python variables.
All these variables are read-only. Using Python variables to communicate between scripts is strongly discouraged; instead, place values in the state tree.
| Source | The responsible node for the event that triggered this script. (If the script belongs to the Init attribute, Source is not defined.) |
| ScriptSource | The node that contained the script, which is either the Source or an ancestor of the Source. |
| Root | The root of the tree. |
(Previously, there had been variables Frame and Display defined, but these were used so rarely we removed them. The current frame can be obtained by calling node.GetFrame(). The current displayed node - i.e. the node to which the Frame node points - can be obtained by the call Slice.FindNodeById(f["DisplayId"]), where f is the current frame.)
These Python variables were already named in the earlier list of events; this list is for quick reference.
| StrokeNode | Defined for a stroke-entered event (InkStrokeHandler). The underlying system will create a Stroke node and add it to the tree as a child of the responsible InkPanel node. The StrokeNode variable points to that node. |
| MoveDelta | Amount and direction of movement when selection is moved (InkStrokeMovedHandler, InkStrokeSelectionResizedHandler). Given as a Point object, a pair of integers referenced by X and Y, in pixel coordinates. The X and Y attributes of the strokes or leaf controls will already have been changed. (MoveDelta takes into account ZoomLevel, in this sense: if the values given in MoveDelta are subtracted from the X and Y attributes of the moved nodes, they will move back to their original position, regardless of zoom level. For example, if the zoom level is 2 and the object was moved 100 pixels to its left, the X value in MoveDelta will be -50; because of the zoom level, this is the amount by which the X attribute of the moved nodes needs to be changed to effect a movement of 100 pixels.) |
| Stretch | A Vector2D object - a pair of floats, accessed by X and Y - giving the change in size of a selected area when its size is changed (InkStrokeSelectionResizedHandler), as a fraction of the original size. The W and H attributes of the affected nodes will already have been changed. ZoomLevel is taken into account, as for MoveDelta. |
| DeletedNodes | Defined for InkStrokeDeletingHandler and PointDeletingHandler. Note that not all deleted nodes are Stroke nodes; leaf controls (e.g. imported images) can also be deleted. |
| ParentOfDeletedNodes | Defined for InkStrokeDeletingHandler and PointDeletingHandler. Since the deleted nodes have been removed from the tree, their parents can no longer be obtained by GetParent(). If multiple strokes nodes with different parents are deleted, the result will be one of those parents, but is otherwise undefined. (Note that for any given deletion event, all deleted nodes must be in the same InkPanel.) |
| AddedNodes | Defined for PointDeletingHandler. List of nodes. All created nodes are Stroke nodes. |
| SelectedNodes | Defined for InkStrokeSelectingHandler. |
| MouseLoc | Point value (using X and Y to get coordinates) giving mouse location. Used for "click" scripts - OnClick, OnRightClick, etc. |
| KeyEventArgs | Defined for OnKeyPress. Variable of type KeyEventArgs. |
| ScrollDir | Defined for ScrollHandler. A string containing either "Horizontal" or "Vertical". (Note: When a scroll bar is moved, attribute ScrollPosHoriz or ScrollPosVert will be set in the responsible Panel or InkPanel node; these are not global variables but attributes; see above.) |
| PenInverted | Defined for OnPenInverted. Boolean value: True if the eraser side of the stylus has just touched the screen; False if it is the pen side of the stylus. |
The tree operations are pretty much what one might expect: operations to add and delete nodes, to search for nodes by name or attribute value, etc. Some are methods of the TreeNode class, so are invoked using dot notation; others are static methods, mainly of the Slice class.
We have given the types of the arguments and return values in C# notation.
Hopefully, this is clear.
Values of type List
The list begins with the simple tree-oriented operations - such
as getting a node's parent or siblings, adding a child to a node, etc. -
that are used most often.
First, we mention
a Python function - not a built-in operation, but
rather a script written using the following built-ins - that is
very useful and is found in the script files of all of our
applications. (We do not at present have a way to load
such globally-useful Python functions automatically in every application.)
The following are methods of the TreeNode class,
so are invoked as node.op(arguments).
These operations are non-static methods of TreeNode.
The sizingFunction argument is explained below.
void Print(string sizingFunction)
All work similarly: Print and ExportToPDF display a tree rooted
at a single node, while PrintChildren and ExportChildrenToPDF print the
children of a node. The Print function invokes a print dialog,
while the Export functions requires that a name be provided by the script.
(There is at present no built-in function that pops up a dialog box
for the user to choose a filename
for the Export function; such a dialog box could be written using
a TextBox.)
N.B. Nodes that are to be printed must have been displayed on the screen. This is a restriction that should be lifted in future versions.
The sizingFunction argument tells the printing routine
how to divide nodes into printed pages.
This is interesting mainly when a node is
too large to fit on the screen and therefore has scroll bars.
sizingFunction is the name of a Python function that is
invoked once for each node that is printed (i.e. exactly once for Print
and Export, once for each child for PrintChildren and ExportChildren).
Before explaining its workings in detail,
we point out that there are several functions defined in the
lecture application that can be passed as the sizingFunction argument:
FitPage fits the entire node onto a single printed page, filling
the page's width and height;
FitPageWithAR fits the entire node onto a single printed page, but
preserves the aspect ratio;
FitWidth scales the node's width
to fit a single printed page, then uses as many pages as necessary;
FitHeight is analogous to FitWidth but fits the height to one page;
NaturalSize does no scaling of the node, but simply transfers it to
the printed page pixel-for-pixel, using as many pages as necessary.
For those wishing to write their own sizingFunction:
The printing process starts, for each node, by "printing" the node to
an internal bit map, call it M, large enough to display the entire node.
The question then is, how is this image divided into pages.
sizingFunction is called with four arguments: the width and
height of M in pixels (two integers), and the width
and height of the "paper" in pixels.
It returns four numbers: the width and height, in pixels, of each
page in M (two integers) and the scaling factor to be applied to each page
from M when moving it to the paper pages (two floats).
For example, to fit the entire node to a single printed page:
That is, the entire node will be a single page,
with the width scaled to fill the width of the paper, and
the height scaled to fill the height of the paper.
As another example, to move the node pixel-for-pixel to the
printed page (no scaling), using as many pages as necessary:
That is, use the size of the paper pages in pixels,
with no scaling.
These operations are invoked as Slice.op(arguments).
Generally speaking, these are operations that do not relate
specifically to trees.
The following operations are not used very heavily,
because locally unique id's are associated with each
tree node that is created (see GetNodeNumber above),
and that is usually sufficient.
SetGlobal can be used to initialize a variable,
in an init script, to a value that does not change during the
execution of an application.
Script writers are strongly discouraged from using these operations
during the execution of an application.
To communicate values between scripts, place values in the tree.
(SetGlobal is used in init files to give a name to a particular node
that many scripts will want to refer to, such as the Lecture node
in the lecture application.
The documentation for the particular application will list these variables.
These variables will be available to all scripts in that application,
in addition to the variables Source, ScriptSource, and Root,
available to all scripts in all applications.)
The Slice networking model is client-server.
Operations are provided to set up a network connection with
an agreed-upon server.
Messages are sent in the form of trees (converted to wire format,
of course, by the system).
The server and clients assign one or more functions (usually one)
to receive messages and take appropriate action based on the
contents of the message (its attributes and children).
We divide the discussion into network initialization and message handling.
Because it is simpler, we explain message handling first.
The result of the initialization process described below is this:
The client or server can now send and receive messages.
To send a message, the client uses one of the following:
The first sends the message to the server, to
be handled by its message handler.
(Obviously, server scripts don't use this.)
The second sends the message to all clients that initially supplied
attribute key=value when connecting to the server.
(However, no message is ever sent back to the original sender,
even if the sender would otherwise qualify.)
Message reception is done by the message handler just mentioned.
Its sole argument is the message.
The format of the message is entirely application-specific.
To give an example, in the networked lecture application,
all messages have name NetworkMessage and all have an attribute MsgType.
Message types include AddSlides, AddStroke, RemoveSlide, and others.
The message handler, function LectureHandler, is basically a
switch statement on the MsgType attribute.
Each message type has its own additional required attributes and children.
For example, a message with MsgType=AddStroke must have an additional
attribute SlideNum, and a child which is the stroke node itself.
The handler locates the slide with that number
and adds the stroke to it.
(Note that the stroke node must first be removed from the message,
since a node can have only one parent at a time.)
In the initialization process, three things must happen:
The machine that chooses to be the server issues the call:
where port is an agreed upon port number.
(As can be seen in the XML files, such as network_lecturer.xml,
we use port number 1400 for Slice communications.)
The absent second argument in the SetNetworkOption call is used for
the IP address when the call is made by a client, as we next show.
The arguments to StartNetwork will be explained in the following section;
in fact, they are optional.
The client must somehow know the server IP address.
If the server is a fixed machine, this can be built into
the XML file or given as a command-line
argument in a Slice desktop shortcut.
If not, users will have to fill it in in some initial dialog
before connecting (see the 242 application for an example).
The network connection is again made by two calls:
where IP-address is the server's IP address, and port is an integer as above.
The client can supply any sequence of key/value pairs (all strings).
As noted above, server and client can use these to make targeted
message sends.
(In the previous section, the server supplied a "Role" attribute.
This would allow clients to send messages by
calling NetworkHelper.SendToClientWithAttribute(msg, "Role", "Server"),
consistent with messages sent to other clients;
however, clients also have the option of simply
saying NetworkHelper.SendToServer(msg), which has the same effect -
that is why we said supplying those parameters to StartNetwork was optional.)
Prior to starting the network,
any participants on the network should declare a
function to handle messages, as follows:
Note that the assigned value is a function,
not a string giving the function name;
that function must have been defined prior to executing this assignment.
Using the "+=" notation - this will be familiar to those who
have worked with C# delegates - it is possible to have multiple message
handlers by simply repeating this assignment with other functions.
Each would be called with the message as argument.
Obviously, they would have to be careful to coordinate their actions.
Also note that the message may be modified by a message-handler -
for example, by removing its children - so this would have to
be taken into account as well.
In practice, we have not used multiple message handlers for any
applications we have written.
To illustrate the initialization process,
the following function is called by participants in the
WIPTE demo app, a
networked lecture
application. It is called by the server and lecturer
immediately upon start-up (keep in mind that the server
must be started first), and by the student machines after the students'
sign-on dialog.
The lecturer's initial tree has attributes UserRole=Lecturer
and UserName=Lecturer in the root node,
while the student trees initially have UserRole=Student and set
the UserName attribute by engaging in the sign-on process.
NotifyServerConnect, called by non-servers after
establishing a connection, is this Python function:
The important thing to note for purposes of this discussion is that
this function involves ordinary message-sending,
as described in the message-handling section above.
The message-handling function, LectureHandler,
on the server will be invoked when this message arrives,
and it will recognize it as a "UserConnected" message and respond accordingly.
These are the other operations provided by NetworkHelper.
Like the above, all are static methods.
TreeNode tree(string,   List<string>, List<TreeNode>)
Creates a node whose name is the first argument,
with attributes given by the second argument, and children
given by the third argument.
The second argument is a list of the form
[key, value, key, value, ...],
where each key is a string and each value either a string,
an integer, or a float;
if not a string, the value is converted to a string (attribute
values are always strings).
TreeNode methods
string GetName()
The name of the node
List
The node's children
int NumChildren()
The number of children this node has
TreeNode GetParent()
The node's parent. Root.GetParent() returns None.
List
The children of this node's siblings (i.e. node.GetParent().GetChildren()),
or a list consisting of just this node, if the node has no parent.
int Position()
The position of this node within its siblings (indexing from zero).
Zero if the node has no parent.
void AppendChild(TreeNode child)
Add the child node as the last child of this node.
Note that a node can never have more than one parent;
either remove the node from its parent before appending it to another,
or clone it.
void RemoveChild (TreeNode ch)
Remove the given child node from this node.
void Remove()
Remove this node from its parent.
List
Remove all of this node's children,
and return the list containing the removed children.
void InsertAt(TreeNode, int)
Make ch the i(th) child of this node (counting from zero), if possible.
If not possible - this node does not
have at least i children - then do nothing.
void InsertAfter(TreeNode ch, TreeNode putAfterThisChild)
Make ch a child of this node, placing it just after node putAfterThisChild.
void InsertBefore(TreeNode ch, TreeNode putBeforeThisChild)
Make ch a child of this node, placing it just before node putBeforeThisChild.
TreeNode PreviousChild()
Get the child previous to this one, or return None if this is the first.
TreeNode NextChild()
Get the child after this one, or return None if this is the last.
TreeNode Clone()
Make an exact copy of this tree (i.e. this node and all descendants).
The copy will differ only in that the node number of each node
(see GetNodeNum below) will be new.
static List
Get a list of clones of the nodes in the argument list.
Since this function is static, it should be called as TreeNode.Clone(node-list).
TreeNode GetChild(string name)
Get child of this node with the given name; the first such node,
if more than one child has the name.
bool HasChild (string name)
Return true if this node has a child with this name, false otherwise.
TreeNode GetChild(int n)
Get the n(th) child of this node (counting from zero),
or None if the node does not have an n(th) child.
TreeNode GetChild(string key, string value)
Get the child of this node that has the given value
for the given attribute (key);
the first such if there is more than one; None if there are none.
void SetAttribute (string key, string value), or, node[key] = value
Set the given attribute of this node to the given value.
It doesn't matter if the attribute was undefined in this node,
or what its previous value was.
void RemoveAttribute(string key)
Undefine this attribute in this node.
Note that, in terms of the drawing of nodes,
this may cause the node to take on a default value;
for example, removing the ForeColor attribute of a leaf
control will cause any text in the node to be rendered in black.
static TreeNode FindNodeById(string value)
Find node in the state tree
that has the given value for the Id attribute.
Id's must be unique in the state tree.
All Id values are kept
in a hash table for quick retrieval.
TreeNode FindNodeByAttribute(string key, string value)
Traverse this subtree, and return the first node (including this)
that has the given value for the given attribute;
return None if none is found.
(Note that this operation may be very expensive and should
be used with care; if it is known that a particular node will
need to be accessed randomly, set the Id attribute and use the
FindNodeById method.)
List
Return a list of all the nodes in this node's subtree that have
the given value for the given attribute.
TreeNode FindChildByAttribute(string key, string value)
Find the first child of this node that has the given value
for the given attribute; or None if there is none.
(This function does a linear search, but is normally much
less expensive than FindNodeByAttribute,
because it looks only through the list of children, not an entire subtree.)
List
Return all the children of this node that have the given
value for the given attribute.
List
Return a list of all the nodes in this node's subtree that have the given name.
List
Return a list of all the children of this node that have the given name.
string GetAttribute(string key), or, node[key]
Get the value associated with the given attribute in this node
(or None, if the attribute is not defined).
bool HasAttribute(string key)
Return true or false depending whether this attribute is defined.
int GetAttributeAsInt(string name)
Get attribute and convert to int; return -1 if attribute is
absent or has wrong format.
void SetIntAttribute(string key, int value)
Convert value to string and set as value of attribute.
float GetAttributeAsFloat(string name)
Get attribute and convert to float;
return zero if attribute is absent or has wrong format.
void SetFloatAttribute(string key, float value)
Convert value to string and set as value of attribute.
int GetX()
Same as GetAttributeAsInt("X"), but returns 0 if attribute is missing.
int GetY()
Same as GetAttributeAsInt("Y"), but returns 0 of attribute is missing.
int GetW()
Same as GetAttributeAsInt("W").
int GetWidth()
Starting with this node, search upward for a node
that has the W attribute, and return that.
int GetH()
Same as GetAttributeAsInt("H").
int GetHeight()
Starting with this node, search upward for a node that
has the H attribute, and return that.
void SetX (int x)
Same as SetIntAttribute("X", x).
void SetY (int y)
Same as SetIntAttribute("Y", y).
void SetW (int w)
Same as SetIntAttribute("W", w).
void SetH (int h)
Same as SetIntAttribute("H", h).
int GetNodeNum()
The unique number assigned to this node when it was created.
Node numbers are unique within a single session, but not globally unique.
Note that when a tree is cloned, each new node has a new node number,
although the new tree is otherwise identical to the cloned one.
TreeNode GetContainer()
The node's container, either a Panel or InkPanel node.
Not defined for nodes that are not descendants of container nodes,
e.g. the root, Frames, and Frame nodes.
TreeNode GetFrame()
The frame containing this node, if any.
This method simply applies GetContainer until a Frame node is found;
returns None if a Frame node is not reached.
TreeNode MakeTreeNode(string nm)
Make a new node of this name. Note that the name of a node cannot be changed.
TreeNode MakeTreeNode (string nm, string typ)
Make a new node with this name and type.
This is used to define a node with a
name that is not the node's type.
Note that the type of a node cannot be changed.
void ModifyIds(string modifier)
Adds the modifier to the beginning of all ids in the subtree.
Can be used on a clone, if the clone is to going to be
attached to the state tree, to make sure that all the ids
it contains are unique.
(Ids must be unique, so this operation should always be applied before
adding a cloned tree to the state tree, if it may contain duplicate ids.)
List GetPoints()
For Stroke nodes only.
Returns a Python list of one of these two forms:
[[x0,y0], [x1,y1], ... ] ((x,y) coordinates of stroke), or
[[x0,y0,p0], [x1,y1,p1], ... ] ((x,y) coordinates and pressure
readings of stroke).
x and y are floats, p an int. The first form is used if the
stroke has no pressure readings (if, for example, it was drawn
with a mouse).
Note: Coordinates are relative to 1 x 1 box; to find actual
sizes of strokes (in pixels), scale using X, Y, W, and Y coordinates
of the Stroke node.
float[] GetPointsAsArray()
For Stroke nodes only.
Gives the points and pressure readings from the stroke as
an array of 3*n floats. Each triple x, y, p, is the
location and pressure of one point.
(See note under GetPoints.)
void SetPoints (List)
For Stroke nodes only.
Set the points and (optionally) pressure readings of a stroke.
Format of argument is same as format of list returned from
GetPoints.
(See note under GetPoints.)
void SetPointsFromArray (float[])
For Stroke nodes only.
Set the points and (optionally) pressure readings of a stroke.
Format of argument is same as format of list returned from
GetPointsAsArray.
(See note under GetPoints.)
static TreeNode FromXml (string xmlString)
Return tree obtained by converting the string to a TreeNode.
string ToXml()
Return string obtained by converting this subtree to XML format.
List
Get all selected (visible) descendants of this node.
List
Get all top-level (visible) selected nodes.
That is, if a node is added to this list, its descendants are not.
List
Get all selected (visible) descendants of this node that are
resizable - strokes, leaf controls, and containers. Omit user nodes.
float GetZoomLevel()
Return value of ZoomLevel attribute of this node, or 1.0 if it is absent.
float InheritZoomLevel()
Return amount of zoom inherited from other containers.
Intended for container nodes; to obtain the zoom for this
container's children, multiply by the zoom level of this container.
Printing operations
void PrintChildren(string sizingFunction)
void ExportToPDF(string sizingFunction, string file)
void ExportChildrenToPDF(string sizingFunction, string file)
def FitPage (dw, dh, pw, ph):
    return (dw, dh, float(pw)/dw, float(ph)/dh)
def NaturalSize (dw, dh, pw, ph):
    return (pw, ph, 1.0, 1.0)
Slice static methods
string SaveApplication()
Save application as XML document.
Filename is obtained from dialog with user; return filename.
string SaveApplication(string filename)
Same as SaveApplication(), but with given filename as
default filename in user dialog.
void Save(TreeNode tree, string filename)
Save given tree as XML document in given file.
bool FileExists(string filename)
Check whether file exists.
string LoadFile(string filename)
Load contents of file.
string LoadFileUsingDialog()
Load contents of file after user dialog.
string GetLastLoadedFileName()
Get filename obtained from user in last call to LoadFileUsingDialog.
bool WriteToFile(string Message, string FileName)
Write string to file; return true iff write succeeded.
bool WriteToFile(string Message, string FileName, bool FileNameIsCompletePath)
Write string to file. If last argument is false,
add filename to current directory to get full pathname.
List<List<string>> ShowImportDialog()
Request file name from user and return list of encoded images.
User dialog will enable user to select files with following extensions:
pdf, ps, eps, ppt represent multiple images
(one for each page); bmp, jpg, jpeg, gif, and png represent single images.
Each returned image is given as three strings:
the encoded image, its width, and its length.
public static bool OpenApplication(string filename, string[] attributes)
Unload current tree and initialize tree from XML document in the given file.
Before executing the init scripts in the new document,
set the given attributes in the root of the new tree.
The second argument is an array of strings of the form "attr=key".
bool OpenApplication(string filename)
Same as OpenApplication(filename, new string[0]).
string GetDesktopPath()
Get path name of the desktop of this machine.
User dialog
bool Prompt(string question, string caption)
Pop up dialog box with given question; request yes/no answer.
(caption argument is title of the dialog box.)
bool Confirm(string question, string caption)
Pop up dialog box with given question; request okay/cancel answer.
(caption argument is title of the dialog box.)
Unique ids
int GetUniqueID()
Get locally unique id.
string GetUniqueIDstring()
Get locally unique id as string.
int PeekUniqueID()
Peek at next locally unique id.
string PeekUniqueIDstring()
Peek at next locally unique id as string.
void SetUniqueID(int lastUniqueId)
Reset starting point for unique ids.
string GetGUID()
Get globally unique id.
Date and time
string GetDateString()
Get date in form mm-dd-yyyy. (mm and dd are one- or two-digit numbers.)
string GetCreationTimeString()
Current time, in ticks (100-nanosecond intervals).
This is a double value, converted to string.
string GetNowDateTimeString(bool YearFirst)
Current time, formatted as y.m.d.h.m.s, or reverse if YearFirst is false.
string GetNowDateTimeString()
Same as GetNowDateTimeString(true).
Global Python variable ops
void SetGlobal(string name, object value)
Create new global Python variable and initialize it.
object GetGlobal(string name)
Get value of Python global variable.
In scripts, the value of a global variable is obtained simply
by naming the variable, so this method is not needed.
Included purely for symmetry.
Clipboard ops
void CopyToClipboard(List
Paste the given nodes, as strings in XML format, into the clipboard.
List
Get XML-formatted strings from clipboard and convert to TreeNodes.
Undo/redo ops
void StartUndo()
Called when system should start populating the undo buffer.
This will normally be placed among the last actions in the init scripts.
void StopUndo()
Empty undo and redo buffers, and do not populate them again until
StartUndo is called again.
Miscellaneous
void ExitApplication()
Exit this application.
Triggers exit event, so calls OnExit script;
may also trigger WindowClosing event.
int ExecuteCommand (string path, bool waitForExit)
Execute command in given file; if wiatForExit is true,
wait until the command is done executing.
int ExecuteCommand (string path)
Same as ExecuteCommand(path, false).
void Pause(int milliseconds)
Pause this thread.
int GetNumberOfLines(string fileContents)
Find the number of newlines inside a string
Networking
Message handling
     NetworkHelper.SendToServer(msg)
     NetworkHelper.SendToClientWithAttribute(msg, key, value)
Network initialization
Establishing a server
     NetworkHelper.SetNetworkOption("TcpPublishingServer", "", port)
     NetworkHelper.StartNetwork("Role", "Server")
Connecting as a client
     NetworkHelper.SetNetworkOption("TcpClient", IP-address, port)
     NetworkHelper.StartNetwork(key, value, key, value, ...)
Declare message handler
     NetworkHelper.TreeNodeMessage += Python-function
Example
Miscellaneous network operations
static void RequestConnectedClients()
A message will
be sent to the server requesting the list of clients.
In response, the server will send a message with name ConnectedClients,
having a single attribute Clients; that attribute will contain a string
of the form ",key,attr,key,attr, ...\n,key,attr... ",
with one line for each client.
static string GetIP()
Get the IP address of this computer.
static event ScriptTreeNode-ClientDisconnectedEvent
TreeNodeClient-Disconnected
static event ScriptTreeNode-ConnectionLostEvent TreeNodeConnectionLost
Each of these is an event like TreeNodeMessage,
for which the script writer can provide a
handler script using "+=" notation.
TreeNodeConnectionLost takes no arguments; it is called only
when the client gets disconnected from the server.
TreeNodeClientDisconnected is for the server.
It is called when a client disconnection has been detected.
Its argument is a tree of the following form:
Its name is ClientDisconnected.
It has as many children as there were attributes supplied by
the client when it connected; each child's name is the name of an attribute,
and it has an attribute Value whose value
is the value given for that attribute.
For example, if the client provided attributes Name=Pete and Role=Student,
then the ClientDisconnected tree has a child named Name with attribute
Value=Pete and a child named Role with attribute Value=Student.