Sysquake Pro – Table of Contents
Sysquake for LaTeX – Table of Contents
SQ File Tutorial
This chapter shows you how to develop an SQ file for Sysquake. Like SQ scripts we saw in the previous chapter, SQ files are programs for Sysquake. But they are built in a stricter framework which provides automatically undo/redo, save/restore, and subplot layout; it also supports menus, choice of plots, periodic processing, a mechanism to compute the minimal amount of information required for the plots displayed at a given time, and an import/export system.
Follow each step of this tutorial, and starting from scratch, you will end up with an SQ file which lets you move the tangent of a quadratic function, and the knowledge to write your own SQ files. When you will need more information, you can refer to the Sysquake Reference chapter.
This tutorial assumes you have a basic knowledge of a procedural programming language, such as C, Pascal, Fortran, a modern Basic dialect, or MATLAB(R). The concepts of variable and function are supposed to be well known.
Structure of an SQ file
This tutorial will describe each element when they are used. However, it is important to notice an important difference between SQ scripts and libraries, which contain LME code, and SQ files, which begin with static declarations of the application components, such as data, figures and menus. In an SQ file, LME code is located in function blocks, i.e. between the lines
functions {@
and
@}
Everything else consists of declarations and comments. Some of the declarations refer to functions defined in function blocks; for instance, a figure declaration includes the function called to draw it, together with its arguments. Sysquake reads all the declarations when the SQ file is loaded in memory; it uses them to reserve space for data, to set up user interface elements, and execute LME functions when required, for instance as a consequence of user actions.
Displaying a Plot
In this section, we will write what is necessary to display in the same graphics the quadratic function, a vertical line which defines a value for x0, and the straight line which is tangent to the quadratic function at x0.
An SQ file is written as a text file using any text editor. If you prefer a word processor, make sure that you save the SQ file as raw text, or ASCII, and not as styled text. On some versions of Sysquake, a built-in editor is available; check if there is a New item in the File menu (Load lets Sysquake load or reload the text of the front window, while Open reads an SQ file from a files). Sysquake handles end of lines in a sensible fashion; do not worry about the different conventions between Mac OS, Unix, Windows and other operating systems. For cross-platform compatibility, restrict yourself to the ASCII character set, and avoid two-bytes characters like Unicode and Japanese kanji (depending on the platform, bytes are interpreted as the native encoding, such as Latin-1 or Shift-JIS, or UTF-8). Once you have written and saved a file you want to test, simply open it in Sysquake. Make sure that the Command window or panel is visible, so that you can see error messages.
We can now begin to write the SQ file.
Step 1: Choosing variables
The most important concept in Sysquake is the set of variables. Variables define the state of the system (we use the word "system" in a broad meaning as what the user perceives from the graphics). Everything that can be changed, be it interactively, by specifying parameters in a dialog box, or by loading an SQ data file, must be stored in variables. In addition, auxiliary variables can be used as a convenience to avoid repetitive computations or to transmit values between handler functions (more about them later). Each variable can contain a real or complex array, a string, a list, or a structure. Variables are identified by a name of up to 32 letters, digits, and the underscore character "_" which begins with a letter (names beginning with the underscore are reserved). As everything else in Sysquake, names are case-sensitive; x and X are two different names and identify two separate variables.
You can declare as many variables as you need. Do not use a big array to pack as many different things as you can; it is much more efficient to have a clean set of variables, so that you can use them and change them more easily.
Sysquake considers the values of the variables as a set. Each time the user changes a variable (interactively or otherwise), Sysquake creates a new set and changes the new values. The value of unmodified variables is retained. The command Undo reverts to the previous set. This is why you should usually not use global variables, which exist only in one copy.
For our example, we define variables a, b, and
c for the coefficients of the quadratic function; variables
d, e, and f for the tangent
To let Sysquake know about our choice, we write the following lines at the beginning of the SQ file:
variable a b c // coefficients of the quadratic function // y=ax^2+bx+c variable d e f // coefficients of the tangent dx+ey=f variable x0 // value of x where the line is tangent
The keyword variable is required; it is followed on the same line by one or more variable names, separated by spaces or tabulators. Everything following the two slashes // is a comment which is ignored by Sysquake.
Step 2: Giving initial values
At the beginning, each variable is set to the empty matrix []. Drawing functions could recognize them and not display anything, but it is nicer for the user to start immediately with default values. In Sysquake, variables are set and used by handler functions. Functions are written in the LME language, and declared to Sysquake by a handler declaration. Handler declarations and function definitions are very similar. They both use variables, which do not necessarily have matching names. Variables in the handler declaration correspond to the set of variables declared at the level of the SQ file; variables in the function definition are meaningful only in the function itself. The input arguments in the handler declarations must be variables or integer numbers; they cannot be expressions. The handler declaration begins with a keyword, for example init to define default values. Here is an init handler for our SQ file:
init (a,b,c,x0,d,e,f) = init
We will use parenthesis for functions with several output arguments. You may use square brackets if you prefer. The function declared above is defined in a function block. We also write a function calcTangent to calculate the tangent of the quadratic function.
function {@ function (a,b,c,x0,d,e,f) = init // initial values for the function coefficients // and the x0 value a = 1; b = 2; c = 4; x0 = 1; (d,e,f) = calcTangent(a,b,c,x0); function (d,e,f) = calcTangent(a,b,c,x0) // tangent to y=f(x) at x0 is y-f(x0)=f'(x0)(x-x0), // where f' is der f // derivative of ax^2+bx+c is 2ax+b d = 2*a*x0+b; e = -1; f = (2*a*x0+b)*x0-(a*x0^2+b*x0+c); @}
Notice the block in {@ @} (function block); now it contains only the init and calcTangent functions, but we will add more functions in the next sections. The function block does not need to follow a particular handler declaration; handlers are identified only by their name. Usually, we will put the function block after all the declarations. In LME code it contains (but not in declarations), the percent symbol can be used instead of the two slashes to begin a comment.
Errors in an SQ file are detected when you open or load it in Sysquake. To let Sysquake analyze your code and catch constructs which might be errors, you can select SQ File Possible Error Warnings in the Preferences. It would be the case if we do not provide initial values for all variables, or if the order of variables in the init handler declaration does not match the one in its implementation, here in function init.
Step 3: Displaying a plot
Each figure is declared by a figure declaration line which contains a name between quotes, and one or more lines declaring handlers for drawing the plot and processing the manipulations with the mouse. For now, we just declare a draw handler (outside the function block), which needs to know the value of the seven variables.
figure "Quadratic Function" draw drawFunc(a,b,c,x0,d,e,f)
The figure displays the quadratic function
function drawFunc(a,b,c,x0,d,e,f) // values of x where the function is evaluated x = -10:0.1:10; // plot the function plot(x, a*x.^2+b*x+c); // plot in red ('r') a vertical line at x0 line([1,0],x0,'r',1); // plot in blue ('b') the tangent at x0 line([d,e],f,'b');
If you have typed all the code above, not forgetting to put the function
drawFunc in the function block, you can open it in Sysquake and
observe your first graphics
If you do not specify which figures you want to display when the SQ file is opened, Sysquake displays the first one. With more than one, you may want to specify explicitly which one(s) to show. Add a command subplot to the init handler with the name of the figure:
subplots('Quadratic Function');
Make sure that the string matches exactly the name of the figure. To display several figures, you would separate their names with tabulators ('\t') for figures on the same row, and with line feeds ('\n') for separating each row. The complete SQ file is shown below.
variable a b c // coefficients of the quadratic function // y=ax^2+bx+c variable d e f // coefficients of the tangent dx+ey=f variable x0 // value of x where the line is tangent init (a,b,c,x0,d,e,f) = init figure "Quadratic Function" draw drawFunc(a,b,c,x0,d,e,f) function {@ function (a,b,c,x0,d,e,f) = init // initial values for the function coefficients // and the x0 value a = 1; b = 2; c = 4; x0 = 1; (d,e,f) = calcTangent(a,b,c,x0); subplots('Quadratic Function'); function (d,e,f) = calcTangent(a,b,c,x0) // tangent to y=f(x) at x0 is y-f(x0)=f'(x0)(x-x0), // where f' is der f // derivative of ax^2+bx+c is 2ax+b d = 2*a*x0+b; e = -1; f = (2*a*x0+b)*x0-(a*x0^2+b*x0+c); function drawFunc(a,b,c,x0,d,e,f) // values of x where the function is evaluated x = -10:0.1:10; // plot the function plot(x, a*x.^2+b*x+c); // plot in red ('r') a vertical line at x0 line([1,0],x0,'r',1); // plot in blue ('b') the tangent at x0 line([d,e],f,'b'); @}
Adding Interactivity
The plot of the previous section is static; until now, we have not seen anything which makes Sysquake different, except for a slightly more complicated set-up. We will now dip into interactivity by allowing the user to move the tangent point and observe the tangent.
Step 4: Writing a mouse drag handler
To enable the manipulation of a graphical element, a mouse drag handler must be declared under the same figure heading as the draw handler. The mouse drag handler is also a function defined in the function block. There is an important difference, however: it returns new values for one or several variables (not necessarily the same as the input).
Values related to the user interaction are obtained as special variables which begin with an underscore "_". We want to drag the vertical red line at x0; hence we need the current x coordinate of the mouse, and an indication about whether the user selected the line. The horizontal position of the mouse during the drag is given by _x1. A graphic ID is given by _id; it corresponds to the last argument of graphical commands like line or plot. If the user clicks far away from any object drawn by a command with an ID, _id is the empty matrix []. Since we want the user to drag the vertical line, we expect to have _id set to 1, the value passed to line in the draw handler.
Special variables can be passed to the handler as input arguments, or used directly in the handler without any declaration. This is what we shall do here to reduce the number of arguments to the minimum.
mousedrag (x0,d,e,f) = dragX0(a,b,c)
The mousedrag handler should calculate not only the new value of x0, but also all other variables which depend on it, i.e. the coefficients of the tangent. The update of the graphics is totally automatic, and you get a multilevel Undo/Redo for free!
function (x0,d,e,f) = dragX0(a,b,c) if isempty(_id) cancel; end x0 = _x1; (d,e,f) = calcTangent(a,b,c,x0);
In this definition (located in the function block), we note the check for an empty id. If we do not click the red line, the handler should not terminate normally; even if we kept the previous values, a new Undo frame would be created, and the first execution of Undo would have no visible effect.
If you type the code above, you have a figure where you can manipulate the vertical line with the mouse and see the tangent move.
Step 5: The final touch, a mouseover handler
Interactive manipulation is much easier if subtle hints about what can be manipulated are displayed. Such hints include the shape of the cursor, which should be a finger only if a click permits the manipulation of an element, and messages in the status bar at the bottom of the window. The mouseover handler, which is called in Manipulate mode when the mouse is over a figure, gives this kind of information to Sysquake. The input arguments are similar to the mousedrag handler. The output arguments are special: they should be _msg, _cursor, or both. _msg should be set to a string which is displayed in the status bar. _cursor should be set to true to have a finger cursor, and to false to have the plain arrow cursor. Canceling the mouseover handler is like setting _msg to the empty string '' and _cursor to false. Note also that if a figure has a mousedown, mousedrag, and/or mouseup handler, but no mouseover handler, the cursor will be set to the finger.
In our case, the user can manipulate the only object with a non-empty id. There is no need to define a function for such a simple task:
mouseover _cursor = ~isempty(id)
Adding messages is not much more complicated, but now we must define a function. To display the value of x0, we can use either the position of the vertical line or the value of x0. The special variable _x0 is the position of the line, not the position of the mouse as in the declaration of the mousedrag handler. The early cancellation of the execution of the handler is easier (and faster) to handle the case where the mouse in not over an object. The handler definition is
function (_msg, _cursor) = overFunc if isempty(_id) cancel; end _msg = sprintf('x0: %g', _x0); _cursor = true;
and its declaration is
mouseover (_msg, _cursor) = overFunc
There is still a problem: the message is not displayed when the user actually drags the vertical line, because the mouseover handler is not called when the mouse button is held down. For this, _msg must be added to the mousedrag handler. One way to do this is to declare the handler as
mousedrag (x0,d,e,f,_msg) = dragX0(a,b,c)
and to define it as
function (x0,d,e,f,msg) = dragX0(a,b,c) if isempty(_id) cancel; end x0 = _x1; (d,e,f) = calcTangent(a,b,c,x0); msg = sprintf('x0: %g', x0);
Menu Entries
It may be useful to set the value of some parameters with a menu entry. In our case, it would be difficult to specify in a figure the coefficients of the quadratic function. An SQ file can define menu handlers; new entries are installed in the Settings menu (which appears only if it is not empty), and the corresponding handler is executed when the entry is selected in the menu.
Let us add a menu entry which displays a dialog box where we can change the coefficients. First, we declare it with
menu "Quadratic Function..." (a,b,c,d,e,f) = menuFunc(a,b,c,x0)
The input arguments allow to display the current values and to calculate the new tangent for the current value of x0. Note how lines can be split between its components. Here is the handler definition:
function (a,b,c,d,e,f) = menuFunc(a,b,c,x0) (a,b,c) ... = dialog('Coefficients a,b,c of ax^2+bx+c:',a,b,c); (d,e,f) = calcTangent(a,b,c,x0);
The dialog function displays three kinds of alert or dialog boxes, depending on the number of input and output arguments. As we use it here, the first argument is a description, and the remaining input arguments are initial values which are displayed in an edit field. They can be modified by the user. When the OK button is clicked, the dialog box is dismissed and the output arguments receive the new values. If the Cancel button is clicked, the execution of the handler is aborted exactly as if the cancel command had been executed.
Each menu entry can be decorated in two ways: a checkmark can be displayed on the left, and the entry can be disabled (it cannot be selected and the text is written in gray). It does not make sense to use these possibilities with our first menu. Let us add support to choose whether the position of x0 is displayed with a vertical line or a small diamond. First, we add a variable whose value is true for a line and false for a diamond.
variable x0Line
We initialize it to true in the init handler, whose declaration becomes
init (a,b,c,x0,d,e,f,x0Line) = init
and definition
function (a,b,c,x0,d,e,f,x0Line) = init // initial values for the function coefficients // and the x0 value a = 1; b = 2; c = 4; x0 = 1; (d,e,f) = calcTangent(a,b,c,x0); subplots('Quadratic Function'); // x0 is displayed as a line x0Line = true;
The draw handler should get the new variable and act accordingly. Here is the new drawFunc handler declaration:
draw drawFunc(a,b,c,x0,d,e,f,x0Line)
and its definition:
function drawFunc(a,b,c,x0,d,e,f,x0Line) // values of x where the function is evaluated x = -10:0.1:10; // plot the function plot(x, a*x.^2+b*x+c); if x0Line // plot in red ('r') a vertical line at x0 line([1,0],x0,'r',1); else // plot in red ('r') a diamond ('<') at (x0,f(x0)) plot(x0,a*x0^2+b*x0+c,'r<',1); end // plot in blue ('b') the tangent at x0 line([d,e],f,'b');
The mousedrag handler needs no modification. Now the most interesting part. We add two menu entries, declared as
separator menu "Line" _checkmark(x0Line) x0Line = 1 menu "Diamond" _checkmark(~x0Line) x0Line = 0
The separator adds a horizontal line or a space between the first menu entry and these two new elements. Between the entry title and the handler declaration, the _checkmark keyword is used to tell Sysquake to display a check mark if the expression is parenthesis is true. This expression may be more complicated than a variable; for the second entry, we use the not operator, so that depending on the value of x0Line, either one or the other is checked. No handler definition is needed here, because we set x0Line to a constant. In handler declarations, only integers are permitted; fortunately, setting x0Line to 1 or 0 works fine.
Here is the complete SQ file:
variable a b c // coefficients of the quadratic function // y=ax^2+bx+c variable d e f // coefficients of the tangent dx+ey=f variable x0 // value of x where the line is tangent variable x0Line init (a,b,c,x0,d,e,f,x0Line) = init menu "Quadratic Function..." (a,b,c,d,e,f) = menuFunc(a,b,c,x0) separator menu "Line" _checkmark(x0Line) x0Line = 1 menu "Diamond" _checkmark(~x0Line) x0Line = 0 figure "Quadratic Function" draw drawFunc(a,b,c,x0,d,e,f,x0Line) mousedrag (x0,d,e,f,_msg) = dragX0(a,b,c) mouseover (_msg,_cursor) = overFunc function {@ function (a,b,c,x0,d,e,f,x0Line) = init // initial values for the function coefficients // and the x0 value a = 1; b = 2; c = 4; x0 = 1; (d,e,f) = calcTangent(a,b,c,x0); subplots('Quadratic Function'); // x0 is displayed as a line x0Line = true; function (d,e,f) = calcTangent(a,b,c,x0) // tangent to y=f(x) at x0 is y-f(x0)=f'(x0)(x-x0), // where f' is der f // derivative of ax^2+bx+c is 2ax+b d = 2*a*x0+b; e = -1; f = (2*a*x0+b)*x0-(a*x0^2+b*x0+c); function (a,b,c,d,e,f) = menuFunc(a,b,c,x0) (a,b,c) ... = dialog('Coefficients a,b,c of ax^2+bx+c:',a,b,c); (d,e,f) = calcTangent(a,b,c,x0); function drawFunc(a,b,c,x0,d,e,f,x0Line) // values of x where the function is evaluated x = -10:0.1:10; // plot the function plot(x, a*x.^2+b*x+c); if x0Line // plot in red ('r') a vertical line at x0 line([1,0],x0,'r',1); else // plot in red ('r') a diamond ('<') at (x0,f(x0)) plot(x0,a*x0^2+b*x0+c,'r<',1); end // plot in blue ('b') the tangent at x0 line([d,e],f,'b'); function (x0,d,e,f,msg) = dragX0(a,b,c) if isempty(_id) cancel; end x0 = _x1; (d,e,f) = calcTangent(a,b,c,x0); msg = sprintf('x0: %g', x0); function (_msg,_cursor) = overFunc if isempty(_id) cancel; end _msg = sprintf('x0: %g', _x0); _cursor = true; @}
More about graphic ID
Graphic ID have an important role: they permit to link drawing code in the draw handler with the code which handles user interactions in the mousedrag and mouseover handlers. Graphic are arbitrary positive integer numbers. Their value is not important, provided they are unique in each figure and they are used in a consistent way.
Graphic ID in declarations
In the SQ file of the tutorial, the ID is used only to detect if the mouse is located near the corresponding graphical object or not. In more complicated cases where multiple graphical objects with different ID are displayed in the same figure, mousedrag and mouseover handlers would typically have a switch statement to react in a different way for different objects. There is an alternative way to let Sysquake choose which part of code to execute, which often leads to simpler SQ files: specify the ID in the handler declaration, right after the mousedrag or mouseover declaration. In our SQ file, the figure declaration would become
figure "Quadratic Function" draw drawFunc(a,b,c,x0,d,e,f,x0Line) mousedrag 1 (x0,d,e,f,_msg) = dragX0(a,b,c,_x1) mouseover 1 _msg = overFunc(_x0)
and the definition of function dragX0
function (x0,d,e,f) = dragX0(a,b,c,x1) x0 = x1; (d,e,f) = calcTangent(a,b,c,x0);
If there were multiple graphical objects with different ID, the figure declaration would have multiple mousedrag handlers. It is also possible to keep a default mousedrag handler (without ID) for remaining objects and for mouse clicks elsewhere in the figure.
Mouseover handlers can also have a specific ID. But there is an additional benefit: the cursor is set automatically to the finger over objects with an ID for which a mousedrag is declared, and to a plain arrow elsewhere. This is why the declaration of the mouseover above does not produce a _cursor output argument anymore.
Constant naming
In programs, a good practice is to give names to all significant constants, especially if they are reused at different locations. LME provides the define programming construct to create named constants. In SQ files, define can also be used outside any function block, so that it has a scope in both declarations and LME code. The special value _auto is set successively to 1, 2, etc.; its main purpose is to produce unique values for constants used as graphic ID. For instance
define kLowId = _auto define kHighId = _auto
defines kLowId as 1 and kHighId as 2. Here is again the complete code of the tutorial SQ file.
variable a b c // coefficients of the quadratic function // y=ax^2+bx+c variable d e f // coefficients of the tangent dx+ey=f variable x0 // value of x where the line is tangent variable x0Line define kLineId = _auto init (a,b,c,x0,d,e,f,x0Line) = init menu "Quadratic Function..." (a,b,c,d,e,f) = menuFunc(a,b,c,x0) separator menu "Line" _checkmark(x0Line) x0Line = 1 menu "Diamond" _checkmark(~x0Line) x0Line = 0 figure "Quadratic Function" draw drawFunc(a,b,c,x0,d,e,f,x0Line) mousedrag kLineId (x0,d,e,f,_msg) = dragX0(a,b,c) mouseover kLineId _msg = overFunc(_x0) function {@ function (a,b,c,x0,d,e,f,x0Line) = init // initial values for the function coefficients // and the x0 value a = 1; b = 2; c = 4; x0 = 1; (d,e,f) = calcTangent(a,b,c,x0); // x0 is displayed as a line x0Line = true; function (d,e,f) = calcTangent(a,b,c,x0) // tangent to y=f(x) at x0 is y-f(x0)=f'(x0)(x-x0), // where f' is der f // derivative of ax^2+bx+c is 2ax+b d = 2*a*x0+b; e = -1; f = (2*a*x0+b)*x0-(a*x0^2+b*x0+c); function (a,b,c,d,e,f) = menuFunc(a,b,c,x0) (a,b,c) ... = dialog('Coefficients a,b,c of ax^2+bx+c:',a,b,c); (d,e,f) = calcTangent(a,b,c,x0); function drawFunc(a,b,c,x0,d,e,f,x0Line) // values of x where the function is evaluated x = -10:0.1:10; // plot the function plot(x, a*x.^2+b*x+c); if x0Line // plot in red ('r') a vertical line at x0 line([1,0],x0,'r',kLineId); else // plot in red ('r') a diamond ('<') at (x0,f(x0)) plot(x0,a*x0^2+b*x0+c,'r<',kLineId); end // plot in blue ('b') the tangent at x0 line([d,e],f,'b'); function (x0,d,e,f) = dragX0(a,b,c,x1) x0 = x1; (d,e,f) = calcTangent(a,b,c,x0); function msg = overFunc(x0) msg = sprintf('x0: %g', x0); @}
Saving Data
Once the user has changed the tangent point, he might find convenient to save it to a file and read it back later. In the SQ file, nothing more is required; the contents of all the variables as well as the information necessary to restore the subplots are written to an SQ data file with the Save command. Opening such a file reloads everything provided that the original file is found. If more control is desired on what is stored in the SQ data file and how it is read back, input and output handlers can be added.