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 supposes 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.
In this section, we will write what is necessary to display in the same graphic 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 at any rate two-bytes characters (like Unicode and Japanese kanjis). 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.
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 matrix, a string, or a list. 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.
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.
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 the same name. 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 {@ @}; now it contains only the init and calcTangent functions, but we will add more functions in the next sections. The block of functions 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 (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 Edit/Preferences submenu. 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.
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, 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 graphic. Congratulations!
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 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.
To enable the manipulation of a graphic 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 are two important differences, however: first, it returns new values for one or several variables (not necessarily the same as the input); second, it accepts as input special variables which describe the drag action. 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. All special variables begin with an underscore "_". The current horizontal position is given by _x1. An object identifier 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 [].
mousedrag (x0,d,e,f) = dragX0(a,b,c,_id,_x1)
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,id,x1) if isempty(id) cancel; end x0 = x1; (d,e,f) = calcTangent(a,b,c,x0);
In this definition, 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 graphic where you can manipulate the vertical line with the mouse and see the tangent move.
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:
mouseover _cursor = overFunc(_id)
Since _id is an empty matrix when the cursor in not over an object with a valid id, the handler definition is simply
function cursor = overFunc(id) cursor = ~isempty(id);
Adding messages is not much more complicated. To display the value of x0, we can add as input argument either the position of the vertical line or the value of x0. Let us choose the position of the line:
mouseover (_msg,_cursor) = overFunc(_id,_x0)
The special argument _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(id, x0) if isempty(id) cancel; end msg = sprintf('x0: %g', x0); cursor = true;
There is still a problem: the message is not displayed when the user actually drags one of the frequencies, 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,_id,_x1)
and to define it as
function (x0,d,e,f,msg) = dragX0(a,b,c,id,x1) if isempty(id) cancel; end x0 = x1; (d,e,f) = calcTangent(a,b,c,x0); msg = sprintf('x0: %g', x1);
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 graphic 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. 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); % 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*x+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
menu "Line" _checkmark(x0Line) x0Line = 1 menu "Diamond" _checkmark(~x0Line) x0Line = 0
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.
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 content 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.