This page uses CSS, and renders properly in the latest versions of Microsoft Internet Explorer and Mozilla.
This is Frontier: The Definitive Guide by Matt Neuburg, unabridged and unaltered from the January 1998 printing.
It deals with the freeware Frontier 4.2.3; for commercial Frontier 8, look here, and for the inexpensive Radio UserLand, look here. See my Web site for information.
Those wishing an offline or printed copy may purchase the book, which is still in print.
Copyright 1998 O'Reilly & Associates, Inc. ALL RIGHTS RESERVED. Reprinted with permission.
TOP UP: Interface NEXT: Dialogs

24

Windows

This chapter discusses manipulation of Frontier's windows, both manually and through UserTalk. This means manipulation of the window as a physical object - its size and shape, for instance. Manipulation of a window's contents is discussed in Chapter 18, Non-Scalars.

The windows studied in this chapter include the Main Window, the Quick Script window, modeless dialog windows, and edit windows. Dialogs are more particularly discussed in Chapter 25, Dialogs. Windows belonging to other programs fall under Chapter 32, Driving Other Applications.

On navigation of the database by way of edit windows, see Chapter 2, Edit Windows.

Manual Window Manipulation

As in most applications that have windows, Frontier's windows are layered on the screen, and bringing a window to the front does not change the layering order of the other windows. Normally, clicking in a window that is not frontmost brings it to the front, and that's all; if you want to do something that requires clicking in the window (press a button, set the insertion point, etc.), you'll have to click again. By holding down the Command key, it is possible to drag a window by its titlebar without bringing it to the front.

A Frontier window can be hidden. This is not the same as closing the window; the window is open, but invisible. There is, unfortunately, no manual way to hide a window; it must be done by way of UserTalk, as explained later in this chapter.

The Main Window represents its database as a whole; closing the Main Window closes its database, offering to save it if it has been changed. (On opening multiple databases, see Chapter 30, Multiple Databases.) The Main Window has certain nonstandard physical features; see Chapter 3, The Database.

Window Menu

The Window menu lists all open windows, both visible and invisible, by title. A window can be brought to the front (and, if it was invisible, made visible) by choosing its title from the Window menu. The menu is divided into eight categorical sections, any of which will be omitted if no windows in a category are open: Main Windows (there can be more than one, if multiple databases are open); non-modal dialogs, including Quick Script; tables; menubars; scripts; outlines; wptexts; and pictures. Within each section, windows are listed in the order in which they were opened.

When it is opened, the title of a database object edit window is the full path of the object, omitting "root", except that the table edit window representing the top level of the database is titled "root". The Main Window's title is the filename of the database it represents (the default database is Frontier.root). Dialog titles are as displayed in their titlebars. Once a window is open, it is possible to change its title programmatically; this changes what appears in its titlebar, and its listing in the Window menu. A changed title is forgotten when the window is closed.

In the Window menu, a diamond-mark appears beside the listing of the current database's Main Window. A checkmark appears beside the listing of the frontmost window. An underlined window listing indicates that that window is "dirty": its contents have been changed somehow, though Frontier's criteria for dirtiness can sometimes be unexpected.1 Invisible windows are listed in italic.

Closing Windows and Close Hooks

A window can be closed by choosing Close from the File menu, or by hitting Command-w, or by clicking the close box at its upper left. Adding the Option key to any of these actions causes all windows to be closed, except for the Main Window - unless the Main Window is frontmost at the time, in which case it is closed (and so is the database).

Closing a database object's edit window manually2 triggers the script at system.misc.closeWindow before the window has a chance to close. This script calls, in turn, any scripts in the table at user.hooks.closeWindow , passing three parameters: the title of the window, a boolean indicating whether the window was modified within the last 15 seconds, and the address of a variable in which the called script may store true to prevent the closing of the window.

A script of this sort, initiated through some cause other than because the user explicitly ran it or it was called from another script, is called a hook. Other hooks are discussed in Chapter 27, Agents and Hooks.

How to take advantage of this particular hook is up to your imagination. There is already a script within user.hooks.closeWindow that calls html.addToChangedPages() ; this is part of Frontier's Web site management, and is used to maintain a list of Web pages that have been altered. A comment in system.misc.closeWindow() suggests how you could catch the closing of the Main Window and save the database programmatically, thus preventing the "Save changes?" dialog from ever appearing. Here's an (annoying) script that simply shows a confirmation dialog every time the user tries to close a window; it would be located at user.hooks.closeWindow.confirmClose.

Example 24-1 confirmClose( )
on confirmClose(title, changed, addrWhoa)
    if dialog.yesno("Really close " + title + "?")
        addrWhoa^ = false
    else
        addrWhoa^ = true

There is a bug in the hook mechanism; while system.misc.closeWindow() is running, the try...else mechanism is broken. It should therefore not be used in your hook script.3

Formatting Windows

The size and position of edit windows may be manually altered in the usual ways: drag the titlebar to set position, drag the resize box to set size, and so on. Such changes are remembered when the window is closed; it will open in the same state the next time.

A splendid feature of Frontier is its "intelligent zoom." Clicking a window's zoom box resizes and, if necessary, repositions the window so that the window is just large enough to show its whole contents, to the limits that the screen will allow. Zooming repeatedly alternates the window's new size and position with the old ones, but zooming after changing a window's size or position always performs the intelligent zoom.

Except for wptext windows, every window has just one text font and size. These can be set with the Font and Size submenus of the Edit menu, or with the Common Styles submenu of the Main menu. (The Common Styles submenu can be customized as desired; see Chapter 26, Menus and Suites.) This applies even to the Main Window and Quick Script window. Programmatic manipulation of a window's font and size are described in Chapter 18, Non-Scalars.

When you make a new database entry by choosing from a menu, a script generates the entry; if that entry is a non-scalar, the script can also set the initial formatting of the entry's edit window. Unfortunately, Frontier is not entirely consistent about taking advantage of this opportunity. For example, when you choose New Script from the Table menu, the menu item script uses the values at user.preferences.scriptFont and user.preferences.scriptFontSize to set the text font and size for the new script object's edit window (and also inserts the first line of the eponymous handler). But there are also user.preferences.outlineFont , user.preferences.outlineFontSize, user.prefer-ences.tableFont, and user.preferences.tableFontSize; yet these are not used when you choose New Outline or New Table from the Table menu.4

A script at samples.basicStuff.windowFormatter , called when you choose Format Windows from the Table menu, does some batch neatening of database object edit windows: it sets each window's text size and font, centers and sizes the window, and, if it's an outline, provides some "intelligent" expansion of subheads. It calls table.visit(), so it recurses infinitely deep in the original table. Running it on the whole database may cause an out-of-memory error, so it is best to run it on tables representing fairly restricted subsections of the database.5

Programmatic Window Manipulation

To be worked on programmatically, a window must be open (though it need not be visible). The only window verb that will have any effect upon a non-open window is window.open() . There is no penalty for calling a window verb on a window which is not open; the verb returns false without raising an error, and a script can test for this and respond as desired.

Most programmatic window manipulations require a reference to the window. The form of this reference, which we may call a window reference, is a little complicated to describe.

For the Main Window or a dialog window (including Quick Script), a window reference is simple enough: it is a string consisting of the window's title. However, for edit windows, Frontier will also accept the address of the object whose edit window this is - or, if you supply a string, Frontier will implicitly coerce it to an address. The complication is that since either a title or an address can be expressed as a string, Frontier doesn't necessarily know, to start with, which of the two you intend.

So when you give a window reference, Frontier begins by assuming that it is the address of a non-scalar object. If dereferencing the window reference resolves to a non-scalar, the resolution process is over. If either the resolution or the window operation fails, though, then if the window reference is a string, Frontier tries to understand it as a window title.

All of this is made trickier by the fact that object references are not case-sensitive, but string matching is. (See Chapter 45, Punctuation.) For example, on my computer, the Main Window must be referred to as "Frontier.root"; calling it "frontier.root" won't work. But the workspace table's edit window can be referred to as "Workspace" or "workspace" (or @workspace or @Workspace). And then, to top it all off, a window's title can be changed programmatically, so a database object's edit window might have a title different from its object reference.

Suppose, for instance, the examples table is open, and that its title has been changed to "yohoho". Then all of the following yield true:

window.isOpen("examples")
window.isOpen("Examples")
window.isOpen("root.Examples")
window.isOpen(@examples)
window.isOpen("yohoho")
workspace.x = "Examples"; window.isOpen(workspace.x)
workspace.y = "yohoho"; window.isOpen(workspace.y)
workspace.z = @examples; window.isOpen(workspace.z)

But the following yields false:

window.isOpen("yoHoho")

because the capitalization is wrong (so there is no match on the window's title).

Now suppose we create a script in the people.[user.initials] table called YOHOHO and open it. Now if we say:

window.bringToFront("yohoho")

then, despite the capitalization, it is people.[user.initials].YOHOHO whose window comes to the front, because the resolution of "yohoho" as an object reference succeeds. Then if we close people.[user.initials].YOHOHO and say this:

window.isOpen("yohoho")

the result is true, because "yohoho" resolves to a database entry, that entry's edit window is not open, the operation fails, and Frontier tries again with "yohoho" interpreted as a title, which works - the examples window is open, and it has this title with this capitalization.

Opening and Closing; Showing and Hiding

To open, and make visible and frontmost, a non-scalar object's edit window:

window.open (addrObject)

It is also possible to open an edit window using edit(), but this additionally makes the window the target, whereas window.open() does not. See Chapter 18.

To close a window:

window.close (windowReference)
filemenu.close ()
filemenu.closeAll ()

To make an open window invisible:

window.hide (windowReference)

UserTalk is the only way to hide a window, so it is useful to make a menu item whose script includes:

window.hide(window.frontmost())

To open a non-scalar object's edit window invisibly (if it is not already open):

target.set (addrObject)

Also makes the window the target; see Chapter 18.

Observe that there is no verb which opens a window invisibly without also making the window the target, as window.open() opens a window visibly without making it the target. It is aesthetically pleasing to be able to open a window invisibly, set its size, location, title as desired, and then show it, so that these changes do not happen before the eyes of the user. We illustrate this in Example 24-3.

To make an open window visible:

window.show (windowReference)

Has no effect upon the window's position in the layering order.

To learn whether a window is open:

window.isOpen (windowReference)

To learn whether a window is open and visible:

window.isVisible (windowReference)
window.isHidden (windowReference)

A window that is not open counts as hidden. This yields some counterintuitive results: for example, window.isHidden(@glubglub) returns true even though there is no such window and no such object! There really is no need for window.isHidden() anyway, as its results are identical to saying !window.isVisible().

Relocating and Resizing

To learn or set an open window's position:

window.getPosition (windowReference, addrX, addrY )
window.setPosition (windowReference, X, Y )

Global coordinates on Mac OS are measured in pixels from the upper left-hand corner of the main screen, with positive X to the right and positive Y downwards. A window's position is measured at the upper left-hand corner of its content region (not its titlebar!). The menubar is usually 20 pixels thick, and a titlebar is usually 18 pixels thick, so 38 is a good minimum Y value.6 The Main Window has no titlebar, so its position is measured at its upper left corner.

When you use window.setPosition(), Frontier will curtail X and Y to prevent any of the window from ending up offscreen. To learn whether this has occurred, use window.getPosition(). See, for instance, Example 12-4. It's good that a script cannot put a window off the screen (where a user would be unable to access it), but it is odd that you can move a window manually to positions where you cannot move it programmatically.

To learn or set an open window's size:

window.getSize (windowReference, addrWidth, addrHeight)
window.setSize (windowReference, width, height)

You cannot use window.setSize() on the Main Window even though you can resize the Main Window manually.

A window's size is the width and height of its content region. When you use window.setSize(), Frontier will curtail width and height to prevent either from becoming smaller than a certain reasonable minimum, or to prevent the whole window (not just its content area) from becoming larger in either dimension than the size of the screen. To learn whether this has occurred, use window. getSize().

The behavior of window.setSize() suggests the following trick for learning the dimensions of the screen.

Example 24-2 screenSize( )
on screenSize()
    local (x, y)
    target.set(@readme) « or any rarely used window
    window.setSize(@readme, 5000, 5000)
    window.getSize(@readme, @x, @y)
    target.clear()
    return {x, y + 38}

To zoom a window:

window.zoom (windowReference)

Equivalent to clicking the zoom box.

Here, we open a window invisibly, set its text font, its text size, its size, and its position, and then show it.

Example 24-3 "Pretty" open
target.set(@workspace)
editmenu.setFont("Geneva"); editmenu.setFontSize("10")
window.setSize(@workspace, 10, 10) « force intelligent zoom
window.zoom(@workspace)
window.setPosition(@workspace, 10, 45)
window.show(@workspace)
target.clear()

Layering Order

To obtain a reference to the frontmost visible window:

window.frontmost ()

If a script is initiated by pressing its edit window's Run or Debug button, then window.frontmost() will not return that script's edit window; it will return the first visible window behind it. Similarly, in Debug mode, windows that come to the front because the execution path runs through them are ignored. This is so that the script can pretend not to be open, for testing purposes.

To obtain a reference to the window behind a given window:

window.next (windowReference)

The string returned by window.frontmost() or window.next() is suitable for immediate use as a window reference.

Together, window.frontmost() and window.next() provide a way to cycle through all windows. Here, for instance, is a script that constructs a list of all open windows.7 It takes elegant advantage of the fact that window.next() returns the empty string when its parameter is a reference to the rearmost window.

Example 24-4 listWindows( )
on listWindows()
    local (theList = {})
    loop (local (w = window.frontmost()); w; w = window.next(w))
        theList[0] = w
    return theList

To move a window to the front or back of the layering order:

window.bringToFront (windowReference)
window.sendToBack (windowReference)

If a window is invisible, window.bringToFront(), but not window.sendToBack(), also makes it visible. This makes sense given that an invisible window cannot be window.frontmost().

To assist me in navigating Frontier's open windows, I like to have on hand three menu items that do the following: send the frontmost window to the back, bring the second window to the front, and bring the rearmost window to the front. Given Example 24-4, the last of these is easy to write.

Example 24-5 cycleWindowsBackwards( )
on cycleWindowsBackwards ()
    local (theList = listWindows())
    window.bringToFront(theList[sizeOf(theList)])

To learn whether a given window is frontmost:

window.isFront (windowReference)

Miscellaneous

To learn or set a window's title:

window.getTitle (windowReference)
window.setTitle (windowReference, newTitle)

To scroll the target window:

window.scroll (direction, count)

On the target, see Chapter 18. window.scroll() equates to pressing the window's scroll arrow the specified number of times, except that it's intuitively backwards: window.scroll(up,1) presses the window's down-pointing scroll arrow (because the window's content moves up). There is no penalty for supplying too large a value for count ; so, for example, to scroll a window to its top, say window.scroll(down,infinity).

To show and hide elements of the Main Window:

mainWindow.hideButtons ()
mainWindow.showButtons ()
mainWindow.hideFlag ()
mainWindow.showFlag ()
mainWindow.hidePopup ()
mainWindow.showPopup ()

The "buttons" commands do the same thing as clicking on the "flag" icon in the Main Window, but the other commands do things to the Main Window that the user cannot countermand, except programmatically; so be careful.

To force a window to update:

window.update (windowReference)

I'm not sure when you'd need window.update(); I've never seen it used except in suites.samples.basicStuff.windowFormatter() , which seems to work just as well without it.

An important problem is how to learn, in a script, what kind of window a given window is. The first question to ask is whether it is an edit window, and the way to find out is by obtaining from Frontier (with window.frontmost() or window.next()) a reference to the window and then testing:

defined(windowReference^)

The reason this works is that if the window is an edit window, the reference to it returned by Frontier is a string that can be dereferenced to the name of a defined object. At this point you can also find out what type of edit window it is by asking what type that object is:

typeOf(windowReference^)

Menu item scripts are a special case, because they are certainly script edit windows, and yet they do not represent any defined object (see Chapter 26). So if a window fails the edit window test, you still need to test whether it is a menu item script window. A special verb exists for this purpose.

To learn whether a given window is a menu item script:

window.oisMenuScript (windowReference)

If the window fails this test, too, it is the Main Window, the Quick Script window, or a non-modal dialog. Since the reference to such a window is its title, you should now be able to identify it.


1. For instance: scrolling a window can be sufficient to dirty it, but expanding subheads may not be. See Chapter 18.

2. Not programmatically, not a dialog window, and not a menu item script window.

3. In fact, the try...else mechanism is used in html.addToChangedPages(), and this can cause problems. user.hooks.closewindow.addToChangedPages() should be rewritten to read like this:

on addToChangedPages (adrpage, flchanged, adrstopclose)
    if defined(adrpage^) « add this line
        html.addToChangedPages (adrpage)

This change prevents html.addToChangedPages() from choking silently if typeOf() doesn't work on the window (because it is a menu item script window, for example).

4. A solution is to modify these menu item scripts; this is not recommended, as they "belong" more to UserLand than to the user, but I must admit that in my copy of the database I've done it anyway.

5. This script pays no attention to the user.preferences settings. In my copy of the database, I have modified the script so that it does.

6. Under Mac OS 8, this will need to be larger - perhaps about 42.

7. But if an invisible window is frontmost, it will not be included in the list.


TOP UP: Interface NEXT: Dialogs

This is Frontier: The Definitive Guide by Matt Neuburg, unabridged and unaltered from the January 1998 printing.
It deals with the freeware Frontier 4.2.3; for commercial Frontier 8, look here, and for the inexpensive Radio UserLand, look here. See my Web site for information.
Those wishing an offline or printed copy may purchase the book, which is still in print.
Copyright 1998 O'Reilly & Associates, Inc. ALL RIGHTS RESERVED. Reprinted with permission.