Terrapin Resources

Advanced Programming

In this chapter, we will discuss the more advanced features of Terrapin Logo. As a “casual” user, you will probably not need to read this section. If you want to write an app that uses controls like buttons, or if you want to work with mouse or touch events, this chapter is for you.

Layouts

When you publish your app or save your workspace, Logo saves the current panel layout along with your workspace. You may also alter a panel’s LAYOUT property from within a Logo program to move or resize a panel.

The LAYOUT property is a four-element list. These are values for the left and top corner, the width and the size. The values are percentage values ranging from 0 to 100, and they refer to the size of your app window. A value of 50, for example, means that the offset or size is half of the window width or height.

See this example:

GPROP “LISTENER “LAYOUT Result: [0 60 100 40]

The Listener panel’s left border is at 0% (the left border of the window). The top is 60% of the total height. The width is 100%, making the panel occupy the entire width of the window, and the height is 40% making it stretch all the way down to the bottom of the window (60% + 40% = 100%).

Using percentage values as panel values makes panels adapt very easily to different screen layouts.

The STATE property controls the display mode of a panel. It is one of four values: NORMAL, MAXIMIZED, MINIMIZED, or HIDDEN.

If you want to have a list of all layouts, read the value of the the global name LAYOUT:

:LAYOUT Result: [GRAPHICS [[0 0 100 60] NORMAL] LISTENER [[0 60 100 40] NORMAL] TOOLBOX [[80 0 20 25] HIDDEN] FILES [[80 25 20 25] HIDDEN] HELP [[70 50 30 50] HIDDEN] EDITOR [[10 10 50 50] HIDDEN] DEBUGGER [[0 5 100 70] HIDDEN]]

You can store that value elsewhere and set LAYOUT to that value later:

MAKE “MY.LAYOUT :LAYOUT …do something with a different layout… MAKE “LAYOUT :MY.LAYOUT

Actually, some Logo commands use the LAYOUT property. Look at this code:

The Initial Layout

After Logo loads and initializes, the classroom version attempts to load an INIT.LGO file from the classroom server, which could change the layout. Also, Logo tries to autoload any layout that Logo has saved previously using the Autosave settings in the Settings dialog.

As soon as Logo has finished its initialization phase and just before the Listener is ready to accept user input, Logo saves the layout found so far into the global :INITIAL.LAYOUT variable. Users can return to Logo’s initial panel layout in one of the following ways:

  1. Enter the command MAKE “LAYOUT :INITIAL.LAYOUT
  2. Select the Window menu item “Initial Layout”
  3. By clicking the Initial Layout toolbar icon

The Default Layout

Logo’s default panel layout is available in the global :DEFAULT.LAYOUT variable. This variable is read-only, but you can always use that variable to reset the layout to the default.

  1. Enter the command MAKE "LAYOUT :DEFAULT.LAYOUT.
  2. Select the Window menu item “Select Layout…” and choose “Graphics, Listener, Editor and Help”.

The Icon Bar

There are two ways to show or hide the icon bar programmatically. The first is to read and alter the contents of the :LAYOUT variable. The second is to access the ICONBAR property list directly. This list has a property STATE that can be set to NORMAL or HIDDEN.

Anchoring

When you create a user interface with a few controls, the controls would usually move when the Graphics panel is resized. Often, this is not desirable, because you want the controls to stay in one place.

Every widget, including bitmaps, turtles, and controls, has an ANCHOR property. This property takes a list of two words (actually, you can also use a single word to set only a single anchor). Its purpose is to anchor the widget so it keeps a constant distance to an edge of the Graphics panel. We have these words:

  • LEFT: The widget keeps a constant distance to the left edge, meaning that it does not move horizontally.
  • RIGHT: The widget keeps a constant distance to the right edge; it moves horizontally when the panel is resized.
  • CENTER: This is the default; the widget is anchored to the center of the panel.
  • TOP: The widget keeps a constant distance to the top edge, meaning that it does not move vertically.
  • BOTTOM: The widget keeps a constant distance to the bottom edge; it moves vertically when the panel is resized.
  • MIDDLE: This is the default; the widget is anchored to the center of the panel.

This is especially convenient for controls that want to stay at the same position regardless of the size of the Graphics panel. If you anchor a control at [LEFT TOP], for example, it will stay at the same position and not move if the panel is resized. An anchor of [RIGHT BOTTOM] would cause the control to move with a constant distance to the right and bottom edges, so if it is in the panel’s lower right corner, it would stay there.

You would still have to position the controls initially, and figure out where to place them by looking at the size of the control, and the size of the Graphics panel.

This is how you get a control’s size:

And this is how you get the size of the Graphics panel:

So what if anchoring does not meet your needs? Well, you can also define an WHEN handler that is called whenever the size of the Graphics panel changes. Here is how do do this:

But please keep in mind that the list of commands should be short; the event handler may be called very often if you resize the Graphics panel!

Grids

A Logo grid is a special control that lets you arrange widgets in a grid. This makes a grid a very powerful feature, because it is not necessary to re-align controls because of changes to the display size when you store them in a grid. Note that a grid cell can only take a single widget.

A grid is a GRID widget, which you create either by dragging the grid symbol from the Toolbox’ “Controls” panel to the Graphics panel, or with the help of the NEW or DECLARE commands. Once you have created a grid, you can access its individual cells with special grid commands. Initially, each grid cell contains a STATICTEXT widget whose text you can set via its TEXT property.

By default, a GRID widget has a size of 3×3 cells. You can change the number of rows by setting the grid’s ROWS property, and you can set the value of its COLUMNS property to set the number of columns. You can also use the SETGRIDDIMS command to change the dimensions of the grid. Changing the grid’s dimensions erases the contents of the grid, and destroys any widgets that have been stored into the grid. A grid adjusts itself to the largest items of a row or column. You can use the FILLGRID command to quickly fill a grid with text content.

A grid cell has “coordinates” that range from 0 to the number of rows or columns minus one. Thus, the top left cell has the coordinates 0, 0, the next cell to the right has 1, 0, and so on. This has been modeled after Logo arrays, which have a similar way to access array elements. For a 3×3 grid, this is how you would address each element:

     
0 0 0 1 0 2
1 0 1 1 1 2
2 0 2 1 2 2

You would put “X” into the middle cell by GSETTEXT "GRID 1 1 "X which would result in…

     
     
  X  
     

Then someone would put “O” into the lower right cell by GSETTEXT "GRID 2 2 "O which would result in:

     
     
  X  
    O

…and so on. If an element has not been defined yet, it contains an empty word.

If you prefer to start grid indexing from 1 on, you can change the “PREFS property ARRAYBASE to the value 1 (or any other value that you prefer as the lowest index). This settings also affects arrays. So, after issuing tthis command:

You would address the grid as follows:

     
1 1 1 2 1 3
2 1 2 2 2 3
3 1 3 2 3 3

It is intentional that a grid is closely modeled after arrays. This makes it possible to quickly exchange data between an array and a grid. You can use the array to perform calculations, and then display the results by using a combination of the LISTARRAY and FILLGRID commands.

Initially, a grid has visible cell borders; you can change the color of the borders by setting the grid’s COLOR property to a different color, or you can make it invisible by using a color with an alpha value of 0, like, for example, [0 0 0 0]. The grid’s BORDER property controls the grid’s outer border in the same way.

To avoid the clobbering of object names with the names of all STATICTEXT widgets in a grid, all grid cells have a special name, which is the name of the grid, a ‘#’ character, the column number, another ‘#’ character and the row number. To set the “X” character in the center cell of our grid, simply use this command:

You can change the properties of the entire grid, or a row or column with the GPPROP command, which makes it easy to apply styles to, for example, a header row.

For this chapter, we will stay with an ARRAYBASE of 0, and a grid name of "GRID. Therefore, start over with the DRAW command, then select the “Controls” part of the Toolbox panel, and drag and drop a Grid control to the Graphics panel. This control should have the name “GRID”.

Grid Properties

A grid has a few extra properties of its own.

Name Inputs Description
COLUMNS 3 Returns or sets the number of grid columns. Note that if you set the value, the grid is recreated, causing the contents to get lost, and any widgets to be released.
CELLSIZE [-1 -1] This property sets the minimum size of each grid cell in pixels. The first list item is the width, and the second item is the height of each cell. If you use values that are ⇐ 0, the table aligns its cell width or height to match the widest item in a column, or the tallest item in a row. This is the default.
COLOR a color Defines the color of the lines that separate each grid cell.
ROWS 3 Returns or sets the number of grid rows. Note that if you set the value, the grid is recreated, causing the contents to get lost, and any widgets to be released.

Try, for example to apply a cell size of 50 by 50 pixels:

Now, go back to the original values:

Let us create a nice table with row and column headers, and numbers that are right aligned.

Let us try to create a table of numbers. Here is the data to fill in:

The “   ” creates an empty word. We want all numbers to be right-aligned:

Now, we will use the GPPROP command to style the table and the headers. This command, if used without brackets, applies a property to all cells of the entire table. If used with brackets, you can add in the column and row numbers. As with the CELLSIZE property, using a value less than the lowest possible index causes the cell to ignore it. If you, for example, want to set a column, use -1 for the row and vice versa.

We want the column headers to be blue and centered:

Finally, we want the row headers to be green, and also centered:

(GPPROP “GRID “ALIGN [CENTER] 0 -1)

Here we are! Your grid should now look like this:

Once the grid has been styled, you can change its data, and the styles will remain. Try some other values:

Can you see how powerful the FILLARRAY command can be?

You can also set or get the text of a single cell. These commands are essentially the same:

Whoa! What is this? GPROP "GRID\#1\#1 "TEXT? Remember the paragraph about special names above? This is such a special name. The name GRID#1#1 is essentially a shortcut for the grid cell of “GRID, column 1 and row 1. You can use this special name whereever you can enter an object name, as for example with the LIST or PPROPS commands.

Maybe it is time to look up the available grid commands? This page has the details.

Click My Cell

Wouldn’t it be great to know when the user has clicked or touched a value? No problem! Every cell contains a widget, and every widget contains a RUN property. If you set that property to a runlist, Logo executes that runlist every time the widget has been clicked or touched.

Grids and Widgets

A grid is perfect if you want to align all kinds of controls properly without having to calculate each control’s position. Logo lets you store any widget into a grid’s cell, including bitmaps, turtles, and, of course, controls. There is a caveat, however: when you erase the grid, or replace the grid’s cell with something else, the widget is erased.

The GSET command lets you store a widget into a grid. Try, for example, this:

And your turtle (its name is “0”, remember?) suddenly appears inside the grid! Unfortunately, the turtle cannot move; it can rotate or change its size. It can even STAMP or FILL, but it cannot draw lines. The same happens to all widgets that you store into a grid; they cannot move anymore.

Now, let us start over. Note that your turtle is gone once the gird is gone, so it is a good idea to start over:

Drag and drop a new grid. Now, drag and drop the checkboxes and a button as well. Then, issue these commands:

As you may have expected, we have a neatly arranged array of controls. For this special use case, we do not need the grid’s borders. To get rid of the border, simply assign a transparent black color to its BORDER property. Remember that a color value is a three- or four-element list, where the last element is the opacity value. A value of 0 means fully transparent.

Now, change the text of the controls:

And you have a great-looking dialog!

You may have noticed that the widget names stay even if they become part of the grid. Actually, you can use both the real and the special names to access the widget. To talk to the button, for example, you can use either "BUTTON or "GRID#2#2. This lets you connect your program logic to names that you have defined even if you decide to rearrange the controls at a later time. See the DECLARE command for more information about how to declare named widgets.

Macros

The new .MACRO and .DEFMACRO commands let you define macros. A macro is almost a Logo procedure, with one significant difference: A macro is supposed to output a list of commands that Logo runs after the macro ends.

Huh?

OK, this is complicated stuff. Let’s try a very simple example:

Here, MY.PRINT 5 would output the command [PRINT 5], which Logo runs afterwards to print 5.

But why do we need such stuff?

Let us assume that you define a new loop command that runs a runlist that you supply as input to your new loop command; something like this:

Now, use this command within some other procedure:

What would now happen if Logo executed the STOP command? You would probably want your procedure to exit. But in the real world, since MYLOOP is a procedure, MYLOOP would exit instead of SOME.PROCEDURE, which is definitely not what you have intended!

And this is where macros have their advantage. Since a macro outputs a list, which Logo then runs within the calling procedure, STOP would not exit MYLOOP, but SOME.PROCEDURE, as you have intended (please note that the simple example above outputs the list that you fed into MYLOOP, so the STOP command is part of that list).

Let us look at a real-world example. Here is your own version of the REPEAT command:

And here is the procedure that uses this command:

If you ran this procedure, you would always have 3 tries even if you guessed the secret word, because the STOP command would end MY.REPEAT, not GUESS!

It would work if you rewrote MY.REPEAT to be a macro:

This looks more complicated than it is. Thankfully, the MACROEXPAND command is here to display the list of commands that a macro outputs. Try MACROEXPAND with the MY.REPEAT command above :

After applying some color to the result to improve its readability:

[ IF READWORD = “SECRET [PRINT “CONGRATS! STOP] MY.REPEAT 2 [IF READWORD = “SECRET [PRINT “CONGRATS! STOP]] ]

You can see, the first part is the :INSTRUCTIONS runlist for MY.REPEAT followed by a new call of MY.REPEAT with :NUM set to one less along with the :INSTRUCTIONS runlist. And, this entire runlist is evaluated within the context of the GUESS procedure so that the STOP command would exit GUESS as intended.

Keep in mind that any macro output is run immediately in the context of whatever called the macro. What an example of dynamic code generation!

Background Programs

Logo programs can run in the background, meaning that the Listener is free to do other things while a program is executing. You can, for example, send a turtle across the screen, while running another Logo program at the same time.

Consider this procedure:

CREEP lets the turtle creep across the screen forever, until you click the Stop button.

Now if you want the turtle to creep without blocking the Listener, you use the LAUNCH command to launch the procedure:

Logo starts running the procedure, but the Listener prompt returns at once, and you can enter more commands while the turtle creeps across the screen.

Creepy, huh? (No pun intended.)

But how do you stop the procedure? Well, LAUNCH returned a number, which is the ID number of the background program. Feed this number to the HALT command to halt the background program.

You can, of course, also click the Stop button, or you could use the HALT command without inputs to stop all background programs:

Example:

> LAUNCH “CREEP Result: 84 > HALT 84 >

Be very careful with background programs! All Logo programs share the same environment. A background program should not change any global names so other Logo programs can continue running.

Changing global variables can have grave consequences. Imagine a Logo program writing to :STANDARD.OUTPUT to create a file. At the same time, a Logo background program creates another file and changes :STANDARD.OUTPUT to write to that file, and all of a sudden, all Logo programs write to the new file at the same time! Or just try to TELL a different set of turtles while the above CREEP procedure runs in the background. All of a sudden, a different set of turtles will start to creep.

By the way: See Logo’s WHEN Something Changes for details.

Debugging background programs

Having multiple programs running at the same time can be difficult to debug. WHenever you enter the debugger, it displays the currently active program, which may not be your main program.

All programs (the main program as well as every background program) run in their own Logo engine; every engine has its own ID, which is a positive integer. The main engine always has the ID #0. The debugger displays the currently active engine.

While debugging, you can use the global variable :LOGOENGINE to figure out which engine you are debugging (the debugger displays the engine number as well). You can switch between engines just by setting the variable to a different value:

If there is no engine 5, Logo throws an error. Use the LAUNCHED command to see a list of active engine numbers.

Finally, the BACKTRACE command displays the stack of active procedures for any engine, including the time when the engine was launched.