User Tools

Site Tools


weblogo:manual:advanced

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:

TO FULLSCREEN
    PPROP "LISTENER "STATE "NORMAL
    PPROP "GRAPHICS "STATE "MAXIMIZED
END

TO SPLITSCREEN
    PPROP "LISTENER "STATE "NORMAL
    PPROP "GRAPHICS "STATE "NORMAL
END

TO FULLSCREEN
    PPROP "LISTENER "STATE "MAXIMIZED
    PPROP "GRAPHICS "STATE "NORMAL
END

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 lauout. Also, Logo tries to auto-load 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 “Original Logo Layout”

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 only set 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, who like 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 would be 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:

GPROP "MYCONTROL "SIZE

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

GPROP "GRAPHICS "SIZE

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

PPROP "EVENTS "GRAPHICS.RESIZED [my list of commands]

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 be 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, who 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:

PPROP "PREFS "ARRAYBASE 1

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:

PPROP "GRID#1#1 "TEXT "HELLO

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 it cell width or height to match the widest item in a column, or the tallest item in a row. This is the default.
GRID TRUE Shows or hides the internal grid (the dotted lines around each grid box. You may want to hide these lines after you have finished your grid.
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:

PPROP "GRID "CELLSIZE [50 50]

Now, go back to the original values:

PPROP "GRID "CELLSIZE [-1 -1]

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:

FILLGRID "GRID [ [|| ONE TWO] [FIRST 0.1 -234.56] [LAST 123 500]]

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.

GPPROP "GRID "ALIGN [RIGHT]

We want the column headers to be blue and centered:

(GPPROP "GRID "ALIGN [CENTER] -1 0)
(GPPROP "GRID "COLOR "BLUE -1 0)

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

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

(GPPROP "GRID "COLOR "GREEN 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:

FILLGRID "GRID [ [|| ONE TWO] [FIRST 100 200] [LAST 300 400]]

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:

GGETTEXT "GRID 1 1
GPROP GGET "GRID 1 1 "TEXT
GPROP "GRID#1#1 "TEXT

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

Would it not 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.

There is more: before Logo runs your runlist, it stores the clicked widget's name as a single-element list into the global variable :EVENTDATA, where it is available to you (see the Events chapter below for more information about events and runlists).

Let us define a procedure that increments the TEXT property of a widget by 1 if it is numeric:

TO INCREMENT :name
    LMAKE "VALUE GPROP :NAME "TEXT
    IF NOT NUMBER? :VALUE STOP
    PPROP :NAME "TEXT :VALUE + 1
END

Then, set this runlist for your table:

GPPROP "GRID "RUN [INCREMENT FIRST :EVENTDATA]

Now, every time you click a number in your grid, it is incremented!

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:

GSET "GRID 1 1 0

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:

ERASE "GRID
DRAW

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

GSET "GRID 0 0 "CHECKBOX
GSET "GRID 1 0 "CHECKBOX.1
GSET "GRID 2 0 "CHECKBOX.2
GSET "GRID 2 2 "BUTTON

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.

PPROP "GRID "BORDER [0 0 0 0]

Now, change the text of the controls:

PPROP "CHECKBOX "TEXT "|Try me|
PPROP "CHECKBOX.1 "TEXT "|You can also try me|
PPROP "CHECKBOX.2 "TEXT "|Or you can add this, too|
PPROP "BUTTON "TEXT "|Done|

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 really simple example:

.MACRO MY.PRINT :WHAT
    OUTPUT LIST "PRINT :WHAT
END

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 which you supply as input to your new loop command; something like this:

TO MYLOOP :runlist
    ; omitting the control code...
    RUN :runlist
END

Now, use this command within some other procedure:

TO SOME.PROCEDURE :N
    ...
    MYLOOP [IF :N = 0 STOP]
    ...
END

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:

TO MY.REPEAT :num :instructions
    IF :num = 0 [STOP]
    RUN :instructions
    MY.REPEAT :num - 1 :instructions
END

And here is the procedure that uses this command:

TO GUESS
    PRINT [GUESS THE SECRET WORD. YOU HAVE 3 TRIES]
    MY.REPEAT 3 [IF READWORD = "SECRET [PRINT "CONGRATS! STOP]]
    PR "SORRY!
END

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:

.MACRO MY.REPEAT :NUM :INSTRUCTIONS
    IF :NUM <= 0 OUTPUT []
    OUTPUT SENTENCE :INSTRUCTIONS(LIST "MY.REPEAT :NUM - 1 :INSTRUCTIONS)
END

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 :

MACROEXPAND [MY.REPEAT 3 [IF READWORD = "SECRET [PRINT "CONGRATS! STOP]]]
Result: [IF READWORD = "SECRET [PRINT "CONGRATS! STOP] MY.REPEAT 2 [IF READWORD = "SECRET [PRINT "CONGRATS! STOP]]]

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:

LAUNCH "CREEP

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:

(HALT)

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: Logo events are background programs as well. Please read the chapter about Logo events for details.

weblogo/manual/advanced.txt · Last modified: 2019/03/06 07:01 by Michael Daumling