Introduction to Using the Window System



Preface

The Lisp Machine window system manual is intended to explain how you, as a programmer, can use the set of facilities in the Lisp Machine known collectively as the window system. Specifically, this document explains how to create windows, and what operations can be performed on them. It also explains how you can customize the windows you produce, by mixing together existing flavors to produce a window with the combination of functionality that your program requires and adding daemons to various operations.

It is assumed that you have a working familiarity with Zetalisp as documented in the Lisp Machine manual. It is also assumed that you have some experience with the user interface of the Lisp Machine, including the ways of manipulating windows, such as the Edit Screen, Split Screen, and Create commands from the system menu. To use the predefined flavors and methods, you need not be familiar with how methods are defined and combined, but you should understand what message passing is, how it is used on the Lisp Machine, what a flavor is, what a "mixin" flavor is, and how to define a new flavor by mixing existing flavors. To use the information provided here on where to add daemons, you must be thoroughly familiar with programming with flavors, and must be willing to refer to the window system source code as the final authority for all questions.

Any comments, suggestions, or criticisms will be welcomed. Please send Arpa network mail to BUG-LMMAN@MIT-MC.

Those not on the Arpanet may send U.S. snail to

Richard M. Stallman
545 Technology Square, Room 914
Cambridge, Mass. 02139

Note from Richard Stallman

The Lisp Machine is a product of the efforts of many people too numerous to list here and of the former unique cooperative environment of the M.I.T. Artificial Intelligence Laboratory. I believe that the commercialization of computer software hinders the further development of systems such as described herein. I consider proprietary software morally objectionable and plan to dedicate my career to promoting the sharing and free exchange of software.

Starting in December 1983 I plan to work on the development of GNU, a complete Unix-compatible software system for standard hardware architectures, to be shared freely with everyone just like EMACS. This will enable people to use computers without agreeing to the idea of proprietary software. This project has inspired a growing movement of enthusiastic supporters. If you would like to join it, write to me at the address on the previous page. Help get programmers sharing again! Contributions of part-time programming help will be very welcome, as will funding from philanthropists to support full-time workers, and donations or loans of computers.

The current implementation of the window system is based on flavors, and was designed and implemented primarily by Howard Cannon and Mike McMahon during 1980. It replaced an earlier version implemented by me, which was based on Smalltalk-like classes. The newer version is generally an improvement, but as Howard Cannon steadfastly refused to discuss the design with me I must decline responsibility for such counterintuitive aspects as the definition of exposure.

About a third of this manual is based on earlier documents written by Dave Moon and Daniel Weinreb. Sarah Smith of LMI helped to correct the manual, and Chris Schneider and Steve Strassman provided useful suggestions.

Introduction to Using the Window System

Table of Contents

1 Concepts

The term window system refers to a large body of software used to manage communications between programs in the Lisp Machine and the user, via the Lisp Machine console. The console consists of a keyboard, a mouse, and one or more screens. All Lisp Machines have at least one high-resolution black-and-white screen, and some machines also have a color screen. The window system can handle any number of screens of various kinds.

The window system controls the keyboard, encoding the shifting keys, interpreting special commands such as the Terminal and System keys, and directing input to the right place. The window system also controls the mouse, tracking it on the screen, interpreting clicks on the buttons, and routing its effects to the right places. The most important part of the window system is its control of the screens, which it subdivides into windows so that many programs can coexist and even run simultaneously without getting in each other’s way, sharing the screen space according to a set of established rules.

1.1 Windows

When you use the Lisp Machine, you can run many programs at once. You can have a Lisp listener, an editor, a mail reader, and a network connection program, or several of each, all running at the same time, and you can switch from one to the other conveniently. Interactive programs get input from the keyboard and the mouse, and send output to a screen. Since there is only one keyboard, it can only talk to one program at a time. However, each screen can be divided into regions, and one program can use one region while another uses another region. Furthermore, this division into regions can control which program the mouse talks to; if the mouse cursor position is in a region associated with a certain program, then mouse clicks are directed to that program, which is then allowed to decide what the clicks mean. Allocating access to screen space and input devices is the most important function of the window system.

The regions into which the screen is divided are known as windows. In your use of the Lisp Machine, you have encountered windows many times. Sometimes there is only one window visible on the screen; for example, when you cold-boot a Lisp Machine, it initially has only one window showing, and it is the size of the entire screen. If you start using the system menu’s Create, Edit Screen, or Split Screen options. you can make windows in various places of various sizes and flavors. Usually windows have a border around them (a thin black rectangle around the edges of the window), and they also frequently have a label in the lower-left hand corner or on top. This is to help the user see where all the windows are, what parts of the screen they are taking up, and what kind of windows they are.

The next several sections begin to explain the detailed concepts of how windows work and what their internal state is. You should probably read over these quickly the first time, without worrying about all the details. You really don’t have to understand all of the complexity to make simple use of the window system; it just helps if you know what sort of thing is going on.

1.2 Capabilities of Windows

A window may or may not be exposed, which means that output can be done on it ((exposure)). At any time at most one window can be selected, which means that input can be done through it ((selection)). These two conditions constitute the window’s status.

Another kind of state information that every window has is its edges: its size and its position (see (sizes-and-positions)). You can specify these numerically, ask for the user to tell you (using the mouse), ask for a window to be near some point or some other window, and so on.

Windows can function as streams by accepting all the operations that streams accept. If you do input operations on windows, they read from the keyboard; if you do output operations on windows, they type out characters on the screen. The value of terminal-io (see (terminal-io-var)) is normally a window, and so input/output functions on the Lisp Machine do their I/O to windows by default.

A window whose flavor incorporates tv:stream-mixin supports all the standard input stream operations and may be passed as the input stream to functions such as read and readline (see (input)). Each such window has an input buffer holding characters that have been typed at the window but not read yet. You can force keyboard input into a window’s input buffer; frequently two processes communicate by one process’s forcing keyboard input into an input buffer from which another process is reading characters (see (tv:stream-mixin-force-kbd-input-method)).

Any window handles the standard output stream operations and can be passed as the output stream to functions such as print and format (see (output)). You can output characters at a cursor position, move the cursor around, selectively clear parts of the window, insert and delete lines and characters, and so on, by means of standard and not-so-standard stream operations. Output of text on windows provides additional features; for example, characters can be drawn in any of a large set of fonts (type faces), and you can switch from one to another within a single window (see (font-section)). Windows can define their own actions for exceptional conditions that affect output, such as reaching the right or bottom edge of the window, or printing more that a window-full without pausing (see (output-exceptions)).

In addition to characters from fonts, you can also display graphics (pictures) on windows (see (graphics-section)). There are operations to draw lines, circles, triangles, rectangles, arbitrary polygons, circle sectors, and cubic splines.

Each window can have any number of blinkers (see (blinkers)). Most windows have one blinker that follows the window’s cursor position; this blinker normally appears as a blinking rectangle. But blinkers need not follow the cursor and need not actually blink (some do and some don’t). For example, the editor shows you what character the mouse is pointing at; this blinker looks like a hollow rectangle. The arrow that follows the mouse is a blinker, too. Blinkers are used to add visible ornaments to a window, or temporary modifications to a window’s normal display. Blinkers are flavor instances with their own standard operations.

Windows are the standard interface to the mouse (see (mouse)). Both mouse motion and mouse clicks are normally handled by messages sent to the window over which the mouse is positioned.

A window’s area of the screen is divided into two parts. Around the edges of the window are the four margins; while the margins can have zero size, usually there is a margin on each edge of the window, holding a border and sometimes other things, such as a label. The rest of the window is called the inside; regular character output and graphics drawing all occur on the inside part of the window. The margins and inside of the window are managed separately so that mixins to add things to the margins can be independent of the program that draws in the window’s inside. See (margins).

For greater flexibility in subdividing a window into multiple areas of different uses, you can create inferior windows or panes within the window. The main window is then called a frame. Each pane can be of a different flavor suitable to its own purpose; thus Peek uses a frame which has a menu and a scrolling window as its two panes. See (window-hierarchy), for information on the hierarchy of windows, and (frames), for a description of frames.

The asynchronously intercepted characters (such as Control-Abort) which take effect instantaneously when typed are handled by the selected window. Each window can specify its own. See (asynchronous-intercepted-characters).

A window can have an associated process. For example, when you type Control-Abort, the process aborted is the one associated with the selected window. Exactly how processes and windows relate depends on the flavor of the window, and, as usual, there are several operations to manipulate the connections. See (processes).

Notifications are a facility for displaying messages from events taking place asynchronously and not related to the program you are running (errors in background processes, qsends from other users, file servers planning to go down, etc.). Notifications work through operations on the selected window, so each window can decide how to display a notification. See (notifications).

Screens are represented by flavor objects also; these are not windows, but share some of the operations and instance variables of windows (see (screens)). Windows and screens collectively are called sheets. Each screen object usually corresponds to a particular piece of display hardware. Screens can be either black-and-white or color. Color screens have more than one bit for each pixel, and most operations on windows do something reasonable on color screens. But the extra bits give you extra flexibility, and so there are some more powerful things you can do to manipulate colors. Color screens also have a color map which specifies which values of the pixels display which colors. See (color).

The who line at the bottom of the screen shows the user something about the state of the Lisp Machine. The window system software implements the who line as a separate screen even though it appears on the same TV monitor as the main screen and its windows. This is why you cannot move the mouse into the who line area, or make windows on the main screen hide the who line. See (who-line) for more information on the who line and how it is interfaced and implemented.

1.3 Higher Level Window Facilities

The higher level window facilities are window flavors that combine the basic capabilities of windows appropriately to provide directly usable techniques for particular common applications. These facilities include menus and other choice windows, typeout windows, and scrolling windows.

Menus allow the user to choose one or several of a fixed set of items. The system menu that you get from double-click-right is an example of one. See (menu). Multiple choice windows allow the user to specify an answer to each of a set of similar multiple-choice questions (see (multiple-choice)). The editor command Meta-X Kill or Save Buffers shows an example of one.

Choose-variable-values windows allow the user to view and modify the values of a set of variables, each variable printed and read according to its own range of possible values. One variable might allow only numbers, while another variable’s value might be restricted to a list of pathnames. See (choose-variable-values).

Typeout windows allow windows such as scroll windows and editor windows, which normally present displays reflecting permanent data bases, to print output in response to individual commands. The typeout window is an inferior of the other window, and exposes itself when output is done on it.

Scrolling windows allow the programmer to define a display which the user can then scroll through. The scrolling window facility provides for scrolling, redisplay, and interaction with the mouse, requiring the programmer only to specify the entire contents to be scrolled through. There are two types of scrolling windows, text scroll windows ((text-scroll-windows)) and general scroll windows ((scroll-windows)), the former being less powerful but simpler. Note that there is a standard interface protocol for the mouse to request scrolling (see (scroll-protocol)). You need not use one of the standard scrolling window facilities to make a window that can scroll if you are willing to implement the scrolling yourself. For example, editor windows and menus can also scroll.

1.4 Windows as Flavor Instances

In the Lisp world, each window is a flavor instance, an instance of some flavor of window. There are many different window flavors available; some of them are described in this manual. All of them contain the component tv:minimum-window.

.defflavor tv:minimum-window The flavor on which all window flavors are built. Any window flavors you define should include this component. This flavor itself is made of the components tv:essential-window, tv:essential-activate, tv:essential-expose, tv:essential-set-edges and tv:essential-mouse. tv:minimum-window has no methods of its own; all are inherited from those components. So you will at times (in the debugger) run across methods of those component flavors. You will also run across methods of tv:sheet, a component of tv:essential-window. However, there is no need for you as a programmer to pay attention to the distinctions among these flavors, and in this manual all the operations, instance variables and init options of tv:minimum-window are documented as being "of windows" rather than of any specific flavor. .end_defflavor

.defflavor tv:window This flavor of window has several mixins that provide much generally useful functionality, including the ability to select the window, graphics operations, labels and borders.

(defflavor tv:window () 
   (tv:stream-mixin tv:borders-mixin tv:label-mixin 
    tv:select-mixin tv:delay-notification-mixin 
    tv:graphics-mixin tv:minimum-window))

The operations of these mixins are specifically identified in this manual. Use the mixins, or use the flavor tv:window, if you want the operations to be available. .end_defflavor

It is often necessary to mix flavors to get the desired window behavior. When doing this, you must pay attention to the correct ordering of flavor components. The earlier components will override later ones. For example, if you want to make a window that will print out notifications on itself by mixing in tv:notification-mixin, you must put it in front of tv:window:

(defflavor my-window () (tv:notification-mixin tv:window))

If you put them in the other order, as in

(defflavor my-window () (tv:window tv:notification-mixin))

you get something equivalent to tv:window. The tv:notification-mixin’s effect is completely lost. The whole point of tv:notification-mixin is that it should override some methods of tv:window (inherited from tv:delay-notification-mixin), and in fact it defines the same operations in a different way. It follows that if tv:notification-mixin comes last, it will be overridden instead.

It is almost always correct to put mixins first in the ordering so that they will override whatever they are added to. One exception occurs with flavors of margin item; there, the ordering is used to control the spatial position of the margin items.

Screens are also represented by flavor instances, which share some of the characteristics of windows because they share the component flavor tv:sheet. Screens are described fully in (screens).

.defflavor tv:sheet tv:sheet is a flavor that windows and screens share. It is also what provides the structure required by the microcode display primitives. Operations defined by this flavor are documented as being "of windows and screens" in this manual. .end_defflavor

Much of the contents of this manual is devoted to describing the instance variables and operations of various flavors of window. They are grouped below by functionality.

There is a vague convention sometimes followed for naming flavors of windows. Here the word frobboz is used to stand for any feature, attribute, or class of windows that would appear in a flavor name (e.g. peek, lisp-listener, or delayed-redisplay-label). Naming conventions are different for instantiable flavors (which are complete and can support instances of themselves) and mixin flavors (which are incomplete and only supply one particular aspect of behavior).

  • frobboz An instantiable flavor whose most distinguishing characteristic is that it is a frobboz. frobboz is preferred to frobboz-window except when it is necessary to make a distinction.
  • frobboz-mixin A flavor which provides the frobboz feature when mixed into other flavors, but is not instantiable. Such mixins often have no components, just :required-flavors.
  • basic-frobboz This form of name is used instead of frobboz-mixin when the flavor is regarded as altering the "essential character" of the window. It does not work to mix two "basic" flavors together unless they are designed to work together. In certain cases a basic-frobboz may contain tv:minimum-window as a component, and may even be instantiable, but usually it is a mixin that must be mixed with tv:minimum-window and other things in order to work.
  • essential-frobboz
  • essential-frobboz-mixin A name like this is generally used for a component of frobboz-mixin, containing the heart of the frobboz facility but not its bells and whistles or its specific interface.

1.5 Using a Window

Many programs never need to create any new windows. Often it suffices to use the standard input, output and graphics operations on an existing window, such as a Lisp listener which is the value of terminal-io when your program is called. For example, here is a graphics demo that will draw a pattern of xored circles on any window which has tv:graphics-mixin (such as a Lisp listener).

(defun green-hornet (&optional (window terminal-io)
		     (separation 40))
 (hacks:with-real-time
  (send window ':clear-screen)
  (send window ':home-down)
  (multiple-value-bind (iw ih)
      (send window ':inside-size)
    (let ((center-x1 (- (truncate iw 2)
			(truncate separation 2)))
	  (center-x2 (+ (truncate iw 2)
			(truncate separation 2)))
	  (center-y (truncate ih 2)))
      (do ((i (- (min center-y center-x1) 10.)
	      (1- i)))
	  ((<= i 5))
	(send window ':draw-circle
	      (if (bit-test 20 i) center-x1 center-x2)
	      center-y
	      i))))
  (send window ':tyi)
  t))

Such programs should try to stick to the most widely-implemented operations. The ideal is to use only the standard stream operations documented in (stream-protocol); then your program will run even with streams that are not windows. With graphics programs such as green-hornet you are forced to use some windows-only operations, but it is still best to stick to the operations provided by the flavor tv:window.

1.6 Creation of Windows

When you want to create a flashy and sophisticated user interface, especially involving mouse-sensitivity or automatic updating, it is time to consider creating your own windows (and your own window flavors, perhaps).

To create a window, use the functions make-instance or instantiate-flavor. (Old programs usually use tv:make-window, which is now equivalent to make-instance but was different in the past).

Function: make-instance flavor-name &rest init-options

Creates, initializes, and returns a new instance of the specified flavor. The init-options argument contains alternating keywords and values; the keywords must be init options accepted by the flavor you are using. The init options accepted by various window flavors are described in this manual.

Example:

(make-instance 'tv:lisp-listener
	       ':borders 4
	       ':font-map (list fonts:bigfnt)
	       ':vsp 6
	       ':edges-from ':mouse
	       ':expose-p t)

creates an exposed Lisp listener with big characters and lots of vertical space between lines.

For more information on this function and on instantiate-flavor, see (manual-make-instance-fun).

Variable: tv:sheet-area

The area in which windows are by default created.

.defmetainitoption windows :name name Every window has a name, which is used primarily for printing the window as a Lisp object, but also serves as a default for the window’s label. If you do not specify a name, the default is constructed from the flavor name and a counter (each flavor has its own) to make the name unique. .end_defmetainitoption

.defmetainstvar windows tv:name The name of the window. .end_defmetainstvar

2 Visibility and Exposure of Windows

The most important piece of information about a window is whether it is actually visible on the screen. A related but different piece of information is whether the window is exposed. Understanding these basic concepts, the subjects of this chapter, is vital to any use of the window system.

Using the system menu Create option you can make two windows that partially overlap. (If you have never done so, you should try it.) The window system is forced to make a choice here: only one of those two windows can be the rightful owner of that piece of the screen. Of these two windows, only one can be (fully) visible at a time; the other one has to be not fully visible, but either partially visible or not visible at all. Only the fully visible window has an area of the screen to use.

If you play around with this, you will see that it looks as if the two windows were two overlapping pieces of paper on a desk, one of which must be on top of the other. Create two Lisp listeners using the Create command of the system menu so that they partially overlap, and then single-click-left on the one that is on the bottom. It will come to the top. Now single-click-left on the other one; it will come back up to the top. The one on top is fully visible, and the other one is not.

2.1 Hierarchy of Windows

Several Lisp Machine system programs and application programs present the user with a window that is split up into several sections, which are usually called "window panes" or "panes". For example, the inspector has six panes in its default configuration: the one you type forms into at the top, the menu, the history list, and the three inspection panes below the first three. The window debugger and ZMail also use elaborate windows with panes. Just as windows on a screen can subdivide the screen, a window’s panes subdivide the screen space of the window. With programs such as the editor, inspector and ZMail, it may not be obvious that the windows you see are panes in another window because that window occupies the full screen. If you go into Edit Screen and reshape one of these, you will see clearly how there is a window with subwindows.

In fact, the panes in an inspector are related to the inspector’s main window just as that window is related to the screen. Windows are arranged in a hierarchy, each window having a superior and a list of inferiors. Usually the top of the hierarchy is a screen. In the example above, the inspector window is an inferior of the screen, and the panes of the window are inferiors of the inspector window. The screen itself has no superior (if you were to ask for its superior, you would get nil).

A window’s superior, its superior’s superior, and so on, are collectively called its ancestors. A window’s inferiors and their inferiors, and so on, are called its descendants.

The position of a window is remembered in terms of its relative position with respect to its superior. To figure out where a window is on the screen, we add this relative position to the absolute position of the superior (which is computed the same way, recursively; the recursion terminates when we finally get to a screen). The important thing about this is that when a superior window is moved, all its inferiors are moved the same amount; they keep their relative position within the superior the same. You can see this if you play with the Move Window command in Edit Screen.

Normally Edit Screen edits the arrangement of the windows on a screen, but it can also edit the arrangement of inferiors (panes) of a window in the same fashion. If you click right on Edit Screen, you get a menu containing all the superiors of the window you pointed at, up to the screen. You can then edit the inferiors of whichever one you choose.

So, what Edit Screen really does is to manipulate a set of inferiors of some specific superior, which may or may not be a screen. The set of inferiors that you are manipulating is called the active inferiors set; each inferior in this set is said to be active. The active inferiors are all fighting it out for a chance to be visible on their superior. If no two active inferiors overlap, there is no problem; they can all be visible. However, whenever two overlap, only one of them can be on top. Edit Screen lets you change which active inferiors get to be on top. There is also a part of the window system called the screen manager whose basic job is to keep this competition straight. For example, it notices that a window that used to be covering up part of a second window has been reshaped, and so the second window is no longer covered and can be made visible. Inactive windows are never visible until they become active; when a window is inactive, it is out of the picture altogether. The screen manager will be discussed at length later ((screen-manager)).

Each superior keeps track of all of its active inferiors as a list in the instance variable tv:inferiors, and each inferior window keeps track of its superior, in the instance variable tv:superior. Superior windows do not keep track of their inactive inferiors; this is a purposeful design decision, in order to allow unused windows to be reclaimed by the garbage collector. So, when a window is deactivated, the window system doesn’t touch it until it is activated again.

.defmetamethod windows :activate Makes the window active in its superior. .end_defmetamethod

.defmetamethod windows :deactivate Makes the window cease to be active in its superior. .end_defmetamethod

.defmetainitoption windows :activate-p t-or-nil If this option is specified non-nil, the window is activated after it is created. The default is to leave it deactivated. .end_defmetainitoption

.defmetamethod windows :kill Killing a window deactivates it but also makes a positive effort to get rid of other entities such as processes or net connections that may be associated with the window. If a window has these things, it may not be satisfactory to just allow the window to be garbage collected; therefore, the :kill operation is provided. A command for the user to get rid of windows should use :kill rather than :deactivate. .end_defmetamethod

.defmetamethod "windows and screens" :active-p t if this window is active in its superior. A screen is always considered active. .end_defmetamethod

.defmetamethod "windows and screens" :inferiors Returns this window or screen’s list of inferiors. .end_defmetamethod

.defmetamethod "windows and screens" :superior Returns this window or screen’s superior. For a screen, it is nil. .end_defmetamethod

.defmetamethod windows :set-superior new-superior Makes this window an inferior of new-superior. .end_defmetamethod

.defmetainitoption windows :superior superior Makes the new window an inferior of superior. If this is not specified, the default is tv:mouse-sheet, which is initially the main black-and-white screen. .end_defmetainitoption

.defmetainstvar "windows and screens" tv:inferiors A list of the active inferiors. .end_defmetainstvar

.defmetainstvar "windows and screens" tv:superior In a window, the value is the window’s superior. In a screen, the value is nil. .end_defmetainstvar

Function: tv:sheet-superior window-or-screen
Function: tv:sheet-inferiors window-or-screen

Accessor defsubsts for the corresponding instance variables.

Function: tv:sheet-me-or-my-kid-p sheet me

t if sheet is an indirect inferior, zero or more levels down, of the sheet me.

Function: tv:map-over-exposed-sheets function

Calls function on every exposed sheet, starting with the screens, their inferiors, and so on down.

Function: tv:map-over-exposed-sheet function sheet

Calls function on every exposed inferior of sheet, to all levels, including sheet itself.

Function: tv:map-over-sheets function

Calls function on every active sheet, starting with the screens, their inferiors, and so on down.

Function: tv:map-over-sheet function sheet

Calls function on every active inferior of sheet, to all levels, including sheet itself.

2.2 Screens

The topmost nodes of the window hierarchy are actually screens rather than windows, a screen being an instance of the flavor tv:screen.

.defflavor tv:screen Screens are also flavor instances, whose flavors incorporate tv:screen. Screens are not windows, but they have much in common with windows, because both incorporate the flavor tv:sheet ((tv:sheet-flavor)). .end_defflavor

Usually each screen object represents an individual piece of display hardware. However, the main black-and-white physical screen that all Lisp Machines have is logically divided into two screens, with different screen objects. These are tv:main-screen and tv:who-line-screen. Because these are separate screens, windows on the main screen cannot be extended onto the who line, and the mouse cannot move onto the who line, etc.

Screens are the objects that know how to parse font specifiers (user-level names for fonts) into font objects that can be used for display. See (font-specifier). Also, each screen can specify a font for each of the standard font purposes (:default, :label, :menu, etc.). See (font-purposes).

Function: tv:sheet-get-screen sheet

Returns the screen that sheet is an indirect inferior of (sheet itself, if it is a screen).

Variable: tv:main-screen

The screen object that represents the Lisp Machine black-and-white display, except for the who line area. This is default superior for windows created with tv:make-window.

Variable: tv:who-line-screen

The screen object that represents the who line area. Each field of the who line is a separate window on this screen.

Variable: tv:default-screen

This is the screen that is "normally used". It is initialized to be the main screen. Certain functions that create a window without reference to the mouse use it as a default for the superior of the window, and window resources with a superior as parameter often create one window initially, with the default screen as the superior.

Variable: color:color-screen

This is the color screen for the 4-bit-pixel color display that some Lisp Machines have. The screen object is always present, but is exposed only when the machine actually has a color screen. See (color).

Variable: tv:all-the-screens

A list of all screen objects. With this list, you can begin a tree walk to cover all active windows.

Function: tv:set-tv-speed &optional (speed 60.5) (wasted-lines 0)

Sets the scanning rate of the main screen, in vertical sweeps per second, to speed. speed is usually a flonum.

The vertical size of the screen is inversely proportional to the number of vertical scans per second, because the display rate in horizontal scan lines per second is fixed.

A nonzero value of wasted-lines directs the system to refrain from using that many horizontal scan lines at the bottom of the screen. If you are using MIT software on a machine built by Symbolics, you may need to do this, since the screens are typically misaligned so that the who line is obscured by the screen’s cabinet. A value of 20 to 30 generally does the trick.

2.3 Pixels

A screen displays an array of pixels. Each pixel is a little dot of some brightness and color; a screen displays a big array of these dots to form a picture. Everything you see on the screen, including borders, graphics, characters, and blinkers, is made up out of pixels.

Each physical screen has a display memory which stores the values of all the pixels. On regular black-and-white screens, each pixel has one of only two values, lit up or not lit up, so the pixel is represented in memory by one bit. Usually 0 is used for the background of a window and the characters or lines on it are made of 1’s, so 1 can be considered "on" and 0 "off". On color screens, pixels have more than one bit. The usual sort of color screen has four bits per pixel. 0 is still often used as the background value and assigned the color black. There is no convention for the use of other pixel values.

Black and white screens have a hardware flag that controls the visual appearance of 1 and 0 pixels. In "black-on-white" mode, 1 is dark and 0 is bright, so windows appear with dark text on a white background. This mode is the default. In "white-on-black" mode, 1 is bright and 0 is dark. Users can switch between these modes with Terminal C.

An individual window can specify 1 for background and 0 for text; this is independent of white-on-black mode (which applies to the whole screen) and is requested with the :reverse-video-p init option or the :set-reverse-video-p operation (see (windows-set-reverse-video-p-method)). These work by controlling the alu functions used for drawing and erasing characters; see (alu-functions). Programs which use the window’s recommended alu functions for their drawing and erasing will automatically display in reverse-video when this is specified. The who line mouse documentation window is an example of a window which uses reverse-video.

Function: tv:black-on-white &optional (screen tv:default-screen)

Make screen display one-bits as black, with zero-bits as white. (This is the default mode.)

Note that this works by setting a bit in the display hardware; as a result, if done on the main screen, it applies to the who line as well.

Function: tv:white-on-black &optional (screen tv:default-screen)

Make screen display one-bits as white, with zero-bits as black.

Function: tv:complement-bow-mode &optional (screen tv:default-screen)

Toggle whether screen displays one-bits as white or as black. This is what Terminal C does.

.definstvar tv:screen tv:bits-per-pixel 1 for a black-and-white screen, larger numbers for other kinds (4 for the standard color screen). .end_definstvar

.definstvar tv:screen tv:buffer The address of the screen memory, as a fixnum. .end_definstvar

.definstvar tv:screen tv:buffer-halfword-array An art-16b array containing the screen memory. .end_definstvar

.definstvar tv:screen tv:control-address The address of the screen’s control register which contains, among other things, the flag controlling black-on-white mode. .end_definstvar

2.4 Bit-Save Arrays

The pixel values that make up a window’s screen image are called its contents. When a window is fully visible, its contents are displayed on a screen so that they can be seen. When the window is not fully visible, its contents are lost unless there is a place to save them. Such a place is called a bit-save array.

A bit-save array is an array of bits of sufficient size to hold a copy of the window’s contents. If a window has a bit save array, its contents are copied into the array when the window ceases to be fully visible. If the window becomes fully visible again, the contents are copied from the bit-save array back onto the screen. In the mean time, programs can use tv:sheet-force-access to do output into the bit-save array while the window is not visible (see (tv:sheet-force-access-fun)), and the window’s inferiors, if any, can be exposed and do output (see (exposure)).

When a window with a bit-save array is partially visible, the visible parts can be displayed correctly by copying them from the bit-save array. This is the behavior you observe if you make a small Lisp listener window with Create and have a full-screen window such as the initial Lisp listener or a Zmacs frame partially visible around it. It happens because the Lisp listener or Zmacs frame has a bit-save array.

If a window does not have a bit-save array, then there is no place to put its contents when it is not visible, so they are lost. When the window becomes visible again, it will try to redraw its contents; that is, to regenerate the contents from some state information in the window. This is done by the :refresh operation documented below. Some windows can do this; for example, editor windows can regenerate their contents based on the editor buffers they are displaying. Other windows, such as Lisp listeners, do not remember what was displayed on them and cannot regenerate their previous contents. Such windows just leave their contents blank, except for the margins (see (margins)), which all windows can regenerate.

The advantage of having a bit-save array is that losing and regaining visibility does not require the contents to be regenerated; this is desirable since regeneration may be computationally expensive, or even impossible. The disadvantage is that the bit-save array can be large and swapping it in can be slow.

When a frame is in use, giving the frame a bit-save array enables the contents of the frame and all the panes to be preserved if the frame ceases to be fully visible. Bit-save arrays for the panes would come into play only if panes were shuffled or substituted within the frame; in most applications, this happens never or rarely, and is accompanied by a thorough redisplay. So normally the frame gets a bit-save array and the panes do not.

.defmetainstvar windows tv:bit-array This instance variable of all windows holds the window’s bit array, or nil if it has none. .end_defmetainstvar

Function: tv:sheet-bit-array window

Accessor defsubst for the corresponding instance variable.

.defmetamethod windows :bit-array Returns the window’s bit array, or nil if it has none. .end_defmetamethod

.defmetamethod windows :save-bits Returns non-nil if this window saves its bits when not exposed. .end_defmetamethod

.defmetamethod windows :set-save-bits flag Tells this window to start or stop saving its bits when not exposed. flag is t to start or nil to stop. .end_defmetamethod

.defmetainitoption windows :save-bits flag flag may be t, nil or :delayed. :delayed causes the window to acquire a bit-save array the first time it is deexposed, but not before. .end_defmetainitoption

.defmetamethod windows :refresh &optional (type ':complete-redisplay) Restore the saved contents of the window or regenerate the contents, according to the value of type (and to whether the window has a bit-save array).

Here are the possible values of type:

  • :complete-redisplay This is the default. The window’s present bit image is completely discarded and regenerated from scratch. The margins are redrawn by invoking :refresh-margins. The default definition of :refresh just leaves the inside blank except for refreshing any exposed inferiors.

    If the window has no bit-save array, type is ignored and the actions for :complete-redisplay are always used.

  • :use-old-bits The complete contents are restored from the bit-save array. This is specified by the system when a window is exposed.
  • :size-changed This keyword is specified when the window’s size has been changed. The contents are restored from the bit-save array, and then the margins are refreshed with :refresh-margins.
  • :margins-only This keyword is specified when the inside portion of the window is completely undisturbed, and only the margins need to be refreshed. The system treats it just like :size-changed.

Window flavors ought when possible to provide :after daemons for :refresh, to complete the job of redrawing the window, which the system itself cannot know how to do. When these daemons run, the instance variable tv:restored-bits-p will be non-nil if the window contents were restored from a bit-save array. If this is so, there is no need for the :after daemons to do anything, except perhaps if the window’s inside size has changed. .end_defmetamethod

.defmetainstvar windows tv:restored-bits-p In :after daemons of :refresh (and therefore also of :expose), this is t if the contents were restored from a bit-save array. If it is nil, the inside of the window was left blank and must be regenerated to whatever extent that is possible. .end_defmetainstvar

2.5 Screen Arrays and Exposure

This section discusses the concepts of screen arrays and of exposed windows. These have to do with how the system decides where to put a window’s contents (its pixels), how the notion of visibility on the screen is extended into a hierarchy of windows, and how programs can control which windows are visible. Do not feel it is your fault if this seems complicated; you do not need to understand it fully on your first reading of the manual.

Each window or screen can have a screen-array, which is where output drawn on the window should go. Drawing characters or graphics is done by changing elements of the window’s screen array. The screen array is stored in the instance variable tv:screen-array. The variable can also be nil, to say that the window does not have a screen array at the present time.

A screen normally has a screen array that is displaced to the special memory that the screen’s hardware displays from. A window that is visible has a screen array; it is an indirect array that points into the area of the superior’s screen-array where the inferior gets displayed on the superior. For example, consider a visible window whose superior is a screen and whose upper-left-hand corner is at location (100,100) in the screen. The window’s screen-array would be an indirect array whose (0,0) element is the same as the (100,100) element of the screen. If you were to set a pixel in the window’s screen-array, the corresponding pixel in the screen (found by adding 100 to each coordinate) would be set to that value.

A visible window more than one level down from the screen has a screen array that indirects more than once. The window’s screen-array points into the middle of its superior’s screen array, which points into the middle of the superior’s superior’s screen array, and so on until the screen is reached. When typeout is done on the window, it will appear on the screen, offset by the combined offsets of all the ancestors, so that it will appear in the correct absolute position on the screen.

Sometimes a window is unable to have a screen array that points to its superior’s screen array. For now, let’s not ask why this might happen, but consider instead what to do about the screen array when this does happen. There are two alternatives. If the window has a bit-save array, then the bit-save array is used as the screen array. If there is no bit-save array, there can be no screen array either. The window’s tv:screen-array variable becomes nil, and there is nowhere for output on this window to go.

For a window w with a bit-save array, w’s inferiors are not affected by where w’s screen array points. w always has a screen array, and its inferiors’ screen arrays can point to that.

But if w has no bit-save array, it may have nil instead of a screen array, and in that case it is impossible for w’s inferiors to have screen arrays pointing into w’s screen array. So they in turn must use their bit-save arrays, if any, as screen arrays, or not have screen arrays. The effect propagates down the hierarchy.

So we see one possible reason why a window may be unable to have a screen array that points into its superior’s: if the superior doesn’t have a screen array at all. There is one other reason: the superior may deny permission for this window to point its screen array into the superior. The superior has an instance variable tv:exposed-inferiors which record all the inferiors permitted to do this. (Only active inferiors are allowed.) This permission can be granted or revoked at any time, and is called exposability. Each window can be made exposable or not exposable using the :expose and :deexpose operations. So, if a window’s superior does not have a screen array, or if the window is not exposable, then the window must scrounge up a screen array itself if it can.

A window is said to be exposed if it has a screen array that points into its superior’s screen array. Note that a window must be exposable in order to be exposed, but the converse is not true. An exposable window is exposed as well if and only if its superior has a screen array.

An exposed window is not necessarily visible. A window is visible if its screen array points, through some number of levels of indirection, into the middle of the screen’s screen array. An exposed window’s screen array points into the middle of something, but that may be a bit-save array in a deexposed ancestor some number of levels up. A window that is exposed but not visible must have some ancestors that are not exposed, and at least one of them must not be exposable either. This diagram of a window w8 and its ancestors shows the pattern of exposed and deexposed windows and how it comes about.

s <-- w1 <-- w2 <-- w3 <-- w4 <-- w5 <-- w6 <-- w7 <-- w8
exposable    not!   exposable again...
				  w5 has a bit-save array
exposed...   deexposed...                exposed...
visible...   invisible...	      ...still invisible...

Output is allowed on a window whenever the window is exposed. Usually exposed windows are visible and the output can be seen on the screen. But output to an exposed window with a deexposed ancestor is also permitted. Then the output goes into the middle of that ancestor’s bit-save array rather than onto the screen. Such output cannot actually be seen. But if the unexposable ancestor that must exist is made exposable, the bit-save array will be copied onto the screen and the output already done will be seen.

Output is not normally allowed on a deexposed window, even if the window has a screen array which is its bit-save array. However, in this case, you can use tv:sheet-force-access to override the prohibition and output onto the bit-save array. Use of :permit as the window’s deexposed typeout action (see (deexposed-typeout-action)) allows all output on such windows to proceed and draw in the bit-save array. A deexposed window with no bit-save array cannot have output done on it in any fashion since it has no contents.

The :expose operation makes a window exposable. If at that time its superior has a screen array, the window will become exposed as well. Or, if the superior later acquires a screen array, the window will become exposed then. This can happen if the superior itself is exposed, or if the superior is given a bit-save array with the :set-save-bits operation.

The :deexpose operation always makes the window unexposable and therefore not exposed.

It is possible for a screen to be deexposed. In particular, if a Lisp Machine does not have a color display physically attached to it, there is still a "color screen" Lisp object in the Lisp world, but it is deexposed (and so are any immediate inferiors it may have). This is so saved Lisp environments can be moved easily between machines with different hardware configurations. The screen object is left deexposed so that programs will not try to output to it. The screen is exposed whenever the Lisp Machine system is booted on a machine that actually has a color screen; then all its exposable inferiors become exposed too. For screens, there is no distinction between exposed and exposable, since there is no superior to have a say in the matter.

In order to maintain the model that windows are like pieces of paper on a desk, it is important that no two windows that both occupy some piece of screen space be exposed at the same time. To make sure that this is true, whenever a window becomes exposed, the system deexposes any of its exposed siblings that it overlaps. (Note: this is not true for temporary windows; see (temporary-window).)

.defmetamethod "windows and screens" :expose &optional inhibit-blinkers bits-action new-left new-top Makes the window exposable, and exposed if possible. This is a very useful operation to attach daemons to, but remember that this operation may be performed on a window that is already exposable. The daemons must not make the assumption that the window is just becoming exposable. If the window is not a direct inferior of the screen, it may not be becoming exposed either.

If the window is not active in its superior, it is first activated.

The arguments to the :expose operation are supplied by the system and usually of interest only to the system’s methods. User invocations of this operation should usually supply no arguments.

If the window actually becomes visible, the window’s blinkers normally appear with their deselected visibilities. If inhibit-blinkers is non-nil, the blinkers are not acted on. If the window is being exposed in order to select it, this is used to save time.

If the window actually becomes visible, bits-action controls how it is put back on the screen. It can be :noop, :restore or :clean. If it is :noop, the window’s screen area is not touched. This is used only in very unusual cases. If it is :clean, the window is sent a :refresh message with argument :complete-redisplay, which should make the window redraw itself from scratch if it can. If bits-action is :restore, the window is sent a :refresh message with argument :use-old-bits, which should make the window copy its bit-save array onto the screen. nil as the bits-action is equivalent to :restore for windows with bit-save arrays and to :clean for windows without them.

new-left and new-top are the offsets within the superior at which to expose the window. They default to the window’s current offsets. These arguments are for use by the :set-edges operation; you should not pass them.

A window cannot be made exposable unless its full size fits within the superior. .end_defmetamethod

.defmetamethod "windows and screens" :deeexpose &optional save-bits-p screen-bits-action remove-from-superior Makes the window not exposed and not exposable. This is a useful operation to add daemons to.

The arguments to the :deexpose operation are supplied by the system, and are usually of interest only to the system’s methods.

save-bits-p defaults to :default. It can also be :force or nil. :default means the bits are saved if the window has a bit-save array. :force gives the window a bit-save array if it doesn’t already have one, so that the bits are always saved. nil does not save the bits.

screen-bits-action controls what to do to the bits on the screen. It may be :noop to do nothing to them, or :clean to erase the area occupied by the window.

If remove-from-superior is nil, the window remains exposable. You should always use t (which is the default) for this argument. The window system uses nil as part of implementing deexposure of an exposable window whose superior loses its screen array. Use of nil at any other time would lead to incorrect results. .end_defmetamethod

.defmetainitoption windows :expose-p t-or-nil If this option is specified non-nil, the window is made exposable after it is created. The default is to leave it deexposed. If the value of the option is not t, it is used as the first argument to the :expose operation (the inhibit-blinkers argument). .end_defmetainitoption

.defmetamethod "windows and screens" :exposable-p t if the window is exposable. .end_defmetamethod

.defmetamethod "windows and screens" :exposed-p t if the window is exposed. .end_defmetamethod

.defmetamethod "windows and screens" :exposed-inferiors Returns a list of all exposable inferiors of this window or screen. .end_defmetamethod

Special Form: tv:with-sheet-deexposed (sheet) &body body

Executes the body with sheet deexposed. If sheet had been exposed, it is reexposed when body exits. Operations that change things about the window often make use of this to reduce the complicated case of an exposed window to the simpler case of a deexposed one.

.defmetamethod "windows and screens" :screen-array Returns the window or screen’s screen array, or nil. .end_defmetamethod

.defmetainstvar "windows and screens" tv:exposed-p t if the window is exposed. .end_defmetainstvar

.defmetainstvar "windows and screens" tv:exposed-inferiors A list of all exposable inferiors of this window or screen. .end_defmetainstvar

.defmetainstvar "windows and screens" tv:screen-array The screen array, or nil if there is none. .end_defmetainstvar

Function: tv:sheet-exposed-p window-or-screen
Function: tv:sheet-exposed-inferiors window-or-screen
Function: tv:sheet-screen-array window-or-screen

Accessor defsubsts for the corresponding instance variables.

2.6 Ability to Output

Whether a window is exposed usually controls whether output can be done on it. In a deexposed window a flag called the output hold flag is normally 1. This causes an output hold exception if an attempt is made to output to the window. The normal result of an output hold exception is that the process doing output waits until the output hold flag is clear. The process wait state during this wait is "Output Hold".

The output hold flag is also set in a window that has exposed inferiors, because output on the window would overwrite the inferiors.

Function: tv:sheet-output-hold-flag window

1 to indicate an output hold exception, or 0 to permit output on the window. This is setf’able.

When a process attempts to type out on a window which is deexposed and has its output hold flag set, what happens depends on the window’s deexposed typeout action. The deexposed typeout action can be any of certain keyword symbols, or it can be a list. After the specified action is taken, if the output hold flag is still set, the process will wait for it to clear. The interesting thing is that the action may affect the value of the output hold flag.

.defmetainstvar windows tv:deexposed-typeout-action The window’s deexposed typeout action. .end_defmetainstvar

.defmetamethod windows :deexposed-typeout-action .defmetamethod1 windows :set-deexposed-typeout-action action Get or set the window’s deexposed typeout action. .end_defmetamethod

.defmetainitoption windows :deexposed-typeout-action action Initializes a window’s deexposed typeout action to action. .end_defmetainitoption

Function: tv:sheet-deexposed-typeout-action window

Accessor defsubst for the instance variable.

Here are the possible values of deexposed typeout action:

  • :normal "deexposed typeout action" This, the default, means "no action". Therefore, the process will always have to wait for the output hold flag to clear.
  • :expose "deexposed typeout action" The action is to send the window an :expose message. This may expose the window (if the superior has a screen-array), and if it does expose the window then the output hold flag will probably be cleared, allowing typeout to proceed immediately. If the superior is the screen, the :expose option provides a very different user interface from the :normal option.
  • :permit "deexposed typeout action" This means to permit typeout even though the window is not exposed, as long as the window has a screen array; i.e., it may type out on its own bit-save array even though it is not exposed. The next time the window is exposed, the updated contents will be retrieved from the bit-save array.

    The action for :permit is to turn off the output hold flag if the window has a screen array. This mode has the disadvantage that output can appear on the window without anything being visible to the user, who might never see what is going on and might miss something interesting.

    It is possible to request that output in this mode to partially visible windows be transferred to the screen periodically. See (deexposed-permit-in-background).

  • :notify "deexposed typeout action" This means that the user should be notified when there is an attempt to do output on the window. The action taken is to send the :notice message to the window with the argument :output (see (windows-notice-method)). The default response to this is to notify the user that the window wants to type out and to put the window on a list for Terminal 0 S to select it. Supdup and Telnet windows have :notify deexposed typeout action by default.
  • :error "deexposed typeout action" The action is to signal an error.
  • a list, (operation arguments...) The action is to send the window a message with operation and arguments.

Functions such as ed, whose purpose is to select a window for the user, should not return immediately. If ed returned immediately, then when called in a Lisp listener with its deexposed typeout action set to :expose, the printing of the value returned by ed would immediately switch back to the Lisp listener, which defeats the purpose of ed. To avoid this behavior, ed calls tv:await-window-exposure.

Function: tv:await-window-exposure

Wait until terminal-io is exposed (more precisely, until its :await-exposure operation returns).

.defmetamethod windows :await-exposure Does not return until the window is exposed. (Some window flavors implement it differently). .end_defmetamethod

Special Form: tv:sheet-force-access (window) body

tv:sheet-force-access allows you to do typeout on a window that has a screen array even if its output hold flag is set. It works by turning off the output hold flag temporarily around the execution of the body. This is useful for drawing on a window while it is not visible. For example, changing the menu items of a menu redraws the menu contents immediately even if the menu is not visible; this is because when the menu does become visible it looks better to the user for it to become visible in one instant with the correct contents.

If the window is exposed, tv:sheet-force-access goes ahead and outputs to it. If the window is not exposed but has a bit-save array, the output goes there.

If the window is not exposed and has not bit-save array tv:sheet-force-access doesn’t do anything at all; it just returns without evaluating its body.

Here is an example: when a text scroll window is given a new item generator, which completely changes the text that it should display, it redisplays the window in its bit-save array if necessary.

(defmethod (tv:text-scroll-window :set-item-generator)
	   (new-item-generator)
  (setq item-generator new-item-generator)
  (tv:sheet-force-access (self)
    (send self ':clear-screen)
    (send self ':redisplay 0 
	  (tv:sheet-number-of-inside-lines))))

2.7 Window Locking

Each window or screen has a lock which is used to prevent two processes from operating on the window at once in a way that might cause inconsistent results. Outputting on the window, activating or deactivating the window, exposing or deexposing the window, and changing the window’s shape all lock the window. This is done with process-lock, via tv:lock-sheet. Note that the window’s inferiors must be locked too.

Another form of locking is called "temp-locking". A window is temp-locked when a temporary window (see (temporary-windows)) is exposed on top of it. All the operations which lock the window will have to wait if the window is temp-locked just as they would if the window were locked in the ordinary manner; however, the lock is not considered owned by a process but rather by the temporary windows that overlap the window. It will stay locked until the temporary windows are all deexposed. The :mouse-select operation and some other things know how to deexpose temporary windows when necessary to cause a window to become unlocked.

Special Form: tv:lock-sheet (window-or-screen) &rest body...

Executes body with window-or-screen locked by this process. Calls to tv:lock-sheet are found in wrappers for operations such as :expose, so you need not call it yourself, but you should be aware that it is being done.

.defmetainstvar "windows and screens" tv:lock The lock. It is nil for an unlocked window, a process that has locked the window, or a list of covering temporary windows if this window is "temp-locked". .end_defmetainstvar

.defmetainstvar "windows and screens" tv:lock-count The number of times the lock is locked. This counts the number of recursive lockings for the same process, for example. .end_defmetainstvar

Function: tv:sheet-lock window-or-screen

Returns the contents of window-or-screen’s lock. This is a defsubst and can be setf’d. It is usually unmodular to use this.

Function: tv:sheet-can-get-lock window-or-screen &optional (lock-id current-process)

Returns t if this window or screen could right now be locked by lock-id; essentially, if it is free or already locked that way (but in fact it is more complicated than this.)

Note that if you call this function with inhibit-scheduling-flag nil, you are likely to be susceptible to a timing error.

Function: tv:sheet-clear-locks

Unlocks the locks of all active windows. For use in an emergency.

2.8 Temporary Windows

Normally, when a window is exposed in an area of the screen where there are already some other exposed windows, the windows that used to be there are deexposed automatically by the window system. This is because the window system normally doesn’t leave two windows both exposed if they overlap. (In the absence of temporary windows, which we are about to introduce, the system never allows two overlapping windows to both be exposed.)

But sometimes there are windows that only get put up on the screen for a very short time. The most obvious examples of such windows are the momentary menus that only appear for long enough for you to select an item. It would be unfortunate if every time a momentary menu appeared, the windows under it had to be deexposed. The ones without bit-save arrays would have their screen image destroyed, forcing them to regenerate it or to reappear empty. The ones with bit-save arrays would not be damaged in this way, but they would have to be deexposed, and deexposure is a relatively expensive operation.

This problem is solved for momentary menus by making them temporary windows. Temporary windows work differently from other windows in the following way: when a temporary window is exposed, it saves away the pixels that it covers up. It restores these pixels when it is deexposed. These pixels may come from several different windows. This way it doesn’t mess up the area of the screen that it uses, even if it covers up some windows that don’t have bit-save arrays.

Also, a temporary window, unlike a normal window, does not deexpose the windows that it covers up. This way the covered windows need not try to save their bits away in their bit-save arrays (if they have them) nor ever have to try to regenerate their contents (if they don’t). They never notice that the temporary window was (temporarily) there.

.defflavor tv:temporary-window-mixin This mixin makes a window a temporary window. .end_defflavor

.defmetamethod windows :temporary-bit-array Non-nil if the window is a temporary window. .end_defmetamethod

There would be some problems if temporary windows were this simple. Suppose there is a normal window, and a temporary window appears over it; some of the contents of the normal window are saved in an array inside the temporary window. Now, if the normal window were moved somewhere else, and possibly became deexposed or overlapped by other windows, and then the temporary window were deexposed, the temporary window would dump back its saved bits where the normal window used to be. This would clobber some other window.

Furthermore, even though normal window is still exposed, output on it must not be permitted, since that could overwrite the temporary window.

Because of problems like these, when a temporary window gets exposed on top of some other windows, all the windows that it covers up (fully or partially) become temp-locked. While a window is temp-locked, any attempt to type out on it will wait until it is no longer temp-locked. Furthermore, any attempt to deexpose, deactivate, move, or reposition a temp-locked window will wait until the window is no longer temp-locked. The temp-locking is undone when the temporary window is deexposed.

Because of temp-locking, you should never write a program that will put a temporary window up on the screen for a "long" time. There should be some action by the user, such as moving the mouse, which will make the temporary window deexpose itself. It is best if any attempt by the user to get the system to do something makes the temporary window go away. While the temporary window is in place, it blocks many important window system operations over its area of the screen. The windows it covers cannot be manipulated, and programs that try to manipulate them will end up waiting until the temporary window goes away.

It works fine to have two or more temporary windows exposed at a time. If you expose temporary window a and then expose temporary window b, and they don’t overlap each other, they can be deexposed in either order, and any windows that both of them cover up will be temp-locked until both of them are deexposed. If b covers up a, then a will be temp-locked just like any other window, and so it will not be possible to deexpose a until b has been deexposed.

2.9 The Screen Manager

Sometimes not all of the screen is in use by fully visible windows. This does not happen in elementary use of the Lisp Machine, since the initial windows in the system are all full-screen-sized, but if you create a small Lisp listener with system menu Create the rest of the screen will be unclaimed by any fully visible window. The part of the window system responsible for dealing with unclaimed parts of the screen is called the screen manager.

The screen manager fills such unclaimed areas by looking for deexposed windows which fall entirely or partly within them. Only active immediate inferiors of the screen are considered, and in a specific priority order described in (screen-manager-priority).

A window that falls entirely within unclaimed areas can be made visible without deexposing any other windows. This is called autoexposure. Since the window is a direct inferior of the screen, exposing it always makes it visible. The screen manager goes on considering the remaining deexposed windows, but with less screen area unclaimed.

A window that overlaps the unclaimed areas but also overlaps a visible window cannot simply be exposed. So it becomes partially visible, which means simply that the screen manager copies the appropriate parts of the window’s contents onto the unclaimed areas. The window is not treated as visible or exposed in any other sense. This gives the visual impression of overlapping pieces of paper on a desk top; the deexposed window is partially covered up by the visible windows, but you can still see those parts that aren’t covered. The contents are copied from the window’s bit-save array. Windows without bit-save arrays are by default ineligible for partial visibility, so other windows later in the order will get a chance for the same screen area; however, it is possible to arrange for windows without bit-save arrays to be partially visible (though the displayed contents may not be accurate).

Windows whose size and position are such that they do not fit within the bounds of the superior cannot be exposed, and the screen manager does not try to autoexpose such windows. However, they can be partially visible like any other windows.

The screen manager has one other job. At the same time that it does autoexposure, it can also select a window if there isn’t any selected window at the time. This is called autoselection. A window is a candidate for autoselection if it is an exposed inferior of the screen and its :name-for-selection is non-nil (see (windows-name-for-selection-method)). For more information, see (selection).

The screen manager does not only manage the inferiors of screens; it can manage the inferiors of windows as well. The system invokes the screen manager on a sheet’s inferiors by sending the sheet a :screen-manage message. This happens for all visible sheets regardless of flavor.

.defmetamethod "windows and screens" :screen-manage The default definition of this operation is to do autoexposure and display of partially visible windows among the active inferiors of this window or screen, as described above. .end_defmetamethod

.defflavor tv:no-screen-managing-mixin Prevents the screen manager from dealing with the inferiors of a window by redefining the :screen-manage operation to do nothing.

When a frame is used by a single program, the program usually expects to have sole control over exposure of panes. Then this mixin can be used to tell the screen manager not to interfere. Constraint frames do not normally need to use this mixin because they avoid problems while changing configurations by deactivating any panes that do not belong in the configuration. Zmacs frames do use this mixin so that the screen manager will not autoexpose various editor windows that belong to the frame. .end_defflavor

.defmetamethod "windows and screens" :screen-manage-autoexpose-inferiors Performs autoexposure of the active inferiors of this window or screen. Used by the default definition of :screen-manage. .end_defmetamethod

2.9.1 Control of Partial Visibility

.defmetamethod windows :screen-manage-deexposed-visibility Should return non-nil if parts of this window ought to be displayed when the window is partially visible. The default definition returns non-nil if the window has a bit-save array. .end_defmetamethod

.defflavor tv:show-partially-visible-mixin If a window has this flavor mixed in, then the screen manager will attempt to show it to the user when it is partially visible even if it doesn’t have a bit-save array. Since there are no saved contents to display, the screen manager must give the window a screen array temporarily, send it a :refresh message so it will draw itself on the screen array, and then display whatever is found there. Often this means that you will see the label and borders of the window, but not the inside. .end_defflavor

.defflavor tv:gray-deexposed-right-mixin .defflavor1 tv:gray-deexposed-wrong-mixin Make any visible parts of the window appear gray if the window is not fully visible. tv:gray-deexposed-wrong-mixin is faster, but does not work for windows that have inferiors. You would use these mixins in windows without bit-save arrays, as a cheaper alternative to tv:show-partially-visible-mixin, to provide something better than blankness when the window ought to be partially visible.

The precise kind of gray is controlled by the instance variable tv:gray-array, which comes with operations :gray-array and :set-gray-array and init option :gray-array. The value must be a two-dimensional array of bits that will be replicated by bitblt; its width must be a multiple of 32. Useful values for tv:gray-array include tv:75%-gray, tv:50%-gray, tv:33%-gray, tv:25%-gray, and tv:12%-gray. .end_defflavor

.defflavor tv:initially-invisible-mixin Causes a window not to appear through screen management, even partially, until it has first been explicitly exposed. This is used in some window flavors (such as editor windows, Supdup windows, and others) of which instances are present in the saved system environment even without the user’s ever having requested them. These windows can be active, and available for System keys to select, but will not become partly visible if some other window is made smaller. .end_defflavor

Recall that if a deexposed window has its deexposed typeout action set to :permit, output on the window can proceed but goes to the bit-save array rather than to the screen. If the window is partially visible, such output could modify the visible parts of the window. You can request that the screen manager check periodically for such output and copy the changed contents to the screen.

Variable: tv:screen-manage-update-permitted-windows

Controls whether the screen manager looks for partially-visible windows with deexposed typeout actions of :permit and updates the visible portion of their contents on the screen. If the value is nil, which it is initially, the screen manager does not do this. Otherwise the value should be the interval between screen updates, in 60ths of a second.

2.9.2 Priority among Windows for Exposure

Suppose there is a section of the screen in which there are no exposed windows, and more than one active, deexposed window could be exposed to fill this area, but the two could not both be exposed (because they overlap). Which one gets to be exposed? Here’s another issue: when the screen manager wants to display pieces of partially-visible windows, there may be more than one deexposed window that could be displayed in a given area of the screen. How does screen manager decide which window to display?

It decides on the basis of a priority ordering. All of the active inferiors of a window are maintained in a specific order, from highest to lowest priority. When there is a section of the screen on which more than one active inferior might be displayed, the inferior that is earliest in the ordering, and so has the highest priority, is the one that gets displayed. This ordering is like the relative heights of pieces of paper on a desk; the highest piece of paper at any point on the desk is the one that you see, and all the rest are covered up.

.defmetamethod "windows and screens" :order-inferiors Sorts the tv:inferiors list of active inferiors of this window or screen into the proper order for considering them for autoexposure or partial visibility. .end_defmetamethod

The default definition of :order-inferiors uses a complicated algorithm which is designed to put the most recently exposed windows first, but also allows the programmer to specify priorities explicitly. If you do not need to know the details, you can safely skip the rest of this subsection.

The algorithm involves a value assigned to each window called its priority, which may be a fixnum or nil. The general idea is that windows with higher numerical priority values have higher priority to appear on the screen. The default value for the priority is nil, which is considered less than any numeric value.

.defmetainstvar windows tv:priority The window’s priority value, a number or nil. .end_defmetainstvar

.defmetamethod windows :priority .defmetamethod1 windows :set-priority new-priority Get or set the window’s priority value. .end_defmetamethod

.defmetainitoption windows :priority priority Initializes the window’s priority value. .end_defmetainitoption

The standard ordering of inferiors puts all exposable inferiors first, followed by the unexposable inferiors in order of decreasing priority. Each group of unexposable inferiors with the same priority is order by how recently they were exposable; the longer an inferior has gone without being exposable, the farther back it moves.

This is done by computing the current ordering based on the past ordering (as remembered by the old value of tv:inferiors). When the window system does anything which should change the ordering, such as making a window exposable or not exposable, it invokes the :order-inferiors operation to update the recorded ordering.

The ordering is updated by moving the exposable windows to the front and sorting the unexposable ones by priority. The sort is stable; that is, unexposable windows with the same priority value keep their previous ordering. Since most of the time numerical priorities are not used anyway (the priorities of most windows are nil), the ordering generally changes only as a result of exposure and deexposure of windows. When a window becomes exposable it gets pulled up to the front of the ordering; then when other windows become exposable instead, this window sinks back down. Thus, the ordering ends up showing simply how recently each window was exposable.

There is also an operation called burying a window, which deexposes the window and puts it at the end of its priority grouping in the ordering. A program typically buries its window when it thinks that the user is not interested in that window and would prefer to see some other windows. The Bury command in Edit Screen is a way for the user to bury a window.

.defmetamethod windows :bury Buries the window. See also tv:deselect-and-maybe-bury-window, a convenient interface to this operation ((tv:deselect-and-maybe-bury-window-fun)). .end_defmetamethod

Negative priorities have a special meaning. If the value of a window’s priority is -1, then the window will not ever be visible at all even if it is only partially covered; however, it will still get autoexposed. If the value of priority is -2 or less, then the window will not even be autoexposed, and so it will simply never be seen unless sent an explicit :expose message.

2.9.3 Delaying Screen Management

The screen manager can potentially interfere with the actions of a program that explicitly deexposes windows. Suppose you send a :deexpose message to an exposed window. The screen manager will run, and will probably autoexpose that very window, canceling the effect of the :deexpose. That window certainly does not overlap any still-visible windows, and it is as recently-exposed as a window can get, so it will be the first candidate for autoexposure.

Explicit deexposure is usually done at the beginning of a sequence of window rearrangements. For example, moving an exposed window is done by deexposing it, changing its position (which is easy when it is deexposed) and reexposing it. We want the screen manager to run when the whole sequence is complete; it should not consider the transient intermediate states. Even if the screen manager did not directly interfere with the program’s deliberate actions, it would waste time and confuse the user by displaying partially visible windows in temporarily-unclaimed screen areas for which the program is already preparing a new use. (This is a general phenomenon. Management is a useful auxiliary function, but managers have a tendency to interfere with work they don’t understand if there is no way to shut them off.)

We shut the screen manager off with the special form tv:delaying-screen-management. While its body is being executed, events that would normally bring about screen management are recorded on a queue instead. When the tv:delaying-screen-management form is exited (whether normally or by throwing), the screen manager looks at the queue and does all necessary screen management in one blow.

Sometimes it happens that screen management cannot be done when the tv:delaying-screen-management form is exited, because relevant windows are locked by other processes. Then the entries are left on the queue. They are handled at some later time when the necessary locks are free by a background process called Screen Manager Background. So the necessary screen management always does eventually get done.

When tv:delaying-screen-management forms are nested, only the outermost one will do any screen management when it is exited.

Special Form: tv:delaying-screen-management body...

The body forms are evaluated sequentially with screen management delayed. The value of the last form is returned.

Special Form: tv:without-screen-management body...

The body forms are evaluated seqentially with screen management delayed. Moreover, if the body completes normally, the queue entries put on by its execution are removed from the queue, on the assumption that the body has itself done all appropriate screen redisplay. If the body terminates abnormally with a throw, the queued entries remain on the queue and are processed by the screen manager eventually.

3 Selection

At any time, keyboard input is directed to at most one window, designated by programs or by the system in response to user commands. This window is called the selected window. A process trying to do input through another window will normally wait until the window is selected (however, the window’s deexposed typein action can change this; see below).

Variable: tv:selected-window

The value of this variable is the selected window. You should not set this variable yourself, but use the defined interfaces described below.

A window’s cursor blinker normally blinks only when the window is selected. This is how the user can tell which window is selected. (You can control what happens to each blinker when its window becomes selected; see (blinker-visibility).)

The user can change the selected window using the Terminal and System keys or the system menu. Also, clicking the mouse on a window usually selects that window if it is meaningful to do so.

The simplest, and paradigmatic, case of window selection happens if you have several independent windows on the screen, such as Lisp listeners. One of them displays a blinking cursor, and input echoes there. The processes in the others remain in a keyboard input wait, as you can see if one of the windows on the screen is a Peek. The mouse, or the Terminal O command, can be used to select a different window.

The selected window needs to handle certain operations that windows in general do not need to handle. The flavor tv:select-mixin defines these operations, and should be used in flavors of windows that are going to be selected. (A window can be useful without being selectable. For example, menus cannot be selected.) The flavor tv:window includes tv:select-mixin.

If two processes try to read from the same window (or windows sharing an input buffer), it is unpredictable which one will get the input. If you are designing an application where this might happen, you must make sure than you will not have two processes actually active and reading input from the same source at the same time. In most applications there will be only one process that ever reads input from any one window or input buffer. In these applications you should use tv:process-mixin in the window flavor to tell the window which process is associated with it (see (tv:process-mixin-flavor)).

The selected window controls the actions performed by the system at the instant a character is typed on the keyboard. Due to typed-ahead commands that switch windows (such as Control-Z in the editor), there is no way to know for certain which window will eventually read a character being typed at a given moment, so letting the selected window decide asynchronous processing for the character is the best that can be done. Asynchronous processing options include asynchronous intercepted characters (see (asynchronous-intercepted-characters)) and case conversion of control characters (see (control-character-case)).

Asynchronous intercepted characters such as Control-Abort which act on a process ask the selected window which process to operate on, with the :process operation (see (tv:select-mixin-process-method)). The who line usually does the same thing to find the process whose run state should be displayed. If you use tv:process-mixin, :process returns the process associated with the window; otherwise, a default definition of :process is inherited from tv:select-mixin and returns whichever process last read input from the window (or from any other window sharing the same input buffer). This is fine for the who line, but can lead to weird results in Control-Abort. So you should use tv:process-mixin whenever it makes sense.

If a process tries to do input from a window whose input buffer is empty and not selected, it cannot get any input and must wait. (The input buffer is selected if this window, or any other window sharing the same input buffer, is the selected window). The wait ends when input appears in the buffer, or when the buffer becomes selected and there is keyboard input available. If the window is not even exposed, a notification may happen in addition. This is controlled by the window’s deexposed typein action, which may be either :normal or :notify. :notify means that input from the window when it is deexposed should notify the user (see (tv:notify-fun)) with a message like "Process X wants typein", and make the window "interesting" so that Terminal 0 S can select it.

.defmetainitoption windows :deexposed-typein-action action Initializes the "deexposed typein action" (see (deexposed-typein-action)) of the window to action. It defaults to :normal. .end_defmetainitoption

.defmetamethod windows :deexposed-typein-action Returns the "deexposed typein action" (see (deexposed-typein-action)) of the window. .end_defmetamethod

.defmetamethod windows :set-deexposed-typein-action action Sets the "deexposed typein action" (see (deexposed-typein-action)) of the window to action. .end_defmetamethod

3.1 How Programs Select Windows

Programs change the selected window using the :select operation.

.defmetamethod windows :select &optional (remember-previous t) Makes this window (or its selection substitute, if any) the selected window. Unless remember-previous is nil, the previous selected window is entered on the list of previously selected windows for the Terminal and System keys to use.

Many application window flavors define daemons for this operation. Note, however, that the daemons will be run whenever this operation is invoked, even if the window is already selected. .end_defmetamethod

.defflavor tv:select-mixin No window can actually be selected unless its flavor includes this mixin. tv:select-mixin is part of tv:window but not part of tv:minimum-window.

Windows whose flavors do not contain this mixin may be sent :select messages only if they have designated other windows as selection substitutes (see below). The ultimate substitute which is finally selected must have tv:select-mixin. .end_defflavor

.defmetamethod windows :selected-p Returns t if this window is the selected window. .end_defmetamethod

.defmetamethod windows :mouse-select args Selects a window, for a mouse click or for asynchronous keyboard input such as the Terminal command.

While mostly the same as sending :select to the window’s alias for selected windows (see (previously-selected-windows-section)), this operation also causes all type-ahead input to remain with the window that used to be selected (see (tv:kbd-snarf-input-fun)).

Note that the :select and :mouse-select operations should not be invoked in the mouse process. This means that if you want to use them in a :mouse-click or :mouse-buttons or :handle-mouse method, you must do

(process-run-function "Select" window-to-select ':mouse-select)

.end_defmetamethod

.defmetamethod windows :deselect &optional (restore-selected t) This operation is invoked by the window system when a window ceases to be selected. This can be because the window is no longer visible, or because some other window is being selected.

Many application window flavors defined daemons for the :deselect operation.

restore-selected controls what will be done with this window in the tv:previously-selected-windows array used by the Terminal S and System commands, and whether to select automatically some other window found in that array. The possible values are

  • :dont-save Do not put the window being deselected into the list anywhere, and do not select any other window.
  • nil or :beginning Put the window being deselected at the front of the list, but do not select any other window.
  • :end Put the window being deselected at the end of the list, but do not select any other window.
  • :first Put the window being deselected at the front of the list, after selecting the window that used to be at the front of the list. This is like what Terminal S does.
  • :last or t Put the window being deselected at the end of the list, and select the window at the front of the list. This is the default.

.end_defmetamethod

Function: tv:deselect-and-maybe-bury-window window deselect-mode

Deselects window, selecting the previously selected window. If that causes window to become deexposed, window is buried. deselect-mode is passed to the :deselect operation, where it controls where to put the window in the list of previously selected windows used by the Terminal and System commands.

Special Form: tv:window-call (window [exit-operation exit-args...]) body...
Special Form: tv:window-mouse-call (window [exit-operation exit-args...]) body...

Execute body with window selected. tv:window-call uses the :select operation to do this, while tv:window-mouse-call uses the :mouse-select operation; that is how they differ. On exit, they reselect the window that had been selected before, and send window a message with operation exit-operation and arguments exit-arguments. exit-operation is often :deactivate. It can also be omitted; then nothing is done to window except for deselecting it because some other window is selected.

These constructs are no longer as useful as they once were. For controlling selection among windows of a team, selection substitutes (see (selection-substitutes)) should be used now instead.

3.2 Teams of Windows

The simple paradigm of selection is based on windows that are independent competitors for the user’s input, such as a pair of Lisp listeners. Another frequent situation is a to have a group of windows that act as a team. Usually the windows consist of a single frame and its panes, and usually they are managed by a single process, but neither of these is necessarily so. Often the windows of a team share an input buffer to make it easier for one process to read input from all of the windows at once (see (shared-input-buffers)); this is an important technique which you should definitely read about if you are designing a team of windows.

The simple paradigm extends cleanly to teams if we imagine that the user regards each team as a unit of selection. In this extended paradigm, the user selects among entire teams as if they were single windows.

Teams are not actual Lisp objects, merely concepts understood by the user and programmer. The window system cannot have a selected team; some window of the team must be selected. Each team’s program chooses a window of the team as the team’s selection representative. The selected window should then be the selection-representative of the user’s chosen team. The selected window can change when the user chooses a new team, or when the user’s chosen team picks a new representative.

To implement this, the programmer of the team first picks one window of the team to be the "leader". This is not the same as the selection representative; that can change from moment to moment, but the leader must be fixed. When the team is a frame and its panes, it is natural to make the frame be the leader. Standard mixins are provided to make this easy to do. These mixins and the techniques of using them are described below, and in the following sections.

The selection representative is implemented as the leader’s selection substitute (see (selection-substitutes). Then the team can be selected with the :select operation on its leader window.

Even when the team allows the user some notion of selecting among the windows of the team–such as, when a Zmacs frame in two-window mode allows the user to mouse either of the editor windows to select it– this is implemented most cleanly by starting from the model of a team which does all selection under program control, and defining the appropriate mouse clicks as commands which tell the team’s program to change its selection representative.

Usually you will want to have only a single item appear for the team in the system menu Select option’s menu. If the team consists of a frame which is the leader and its panes, this can be done with tv:inferiors-not-in-select-menu-mixin in the frame’s flavor. More complicated behavior is also possible; for example, Zmacs frames in two-window mode allow each editor window to have its own entry in the Select menu.

Also, Terminal and System commands should reselect the team by selecting its current selection representative. This is done by making them record and reselect the team’s leader. If the team consists of a frame which is the leader and its panes, this can be done with tv:alias-for-inferiors-mixin in the frame’s flavor. (In case you are curious, Zmacs frames follow this pattern exactly. The frame is the alias for any editor windows inside it.)

The following subsections describe the details of how these things are done.

3.2.1 The System Menu Select Option

When the Select option in the system menu is used, it gets the list of alternatives to offer by sending each screen a :selectable-windows message. This operation is normally defined to recurse down the window hierarchy and ask each window whether it wants to be included. Each window is sent a :name-for-selection message. The value should be either nil (omit this window) or a string, which is the string to display in the menu of windows.

.defmetamethod windows :selectable-windows Returns an alist of strings versus windows, which will become part of the alist that will be displayed in the system menu Select option’s menu. The alist returned should describe this window and its inferiors, or whichever of them ought to appear in that menu.

The normal definition includes this window using its :name-for-selection as the car of the alist element, or omits this window if its :name-for-selection is nil. It then appends the :selectable-windows values obtained from the window’s inferiors. .end_defmetamethod

.defflavor tv:inferiors-not-in-select-menu-mixin This mixin redefines :selectable-windows to ignore the window’s inferiors. They are not asked whether they should be included. This is an easy way to make a team of a frame and its panes have only one entry, the entry for the frame. .end_defflavor

.defmetamethod windows :name-for-selection This operation is supposed to return a string to display in the system menu Select option’s menu of windows for this window. It may also return nil, meaning do not list this window in the menu.

The default definition uses the window’s label string if any, or else its name. Many applications redefine the operation. tv:not-externally-selectable-mixin redefines it to return nil.

If you want more complicated behavior from a team than simply having a single entry, you can get it by redefining this operation on the flavors of various windows in the team.

This operation also affects autoselection, done by the screen manager. A window can be autoselected only if its :name-for-selection is non-nil. .end_defmetamethod

3.2.2 Selection with Terminal and System Commands

Variable: tv:previously-selected-windows

This variable’s value is an art-q array whose contents are all the active windows, not including the selected window, which the Terminal and System key commands for window selection should know about. The windows of a team are generally all represented by a single member of the team, which we can call the "leader". Typically the "leader" is a frame which contains the rest of the team as panes, but this is not required.

The Terminal S command can be thought of as acting on a combined list that contains the selected window followed by the previously selected windows. So, Terminal n S rotates the first n elements of this list, so that the selected window becomes the first previously-selected window, and the nth previously selected window becomes the selected window. The System key also uses this data base to find a window of the appropriate flavor to select, or to rotate through all the windows of that flavor.

Windows are put on tv:previously-selected-windows and taken off of it automatically when they are selected, deselected, activated or deactivated. Attention is required from the applications programmer only to identify teams of windows that should be treated as a unit. The system uses the :alias-for-selected-windows operation to inquire about this.

.defmetamethod windows :alias-for-selected-windows Should return the window to represent this one in tv:previously-selected-windows. When this window gets deselected, its alias is what will be recorded in that array. In the simple paradigmatic case of independent Lisp listeners, the alias of each Lisp listener is itself. For a window in a team, this should return the team’s "leader" window.

The default definition of this operation is to return the superior’s :alias-for-inferiors if that is non-nil, otherwise to return this window itself. .end_defmetamethod

.defmetamethod windows :alias-for-inferiors Should return a window to serve as the alias for all inferiors to all levels of this window, if that is desired. Otherwise it should return nil.

The default definition returns this window’s superior’s :alias-for-inferiors. Thus, if an ancestor of this window says it wants to be an alias for all of its descendants, we pass on its request, but otherwise we allow the descendants to decide for themselves. .end_defmetamethod

.defflavor tv:alias-for-inferiors-mixin This mixin makes a window be an alias for all of its inferiors. Thus, the window and all of its inferiors form a team considered as a unit by the Terminal and System commands, and this window is the "leader". .end_defflavor

If two windows in a hierarchy, one above the other, both have tv:alias-for-inferiors-mixin, then the higher one "wins". Put another way, windows are grouped into the largest possible teams, and there are no subteams within teams.

Note also that no record is kept of which window in a team was actually selected most recently. tv:previously-selected-windows records only the alias or team leader window, and this is the window that will receive the :select message if a Terminal command is given to switch back to that team. The way to make sure that the proper window within the team is selected is to use selection substitutes, as described in the following section.

.defflavor tv:not-externally-selectable-mixin This mixin makes a window (and its descendants) have the window’s superior as an alias, and keeps the window out of the Select menu.

Using this mixin, you can control more closely which windows are distinguished by the Select menu and by Terminal commands: instead of making the top of the team’s hierarchy be an alias for all of its descendants, specifically chosen descendants are given this mixin so that they are not distinguished, and any other descendants remain distinguished.

An older name for this mixin, which still works, is tv:dont-select-with-mouse-mixin. .end_defflavor

3.3 Selection Substitutes

Every window has the ability to designate a "selection substitute". If a window has a substitute, requests to select or deselect the original window will be passed along to the substitute. The substitute may have a substitute of its own, and so on. A window’s selection substitute is remembered in the instance variable tv:selection-substitute, whose value is another window or nil.

.defmetainstvar windows tv:selection-substitute The window’s selection substitute, or nil. .end_defmetainstvar

The main use of selection substitutes is for controlling selection within a team of windows. The team has one window designated as the leader; all user requests to select the team come as :select messages to the team leader as a result of arrangements described in the previous section. As a result, the team’s program can choose a selected window within the team by making it the leader’s selection substitute.

The :alias-for-selected-windows of the substitute window should be the same as that of the window it substitutes for, to avoid paradoxical results from the Terminal command. With a hierarchical team of windows, this is usually arranged by using tv:alias-for-inferiors-mixin in the top window of the team. The substitute window should not appear in the system menu Select menu, for if it did, its entry and the entry for the window it substitutes for would be functional duplicates. tv:inferiors-not-in-select-menu-mixin in the top window of the team serves to prevent the duplicate entry.

These operations on windows are provided for working with selection substitutes:

.defmetamethod windows :selection-substitute Returns this window’s selection substitute, or nil if the window does not currently have one. .end_defmetamethod

.defmetamethod windows :ultimate-selection-substitute Returns this window’s substitute’s substitute... and so on until a window is reached that has no substitute. If this window has no substitute, it itself is returned. .end_defmetamethod

.defmetamethod windows :self-or-substitute-selected-p t if this window, or its substitute, or its substitute’s substitute, etc., is selected. .end_defmetamethod

.defmetamethod windows :set-selection-substitute substitute Sets this window’s selection substitute to substitute (another window or nil). If it was previously the case that this window or its substitute was selected, then the window’s new substitute (or the window itself) will be selected afterward. Thus, the value of :self-or-substitute-selected-p on this window is not changed by this operation. .end_defmetamethod

Note that when the team’s program uses :set-selection-substitute on the team’s leader window to change the selected pane within the team, it does not matter whether the team is currently selected. The "right" results will happen if the team is deselected and reselected at any time.

To switch the selected pane temporarily, use

Special Form: tv:with-selection-substitute (window for-window) body...

Executes body with window as the substitute for for-window. On exit, it sets for-window back to whatever it used to be, and deexposes or deactivates window if appropriate.

Also useful is

Special Form: tv:preserve-substitute-status window body...

Executes body, then selects window if window or its substitute had been selected to begin with.

.defmetamethod windows :remove-selection-substitute window-to-remove suggested-substitute Makes sure that window-to-remove is not this window’s substitute, suggesting suggested-substitute (possibly nil) as a substitute instead. The standard implementation of this operation simply sets the substitute to suggested-substitute if the substitute was window-to-remove. This operation is used and documented so that particular windows can define their own ways of calculating the new value for the substitute, perhaps ignoring suggested-substitute.

When a typeout window is deactivated, this operation is used to make sure that it ceases to be another window’s substitute. .end_defmetamethod

3.3.1 Non-Hierarchical Selection Substitutes

Some programs wish to "replace" one window with another temporarily. For example, the functions supdup and telnet can behave this way, giving the appearance of temporarily changing the Lisp listener or other window in which they are called into a Supdup or Telnet window. They do this by creating a suitable Supdup or Telnet window and making it the substitute for the original window. In this case, the substitute window will have the same edges and the same superior as the original window. It is not an inferior of the original window. It is not required that the "replacement" window be the same size as the original, either.

Non-inferior selection substitutes are usually established and deestablished by using tv:with-selection-substitute in a straightforward manner. The only thing that requires special attention is to make sure that the original window is the :alias-for-selected-windows of the substitute. In the case of supdup this is desirable to complete the illusion that the Lisp listener has "magically" changed temporarily into a Supdup window. Since the substitute window is not a descendant of the original one, it must have some other way to find the original window (such as an instance variable for the specific purpose) and a specially defined :alias-for-selected-windows method to return the original window.

3.4 The Status of a Window

A window’s status is a keyword that encodes whether the window is selected, whether it is exposed, and whether it is active.

.defmetamethod windows :status Returns one of these symbols:

  • :selected Means this is the selected window.
  • :exposed Means this is exposed but not selected. It may not be visible.
  • :exposed-in-superior Means this window is exposable but its superior has no screen array.
  • :deexposed Means this window is active in its superior but not exposable.
  • :deactivated Means this window is not even active.

.end_defmetamethod

.defmetamethod windows :set-status status Restores the window’s status to status, by selecting or deselecting, exposing or deexposing, and activating or deactivating, as necessary. status must be one of the possible values of the :status operation.

The :status and :set-status operations are useful for selecting a window temporarily and then restoring everything as it was. :set-status is correct for this because it may be necessary to deexpose the window or deactivate it in addition to deselecting it. .end_defmetamethod

3.5 Windows and Processes

A self-contained interactive system that has its own window(s) usually has its own process to drive the windows. Peek, Zmacs, ZMail and the inspector all do this when invoked through the System key. Usually each window you create has its own process; there is a Peek process for each Peek window, so different Peek windows run completely independently.

Whether a window is managed by a dedicated process or by various processes is not a crucial decision. The program that reads commands from the window and draws on the window can always be run in one dedicated process, or in different processes at different times (though if you run it in two processes at once, you had better be careful to keep them from confusing each other). The mechanisms of selection and exposure that control whether input and output are possible on a window at a given time work automatically on any process(es) that try to do the input or output. So when there is a dedicated process for a window, often the only connection between them is that the dedicated process is running a program that has a pointer to that window (typically the value of terminal-io in the process is that window).

For example, the inspector you get with System I has a dedicated process, whereas the one you get by calling inspect runs in the process that inspect is called in. Yet these two windows have the same flavor, and the same function, tv:inspect-command-loop, does the main work. The only differences are in deciding when to deexpose the window, what to do when that happens, when it can be reused, what to do if the user types End, and other things related directly to the difference in the two user interfaces for entering and exiting.

The inspector makes an instructive example for comparing these two ways of managing a window. The function inspect allocates a window out of a resource of reusable windows of the right flavor (see defresource, (defresource-fun)). It sends the window some messages to initialize it for this particular session of use; this is how it tells the window about the object that is the argument to inspect. Then it selects the window manually using tv:window-call (see (tv:window-call-fun)) and calls the inspector program. When the user types End, the program returns, tv:window-call reselects the old window and deactivates the inspect window, and inspect returns. inspect uses an unwind-protect so that aborting outside of inspect for any reason brings back the old window.

Typing System I finds or creates an an inspect window of the same flavor. When no init options are specified, this flavor’s default init plist specifies the creation of a process, which is initialized to call the inspector program. If the user types End and the inspector command loop returns, the top-level function in the dedicated process buries the inspector window and loops back to the beginning. That’s all that is necessary to make System I work. The resource that inspect uses explicitly specifies an init option when it constructs a window, so that no process is made.

.defflavor tv:process-mixin Provides an instance variable tv:process which can remembers a process associated with the window. A window that will sometimes be used with a dedicated process should have this mixin.

The most valuable service that this flavor provides is an easy way to create and initialize a process for each window that is created, and inform the process which window it was created for. Once this is done, for the most part the desired results follow without special effort.

Selecting the window or making it visible will give the process a run reason. The window itself is used as the run reason. Also, this will reset the process if it is flushed (waiting with false as its wait function).

The :kill operation on the window will invoke the :kill operation on the process.

Use of tv:process-mixin guarantees that the :process operation will return the explicitly specified process, regardless of which process has most recently read from the window. .end_defflavor

.definstvar tv:process-mixin tv:process The process associated with the window, or nil. .end_definstvar

.definitoption tv:process-mixin :process process-or-description Specifies the process for this window. The argument can be a process, or it can be a list, which is used as a description for creating a process. The list looks like

(initial-function make-process-options)

When the process starts up, it will call initial-function with the window as its sole argument. Usually the initial function should bind terminal-io to the argument.

If this option is omitted or nil, the window starts out without a process. .end_definitoption

.defmethod tv:process-mixin :process .defmethod1 tv:process-mixin :set-process process Gets or sets the process associated with this window. nil is a legal value, which means that the window has no process associated with it, even though it has the ability to have one. .end_defmethod

.defmetamethod windows :processes Returns a list of processes dedicated to this window. :append method combination is used, so that all the processes mentioned by any of the methods are put into the final answer. These are the processes that the :kill operation will kill.

The default is to return nil. tv:process-mixin contributes a suitable method. .end_defmetamethod

These process-related operations are defined on tv:select-mixin so that they are always supported by the selected window. Since windows lacking tv:process-mixin do not explicitly remember a process, a heuristic is used to come up with a process to operate on: it is the last process to have read input from this window’s input buffer. (Think about the fact that the input buffer may be shared with other windows.)

tv:process-mixin is always put before tv:select-mixin in the components of a window flavor, so this method will be overridden.

.defmethod tv:select-mixin :process Gets a process somehow associated with this window, heuristically if necessary. .end_defmethod

.defmethod tv:select-mixin :set-process process Records process in the place where the last process to read input from this window would normally be recorded. .end_defmethod

.defmethod tv:select-mixin :arrest .defmethod1 tv:select-mixin :un-arrest Arrests or unarrests the process returned by the :process operation. The arrest reason used or revoked is not specified (it defaults). .end_defmethod

.defmethod tv:select-mixin :call Selects an idle Lisp listener window (possibly this window, if it is an idle Lisp listener). If the window selected is not this one, arrest this window’s process with arrest reason :call. This arrest reason is removed automatically by selecting this window again. .end_defmethod

.defflavor tv:reset-on-output-hold-mixin Causes any process that tries to draw on this window when it has an output hold to be reset when it does so (see the :reset operation on processes, (si:process-reset-method)). .end_defflavor

.defflavor tv:truncating-pop-up-text-window-with-reset A temporary window that truncates lines and also resets processes that try to output on it when it has output hold. This flavor is what Terminal F uses. .end_defflavor

4 Sizes and Positions

This chapter is about examining and setting the sizes and positions of windows. There are many different operations that let you express things in different forms that are convenient in varying applications. Usually, sizes are in units of pixels. However, sometimes we refer to widths in units of characters and heights in units of lines. The number of horizontal pixels in one character is called the character-width, and the number of vertical pixels in one line is called the line-height; these two quantities are explained on (char-width-and-line-height).

A window has two parts: the inside and the margins. The margins include borders, labels, and other things; the inside is used for drawing characters and graphics. Some of the operations below deal with the outside size (including the margins) and some deal with the inside size.

Since a window’s size and position are usually established when the window is created, we will begin by discussing the init-options that let you specify the size and position of a new window. To make things as convenient as possible, there are many ways to express what you want. The idea is that you specify various things, and the window figures out whatever you leave unspecified. For example, you can specify the right-hand edge and the width, and the position of the left-hand edge will automatically be figured out. If you underspecify some parameters, defaults are used. Each edge defaults to being the same as the corresponding inside edge of the superior window; so, for example, if you specify the position of the left edge, but don’t specify the width or the position of the right edge, then the right edge will line up with the inside right edge of the superior. If you specify the width but neither edge position, the left edge will line up with the inside left edge of the superior; the same goes for the height and the top edge.

In order for a window to be exposed, its position and size must be such that it fits within the inside of the superior window. If a window is not exposed, then there are no constraints on its position and size; it may overlap its superior’s margins, or even be outside the superior window altogether.

All positions are specified in pixels and are relative to the outside of the superior window.

4.1 Init Options for Sizes and Positions

.defmetainitoption windows :left left-edge .defmetainitoption1 windows :x left-edge .defmetainitoption1 windows :top top-edge .defmetainitoption1 windows :y top-edge .defmetainitoption1 windows :position (left-edge top-edge) .defmetainitoption1 windows :right right-edge .defmetainitoption1 windows :bottom bottom-edge .defmetainitoption1 windows :width outside-width .defmetainitoption1 windows :height outside-height .defmetainitoption1 windows :size (outside-width outside-height) .defmetainitoption1 windows :inside-width inside-width .defmetainitoption1 windows :inside-height inside-height .defmetainitoption1 windows :inside-size (inside-width inside-height) .defmetainitoption1 windows :edges (left-edge top-edge right-edge bottom-edge) These options set various position and size parameters. The size and position of the window are computed from the parameters provided by these and other options, and the set of defaults described above. Note that all edge parameters are relative to the outside of the superior window. .end_defmetainitoption

.defmetainitoption windows :character-width spec This is another way of specifying the width. spec is either a number of characters or a character string. The inside width of the window is made to be wide enough to display those characters, or that many characters, in font zero. .end_defmetainitoption

.defmetainitoption windows :character-height spec This is another way of specifying the height. spec is either a number of lines or a character string containing a certain number of lines separated by carriage returns. The inside height of the window is made to be that many lines. .end_defmetainitoption

.defmetainitoption windows :integral-p t-or-nil The default is nil. If this is specified as t, the inside dimensions of the window are made to be an integral number of characters wide and lines high, by making the bottom margin larger if necessary. .end_defmetainitoption

.defmetainitoption windows :edges-from source Specifies that the window is to take its edges (position and size) from source, which can be one of:

  • a list The elements of the list should be the four edges, left, top, right and bottom, all relative to this window’s superior.
  • a string The inside-size of the window is made large enough to display the string, in font zero.
  • a list (left-edge top-edge right-edge bottom-edge) Those edges, relative to the superior, are used, exactly as if you had used the :edges init-option (see above).
  • :mouse The user is asked to point the mouse to where the top-left and bottom-right corners of the window should go. (This is what happens when you use the Create command in the system menu, for example.)
  • a window That window’s edges are copied.

.end_defmetainitoption

.defmetainitoption windows :minimum-width n-pixels .defmetainitoption1 windows :minimum-height n-pixels In combination with the :edges-from :mouse init option, these options specify the minimum size of the rectangle accepted from the user. If the user tries to specify a size smaller than one or both of these minima, he will be beeped at and prompted to start over again with a new top-left corner. .end_defmetainitoption

The group of operations below is used to examine or change the size or position of a window. Many operations that change the window’s size or position take an argument called option. The reason that this argument exists is that certain new sizes or positions are not valid. One reason that a size may not be valid is that it may be so small that there is no room for the margins; for example, if the new width is smaller than the sum of the sizes of the left and right margins, then the new width is not valid. A new setting of the edges is also invalid if the window is exposed and the new edges are not enclosed inside its superior. In all of the operations that take the option argument, option may be either nil or :verify. nil means that you really want to set the edges, and if the new edges are not valid, an error should be signalled. :verify means that you only want to check whether the new edges are valid or not, and you don’t really want to change the edges. If the edges are valid, the operation with :verify returns t; otherwise it returns two values: nil and a string explaining what is wrong with the edges. (Note that it is valid to set the edges of a deexposed inferior window in such a way that the inferior is not enclosed inside the superior; you just can’t expose it until the situation is remedied. This makes it more convenient to change the edges of a window and all of its inferiors sequentially; you don’t have to be careful about what order you do it in.)

4.2 Flavor Operations for Sizes and Positions

.defmetamethod windows :size Returns two values, the outside width and outside height. .end_defmetamethod

.defmetamethod windows :height .defmetamethod1 windows :width Return the window’s height or its width. .end_defmetamethod

.defmetamethod windows :set-size new-width new-height &optional option Sets the outside width and outside height of the window to new-height and new-width, without changing the position of the upper-left corner. .end_defmetamethod

.defmetamethod windows :inside-size Returns two values, the inside width and the inside height. .end_defmetamethod

.defmetamethod windows :inside-height .defmetamethod1 windows :inside-width Return the inside height of the window or the inside width. .end_defmetamethod

.defmetamethod windows :set-inside-size new-inside-width new-inside-height &optional option Set the inside width and inside height of the window to new-inside-height and new-inside-width, without changing the position of the upper-left corner. The margin sizes are recomputed according to their contents, which in simple cases means they will stay the same. .end_defmetamethod

.defmetamethod windows :position Returns two values, the x and y positions of the upper-left corner of the window, in pixels, relative to the superior window. .end_defmetamethod

.defmetamethod windows :set-position new-x new-y &optional option Sets the x and y position of the upper-left corner of the window, in pixels, relative to the superior window. .end_defmetamethod

.defmetamethod windows :edges Returns four values, the left, top, right, and bottom edges, in pixels, relative to the superior window. .end_defmetamethod

.defmetamethod windows :set-edges new-left new-top new-right new-bottom &optional option Sets the edges of the window to new-left, new-top, new-right, and new-bottom, in pixels, relative to the superior window. .end_defmetamethod

.defmetamethod windows :inside-edges Returns four values, the left, top, right, and bottom inside edges, in pixels, relative to the top-left corner of this window. This can be useful for clipping. Note that this operation is not analogous to the :edges operation, which returns the outside edges relative to the superior window. .end_defmetamethod

.defmetamethod windows :center-around x y Without changing the size of the window, positions the window so that its center is as close to the point (x,y) as is possible without hanging off an edge. The coordinates are in pixels relative to the superior window. .end_defmetamethod

.defmetamethod windows :expose-near mode &optional (warp-mouse-p t) If the window is not exposed, changes its position according to mode and exposes it (with the :expose operation; see (windows/ and/ screens-expose-method)). If it is already exposed, does nothing.

mode should be a list; it may be any of the following:

  • (:point x y) Position the window so that its center is as at the point (x,y), in pixels, relative to the superior window, or as close as possible without hanging off an edge of the superior.
  • (:mouse) This is like the :point mode above, but the x and y come from the current mouse position instead of the caller. This is like what pop-up windows do. In addition, if warp-mouse-p is non-nil, the mouse is warped (see (warp)) to the center of the window. (The mouse moves only if the window is near an edge of its superior; otherwise the mouse is already at the center of the window.)
  • (:rectangle left top right bottom) The four arguments specify a rectangle, in pixels, relative to the superior window. The window is positioned somewhere next to but not overlapping the rectangle. In addition, if warp-mouse-p is non-nil, the mouse is warped (see (warp)) to the center of the window.
  • (:window window-1 window-2 window-3 ...) Position the window somewhere next to but not overlapping the rectangle that is the bounding box of all the window-ns. You must provide at least one window. Usually you only give one, and this means that the window is positioned touching one edge of that window. In addition, if warp-mouse-p is non-nil, the mouse is warped (see (warp)) to the center of the window.

.end_defmetamethod

.defmetamethod windows :change-of-size-or-margins &rest options This is the primitive operation for changing a window’s size or the size of its margins. All the other operations to do so end up calling this one, after all error checking has been done.

This operation should not be called by users; to change the size, use :set-size or another higher-level operation, and the margin sizes should be managed by the flavors that are responsible for computing how big they should be (tv:borders-mixin, etc.).

However, this operation is a good place to add :after daemons to recompute other data structure or change the size of inferiors according to the window’s new size. In the :after daemon, the window’s size and margins will already be altered to their new values. .end_defmetamethod

4.3 Low Level Edges Functions

.defmetainstvar windows tv:x-offset .defmetainstvar1 windows tv:y-offset The position of the window’s outside left (or top) edge relative to the window’s superior. .end_defmetainstvar

.defmetainstvar windows tv:width .defmetainstvar1 windows tv:height The total width or height of the window. .end_defmetainstvar

Recall that a sheet is either a window or a screen.

Function: tv:sheet-width window
Function: tv:sheet-height window
Function: tv:sheet-x-offset window
Function: tv:sheet-y-offset window

Return the value of the corresponding instance variable of window. These are accessor defsubsts created by the :outside-accessible-instance-variables option of defflavor. They can therefore be setf’d, but doing so is usually unwise.

Function: tv:sheet-inside-width &optional (window self)
Function: tv:sheet-inside-height &optional (window self)

Return the inside width or height of the window.

When used without an argument, these defsubsts refer directly to the instance variables, and therefore must be called from methods or functions which use (declare (:self-flavor ...)).

Function: tv:sheet-number-of-inside-lines &optional (window self)

Returns the number of lines (of height equal to tv:line-height) that fit in the inside height of the window.

When used without an argument, these defsubsts refer directly to the instance variables, and therefore must be called from methods or functions which use (declare (:self-flavor ...)).

Function: tv:sheet-calculate-offsets window superior

Returns the x and y positions of window’s upper left corner in superior as two values. window must be an indirect inferior of superior, zero or more levels down. If window and superior are the same window, the values are zero.

Function: tv:sheet-overlaps-p sheet left top width height
Function: tv:sheet-overlaps-edges-p sheet left top right borrom

t if sheet overlaps the specified rectangle. The edges specified are relative to sheet’s superior.

Function: tv:sheet-overlaps-sheet-p sheet-a sheet-b

t if the two sheets overlap. This is a geometrical test, and it does not matter where in the hierarchy the two sheets are.

Function: tv:sheet-within-p sheet left top right bottom

t if sheet is contained within the specified rectangle, given relative to sheet’s superior.

Function: tv:sheet-within-sheet-p sheet outer-sheet

t if sheet is within outer-sheet’s area. This is a geometrical test, and it does not matter where in the hierarchy the two sheets are.

Function: tv:sheet-bounds-within-sheet-p left top width height outer-sheet

t if the specified rectangle is within outer-sheet. The edges are specified relative to outer-sheet’s superior.

Function: tv:sheet-contains-sheet-point-p sheet top-sheet x y

t if sheet contains the point (x,y) in top-sheet.

5 Input

Windows can be given the ability to function as input streams (see (stream-protocol)). This is implemented by the mixin tv:stream-mixin, which is a component of tv:window. (Originally, both input and output stream operations were defined on this mixin, but now the output operations are available on all windows since a window is fairly useless if you don’t draw on it.) Input characters normally come from the terminal keyboard, but can also come from mouse clicks, or anything else you may decide to program to generate input.

.defflavor tv:stream-mixin This mixin defines the standard input stream operations for doing input from the keyboard, as well as some nonstandard input operations defined in the following sections. .end_defflavor

Keyboard input is done through windows so that selection of windows can control which process can read input at a given time. In fact, this is why the concept of selection exists: by making each process that does its output to a window also use that window to read input, and by making a single "selected" window the only window on which input operations can proceed, we enable the user to decide which process to direct his input to by selecting the corresponding window.

Reading characters from a window normally returns a fixnum that represents a character in the Lisp Machine character set, possibly with extra bits that correspond to the Control, Meta, Super, and Hyper keys. Character constants in code are written with the #\ or #/ construct and are described in the Lisp Machine manual in (character-set).

Programs decode keyboard characters with ldb and dpb using the following byte fields:

Though keyboard input characters are currently fixnums, it is possible that a new, special data type for characters will exist in the future. The #\ construct will produce a character object rather than a fixnum, and the elements of a string will be character objects rather han fixnums. Characters will behave just like fixnums in arithmetic and = but will not be eq to fixnums. The :tyi and related stream operations will continue to return fixnums; new operations will be defined which return character objects instead. It will still be possible to use ldb and dpb with these byte field names on fixnums and character objects indiscriminately.

Note that reading characters from a window does not echo the characters; it does not type them out. If you want echoing, you can echo the characters yourself, or call the higher-level functions such as tyi, read, and readline; these functions accept a window as their stream argument and will echo the characters they read. This is in accord with the general Lisp Machine input stream conventions.

The console hardware actually sends codes to the Lisp Machine whenever a key is depressed or lifted; thus, the Lisp Machine knows at all times which keys are depressed and which are not. You can use the tv:key-state function to ask whether a key is down or up. Also, you can arrange for reading from a window to read the raw hardware codes exactly as they are sent, by putting a non-nil value of the :raw property on the property list of the input buffer; however, the format of the raw codes is complicated and dependent on the hardware implementation. It is not documented here.

Variable: tv:kbd-last-activity-time

The value returned by the function time when the last input character was typed.

5.1 Input Buffers

Every window that generates input or from which input is read must have an input buffer that holds characters that are typed by the user before any program reads the characters. When you type a character, it enters the selected window’s buffer. (This is not precisely true, but it’s a good first mental model. See (io-buffer).) Reading input from a window, with the :tyi operation for example, takes objects out of the window’s input buffer. tv:stream-mixin gives the window an input buffer, but some other flavors (such as command menus) provide an input buffer without tv:stream-mixin. The input buffer lives in an instance variable of the window, called tv:io-buffer.

Input buffers are examples of I/O buffers, which are a general facility provided by the window system. You can explicitly manipulate input buffers in order to get certain advanced functionality, by using the :io-buffer init-option and the :io-buffer and :set-io-buffer operations. Another thing you can do is put properties on the I/O buffer’s property list; this lets you request various special features. I/O buffers are explained on (io-buffer).

A window can be thought of as generating input when the keyboard is used while the window is selected. This is the way that ordinary characters normally get into the input buffer. But input can be generated at any place in the program by means of the :force-kbd-input operation. For example, mouse clicks are often handled by forcing input which is read by the window’s command-interpreting process (see (tv:list-mouse-buttons-mixin-flavor)). Then we say that the mouse click also generates input.

All the input, no matter how generated, ends up mixed together in the same input buffer, in chronological order. All the input operations take input from the buffer in that order.

Normally each window that can generate input has its own input buffer. If a process is managing more than one window that can generate input, a program to look for input from all the windows at once would be cumbersome. So it is not done this way. Instead, all the windows are made to share a single input buffer. Then all input generated by all of the windows goes into that buffer, from which the input can be read through any one of the windows. The program simply reads input from one of the windows–always the same one, if the programmer prefers–and gets all the input intended for it. All the keyboard input directed at it, and all mouse clicks on its windows, get merged into a single chronological input stream.

The input buffer does not record which window was "responsible" for generating input read from a shared input buffer. For mouse clicks the program may need to know which window the mouse was clicked on in order to obey the command properly. The standard way to pass this information is to use a list as the input character and make the window clicked on one of the elements of the list.

The window(s) used for input operations must have tv:stream-mixin. The other windows need only be able to put input into the right input buffer. It is often easiest to use tv:stream-mixin for them as well, and generate the input with :force-kbd-input. However, it is sufficient for such windows to support the :io-buffer operation by returning the correct shared input buffer, and put the input they generate into that buffer in any way that works, such as with the function tv:io-buffer-put, or by invoking :force-kbd-input on another window known to have tv:stream-mixin and to share the same input buffer.

If a frame includes a pane that is handled by its own process (such as a Zmacs frame), that pane should not share the input buffer used by the rest of the panes. In general, there should be one input buffer for each process you are using, and that input buffer should be shared by the windows which go with that process.

In general, the way to make windows share an input buffer is to create one using tv:make-default-io-buffer and then specify it for the :io-buffer init keyword when each pane is created. There are also frame flavors that automatically make the panes share an input buffer.

.definstvar tv:stream-mixin tv:io-buffer The window’s input buffer. .end_definstvar

.definitoption tv:stream-mixin :io-buffer spec Initializes the input buffer of the window. spec may be an I/O buffer, a number or a list. If it is a number, an I/O buffer is made with that size, no input function, and the default output function. If it is a list, it is interpreted as

(size input-function output-function)

but if the output-function is nil or omitted, tv:kbd-default-output-function is used. .end_definitoption

.defmethod tv:stream-mixin :io-buffer .defmethod1 tv:stream-mixin :set-io-buffer io-buffer Return or set the window’s input buffer. .end_defmethod

5.2 Blips

Input need not be made of characters; lists are often used as well for program-generated input, especially for representing mouse clicks in different kinds of mouse-sensitive areas. "Characters" which are lists are called blips. The car of the list is by convention a symbol which identifies the kind of blip. Look for "blip types" in the concept index to find the places in this manual that define various kinds of blips.

Caution: when using blips, you should keep in mind that the blips may be discarded if the process has called any function that does not know what to do with them. The debugger and break are such functions, so this can happen at any time. Blips either should describe mouse actions, which can safely be ignored if they happen when they are not meaningful, or should notify the process to check other data structures. A blip should not be used to indicate a request or response from another process, since this information must not be lost. Instead, put the data on a separate queue and have the process check the queue after every command. A blip that executes as a no-op command will serve to wake the process up if it is waiting for input when the data goes on the queue.

There is a technique you can use to cause blips to be handled even in the middle of calls to read, the debugger, and other programs that do not look for blips. It is to give your window flavor an :around method for :any-tyi. This :around method can look at the value being returned; if it is one of certain types of blips, you can handle it and then loop around, calling the original :any-tyi handler again without returning to the caller. If it is anything else, you just return it.

5.3 Stream Input Operations

.defmethod tv:stream-mixin :any-tyi &optional eof-action Reads and returns the next character of input from the window, waiting if there is none. The character comes from the window’s input buffer if it contains any characters; otherwise, it comes from the keyboard. eof-action is ignored since "end-of-file" is not meaningful for windows; this argument exists only because it is part of the input stream protocol. .end_defmethod

.defmethod tv:stream-mixin :tyi &optional eof-action Like :any-tyi but throws away any blips ("characters" which are lists) that it receives. It keeps on reading until it finds an actual character, and returns that. Discarded blips will never be seen as input. .end_defmethod

.defmethod tv:stream-mixin :any-tyi-no-hang &optional eof-action Like :any-tyi if input is already present in the buffer, but returns nil right away if the buffer is empty. This is used by programs that continuously do something until a key is typed, then look at the key and decide what to do next. .end_defmethod

.defmethod tv:stream-mixin :tyi-no-hang &optional eof-action Like :any-tyi but throws away any blips ("characters" which are lists) that it receives. It keeps on reading until it finds an actual character or the buffer empty; then it returns the character or nil. Discarded blips will never be seen as input. .end_defmethod

.defmethod tv:stream-mixin :mouse-or-kbd-tyi .defmethod1 tv:stream-mixin :mouse-or-kbd-tyi-no-hang These are like the :tyi and :tyi-no-hang operations, except that blips of a certain kind are not discarded and do count as input. These are blips whose car is the symbol :mouse-button. In this case, the first value returned is the third element (caddr) of the blip, and the second value returned is the whole blip. By convention, the third element of such a blip is a character whose %%kbd-mouse bit is 1, which identifies the button that the user clicked (see (mouse-blip)). All other blips are discarded, as they are by :tyi and :tyi-no-hang. The first value is always a fixnum. .end_defmethod

.defmethod tv:stream-mixin :list-tyi This is the "opposite" of :tyi. It returns only blips and discards real characters. .end_defmethod

.defmethod tv:stream-mixin :untyi character Put character back into the window’s input buffer so that it will be the next character returned by :tyi. Note that character must be exactly the last character that was read, and that it is illegal to do two :untyi’s in a row. This is used by parsers that look ahead one character, such as read. .end_defmethod

.defmethod tv:stream-mixin :force-kbd-input input input is inserted into the window’s input buffer, to be read by the :tyi or other input operation in its turn. input may be a character or a list (a blip). It may also be a string; then all the characters of the string are forced as input, one by one.

This is the standard way that blips are put into the input stream (see (blips)). .end_defmethod

.defmethod tv:stream-mixin :listen Returns t if there are any characters available to :tyi, or nil if there are not. For example, the editor uses this to defer redisplay until it has caught up with all of the characters that have been typed in. .end_defmethod

.defmethod tv:stream-mixin :wait-for-input-with-timeout timeout Waits until either input is available or timeout 60ths of a second have elapsed. .end_defmethod

.defmethod tv:stream-mixin :clear-input Clears this window’s input buffer. This flushes all the characters that have been typed at this window, but have not yet been read. .end_defmethod

.defmethod tv:stream-mixin :playback Returns an array describing the last n characters read from this window, for some value of n (which is the size of the array). The array elements are used in a circular fashion, the last one being followed by the first one, and array leader element 1 contains the index of the last slot stored into (the one containing the last character read). The editor command Help L uses this operation. .end_defmethod

.defmethod tv:stream-mixin :rubout-handler options function &rest args Applies function to args inside an environment where inputting from this window will echo the characters typed and provide for simple input editing. This is documented in more detail in the Lisp Machine manual.

options is an assq list of keyword symbols and arguments to them. The options acceptable to windows are:

  • :full-rubout flag If the user rubs out all of the characters that he has typed in, normally the rubout-handler just waits for more characters. If the :full-rubout option is supplied, the rubout handler returns to the caller in this situation. Two values are returned, nil and flag.
  • :initial-input string Treat the characters in string as typeahead before reading anything from the keyboard.
  • :pass-through ch1 ch2... Treat the characters ch1, ch2, etc. as ordinary characters even if they would normally be special commands to the rubout-handler.
  • :prompt function function is a function to be called before reading any characters; typically it will display a prompt. The arguments to function are the window and a flag. When the rubout-handler is first entered the flag is nil, but if it is necessary to prompt again, for instance if the user cleared the screen, function is called with the character the user typed (e.g. #\clear-screen) as its flag argument.

    function can also be a string; then it is simply printed as the prompt.

  • :reprompt function The same as :prompt except that the function is not called the first time through. If both :prompt and :reprompt are used, the :prompt is used the first time and the :reprompt is used on reprinting.

.end_defmethod

.defmethod tv:stream-mixin :save-rubout-handler-buffer Returns a description of the rubout handler buffer’s contents, and clears it out. Two values are returned: a string and a fixnum (which is the current cursor index in the string). This is used on entry to the function break so that typing the Break key interfaces properly with rubout handling. .end_defmethod

.defmethod tv:stream-mixin :restore-rubout-handler-buffer string index Loads the rubout handler buffer contents from string and sets the cursor position to index. The arguments are usually two values obtained from :save-rubout-handler-buffer. .end_defmethod

.defmethod tv:stream-mixin :refresh-rubout-handler &optional discard-last-character Requests the rubout handler to reprint its buffer and reprompt. If discard-last-character is non-nil, the last character in the buffer is discarded first. This is used by :restore-rubout-handler-buffer.

If you are reading input using the rubout handler, but want to process certain characters immediately (perhaps the character Help) and not leave them as part of the ordinary input, use this operation with argument t. .end_defmethod

.defflavor tv:preemptable-read-any-tyi-mixin This flavor defines the :preemptable-read operation. .end_defflavor

.defmethod tv:preemptable-read-any-tyi-mixin :preemptable-read options function &rest arguments You may have noticed that in the inspector and in the Window Error Handler, if you start typing in a Lisp expression, and then while in the middle of typing it you use the mouse to select an object by pointing at it, the program sees the object you moused. If nothing special were done, though, the blip sent by the mouse process would get put at the end of the input buffer and would not be seen because of the characters that you have typed. This mixin is what is used to solve the problem.

The :preemptable-read operation takes the same arguments as the normal :rubout-handler operation, and does the same thing if the mouse is not used. (In fact, it has nothing to do with the read function, despite the name.) The difference is that if any blip is sent to the window, the operation returns the blip as the first value and the symbol :mouse-char as the second value. (It does this even if the blip did not come from the mouse; most blips do.) The characters that were in the rubout-handler buffer when the blip arrived will come back the next time a :preemptable-read operation is used, so the user can keep typing his expression in. .end_defmethod

These obsolete functions are still used in some old code:

Function: kbd-tyi

Performs :tyi on terminal-io.

Function: kbd-tyi-no-hang

Performs :tyi-no-hang on terminal-io.

Function: kbd-char-available

Performs :listen on terminal-io.

5.4 I/O Buffers

An I/O buffer is an array of fixed size used as a ring buffer. Typically, characters are put into the buffer by one process and removed by another in FIFO order. The process that is removing characters can wait if the buffer is empty, and a process putting in characters can wait if the buffer is full (or it could throw away the characters). Each window with tv:stream-mixin has an input buffer which is an I/O buffer, and there is also one global I/O buffer for the keyboard itself.

Note that the things stored in an I/O buffer can be any Lisp objects. They do not have to be characters, in any sense. But in practice I/O buffers are in fact used for storing characters (which may be lists), so that is how this section is written.

An I/O buffer has these slots in its leader.

  • tv:io-buffer-size The number of slots in the input buffer.
  • tv:io-buffer-input-pointer The index at which the next character inserted should be stored.
  • tv:io-buffer-output-pointer The index at which the next available character is present.

    If the input and output pointers are equal, the buffer is empty. If the output pointer points at the slot after the input pointer, the buffer is considered full (in fact, one slot is still empty. It cannot be used).

  • tv:io-buffer-output-function A function to be called when characters are removed, or nil. It is called with the buffer and the character as arguments. Its value should be a translated version of the character (this is usually the same as the argument). It can also return a non-nil second character, which says that the character should be discarded. In this case, tv:io-buffer-get will remove the following character, or wait for one.

    In window input buffers this is usually a function that checks for and handles synchronous interception.

  • tv:io-buffer-input-function A function to be called when characters are inserted, or nil. The window system does not actually use this feature. The calling conventions are the same as for the output function.
  • tv:io-buffer-state This may be set to t, nil, :input or :output to control what can be done with the buffer. Characters can be put in if this is nil or :input, and can be removed if this is nil or :output.
  • tv:io-buffer-plist A property list containing various properties.
  • tv:io-buffer-last-input-process The last process that put a character in this I/O buffer.
  • tv:io-buffer-last-output-process The last process that removed a character from this I/O buffer.
  • tv:io-buffer-record An array that records the last n characters read from this I/O buffer, for some fixed n. This array too is a ring buffer, but nothing is ever "removed" from it; after it is full, it contains the last n things stored into it. The accessor tv:io-buffer-record-pointer gets the index of the last slot stored into.
Function: tv:io-buffer-empty-p io-buffer
Function: tv:io-buffer-full-p io-buffer

Non-nil if the buffer is empty, or full.

Function: tv:make-io-buffer size input-function output-function plist state

Creates and returns an I/O buffer, initializing some of the slots from its arguments and the others in a default or reasonable fashion. The buffer is initially empty.

Function: tv:io-buffer-put buffer character &optional no-hang-p

Inserts character into buffer, waiting if it is full unless no-hang-p. This function also waits if the buffer’s state does not permit input. It returns t if the character was inserted.

Function: tv:io-buffer-get buffer &optional no-hang-p

Removes the next character from buffer. If the buffer is empty, normally we wait for a character to appear, but if no-hang-p is non-nil we return nil immediately. This function also waits if the buffer’s state does not permit output. The character removed is put in buffer’s io-buffer-record array.

Function: tv:io-buffer-unget buffer character

Inserts character into buffer as the next character to be removed rather than as the last one to be removed. This is used for undoing tv:io-buffer-get, and it is an error if character does not match the last character removed. character is removed from the io-buffer-record array, by backing up its pointer, just to avoid duplication when character is read a second time.

This function should not be used more than once between input operations.

Function: tv:io-buffer-push buffer character

Inserts character into buffer as the next character to be removed; that is, in a LIFO manner. This is as opposed to tv:io-buffer-put which inserts a character at the end.

Function: tv:io-buffer-clear buffer

Makes buffer empty.

Function: tv:process-typeahead buffer function

Uses function as a filter for the characters in buffer. function is called once for each character, with the character as its sole argument. If function returns non-nil, that value is stored back in the buffer instead of the original character. If function returns nil, the character is deleted from the input buffer.

5.4.1 I/O Buffers and Type Ahead

We have said (see (input-buffer)) that keyboard input goes into the selected window’s input buffer. This is not precisely true. Program-generated input made with :force-kbd-input does go directly into the window’s input buffer, but keyboard input actually goes into another I/O buffer called the keyboard input buffer. (There is only one of these in the system.) The characters move from the keyboard input buffer to the selected window’s input buffer whenever a program tries to read input from that buffer and it is empty. The keyboard input is not assigned to a selected window until the instant the program is ready to read it.

Asynchronous window-switching commands, such as Terminal S, and mouse clicks that select a window, actually copy the contents of the keyboard input buffer into the buffer of the window that is being deselected. If you type some commands to the editor, and then type System L before the editor has read its commands, those commands will still go to the editor, not to the Lisp listener you have selected.

By contrast, synchronous window-switching such as is done by the functions ed, supdup and inspect, and by "exit" commands in various programs, do not do this, since any further typed-ahead input should go to the program being switched to.

5.4.2 I/O Buffers as Input Buffers

Function: tv:make-default-io-buffer

Creates and returns an I/O buffer of the sort used for window input buffers, with all slots suitably initialized. The output function used is tv:kbd-default-output-function.

Function: tv:kbd-default-output-function buffer char

This is the default value for a window input buffer’s output function. It checks the character against the value of tv:kbd-intercepted-characters and also checks tv:kbd-tyi-hook.

Function: tv:kbd-io-buffer-get buffer &optional no-hang-p (whostate "Keyboard")

Removes a character from buffer, or possibly from the keyboard input buffer. The keyboard input buffer can be read from only if buffer is the input buffer of the selected window, and it is used only if buffer is empty. When a character is read from the keyboard input buffer, buffer’s output function is executed, as if the character had been put into buffer and then read from there.

whostate is passed as the first argument to process-wait if this function has to wait.

Function: tv:kbd-wait-for-input-with-timeout buffer timeout &optional (whostate "Keyboard")

Waits until either tv:kbd-io-buffer-get would not hang on buffer or timeout elapses. timeout is in 60ths of a second. whostate appears in the who line while we wait.

Function: tv:kbd-wait-for-input-or-deexposure buffer window &optional (whostate "Keyboard")

Waits until either tv:kbd-io-buffer-get would not hang on buffer or window is not exposed. whostate appears in the who line while we wait.

Function: tv:kbd-snarf-input buffer &optional not-from-hardware

Transfer any characters that tv:kbd-io-buffer-get could now get from buffer right into buffer. This is what asynchronous selection commands use to make sure that type-ahead for the window being deselected remains with that window.

Function: tv:kbd-char-typed-p

Non-nil if input is available in the selected window. This can be used in programs that loop with interrupts disabled, to tell when the user types a key.

The window system defines the meaning of certain properties on the tv:io-buffer-plist of a window input buffer. These are

  • :raw Non-nil to inhibit translation of characters from hardware codes to the Lisp Machine character set. The effect of this is hardware dependent.
  • :asynchronous-characters An alist which controls which characters are intercepted asynchronously when this window is selected.
  • :dont-upcase-control-characters Non-nil prevents the Control (etc.) keys from causing special treatment of alphabetic case. Normally, typing Control-Shift-A produces the character #\Control-/a with a lower case "a", while Control-A produces #\Control-A; and the same for Meta, Super and Hyper. If this property is non-nil, the two inputs are interchanged in meaning, so that Shift produces an upper case character with or without Control.

5.5 Intercepted Characters

There are several characters that are specially intercepted by the window system. Some are intercepted when a process tries to read them, and some are intercepted as soon as they are typed. The first kind are called synchronously intercepted characters and the second are called asynchronously intercepted characters. The latter come in two kinds: global asynchronous characters such as Terminal and System which are always available (see (global-asynchronous-characters)), and others defined by the selected window, normally including Control-Abort and so on (see (asynchronous-intercepted-characters)).

5.5.1 Synchronously Intercepted Characters

Synchronous interception is performed by the io-buffer-output-function of the window input buffer (see (io-buffer-output-function)). By default, this function is tv:kbd-default-output-function, which uses the variable tv:kbd-intercepted-characters to decide which characters to intercept and how to handle them. A program can change its set of synchronously intercepted characters simply by binding this variable before reading input. Its default value specifies the characters Abort, Meta-Abort, Break, and Meta-Break.

Variable: tv:kbd-intercepted-characters

The value is an alist specifying the characters to be intercepted synchronously (that is, when read by the program). Since the variable is looked at by a subroutine of the :tyi operation itself, what matters is current binding at the time the :tyi is done.

Each element of this list should look like

(character function).

Then function will be called if character is read, with character as argument.

function should return two values. The second should be non-nil to say that the character has been handled by the function and should not be returned to the calling program as ordinary input. If the second value is nil, the first value should be a translated character to use as input instead of the character typed. (This can be and usually is the same character that was typed.) The first value is ignored if the second is non-nil. In practice, function usually returns its argument and t.

function should begin by setting inhibit-scheduling-flag to nil.

It is reasonable to add new entries to the top level value of this variable, and also for programs to bind the variable. It is probably unwise to remove the standard entries in the top level value.

Variable: tv:kbd-standard-intercepted-characters

This is the that which is the initial value of tv:kbd-intercepted-characters.

Function: tv:kbd-intercept-abort char &rest ignore
Function: tv:kbd-intercept-abort-all char &rest ignore

These functions implement the standard meanings of the Abort and Meta-Abort keys. They are suitable for use in tv:kbd-intercepted-characters. The first signals the sys:abort condition; the second resets the current process.

If terminal-io handles the :inhibit-output-for-abort-p operation and it returns non-nil, the string "[Abort]" will not be printed.

Function: tv:kbd-intercept-break char &rest ignore
Function: tv:kbd-intercept-error-break char &rest ignore

These functions implement the standard meanings of the Break and Meta-Break keys. They are suitable for use in tv:kbd-intercepted-characters. The first calls break; the second invokes the debugger.

Furthermore, if the variable tv:kbd-tyi-hook is non-nil, then it is considered to be a user function that can intercept the character at this point.

By convention, programs are all expected to use the Abort key as a command to abort things in some appropriate sense for that program. If you don’t do anything special, Abort will be intercepted automatically. But some programs may want to do something specific when the user types Abort. The system default action can be replaced by binding the variable tv:kbd-intercepted-characters so that Abort goes to your own intercept routine instead of tv:kbd-intercept-abort, or so that Abort is read as an input character from the stream like any other and then is handled by your program.

Variable: tv:kbd-tyi-hook

The default io-buffer-output-function (tv:kbd-default-output-function), before it does anything else, sees whether the value of tv:kbd-tyi-hook is non-nil; if so, it assumes that the value is a function of one argument, and it applies the function to the character that was typed. If the function returns a non-nil value, then the character will not be returned to callers of :tyi or other input operations; otherwise, the character is processed normally.

The idea is that you can write a function that intercepts anything passing through an input buffer that uses the default io-buffer-output-function. Your function gets passed the character, and returns nil if it doesn’t want to handle it, or t if it has taken care of the character.

5.5.2 Asynchronously Intercepted Characters

Each window that has tv:stream-mixin can define a set of characters to be intercepted asynchronously when that window is selected. The interception is done through a different mechanism from that used for synchronous interception, but the same handling functions such as tv:kbd-intercept-abort can ultimately be used. By default, a window requests asynchronous interception of the four characters Control-Abort, Control-Meta-Abort, Control-Break, and Control-Meta-Break. The default meanings of these keys are given in (call-and-abort). You can change the set of such asynchronous keys on a per-window basis.

Since the interception is done by the keyboard process, the characters cannot straightforwardly be specified by a variable for the program to bind. So each window has a list of them (which is actually stored as the :asynchronous-characters property on the input buffer’s property list).

.definitoption tv:stream-mixin :asynchronous-characters alist alist specifies the characters to be intercepted asynchronously while this window is selected, and what they should do.

Each element consists of a character, a function to call, and optionally some extra arguments to be passed to it. When the function is called, its arguments will be the character, the selected window, and any specified additional arguments from the alist element.

If the init option is not specified, the default comes from the value of tv:kbd-standard-asynchronous-characters, the initial value of which is

((#\c-abort tv:kbd-asynchronous-intercept-character
  (:name "Abort" :priority 50.)
  tv:kbd-intercept-abort)
 (#\c-m-abort tv:kbd-asynchronous-intercept-character
  (:name "Abort All" :priority 50.)
  tv:kbd-intercept-abort-all)
 (#\c-break tv:kbd-asynchronous-intercept-character
  (:name "Break" :priority 40.)
  tv:kbd-intercept-break)
 (#\c-m-break tv:kbd-asynchronous-intercept-character
  (:name "Error Break" :priority 40.)
  tv:kbd-intercept-error-break))

How these work is explained below. .end_definitoption

.defmethod tv:stream-mixin :asynchronous-character-p character Returns non-nil if this window defines character for asynchronous interception. .end_defmethod

.defmethod tv:stream-mixin :handle-asynchronous-character character Invokes the handler a defined for asynchronous interception of character. This runs the handler function in your current process. But since handler functions typically do process-run-function, it usually doesn’t matter. .end_defmethod

.defmethod tv:stream-mixin :add-asynchronous-character character handler-function &rest additional-args Define character for asynchronous interception in this window, to be handled by handler-function and the additional-args. This adds an element

(character handler-function . additional-args)

to the alist on the input buffer’s property list. .end_defmethod

.defmethod tv:stream-mixin :remove-asynchronous-character character Removes character’s element from the alist, so that it is no longer intercepted asynchronously in this process. .end_defmethod

Asynchronous interception is done by the Keyboard process, and the handler function runs in that process. Therefore, it must obey some strict conventions. It must not do any I/O, or wait for anything; it should not run for very long; it should not get an error. It is usually easiest to create another process and do the real work there, using process-run-function.

Function: tv:kbd-asynchronous-intercept-character character window process-options subhandler additional-subhandler-args

This function is provided as a convenient way to set up the handling of an asynchronously intercepted character. It enables you to interface to the same functions used for synchronous interception. It is used with at least two additional arguments: the process name and options for process-run-function, and the function to call in the new process. Thus,

(#\c-break tv:kbd-asynchronous-intercept-character
 (:name "Break" :priority 40.)
 tv:kbd-intercept-break)

arranges to create a process named "Break" with priority 40, and call tv:kbd-intercept-break in that process.

subhandler, which is tv:kbd-intercept-break in this example, is passed as arguments character, window, and the additional-subhandler-args if any.

5.5.3 Global Asynchronous Characters

The Terminal and System keys are also intercepted asynchronously, but since their functions do not usually relate to the selected window, they are not controlled by the selected window’s alist of asynchronous characters. These are called global asynchronous characters.

Variable: tv:kbd-global-intercepted-characters

This is an alist whose value controls the characters intercepted regardless of the selected window. Its elements look and work just like those of the alist specified in the :asynchronous-characters init option for a window.

The initial value is

((#\terminal tv:kbd-esc)
 (#\system tv:kbd-sys))

Terminal and System are defined to call functions that read another character and dispatch on it. The meaning of the second character is controlled by an alist so you can define new Terminal and System commands.

Variable: tv:*escape-keys*

The value of this variable is an alist, each entry of which describes a subcommand of the Terminal key. (Escape is the old name for the Terminal key.) Rather than modifying the list yourself, use tv:add-escape-key or tv:remove-escape-key (below). Entries on the list are of the form:

   (char function documentation option1 option2 ...)

char is the character that should be typed after Terminal to get the new command. The character gets upper-cased before it is searched for in this list, so don’t use lower case characters. function may either be a list to be evaluated, or a symbol, which is the name of a function to be applied to one argument. This is either the numeric argument specified by the user (as in Terminal 0 S), or nil if the user gave no argument.

documentation should be a string giving documentation, or a form that gets evaluated and returns either a string or nil. The string will be printed by Terminal Help, except that nil means to omit this character from the Terminal Help display.

Normally function is evaluated or applied in a new process created for the purpose, but if you give the :keyboard-process option it will run in the keyboard process. This option exists because certain of the built-in commands must work this way. If you add your own, you should not use this option, since you do not want to interfere with the operation of the keyboard process. The cost of creating a new process is quite low.

If the :typeahead option is specified, then everything typed before the Terminal key will be shoved into the selected I/O buffer, i.e. it will be treated as typeahead to the currently selected window. Use this option with commands that change the selected window, to ensure that the user’s typed input goes where he expected it to when he typed it.

Here is a sample element:

   (#\clear-screen
    (tv:kbd-screen-redisplay)
    "Clear and redisplay all windows.")
Function: tv:add-escape-key char function documentation &rest options

Adds an element to tv:*escape-keys*, and puts it in the right place alphabetically.

Function: tv:remove-escape-key char

Removes any element for char from tv:*escape-keys*.

Variable: tv:*system-keys*

The value of this variable is an alist, each entry of which describes a subcommand of the System key. Use the functions tv:add-system-key and tv:remove-system-key (below) to modify the list rather than doing it yourself. Entries are of the form:

   (char find documentation create)

char is the character that should be typed after System to get the new command. The character gets upper-cased before it is searched for in this list, so don’t use lower case characters. documentation should be a string to be printed by System Help.

If find is an instance of a flavor, then it should be a window, and the System command will select that particular window. However, normally find is the name of a flavor. If it is, the System command first searches the previously-selected-windows list for a window of that flavor, and selects one if it finds one. Otherwise, if the currently selected window is of that flavor, it beeps. Otherwise, it looks at create to figure out what to do. find can also be a list; then it is evaluated and the value should be a window or a flavor name to be used as described above.

If create is nil, it beeps. If create is t, a new window of flavor find is created by calling make-instance with no options, and is selected. If create is some other symbol, it is the name of the flavor of window to be created. (This can be different from the flavor to look for, which might be a mixin that is component of several different flavors all of which are suitable to select when this key is typed.) Otherwise, create is a form to be evaluated to create a window. The System command runs in a newly-created process and so the form is evaluated in its own process, not the keyboard process.

If the character typed after the System key is typed with the Control shift, existing windows are ignored and a new window is created according to create.

Here is a sample element:

   (#/E zwei:zmacs-frame "Editor" t)
Function: tv:add-system-key char find documentation &optional (create t)

Adds an element to tv:*system-keys*, and puts it in the right alphabetical position.

Function: tv:remove-system-key char

Removes any element for char from tv:*system-keys*.

Function: tv:find-window-of-flavor flavor-name

Returns a previously selected window of flavor flavor-name. Windows are found in tv:previously-selected-windows ((tv:previously-selected-windows-var)) and checked with typep.

Function: tv:select-or-create-window-of-flavor flavor-name

Selects a previously selected window of flavor flavor-name, or, if none exists, creates a new one and selects it.

5.6 Polling The Keyboard Explicitly

Another way of using the keyboard, different from reading a stream of input characters from a window, is to treat it as a "random access" device and look at the instantaneous state of particular keys. Spacewar does this.

Function: tv:key-state key-name

Returns t if the keyboard key named key-name is currently depressed, nil if it is not.

key-name may be the symbolic name of a shift key, from the table below, or the character code of a non-shift key, which is the character you get when you type that key without any shifts: a lower-case letter, a digit, or a special character. Shift keys that come in pairs have three symbolic names; one for the left-hand key, one for the right-hand key, and one for both, which is considered to be depressed if either member of the pair is. The shift key names are:

:shift		:left-shift		:right-shift
:greek		:left-greek		:right-greek
:top		:left-top		:right-top
:control	:left-control		:right-control
:meta		:left-meta		:right-meta
:super		:left-super		:right-super
:hyper		:left-hyper		:right-hyper
:caps-lock	:alt-lock		:mode-lock
:repeat

6 Output of Text

All windows can function as output streams, displaying the output as if on the screen of an ordinary display terminal. The flavor tv:minimum-window implements the operations of the Lisp Machine output stream protocol (see (stream-protocol)), as well as many additional output operations such as :insert-line. Every window has a current cursor position; its main use is to say where to put characters that are drawn. The way a window handles the operations asking it to type out is by drawing that character at the cursor position, and moving the cursor position forward past the just-drawn character.

Cursor position arguments to stream operations are always expressed in "inside" coordinates (see (inside-size)); that is, coordinates relative to the top-left corner of the inside part of the window, so the margins don’t count in cursor positioning. The cursor position always stays in the inside portion of the window–never in the margins. The point (0,0) is at the top-left corner of the window; increasing x coordinates are further to the right and increasing y coordinates are further towards the bottom. (Note that y increases in the down direction, not the up direction!)

.defmetainstvar windows tv:cursor-x .defmetainstvar1 windows tv:cursor-y The window’s current cursor position. Note that these variables use "outside" coordinates, unlike the arguments to stream operations. .end_defmetainstvar

The x cursor position is the position of the left edge of the character box of the next character output. (The leftmost nonzero pixels of the character may be either left or right of the edge of the character box, according to the left-kern of the character; see (font-left-kern)).

The y cursor position is the position of the top of the vertical extent for the line being output. If only a single font is in use, the top of the character box is at this vertical position.

In fact, characters are positioned so that their baselines come out on the baseline of the line. This way, characters of different fonts juxtaposed in one line come out with baselines aligned rather than with their top edges aligned. The position of the character’s baseline is a property of its font. The window’s baseline is computed from the set of fonts in use, to provide enough space above the baseline for any of the fonts (see (windows-tv:baseline-instvar)).

When a character is drawn, it is combined with the existing contents of the pixels of the window according to an alu function. The different alu functions are described in (aluf). When characters are drawn, the value of the window’s char-aluf is the alu function used. Normally, the char-aluf says that the bits of the character should be bit-wise logically ored with the existing contents of the window (tv:alu-ior). This means that if you type a character, then set the cursor position back to where it was and type out a second character, the two characters will both appear, ored together one on top of the other. This is called overstriking. Erasure is also done using an alu function which the window can specify, called the erase-aluf. Normally this is an alu function which ands the old pixel value with the complement of the area erased (tv:alu-andca).

.defmetainstvar windows tv:char-aluf .defmetainstvar1 windows tv:erase-aluf The window’s char-aluf and erase-aluf. .end_defmetainstvar

Reverse-video windows work by interchanging the normal values of the char-aluf and erase-aluf, so that erasing an area sets it to one while drawing a character clears the character’s pixels to zero.

Every window has a font map. A font map is an array of fonts in which characters on the window can be typed. At any time, one of these is the window’s current font; the operations that type out characters always type in the current font. Details of fonts and the font map appear below (see (font-section)). For now, we describe fonts only enough to explain the character-width and line-height of the window; these two units are used by many of the operations documented in this section. The character-width is the char-width attribute–the width of a "typical" character–of the first font in the font map. The line-height is the sum of the vsp of the window and the maximum of the char-heights of all the fonts. The vsp is an attribute of the window that controls how much vertical spacing there is between successive lines of text. That is, each line is as tall as the tallest font is, and you can add vertical spacing between lines by controlling the vsp of the window. Operations for controlling the vsp are documented on (vsp-operations). There is no instance variable holding the vsp, but the system can recompute it from the line-height and the font map.

.defmetainstvar windows tv:char-width .defmetainstvar1 windows tv:line-height The character-width and line-height of the window. The line height is actually used for outputting a #\return character. The character width is not used at all for ordinary output, since each font determines its own widths. Both are used for interpreting cursor positions expressed in characters or lines. .end_defmetainstvar

Every window has a current font, which the operations use to figure out what font to type in. If you are not interested in fonts, you can ignore this and something reasonable will happen. In some fonts, all characters have the same width; these are called fixed-width fonts. The default font is an example. In other fonts, each character has its own width; these are called variable-width fonts. With variable-width fonts, it is not fully meaningful to express horizontal positions in numbers of characters, since different characters have different widths. Some of the functions below do use numbers of characters to designate widths; there are warnings along with each such use explaining that the results may not be meaningful if the current font has variable width.

Function: tv:sheet-cursor-x window
Function: tv:sheet-cursor-y window
Function: tv:sheet-char-aluf window
Function: tv:sheet-erase-aluf window
Function: tv:sheet-char-width window
Function: tv:sheet-line-height window

Accessor defsubsts for the corresponding instance variables. It may be reasonable to setf the first four of them.

6.1 How A Character Is Printed

Typing out a character does more than just drawing the character on the screen. The cursor position is moved to the right place; non-printing characters are dealt with reasonably; if there is an attempt to move off the right or bottom edges of the screen, the typeout wraps around appropriately; more breaks are caused at the right time if more processing is enabled. Here is the complete explanation of what typing out a character does. You may want to remind yourself how the Lisp Machine character set works; see (character-set). You don’t have to worry much about the details here, but in case you ever need to know, here they are. If you aren’t interested, skip ahead to the definitions of the operations.

First, any output exceptions that are present are dealt with, and made to go away. See (output-exceptions), for an explanation of this.

When all exceptions have been dealt with, the character finally gets typed out. If it is a printing character, it is typed in the current font at the cursor position and the cursor position is moved to the right by the width of the character. If it is one of the format effectors #\return, #\tab, and #\backspace, it is handled in a special way to be described in a moment. All other special characters have their names typed out in tiny letters surrounded by a lozenge, and the cursor position is moved right by the width of the lozenge. If an undefined character code is typed out, it is treated like a special character; its code number is displayed in a lozenge.

#\tab moves the cursor position to the right to the next tab stop, moving at least one character-width. Tab stops are equally spaced across the window. The distance between tab stops is tab-nchars times the character-width of the window. tab-nchars defaults to 8 but can be changed (see (windows-tab-nchars-init-option)).

Normally #\return moves the cursor position to the inside left edge of the window and down by one line-height, and clears the line (see (windows-clear-eol-method)). It also deals with more processing and the end-of-page condition as described above. However, if the window’s cr-not-newline-flag is on, the #\return character is not regarded as a format effector and is displayed as "return" in a lozenge, like other special characters.

If the character being typed out is a #\backspace, the result depends on the value of the window’s backspace-not-overprinting-flag. If the flag is 0, as is the default, the cursor position is moved left by one character-width (or to the inside left edge, whichever is closer). If the flag is 1, #\backspaces are treated like all other special characters.

6.2 Stream Output Operations

.defmetamethod windows :tyo ch &optional font Type ch on the window, as described above. Basically, type the character ch in font or the current font at the cursor position, and advance the cursor position. .end_defmetamethod

.defmetamethod windows :string-out string &optional (start 0) (end nil) Type string on the window, starting at the character start and ending with the character end. If end is nil, continue to the end of the string; if neither optional argument is given, the entire string is typed. This behaves exactly as if each character in the string (or the specified substring) were printed with the :tyo operation, but it is much faster. .end_defmetamethod

.defmetamethod windows :fat-string-out string &optional (start 0) (end nil) Type the fat string string on the window. This is like :string-out except that the %%ch-font field of each character is used as the font to draw that character in. The window’s current font is not used. .end_defmetamethod

.defmetamethod windows :line-out string &optional (start 0) (end nil) Do the same thing as :string-out, and then advance to the next line (like typing a #\return character). The main reason that this operation exists is so that the stream-copy-until-eof function (see (stream-copy-until-eof-fun)) can, under some conditions, move whole lines from one stream to another; this is more efficient than moving characters singly. The behavior of this operation is not affected by the :cr-not-newline-flag init-option (see (windows-cr-not-newline-flag-init-option)). .end_defmetamethod

.defmetamethod windows :string-out-centered string left right y-pos Output string (or the portion from start to end), centered between x positions left and right, at y position y-pos (which defaults to the current cursor position). The cursor is left at the end of the string. If the string is multiple lines, the entire rectangular shape it occupies is centered as a unit. To center lines individually, output each line individually with this operation. .end_defmetamethod

.defmetamethod windows :fresh-line Get the cursor position to the beginning of a blank line. Do this in one of two ways. If the cursor is already at the beginning of a line (that is, at the inside left edge of the window), clear the line to make sure it is blank and leave the cursor where it was. Otherwise, advance the cursor to the next line and clear the line just as if a #\return had been output. The behavior of this operation is not affected by the :cr-not-newline-flag init-option (see (windows-cr-not-newline-flag-init-option)). .end_defmetamethod

.defmetamethod windows :beep &optional beep-type Attempt to attract the user’s attention, by either making a sound with the keyboard or flashing the screen into and out of inverse video or both.

If beep’s value is nil, both are done. If the value is :beep, only the sound is made. If it is :flash, only flashing the screen is done.

No standard meanings have been assigned to beep-type yet. .end_defmetamethod

Function: beep &optional beep-type (stream standard-output)

Beeps by sending a :beep message to stream, passing beep-type as an argument. If the stream does not handle the :beep operation, a sound is made on the keyboard instead.

.defmetamethod windows :display-lozenged-string string Output string in a lozenge. This is how special characters are echoed. .end_defmetamethod

Function: tv:sheet-line-out sheet string start end set-xpos set-ypos dwidth

This is a complicated primitive whose interface is arranged to do exactly what the editor needs for buffer display, to make the editor as fast as possible.

It outputs part of string on sheet like the :fat-string-out operation, but stops if it reaches the right margin (outputting a right margin character if any output remains, if the window calls for that).

If set-xpos and set-ypos are non-nil, the cursor is moved there and a :clear-eol is done, before output starts. If one of these arguments is nil, that dimension of cursor position is not changed. If both are nil, the cursor is not moved and nothing is cleared.

If dwidth is non-nil, it should be a positive number. Output actually starts at index (1- start) in the string, and at x position dwidth less that the cursor position (as found or as set by set-xpos). However, if a :clear-eol is done, it starts at set-xpos. Non-nil dwidth is to be used if the previous character of the string is in an italic font, and is already present on the screen before the output now being done. It causes that character to be output again, presumably overprinting itself, in case a corner of it was erased accidentally because it protrudes to the right of its allocated space.

Returns two values, the final index in the string and the final x cursor position. The window’s cursor is not guaranteed to be moved there; it is undefined on exit from this function. But the value will be correct.

6.3 Output Exceptions

Before doing output to a window, various exceptional conditions are checked for. If an exceptional condition is discovered, a standard operation is invoked to handle it. Redefining or adding daemons to these operations can change the handling of exceptions. For example, output with the cursor too close to the right margin causes an end of line exception; the handling of this exception is what moves the cursor to the next line, or truncates the line, or whatever the window’s flavor arranges for.

The exceptions are actually indicated by flags, bits, set in the window. The operation to handle the exception should do nothing if it is invoked when the corresponding flag is not set, and should not return with the flag still set (or an error will be signaled). The end-of-page and more flags are set and cleared automatically by moving the cursor; as long as things are done properly, they will be set if and only if the cursor is in the right place for them. So the exception handler need only make sure to move the cursor to a good place. The output hold exception handler usually just waits for or brings about a situation in which the reason for the output hold is gone (usually because the window has been exposed).

.defmetamethod windows :handle-exceptions Performs the exception processing described by all the rest of this section. Exceptions are processed in this order:

Output Hold, End-of-Page, **MORE**, and End-of-Line. .end_defmetamethod

6.3.1 Output Hold and End of Page Exceptions

First, if the window’s output hold flag is set, an output hold exception happens. The operation :output-hold-exception is invoked to handle it.

Function: tv:sheet-output-hold-flag window

Returns the output hold flag of window, which is 1 if there is a hold and 0 if not. This is a setf’able accessor defsubst.

.defmetamethod windows :output-hold-exception This operation should not return until the output hold is gone. It may wait for the output hold flag to be cleared, or try to cause it to be cleared. The default handler acts based according to the window’s deexposed typeout action (see (deexposed-typeout-action)). .end_defmetamethod

Next, if the end-of-page flag is set (normally the case if the y-position of the cursor is less than one line-height above the inside bottom edge of the window), the :end-of-page-exception operation is invoked.

Function: tv:sheet-end-page-flag window

Returns the end-of-page flag of window, which is 1 if the next output operation should wrap and 0 otherwise. This is a setf’able accessor defsubst.

.defmetamethod windows :end-of-page-exception This operation is invoked to handle the end-of-page exception when present. It should do nothing if invoked when the flag is zero.

The default definition is simply to move the cursor to the top line, clear that line, and set the vertical position for the next **MORE** if more-processing is enabled. .end_defmetamethod

6.3.2 **MORE** Exceptions

Next, if the window’s more flag is set, a more exception happens. The more flag gets set when the cursor is moved to a new line (e.g. when a #\return is typed) and the cursor position is thus made to be below the more vpos of the window. (If tv:more-processing-global-enable is nil, this exception is suppressed and the more flag is turned off.) The :more-exception operation is invoked to handle the exception.

Function: tv:sheet-more-flag window

Returns the more flag, which is 1 if the next output operation should do a **MORE**, and 0 otherwise. This is a setf’able accessor defsubst.

.defmetainstvar windows tv:more-vpos The vertical position at which the next **MORE** should happen in output on the window. .end_defmetainstvar

.defmetamethod windows :more-vpos Returns the window’s tv:more-vpos. .end_defmetamethod

Function: tv:sheet-more-vpos window

Accessor defsubst for the preceding instance variable.

Variable: tv:more-processing-global-enable

**MORE** processing does not happen if this variable is nil during the output operation in which the **MORE** would have happened.

.defmetamethod windows :more-exception The :more-exception handler in the tv:minimum-window flavor does a :clear-eol operation, types out **MORE**, reads a character using the :more-tyi operation, restores the cursor position to where it originally was when the :more-exception was detected, does another :clear-eol to wipe out the **MORE**, and resets the more vpos. The character read in is ignored.

This operation works by calling a subroutine, tv:sheet-more-handler, if the more flag is set. It should do nothing if the flag is zero. It is safe to redefine it to call that function with different arguments, or to do other things as well. It is very risky to write a new definition from scratch, as tv:sheet-more-handler is tricky. .end_defmetamethod

Function: tv:sheet-more-handler &optional (operation ':tyi) (more-string "**MORE**")

Implements the standard handling of more exceptions, described above, using operation to read the input and more-string as the output to be printed and then erased.

Note that the more flag is set only when the cursor moves to the next line, because a #\return is typed out, after a :line-out, or by the :end-of-line-exception handler described below. It is not set when the cursor position of the window is explicitly set (e.g. with :set-cursorpos); in fact, explicitly setting the cursor position clears the more flag. The idea is that when typeout is being streamed out sequentially to the window, more-exceptions happen at the right times to give the user a pause in which to read the text that is being typed, but when cursor positioning is being used the system cannot guess what order the user is reading things in and when (if ever) is the right time to stop. In this case it is up to the application program to provide any necessary pauses.

The algorithm for setting the more vpos is too complicated to go into here in all its detail, and you don’t need to know exactly how it works, anyway. It is careful never to overwrite something before you have had a chance to read it, and it tries to do a **MORE** only if a lot of output is happening. But if output starts happening near the bottom of the window, there is no way to tell whether it will just be a little output or a lot of output. If there’s just a little, you would not want to be bothered by a **MORE**. So it doesn’t do one immediately. This may make it necessary to cause a **MORE** break somewhere other than at the bottom of the window. But as more output happens, the position of successive **MORE**s is migrated and eventually it ends up at the bottom.

.defflavor tv:autoexposing-more-mixin If you mix in this flavor, when a :more-exception happens, the window will be exposed (an :expose message will be sent to it). This is intended to be used in conjunction with having a deexposed typeout action of :permit (see (deexposed-permit)), so that a process can type out on a deexposed window and then have the window expose itself when a **MORE** break happens. .end_defflavor

6.3.3 End of Line Exceptions

Finally, if the cursor is at or near the end of the line so that there is no room to output the next character, an end-of-line exception happens. The :end-of-line-exception operation is invoked to handle it. A flag is not used to trigger this exception since the condition depends on the width of the character to be output.

.defmetamethod windows :end-of-line-exception This operation is defined by default to advance the cursor to the next line, just as typing a #\return character does normally (see below). Doing so may, in turn, cause an :end-of-page-exception or a :more-exception to happen. Furthermore, if the right margin character flag is on (see (windows-right-margin-character-flag-init-option)), then before going to the next line, an exclamation point in font zero is typed at the cursor position. When this flag is on, end-of-line exceptions are caused a little bit earlier, to make room for the exclamation point. .end_defmetamethod

.defmetamethod windows :tyo-right-margin-character If a right-margin character is to be printed, this operation is invoked to print it. It can simply :tyo the character. .end_defmetamethod

The way the cursor position goes to the next line when it reaches the right edge of the window is called horizontal wraparound or continuation. You can make windows that truncate lines instead of wrapping them around by using tv:line-truncating-mixin.

.defflavor tv:line-truncating-mixin This mixin gives a window the ability to truncate lines at the right margin instead of continuing output onto the next line as usual (see continuation, (continuation)). Truncation is performed if the window’s truncate-line-out flag is set. When the cursor position is near the right-hand edge of the window and there is an attempt to type out a character, the character simply will not be typed out. .end_defflavor

.definitoption tv:line-truncating-mixin :truncate-line-out-flag flag Initializes the truncate-line-out flag of the window to flag. One means truncate and zero means do not. .end_definitoption

Function: tv:sheet-truncate-line-out-flag window

Returns the truncate-line-out flag of the window, which is zero or one. One means truncate and zero means do not; however, the flag matters only if tv:line-truncating-mixin is in use. This is a defsubst which may be setf’d.

.defflavor tv:truncating-window This flavor is built on tv:window with tv:line-truncating-mixin mixed in. If you instantiate a window of this flavor, it will be like regular windows of flavor tv:window except that lines will be truncated instead of wrapping around. .end_defflavor

6.4 Cursor Motion

The window’s cursor position is where the upper left corner of the next output character will appear, with a vertical offset if necessary to match up the baselines of various fonts (see (font-baseline)). Recall that cursor position arguments and values of stream operations are relative to the inside upper left corner of the window.

.defmetamethod windows :read-cursorpos &optional (units ':pixel) Return two values: the x and y coordinates of the cursor position. These coordinates are in pixels by default, but if units is :character, the coordinates are given in character-widths and line-heights. (Note that character-widths don’t mean much when you are using variable-width fonts.) .end_defmetamethod

.defmetamethod windows :increment-cursorpos x y &optional (units ':pixel) Advances the cursor position the specified amount in each coordinate. The units may be specified as with :read-cursorpos. This operation is considered to be sequential motion of the cursor through a variable amount of space, rather than instantaneous jumping of the cursor. What this means is that exceptions happen, just as if output were being done. So the cursor wraps around at the margins (or does whatever this window does for :end-of-line-exception and :end-of-page-exception), and **MORE** processing happens at the appropriate place. .end_defmetamethod

The following few operations do cursor motion rather than advancing the cursor. The end-of-page, more and end-of-line exception flags will be set if the cursor is moved to a position where they ought to be on, and can be cleared if they were previously on and the cursor is moved to a place where they ought to be off. Exception handling does not take place.

.defmetamethod windows :set-cursorpos x y &optional (units ':pixel) Moves the cursor position to the specified coordinates. The units may be specified as with :read-cursorpos. If the coordinates are outside the window, move the cursor position to the nearest place to the specified coordinates that is in the window. .end_defmetamethod

.defmetamethod windows :home-cursor Moves the cursor to the upper left corner of the window. .end_defmetamethod

.defmetamethod windows :home-down Moves the cursor to the lower left corner of the window. .end_defmetamethod

.defmetamethod windows :forward-char &optional char Moves the cursor forward one character position, or the width of char in the current font if char is specified. Exceptions are processed, so this is like outputting a space which has the appropriate width. .end_defmetamethod

.defmetamethod windows :backward-char &optional char Moves the cursor backward one character position, or the width of char in the current font if char is specified. Exceptions are processed, but there is no reverse-wraparound. At the left margin, the cursor does not move. .end_defmetamethod

.defmetamethod windows :size-in-characters Returns two values, the dimensions of the window, in units of character-widths and line-heights. (Note that character-widths don’t mean much when you are using variable-width fonts.) .end_defmetamethod

.defmetamethod windows :set-size-in-characters width-spec height-spec &optional option Sets the inside size of the window, according to the two specifications, without changing the position of the upper-left corner. width-spec and height-spec are interpreted the same way as arguments to the :character-width and :character-height init-options, respectively. option is passed along to :set-edges ((windows-set-edges-method)). .end_defmetamethod

6.5 Erasing

All the erasing operations operate on the window pixels by drawing the area to be erased using the window’s erase-aluf as the alu function (see (windows-tv:erase-aluf-instvar)). This is by default tv:alu-andca, which clears the screen bits of the screen area drawn.

.defmetamethod windows :clear-char &optional char Erases the character at the current cursor position. When using variable-width fonts, you tell it the character code of the character you are erasing, so that it will know how wide the character is (it assumes the character is in the current font). If you don’t pass the char argument, it simply erases a character-width, which is fine for fixed-width fonts. .end_defmetamethod

.defmetamethod windows :clear-string string &optional start end Erases enough space, starting at the cursor, to contain string (or the portion of string from start to end), printed in the current font. The entire height of the line is erased, so it does not matter whether the text on the screen is string or something else. string determines only how far to erase. If a fixed-width font is in use, this is equivalent to doing :clear-char once for each character in string. This operation becomes desirable because of variable-width fonts. .end_defmetamethod

.defmetamethod windows :clear-eol Erases from the current cursor position to the end of the current line; that is, erases a rectangle horizontally from the cursor position to the inside right edge of the window, and vertically from the cursor position to one line-height below the cursor position. .end_defmetamethod

.defmetamethod windows :clear-eof Erases from the current cursor position to the bottom of the window. In more detail, first does a :clear-eol, and then clears all of the window past the current line. .end_defmetamethod

.defmetamethod windows :clear-screen Erases the whole window and moves the cursor position to the upper left corner of the window. .end_defmetamethod

.defmetamethod windows :clear-between-cursorposes start-x start-y end-x end-y Erases an area starting at cursor position start-x and start-y, wrapping around if necessary at the end of the line or the page, until end-x and end-y are reached.

Though the arguments are expressed as cursor positions, the cursor position of the window is not changed. .end_defmetamethod

6.6 Inserting and Deleting Lines and Characters

Inserting a character means printing it at the cursor but pushing the rest of the text on the line toward the right margin. Similarly, deleting a character means pulling the following text on the line back toward the left so that the position occupied by the character is closed up. Inserting and deleting lines work the same way vertically, moving the lines below the cursor down or up.

The operations that take a numeric argument specifying the amount of space to insert or delete also take an argument specifying the unit (either :pixel or :character) in which the space has been measured. The unit argument’s meaning is the same as in the :read-cursorpos operation ((windows-read-cursorpos-method)) but the default is :character rather than :pixel.

.defmetamethod windows :delete-char &optional (n 1) (unit ':character) Without an argument, deletes the character at the current cursor position. Otherwise, deletes n characters (or n pixels if unit is :pixel), starting at the cursor position. Move the display of the part of the current line that is to the right of the deleted section leftwards to close the resultant gap. (If unit is :character, this assumes all characters are one character-width wide, and so will not do anything useful with variable-width fonts.) .end_defmetamethod

.defmetamethod windows :delete-string string &optional (start 0) (end nil) This is for deleting specific strings in the current font. It is one of the things to use when dealing with variable-width fonts.

If string is a string, excise a region exactly as wide as that string, or a substring specified by start and end, and moves the display of the part of the current line that is to the right of the excised region leftwards to close the gap.

If string is a number, it is considered to be a character code. The single character is treated like a string containing that character. .end_defmetamethod

.defmetamethod windows :delete-line &optional (n 1) (unit ':character) Without an argument, deletes the line that the cursor is on. Otherwise deletes n lines, or n rows of pixels if unit is :pixel, starting with the one the cursor is on. Moves the display below the deleted section up to close the resulting gap. .end_defmetamethod

.defmetamethod windows :insert-char &optional (n 1) (unit ':character) Opens up a space the width of n characters (or n pixels if unit is :pixel) in the current line at the current cursor position. Shifts the characters to the right of the cursor further to the right to make room. Characters pushed past the right-hand edge of the window are lost. (If unit is :character, this assumes all characters are one character-width wide, and so will not do anything useful with variable-width fonts.) .end_defmetamethod

.defmetamethod windows :insert-string string &optional (start 0) (end nil) (type-too t) Inserts a string at the current cursor position, moving the rest of the line to the right to make room for it.

The string to insert is specified by string; a substring thereof may be specified with start and end, as with :string-out.

string may also be a number, in which case the character with that code is inserted.

If type-too is specified as nil, the string is not actually printed. The space opened up is big enough for the string, but is left blank. .end_defmetamethod

.defmetamethod windows :insert-line &optional (n 1) (unit ':character) Takes the line containing the cursor and all the lines below it, and moves them down one line. The line containing the cursor is moved in its entirety, not broken, no matter where the cursor is on the line. A blank line is created at the cursor. If an argument n is given, opens up n blank lines, or n rows of pixels if unit is :pixel. Lines pushed off the bottom of the window are lost. .end_defmetamethod

6.7 Anticipating the Effect of Output

The following operations do not output, but provide information about what would happen to the cursor and the screen if output were done.

.defmetamethod windows :character-width char &optional (font tv:current-font) Returns the width of the character char, in pixels. The current font is used if font is not specified. If char is a Backspace, :character-width can return a negative number. For Tab, the number returned depends on the current cursor position. If char is Return, the result is defined to be zero. .end_defmetamethod

.defmetamethod windows :compute-motion string &optional (start 0) (end nil) (x tv:cursor-x) (y tv:cursor-y) (cr-at-end-p nil) (stop-x 0) stop-y bottom-limit right-limit font line-height tab-width This is used to figure out where the cursor would end up if you were to output string using :string-out. It does the right thing if you give it just the string as an argument. start and end can be used to specify a substring as with :string-out. x and y can be used to start your imaginary cursor at some point other than the present position of the real cursor. If you specify cr-at-end-p as t, it pretends to do a :line-out instead of a :string-out; a Return is output after the specified portion of the string.

stop-x and stop-y define the size of the imaginary window in which the string is being printed; the printing stops if the cursor becomes simultaneously  both of them. The imaginary printing stops after the character which goes past the stop point. Note that this is not the same as the meaning of the stop-x argument to the :string-length operation! The stop coordinates default to the lower left-hand corner of the window. (This corner is reached before the lower right-hand one, since output goes from left to right on each line.)

bottom-limit and right-limit are vertical and horizontal positions at which to wrap around; they default to the inside height and width of the window. They differ from the stop-x and stop-y in that these act independently when the cursor reaches either one, and they cause the cursor position to change rather than terminating processing.

The computation normally uses font, or the window’s current font if font is nil. However, if string is of type art-fat-string, each character’s %%ch-font field is used as an index in the window’s font map to find the font for that character, and font is ignored except possibly for defaulting the tab-width.

For vertical spacing, line-height is used. The default for line-height is font’s line height if font is non-nil, else the window’s line-height.

tab-width specifies the distance between tab stops, in pixels. If it is omitted, the default is (tv:sheet-tab-width self) if no font is specified, or (* (tv:sheet-tab-nchars self) (tv:font-char-width font)) if a font is specified.

Four values are returned:

  • final-x
  • final-y The positions at which output stopped.
  • final-index nil if the entire imaginary output was completed without reaching or passing the stop point; or the index in string of just after the character that reached or passed the stop point; or t if an implicit Return was requested and it reached or passed the stop point.
  • maximum-x The largest x position value reached during processing.

All coordinates for this operation are cursor positions, relative to the window’s inside edges. However, if you specify all the arguments you can use any origin of coordinate system you like, as long as you interpret the values in the same coordinate system. .end_defmetamethod

.defmetamethod windows :string-length string &optional (start 0) (end nil) stop-x (font current-font) (start-x 0) tab-width This is very much like :compute-motion, but works in only one dimension. It tells you how far the cursor would move if string were to be displayed in the current font starting at the left margin, or at start-x if that is specified. start and end work as with :string-out to specify a substring of string. If stop-x is not specified or nil, the window is assumed to have infinite width; otherwise the simulated display will stop before any character which would move the cursor past stop-x pixels from the left edge. If a character exactly reaches stop-x, it can fit. Note that this is not the same as the handling of stop-x in the :compute-motion operation.

The computation normally uses font, or the window’s current font if font is nil. However, if string is of type art-fat-string, each character’s %%ch-font field is used as an index in the window’s font map to find the font for that character, and font is ignored except possibly for defaulting the tab-width.

tab-width specifies the distance between tab stops, in pixels. If it is omitted, the default is (tv:sheet-tab-width self) if no font is specified, or (* (tv:sheet-tab-nchars self) (tv:font-char-width font)) if a font is specified.

:string-length returns three values:

  • final-x Where the imaginary cursor ended up.
  • final-index The index of the next character in the string (the length of the string if the whole string was processed, or the index of the character which would have moved the cursor past stop-x),
  • maximum-x The maximum x coordinate reached by the cursor (this is the same as the first value unless there are Return or Backspace characters in the string).

.end_defmetamethod

6.8 Explicit (Non-Cursor) Output

A window includes some state information which changes as output is done. These include the cursor position, the current font, alu function, and exception flags. The presence of this information makes the window behave coherently as a stream, so that the output from one operation follows that of the previous operation. But sometimes this is not desirable. The "explicit" output operations use a window only for its position and size, with all additional information passed by the caller explicitly. This way, multiple streams of output to the same window can exist, which do not interfere with each other by trying to use a single cursor.

The x and y position arguments used by these operations are relative to the outside edges of the window. This is different from the stream and higher-level operations. It is because these operations are frequently used for drawing parts of the margins, such as labels and margin regions.

.defmetamethod windows :string-out-explicit string start-x start-y x-limit y-limit font alu &optional (start 0) end multi-line-line-height Outputs string (or the portion from start to end) onto the window starting at start-x and start-y, neither using nor moving the window’s cursor position. If x-limit or y-limit is non-nil, output stops if it reaches that position.

Output is done in font using alu function alu. The window’s current font and alu function are not used or set. If there are Return characters in the output, and multi-line-line-height is nil, they are printed as "Return" in a lozenge. If multi-line-line-height is a number, that number is used as the line height, ignoring the window’s line height, and the horizontal output position moves to start-x rather than the left margin for the next line of output.

Note that the arguments of tv:sheet-string-out-explicit are in a different order. The argument order of this operation was cleaned up.

The operation returns three values: the final x position, the final y position, and the final index in the string. You can use these to do multiple operations in consecutive places on the screen. .end_defmetamethod

.defmetamethod windows :string-out-centered-explicit string &optional left y-pos right y-limit font alu (start 0) end multi-line-line-height Outputs string (or the portion from start to end) centered between x positions left and right, at y position y-pos. If y-limit is reached, output stops. left and right default to the inside edges of the window.

Output is done in font and alu, which default to the ones current for the window, and lines are separated by multi-line-line-height (which defaults to the window’s line height). .end_defmetamethod

.defmetamethod windows :string-out-x-y-centered-explicit string &optional left top right bottom font alu start end multi-line-line-height Displays string (or the portion from start to end) with the rectangle it occupies centered both horizontally and vertically. Horizontally it is centered between left and right, and vertically between top and bottom. The defaults for these arguments are the inside edges of the window.

Output is done in font and alu, which default to the ones current for the window, and lines are separated by multi-line-line-height (which defaults to the window’s line height). .end_defmetamethod

6.9 Window Parameters Affecting Output

The following operations and initialization options initialize, get, and set various window attributes which are relevant to the typing out of characters. (See also the operations to manipulate the current font, on (windows-set-current-font-method).)

.defmetainitoption windows :more-p t-or-nil Initializes whether the window should have more processing. It defaults to t. .end_defmetainitoption

.defmetamethod windows :more-p Returns t if more processing (see (more-processing)) is enabled; otherwise, return nil. .end_defmetamethod

.defmetamethod windows :set-more-p more-p If more-p is nil, turns off more processing (see (more-processing)); otherwise turns it on. .end_defmetamethod

.defmetainitoption windows :vsp n-pixels Initializes the window’s vsp. It defaults to 2. .end_defmetainitoption

.defmetamethod windows :vsp Returns the value of vsp for this window (see (vsp)). .end_defmetamethod

.defmetamethod windows :set-vsp new-vsp Sets the value of vsp for this window (see (vsp)) to new-vsp. .end_defmetamethod

.defmetamethod windows :reverse-video-p Returns nil normally or t if the window displays in white on black rather than black on white. This is separate from the whole screen’s inverse video mode, which is what Terminal C sets. .end_defmetamethod

.defmetamethod windows :set-reverse-video-p t-or-nil Enables or disables reverse-video display. Changing this mode inverts all of the bits in the window. .end_defmetamethod

.defmetainitoption windows :reverse-video-p t-or-nil Initializes the use of reverse-video display. .end_defmetainitoption

.defmetainitoption windows :right-margin-character-flag x If x is 1, the window should print an exclamation point in the right margin when :end-of-line-exception happens; if x is 0, it should not. The default is 0. See (right-margin-character-flag-blurb). .end_defmetainitoption

Function: tv:sheet-right-margin-character-flag &optional (window self)

Returns the flag which controls printing of characters at the right margin on wrap-around on window. This is a setf’able accessor macro.

.defmetainitoption windows :backspace-not-overprinting-flag x If x is 0, output of #\backspace will move the cursor position backward; if it is 1, it will display "overstrike" in a lozenge (that is, #\backspace will be just like other special characters). The default is 0. See (backspace-not-overprinting-flag-blurb). .end_defmetainitoption

Function: tv:sheet-backspace-not-overprinting-flag &optional (window self)

Returns the flag which controls how Backspace prints on window. This is a setf’able accessor macro.

.defmetainitoption windows :cr-not-newline-flag x If x is 0, output of #\return will move the cursor position to the beginning of the next line and clear that line; if it is 1, it will display "return" in a lozenge (that is, #\return will be just like other special characters). The default is 0. This flag does not affect the behavior of the :line-out nor the :fresh-line operations. .end_defmetainitoption

Function: tv:sheet-cr-not-newline-flag &optional (window self)

Returns the flag which controls how Return prints on window. This is a setf’able accessor macro.

.defmetainitoption windows :tab-nchars n n is the separation of tab stops on this window, in units of the window’s char-width. This controls how the #\tab character prints. n defaults to 8. .end_defmetainitoption

Function: tv:sheet-tab-nchars &optional (window self)

Returns the distance between tab stops, measured in units of window’s char-width.

Function: tv:sheet-tab-width &optional (window self)

Returns the distance between tab stops, measured in pixels.

7 Fonts

Having used the Lisp Machine for a while, you have probably noticed that characters can be typed out in any of a number of different typefaces. Some text is printed in characters that are small or large, boldface or italic, or in different styles altogether. Each such type face is called a font. A font is conceptually an array, indexed by character code, of pictures showing how each character should be drawn on the screen.

A font is represented inside the Lisp Machine as a Lisp object. Each font has a name. The name of a font is a symbol, usually in the fonts package, and the symbol is bound to the font. A typical font name is tr8. In the initial Lisp environment, the symbol fonts:tr8 is bound to a font object whose printed representation is something like

#<font tr8 234712342>

The initial Lisp environment includes many fonts. Usually there are more fonts stored in QFASL files in file computers. New fonts can be created, saved in QFASL files, and loaded into the Lisp environment; they can also simply be created inside the environment.

Drawing of characters in fonts is done by microcode and is very fast. The internal format of fonts is arranged to make this drawing as fast as possible. This format is described later, but you almost certainly do not need to worry about it.

7.1 Specifying Fonts

You can control which font is used when output is done to a window. Every window has a font map and a current font. The font map is conceptually an array of fonts; with a small non-negative number, the font map associates a font. The current font of a window is always one of the fonts in the window’s font map. Whenever output is done to a window, the characters are printed in the current font. You can change the font map and the current font of a window at any time with the appropriate operations.

The reason why the window has a font map rather than merely a current font is that it is necessary to know all the fonts that will be used before doing any output in order to know how to position the output properly (so that output in different fonts on the same line will look right).

In addition, certain output operations can accept fat strings (arrays of type art-fat-string) which contain 16-bit characters, and regard the top 8 bits of each character as a font number to look up in the font map. These include :compute-motion, :string-length and :fat-string-out.

.defmetamethod windows :font-map Returns the font map of the window. The object returned is the array that is actually being used to represent the font map inside the window. The elements are actual font objects.

You should not alter anything about this array, since the window depends on it in order to function correctly. To change the font map, use the :set-font-map operation. .end_defmetamethod

.defmetamethod windows :set-font-map new-map Sets the font map to contain the fonts given in new-map. Returns the array of fonts that actually represents the font map inside the window (don’t mess with this array!). new-map may be an array of font specifiers, in which case this array is installed as the new internal array of the window, and the font specifiers are replaced by fonts. Font specifiers are described in the following section; a font or the name of a font may be used.

new-map may also be a list of font specifiers, in which case the array is created from the list in the style of fillarray, with the last element of the list filling in the remaining elements of the array if any (the array is made at least 26. elements long, or long enough to hold all the elements of the list).

If new-map is nil, all the elements of the map are set to the default font of the screen.

The current font is set to zero (the first font in the list or array). The line height and baseline of the window are adjusted appropriately (see below).

The specified font specifiers are remembered so that the :change-of-default-font operation can cause the map to be recomputed from them. This is in case one of the specifiers is a purpose keyword. .end_defmetamethod

.defmetainitoption windows tv:font-map new-map This option lets you initialize the font map. new-map is interpreted the same way it is interpreted by the :set-font-map operation. .end_defmetainitoption

.defmetainstvar windows tv:font-map The window’s font map. .end_defmetainstvar

.defmetamethod windows :current-font Returns the current font, as a font object. .end_defmetamethod

.defmetamethod windows :set-current-font new-font Sets the current font of the window. new-font may be a number, in which case that element of the font map becomes the current font. It may also be a font specifier, in which case the font that the specifier describes is used, unless that font is not in the font map, in which case an error is signalled. Only fonts already in the font map may be selected. .end_defmetamethod

.defmetainstvar windows tv:current-font The window’s current font. .end_defmetainstvar

.defmetamethod windows :baseline Returns the maximum baseline of all the fonts in the font map. The bases of all characters will be aligned so as to be this many pixels below the y cursor position, which is top of the line on which the characters are printed. In other words, when a character is drawn, it will be drawn below the cursor position, by an amount equal to the difference between this number and the baseline of the font of the character. .end_defmetamethod

.defmetainstvar windows tv:baseline The position of the baseline of a text line, in pixels from the top of the line’s vertical extent (its cursor position). .end_defmetainstvar

Function: tv:sheet-font-map window
Function: tv:sheet-baseline window
Function: tv:sheet-current-font window

Accessor defsubsts for the corresponding instance variables.

You can use the List Fonts command in Zmacs to get a list of all of the fonts that are currently loaded into the Lisp environment. Here is a list of some of the useful fonts:

  • fonts:cptfont This is the default font, used for almost everything.
  • fonts:medfnt This is the default font in menus. It it a fixed-width font with characters somewhat larger than those of cptfont.
  • fonts:medfnb This is a bold version of medfnt. When you use Split Screen, for example, the Do It and Abort items are in this font.
  • fonts:hl12i This is a variable-width italic font. It is useful for italic items in menus; ZMail uses it for this in several menus.
  • fonts:tr10i This is a very small italic font. It is the one used by the inspector to say "More above" and "More below".
  • fonts:hl10 This is a very small font used for non-selected items in Choose Variable Values windows.
  • fonts:hl10b This is a bold version of hl10, used for selected items in Choose Variable Values windows.

7.1.1 Font Specifiers

Different kinds of screen require different kinds of fonts. The two kinds of screens currently supported are black-and-white screens with one bit per pixel, and color screens with four bits per pixel. Color screens with eight bits per pixel will certainly be supported in the near future, and other kinds of screen may appear. However, it is nice to be able to write programs that will work no matter what screen their window is created on. The problem is that if your program specifies which fonts to use by actually naming specific fonts, then the program will only work if the window that you are using is on the same kind of screen as the fonts you are using were designed for.

To solve this problem, a program does not have to specify the actual font to be used. Instead, it specifies a certain symbol that stands for a whole collection of fonts. All of these fonts are the same except that they work on different kinds of screens. The symbol that you use is the name of the member of the collection that works on the black-and-white screen. In other words, when you want to specify a font, always use the name of a black-and-white font rather than a font itself. Every screen knows how to understand these symbols and find an appropriate font to use. This symbol is called a font specifier, because it describes a font rather than actually being a font.

A font object may be supplied as a font specifier. This does not mean to use the font as specified; it means to use the font’s name as a font specifier. Thus, if you supply the font object for the black-and-white font cptfont for a window on a color screen, the symbol fonts:cptfont is used as a font specifier, resulting in the color version of cptfont actually being used.

The functions that understand font specifiers have some cleverness in order to make life easier for you. If you pass in the name of a font that is not loaded into the Lisp environment, an attempt will be made to load it from the file server, using the name of the font as the name of the file, leaving the version and type unspecified, using the load function. The filename used is SYS: FONTS; fontname QFASL. Also, the color screen knows how to create color versions of fonts on the fly if they do not already exist. Either of these things may make your program run slowly the first time you run it, and so, if you care, you can load the file yourself and create a color version of the font yourself (see (color:make-color-font-fun)).

Since different users like to use different fonts, we provide a facility called font purposes. Wherever a font specifier is used, the program can specify a purpose keyword instead. This means, "use whatever font the user likes to use for this particular purpose". The window remembers when a purpose was specified instead of a particular font, so that if the user changes the standard font for that purpose, all the existing windows that were told to use that purpose will change font. The user specifies a standard font for a purpose with tv:set-default-font, tv:set-standard-font or tv:set-screen-standard-font. Each screen has its own alist mapping font purposes to font names, but normally they are all altered in parallel. Defined purpose keywords include

  • :default "font purpose" This is the font name for ordinary output. It is also called the default font.
  • :menu "font purpose" This is the font name for use in most menu items.
  • :menu-standout "font purpose" This is the font name for menu items that are supposed to stand out. It is normally an italic font.
  • :label "font purpose" This is the font name used by default for labels.
  • :margin-choice "font purpose" This is the default font name for margin choice boxes (see (margin-choice)).

It is up to each program to decide when any of these purpose keywords is appropriate.

.defmethod tv:screen :parse-font-specifier font-specifier Parses a font specifier in the proper way for this window, according to the screen the window is on. The value is a font object. .end_defmethod

.defmethod tv:screen :parse-font-name font-specifier Parses a font specifier in the proper way for this window, according to the screen the window is on. The value is a font name: a symbol which, evaluated repeatedly, ultimately produces a font. .end_defmethod

Function: tv:font-evaluate font-name

Returns the font that font-name is the name of; this is done by evaluating font-name repeatedly until the result is not a symbol.

Function: tv:set-standard-font purpose font-specifier

Sets the standard font for purpose purpose on each screen based on font-specifier. font-specifier is turned into a font by each screen individually, and that font becomes the new standard font for purpose on that screen. All windows on the screen that were set up to use the standard font for this purpose will switch to using the newly specified font.

Function: tv:set-default-font font-specifier

Sets the standard font for purpose :default.

Function: tv:set-screen-standard-font screen purpose font-specifier

Sets the standard font for purpose on screen only.

.defmetamethod windows :change-of-default-font old-font new-font Informs the window that the meaning of some standard font-name symbols has changed. If the window uses any of them, it may need to recompute various things; for example, if that font is used in the label, the window’s inside size may be changed; if it is used in the window’s font map, the line height may be changed. Either one means the number of lines may change, and this may require adjustment of other data. This can be done by an :after daemon on this operation.

In addition, the operation must be passed along to all inferiors and potential inferiors. .end_defmetamethod

7.2 Attributes of Fonts

Fonts, and characters in fonts, have several interesting attributes. One attribute of each font is its character height. This is a non-negative fixnum used to figure out how tall to make the lines in a window. We have mentioned earlier that each window has a certain line height. The line height is computed by examining each font in the font map, and finding the one with the largest character height. This largest character height is added to the vsp specified for the window (see (vsp)), and the sum is the line height of the window. The line height, therefore, is recomputed every time the font map is changed or the vsp is set. It works this way so that there will always be enough room on any line for the largest character of the largest font to be displayed, and still leave the specified vertical spacing between lines. One effect of this is that if you have a window that has two fonts, one large and one small, and you do output in only the small font, the lines will still be spaced far enough apart that characters from the large font will fit. This is because the window system can’t predict when you might, in the middle of a line, suddenly switch to the large font.

Another attribute of a font is its baseline. The baseline is a non-negative fixnum that is the number of raster lines between the top of each character and the base of the character. (The "base" is usually the lowest point in the character, except for letters that descend below the baseline such as lower case "p" and "g".) This number is stored so that when you are using several different fonts side-by-side, they will be aligned at their bases rather than at their tops or bottoms. So when you output a character at a certain cursor position, the window system first examines the baseline of the current font, then draws the character in a position adjusted vertically to make the bases of all the characters line up.

There is another attribute called the character width. This can be an attribute either of the font as a whole, or of each character separately. If there is a character width for the whole font, it is as if each character had that character width separately. The character width is the amount by which the cursor position should be moved to the right when a character is output on the window. This can be different for different characters if the font is a variable-width font, in which a "W" might be much wider than an "i". Note that the character width does not necessarily have anything to do with the actual width of the bits of the character (although it usually does); it is just defined to be the amount by which the cursor should be moved.

There is another attribute that is an attribute of each character separately; it is called the left kern. Usually it is zero, but it can also be a positive or negative fixnum. When the window system draws a character at a given cursor position, and the left kern is non-zero, then the character is drawn to the left of the cursor position by the amount of the left kern, instead of being drawn exactly at the cursor position. In other words, the cursor position is adjusted to the left by the amount of the left kern of a character when that character is drawn, but only temporarily; the left kern affects only where the single character is drawn and does not have any cumulative effect on the cursor position.

A font that does not have separate character widths for each character and does not have any non-zero left kerns is called a fixed-width font. The characters are all the same width and so they line up in columns, as in typewritten text. Other fonts are called variable-width because different characters have different widths and things do not line up in columns. Fixed-width fonts are typically used for programs, where columnar indentation is used, while variable-width fonts are typically used for English text, because they tend to be easier to read and to take less space on the screen.

Each font also has attributes called the blinker width and blinker height. These are two non-negative fixnums that tell the window system a nice-looking width and height to make a rectangular blinker for characters in this font. These attributes are completely independent of everything else and are used only for making blinkers. Using a fixed width blinker for a variable-width font is not the nicest-looking thing to do; instead, the editor actually re-adjusts its blinker width as a function of what character it is on top of, making a wide blinker for wide characters and a narrow blinker for narrow characters. But if you don’t want to go to this trouble, or don’t necessarily know just which character the blinker is on top of, you can just use the font’s blinker width as the width of your blinker. For a fixed-width font there’s no problem.

There is also an array for each font called the char-exists table. It is an art-1b array with a 1 for each character that actually exists in the font, and a 0 for other characters. This table is not used by the character-drawing software; it is just for informational purposes. Characters that do not exist have pictures with no bits "on" in them, just like the "space" character. Most fonts implement most of the printing characters in the character set, but some are missing some characters.

7.3 Format of Fonts

This section explains the internal format in which fonts are represented. Most users do not need to know anything about this format; you can skip this section without loss of continuity.

Fonts are represented as arrays. The body of the array holds the bits of the characters, and the array leader holds the attributes of the font and characters as well as information about the format of the body of the array. Note that there is only one big array holding all the characters, rather than a separate array for each character. The format in which the bits are stored is specially designed to maximize the speed of character drawing and to minimize the size of the data structure, and so it is not as simple you might expect.

FED operates on fonts by converting them into a different type of object containing the same data. This new object is called a font descriptor; it is simpler and easier to work with. See the files SYS: IO1; FNTDEF LISP for the format of font descriptors, and SYS: IO1; FNTCNV LISP for functions to operate on them, and to convert between font descriptors and fonts.

The font format works slightly differently depending on whether the font contains any characters that are wider than thirty-two bits. If there are any such characters, then the font is considered to be "wide", and a single character may be made up of several subcharacters to be drawn side by side. A wide font stores subcharacters instead of characters as such, and has a table indicating which subcharacters belong to each character of the character set. For the time being, we will discuss only narrow fonts in which there is no need to distinguish characters from subcharacters because each character is made of a single subcharacter.

Each character in a font has an array of bits stored for it. The dimensions of this array are called the raster width and raster height. The raster width and raster height are the same for every character of a font; they are properties of the font as a whole, not of each character separately. Consecutive rows are stored in the array; the number of rows per character is the raster height, and the number of bits per row is the raster width. An integral number of rows are stored in each word of the array; if there are any bits left over, those bits are unused. Thus no row is ever split over a word boundary. Rows are stored right-adjusted, from right to left. When there are more rows than will fit into a word, the next word is used; remaining bits at the left of the first word are ignored, and the next row is stored right-adjusted in the next word, and so on. An integral number of words is used for each character.

For example, consider a font in which the widest character is seven bits wide and the tallest character is six bits tall. The raster width of the font is seven and the raster height is six. Each row of a character is seven bits, and so four of them fit into a thirty-two bit word, with four bits wasted. The remaining two rows require a second word, the rest of which will be unused because the number of words per character must be an integer. So this font will have four rows per word, and two words per character. To find the bits for character three of the font, you multiply the character number, three, by the number of words per character, two, and find that the bits for character three start in word six. The rightmost seven bits of word six are the first row of the character, the next seven bits are the second row, and so on. The rightmost seven bits of the seventh word are the fifth row, and the next seven bits of the seventh word are the sixth and last row.

Note that we have been talking about "words" of the array. The character-drawing microcode does not actually care what type the array is; it only looks at machine words as a whole, unlike most of the array-referencing in the Lisp Machine. In a Lisp-object-holding array such as an art-q array, the leftmost eight bits are not under control of the user, and so these kinds of arrays are not suitable for fonts. In general, you need to be able to control the contents of every bit in the array, and so usually fonts are art-1b arrays. This means you need to know the internal storage layout of bits within an art-1b array in order to fully understand the font format, so here it is: the zeroth element of an art-1b array is the rightmost bit of the zeroth word, and successive elements are stored from right to left in that word. The thirty-third element is the rightmost bit in the next word, and so on.

Now, if there are any characters in the font that are wider than 32 bits, then even a single row of the font will not fit into a word. Such characters are divided into subcharacters no more than 32 bits wide, and the character is drawn by drawing all its subcharacters, one by one, side by side. The character drawing microcode can only handle ordinary narrow characters, and it is invoked once for each subcharacter in order to draw a wide character. In order to make this work, the wide font stores subcharacters in the same way a narrow font stores its characters.

In addition, the wide font has a font indexing table which gives the first subcharacter number for each character code. (In a narrow font, the font indexing table is nil.) The character W would be drawn by finding the value at index 127 (the code for W) in the font indexing table, and the value at index 130. Suppose that these are 171 and 173. Then W is made up of subcharacters 171 and 172. Either of these subcharacters’ bits can be found in the same way that the bits for character code 171 or 172 would be found in a narrow font.

The array leader of a font is a structure defined by defstruct. Here are the names of the accessors for the elements of the array leader of a font.

Function: tv:font-name font

The name of the font. This is a symbol whose value is the font and which serves to name the font. The print-name of this symbol appears in the printed representation of the font.

Function: tv:font-char-height font

The character height of the font; a non-negative fixnum.

Function: tv:font-char-width font

The character width of the characters of the font; a non-negative fixnum. If the tv:font-char-width-table of this font is non-nil, then this element is ignored except that it is used to compute the distance between horizontal tab stops; it is typically the width of a lower-case "m".

Function: tv:font-baseline font

The baseline of this font; a non-negative fixnum.

Function: tv:font-char-width-table font

If this is nil then all the characters of the font have the same width, and that width is given by the tv:font-char-width of the font. Otherwise, this is an art-q array of non-negative fixnums, one for each logical character of the font, giving the character width for that character. The array must be an art-q array for the sake of the sys:%string-translate function.

Function: tv:font-left-kern-table font

If this is nil then all characters of the font have zero left kern. Otherwise, this is an array of fixnums, one for each logical character of the font, giving the left kern for that character.

Function: tv:font-blinker-width font

The blinker width of the font.

Function: tv:font-blinker-height font

The blinker height of the font.

Function: tv:font-chars-exist-table font

This is an art-1b array with one element for each logical character of the file. The element is 1 if the character exists and 0 if the character does not exist.

Function: tv:font-raster-height font

The raster height of the font; a positive fixnum.

Function: tv:font-raster-width font

The raster width of the font; a positive fixnum.

Function: tv:font-rasters-per-word font

The number of rows of a character stored in each word of the font; a positive fixnum.

Function: tv:font-words-per-char font

The number of words stored for each character or subcharacter; a positive fixnum.

Function: tv:font-indexing-table font

If this is nil, then no characters of this font are wider than thirty-two bits. Otherwise, this is the font indexing table of the font, an array indexed by character code, containing the number of the first subcharacter for that character code. There is an extra array element at an index one greater than the largest character code; it says where the subcharacters of the largest character code stop.

7.4 Color Fonts

We mentioned earlier that you need to use different fonts to draw on different kinds of screen. To draw on a color screen, you must use a color font. If you just pass in a font specifier when you specify an element of a font map, then a color version of that font will be created if there isn’t one already, and it will be used as the font.

A color font is almost the same as a regular black-and-white font except that for each pixel there are many bits. For example, for a four-bit color display (the only type presently supported), there are four bits for each pixel. While nothing prevents each pixel of a font from having any value it wants, usually each pixel is either zero or one other specific value; that is, color fonts do not usually have multicolored characters in them, or two characters of different color.

Color fonts can be created from black-and-white fonts by the following function:

Function: color:make-color-font bw-font &optional (color 17) (suffix "")

Creates and returns a new font. bw-font should be an existing black-and-white font. The new font has all the same attributes as bw-font, and each character has the same attributes as the corresponding character in bw-font. For each zero-valued pixel in bw-font, the pixel in the new font is zero as well. For each one-valued pixel in bw-font, the pixel in the new font has value color. The name of the new font is formed by appending "color-", the print-name of the name of bw-font, and suffix together to form a string, and then interning that string in the fonts package.

When a font specifier is examined and the window system decides to make a color version of the font, it calls color:make-color-font with only one argument, letting the others default. So, for example, if a color version of fonts:foo-font is automatically created, its name will be fonts:color-foo-font, and its pixels will have the value 17 wherever those in the original font have the value one. However, you can call color:make-color-font to make many color versions of the same black-and-white font, each in a different color.

Something to keep in mind when using color fonts is that when characters of a color font are drawn, onto a color window, and the char-aluf of the window is tv:alu-ior (as it normally is), then the bits of the pixels of the character will be bit-wise "or"’ed with the existing bits in the pixels of the window. If the existing bits (that is, the background against which the character is being drawn) are all zero, there’s no problem. But if they are not, the resulting values of the pixels will be some color determined by a bit-wise "or" of two color values, which is unlikely to yield meaningful results. Unless this is actually what you want, you should make sure that the background is made of zeroes before drawing characters onto a color window.

8 Drawing Graphics

A window can be used to draw graphics (pictures). There is a set of operations for drawing lines, circles, sectors, polygons, cubic splines, and so on, implemented by the flavor tv:graphics-mixin. The tv:graphics-mixin flavor is a component of the tv:window flavor, and so the operations documented below will work on windows of flavor (or flavors built on) tv:window.

.defflavor tv:graphics-mixin Defines the standard window graphics operations. .end_defflavor

There are also some operations in this section that are in tv:stream-mixin ((tv:stream-mixin-flavor)) rather than tv:graphics-mixin, because they are likely to be useful to any window that can draw characters, but such windows might not want the full functionality of tv:graphics-mixin. These operations are :draw-rectangle, and the :bitblt operation and its relatives. (If you are building on tv:window anyway, this doesn’t affect you, since tv:window includes both of these mixins.)

The cursor position is not used by graphics operations; the operations explicitly specify all relevant coordinates. All coordinates are in terms of the inside size of the window, just like coordinates for typing characters; the margins don’t count. Remember that the point (0,0) is in the upper left; increasing y coordinates are lower on the screen, not higher. Coordinates are always fixnums.

As with typing out text, before any graphics are typed the process must wait until it has the ability to output (see (ability-to-output)). The "output hold flag" must be off and the window must not be temp-locked. The other exception conditions of typing out are not relevant to graphics.

All graphics functions clip to the inside portion of the window. This means that when you specify positions for graphic items, they need not be inside the window; they can be anywhere. Only the portion of the graphic that is inside the inside part of the window will actually be drawn. Any attempt to write outside the inside part of the window simply won’t happen.

8.1 Alu Functions

Most graphics operations take an alu argument, which controls how the bits of the graphic object being drawn are combined with the bits already present in the window. In most cases this argument is optional and defaults to the window’s char-aluf (see (char-aluf)), the same alu function as is used to draw characters, which is normally inclusive-or. The following variables have the most useful alu functions as their values:

Variable: tv:alu-ior

Inclusive-or alu function. Bits in the object being drawn are turned on and other bits are left alone. This is the char-aluf of most windows. If you draw several things with this alu function, they will write on top of each other, just as if you had used a pen on paper.

Variable: tv:alu-andca

And-with-complement alu function. Bits in the object being drawn are turned off and other bits are left alone. This is the erase-aluf of most windows. It is useful for erasing areas of the window or for erasing particular characters or graphics.

Variable: tv:alu-xor

Exclusive-or alu function. Bits in the object being drawn are complemented and other bits are left alone. Many graphics programs use this. The graphics operations take quite a bit of care to do "the right thing" when an exclusive-or alu function is used, drawing each point exactly once and including or excluding boundary points so that adjacent objects fit together nicely. The useful thing about exclusive-or is that if you draw the same thing twice with this alu function, the window’s contents are left just as they were when you started; so this is good for drawing objects if you want to erase them afterwards.

Variable: tv:alu-seta

Alu function to copy the input bits into the output bits, ignoring the old values of the output bits. This is not useful with the drawing operations, because the exact size and shape of the affected region depend on the implementation details of the microcode. The seta function is useful with the bitblt operations, where it causes the source rectangle to be transferred to the destination rectangle with no dependency on the previous contents of the destination.

Variable: tv:alu-and

"And" alu function. Like tv:alu-seta, this is not useful with the drawing operations, but can be useful with the bitblt operations. 1 bits in the input leave the corresponding output bit alone, and 0 bits in the input clear the corresponding output bit.

8.2 Flavor Operations for Graphics

.defmethod tv:graphics-mixin :point x y Returns the numerical value of the picture element at the specified coordinates. The result is 0 or 1 on a black-and-white TV. Clipping is performed; if the coordinates are outside the window, the result will be 0. .end_defmethod

.defmethod tv:graphics-mixin :draw-point x y &optional alu value Draws value into the picture element at the specified coordinates, combining it with the previous contents according to the specified alu function (value is the first argument to the operation, and the previous contents is the second argument.) value should be 0 or 1 on a black-and-white TV. Clipping is performed; that is, this operation will have no effect if the coordinates are outside the window. value defaults to -1, which is a pixel with all bits 1. .end_defmethod

.defmethod tv:stream-mixin :bitblt alu width height from-array from-x from-y to-x to-y Copies a rectangle of bits from from-array onto the window. The rectangle has dimensions width by height, and its upper left corner has coordinates (from-x,from-y). It is transferred onto the window so that its upper left corner will have coordinates (to-x,to-y). The bits of the transferred rectangle are combined with the bits on the display according to the Boolean function specified by alu. As in the bitblt function, if from-array is too small it is automatically replicated.

See the discussion of the bitblt function ((bitblt-fun)) for complete details. Note that to-array is constrained as described there. See also the tv:make-sheet-bit-array function below ((tv:make-sheet-bit-array-fun)). .end_defmethod

.defmethod tv:stream-mixin :bitblt-from-sheet alu width height from-x from-y to-array to-x to-y Copies a rectangle of bits from the window to to-array. All the other arguments have the same significance as in :bitblt.

See the discussion of the bitblt function ((bitblt-fun)) for complete details. Note that to-array is constrained as described there. See also the tv:make-sheet-bit-array function below ((tv:make-sheet-bit-array-fun)). .end_defmethod

.defmethod tv:stream-mixin :bitblt-within-sheet alu width height from-x from-y to-x to-y Copies a rectangle of bits from the window to some other place in the window. All the other arguments have the same significance as in :bitblt. Note that width or height may be negative, in which case the coordinates to be copied extend to lower values from the specified starting values, and copying is done in reverse order. The order bits are copied makes no difference when copying between different arrays but is important when copying between overlapping portions of one array. .end_defmethod

.defmethod tv:graphics-mixin :draw-char font char x y &optional alu Displays the character with code char from font font on the window with its upper left corner at coordinates (x,y). This lets you draw characters in any font (not just the ones in the font map), and it lets you put them at any position without affecting the cursor position of the window. .end_defmethod

.defmethod tv:graphics-mixin :draw-line x1 y1 x2 y2 &optional alu (draw-end-point t) Draws a line on the window with endpoints (x1,y1) and (x2,y2). If draw-end-point is specified as nil, does not draw the last endpoint (that is, stops drawing just before that point instead of at it). This is useful with alu function tv:alu-xor when multiple connected lines are in use, since drawing an endpoint once each for two lines would cancel out. .end_defmethod

.defmethod tv:graphics-mixin :draw-lines alu x0 y0 x1 y1 ... xn yn Draws n lines on the screen, the first with endpoints (x0,y0) and (x1,y1), the second with endpoints (x1,y1) and (x2,y2), and so on. The points between lines are drawn exactly once and the last endpoint, at (xn,yn), is not drawn. .end_defmethod

.defmethod tv:graphics-mixin :draw-dashed-line x0 y0 x1 y1 alu dash-spacing space-literally-p offset dash-length Draws a line divided into dashes. The first five arguments are the same as those of the :draw-line operation.

The argument dash-spacing specifies the period of repetition of the dashes; it is the length of a dash plus the length of a space between dashes. Its default value is 20. dash-length is the length of the actual dash; it defaults to half the spacing.

If space-literally-p is nil, the spacing between dashes is adjusted so that the dashes fit evenly into the length of line to be drawn. If it is non-nil, the spacing is used exactly as specified, even though that might put the end point in the middle of a space between dashes.

A nonzero offset is used if you want a space between the starting point and the beginning of the first dash. The value is the amount of space desired, in pixels. The same space will be provided at the end point, if space-literally-p is nil. offset defaults to zero. .end_defmethod

.defmethod tv:graphics-mixin :draw-curve x-array y-array &optional end alu closed-p Draws a sequence of connected line segments. The x and y coordinates of the points at the ends of the segments are in the arrays x-array and y-array. The points between line segments are drawn exactly once and the point at the end of the last line is not drawn at all; this is especially useful when alu is tv:alu-xor. The number of line segments drawn is 1 less than the length of the arrays, unless a nil is found in one of the arrays first in which case the lines stop being drawn. If end is specified it is used in place of the actual length of the arrays.

If closed-p is non-nil, the end point is connected back to the first point. .end_defmethod

.defmethod tv:graphics-mixin :draw-wide-curve x-array y-array width &optional end alu closed-p Like :draw-curve but width is how wide to make the lines. .end_defmethod

.defmethod tv:stream-mixin :draw-rectangle width height x y &optional alu Draws a filled-in rectangle with dimensions width by height on the window with its upper left corner at coordinates (x,y). .end_defmethod

.defmethod tv:graphics-mixin :draw-triangle x1 y1 x2 y2 x3 y3 &optional alu Draws a filled-in triangle with its corners at (x1,y1), (x2,y2), and (x3,y3). .end_defmethod

.defmethod tv:graphics-mixin :draw-circle center-x center-y radius &optional alu Draws the outline of a circle centered at the point center-x, center-y and of radius radius. .end_defmethod

.defmethod tv:graphics-mixin :draw-circular-arc center-x center-y radius start-theta end-theta &optional alu Draws part of the outline of a circle centered at the point center-x, center-y and of radius radius.

The part of the circle to be drawn is specified by start-theta and end-theta. These angles are in radians; an angle of zero is the positive x direction, and angles increase counter-clockwise. The arc starts at start-theta and goes through increasing angles, passing through zero if necessary, to stop at end-theta. .end_defmethod

.defmethod tv:graphics-mixin :draw-filled-in-circle center-x center-y radius &optional alu Draws a filled-in circle specified by its center and radius. .end_defmethod

.defmethod tv:graphics-mixin :draw-filled-in-sector center-x center-y radius theta-1 theta-2 &optional alu Draws a "triangular" section of a filled-in circle, bounded by an arc of the circle and the two radii at theta-1 and theta-2. These angles are in radians; an angle of zero is the positive-X direction, and angles increase counter-clockwise. .end_defmethod

.defmethod tv:graphics-mixin :draw-regular-polygon x1 y1 x2 y2 n &optional alu Draws a filled-in, closed, convex, regular polygon of (abs n) sides, where the line from (x1,y1) to (x2,y2) is one of the sides. If n is positive then the interior of the polygon is on the right-hand side of the edge (that is, if you were walking from (x1,y1) to (x2,y2), you would see the interior of the polygon on your right-hand side; this does not mean "toward the right-hand edge of the window"). .end_defmethod

.defmethod tv:graphics-mixin :draw-cubic-spline px py z &optional curve-width alu c1 c2 p1-prime-x p1-prime-y pn-prime-x pn-prime-y Draws a cubic spline curve that passes through a sequence of points. The arrays px and py hold the x and y coordinates of the sequence of points; the number of points is determined from the active length of px. Through each successive pair of points, a parametric cubic curve is drawn with the :draw-curve operation, using z points for each such curve. If curve-width is provided, the :draw-wide-curve operation is used instead, with the given width. The cubics are computed so that they match in position and first derivative at each of the points. At the end points, there are no derivatives to be matched, so the caller must specify the boundary conditions. c1 is the boundary condition for the starting point, and it defaults to :relaxed; c2 is the boundary condition for the ending point, and it defaults to the value of c1. The possible values of boundary conditions are:

  • :relaxed Makes the derivative zero at this end.
  • :clamped Allows the caller to specify the derivative. The arguments p1-prime-x and p1-prime-y specify the derivative at the starting point, and are only used if c1 is :clamped; likewise, pn-prime-x and pn-prime-y specify the derivative at the ending point, and are only used if c2 is :clamped.
  • :cyclic Makes the derivative at the starting point and the ending point be equal. If c1 is :cyclic then c2 is ignored. To draw a closed curve through n points, in addition to using :cyclic, you must pass in px and py with one more than n entries, since you must pass in the first point twice, once at the beginning and once at the end.
  • :anticyclic Makes the derivative at the starting point be the negative of the derivative at the ending point. If c1 is :anticyclic then c2 is ignored.

.end_defmethod

Function: tv:spline px py z &optional cx cy c1 c2 p1-prime-x p1-prime-y pn-prime-x pn-prime-y

This subroutine of the :draw-cubic-spline operation is also useful in its own right. It does the computation of the spline to be drawn, then converts it into a sequence of line segments, returning arrays of x and y coordinates of endpoints of lines. :draw-cubic-spline works by passing these arrays to the :draw-curve operation.

The function returns three values, an array of x coordinates, an array of y coordinates, and the number of active points in those arrays. (The arrays are not required to have fill pointers.)

The arrays to be used can be supplied as the cx and cy arguments, or else new arrays will be created. If arrays are supplied and too short, they will be made longer.

8.3 Low-Level Graphics Using Subprimitives

Drawing graphics on a window is usually done by sending messages to the window. However, there is a certain overhead in sending each message. If your application requires speed, you can go to some more trouble by writing your very own method to do graphics. It is a good idea not to do this until you know that using existing messages will not work; it is easier and less bug-prone to use the existing messages than to write handlers for new ones.

To write a new method you must have a flavor to which to attach that method. In this case, we want to add some graphics messages to existing kinds of windows. So, what we want here is a mixin flavor. You will define a new mixin flavor for your application. You will add methods to this flavor to do the things you need to do. Then, when you want to create an actual window to use, you will create a window of a new flavor; this new flavor will include, as one of its mixins, your new mixin. For a simple case, you might use the following flavor definitions:

(defflavor circus-mixin () ()
   (:required-flavors tv:essential-window))
   ;;This makes the instance variables of tv:essential-window accessible.

(defmethod (circus-mixin :draw-clown) (size weight happy-p)
  ...)

(defmethod (circus-mixin :draw-tent) 
           (height &optional (number-of-rings 3))
  ...)

(defflavor circus-window () (circus-mixin tv:window))

Now you can instantiate windows of flavor circus-window, and they will support your new messages.

Within the definition of a primitive output operation you will use the graphics subprimitives such as sys:%draw-char rather than the high-level operations described in previous sections. To avoid errors, you should use these subprimitives only from within window methods that provide the error checking that the subprimitives lack.

In addition, the subprimitives must be used only within the body of a tv:prepare-sheet special form. An error is signaled if they are used elsewhere.

Special Form: tv:prepare-sheet body...

Executes body in an environment in which it is safe to draw on the window. tv:prepare-sheet waits until the window is not output-held or locked, and then opens all blinkers that could be on top of the window so that they will not interfere with the output (see (open-blinkers)). It also turns off interrupts so that the window will remain unlocked and the blinkers will remain open.

Because interrupts are turned off, you must be careful in writing the body. It should execute for no longer than you would mind being unable to do a Control-Abort. It also must not wait for anything, since that would allow the blinkers to reappear and defeat the whole purpose of preparing the sheet.

The microcode subprimitives generally use coordinates relative to the outside edges of the window. This is unlike the high-level interfaces, which use cursor positions, in which the margins of the window do not count. Also, subprimitives do little or no clipping or other testing for coordinates that are out of bounds. The results of passing erroneous coordinates are unpredictable; in principle, the machine might halt.

Another place you can use the subprimitives is inside the :blink operation of a blinker. This operation is always invoked in a suitable environment for calling them, including interrupts off. Because blinkers are always drawn by xor’ing, it does not actually matter whether any other blinkers are present.

These instance variables and macros are useful in writing output primitives:

  • (tv:sheet-inside-left)
  • (tv:sheet-inside-right) Return the positions of the inside left edge and the inside right edge, both relative to the outside left edge. If your operation is intended to output on the inside of the window, these may be useful for clipping, and also for converting cursor positions to low-level coordinates.
  • (tv:sheet-inside-top)
  • (tv:sheet-inside-bottom) Return the positions of the inside top edge and the inside bottom edge, both relative to the outside top edge.
  • (tv:sheet-inside-width)
  • (tv:sheet-inside-height) Return the inside size of the window.
  • tv:width
  • tv:height The total width and height of the window, including the margins.
  • tv:cursor-x
  • tv:cursor-y The current cursor position, expressed in outside coordinates. That is to say, these values are not "cursor positions" in the usual sense of that term, but they do describe the position of the cursor.
  • tv:screen-array The array of bits that hold the contents of the window. Usually this is an indirect array that points to part of the screen, although it may also point to the superior’s bit-save array, as described in (exposure). You can use ar-2-reverse and as-2-reverse on this array, indexed by coordinates relative to the outside edges, to examine and draw individual points. The dimensions of this array will be the width and height.
  • tv:char-aluf
  • tv:erase-aluf These are the alu function codes (see (alu-functions)) that are supposed to be used for normal drawing and erasing. :tyo, :string-out and so on all use tv:char-aluf and all the standard erase operations use tv:erase-aluf. If your operation is a kind of drawing or a kind of erasing, it may be correct for you to use one of these two.

    Usually tv:char-aluf is tv:alu-ior, which means to turn on (set to all ones) the corresponding bits in the array. tv:erase-aluf is usually tv:alu-andca, which means to turn off (set to zero) the relevant bits. However, they would be different if the window were in reverse video mode. Reverse video mode is not a highly-used feature, but by using these variables you can make your extensions work correctly in reverse video mode, so it is cleaner to use them.

    However, you may use any alu function. tv:alu-xor is often useful. tv:alu-seta is usually not wise to use, since it will often result in the alteration of bits that you did not expect to change, but which happen to fall in the same word as the ones you were working on.

  • tv:current-font This is the window’s current font. If you are drawing characters, it is usually correct to use this font.

Here is an example from the tv:graphics-mixin flavor, changed by adding the tv: prefixes in the places where you would need them if you were to write this outside the tv package.

(defmethod (graphics-mixin :draw-point)
	   (x y &optional (alu tv:char-aluf) (value -1))
  (tv:prepare-sheet (self)
    (setq x (+ x (tv:sheet-inside-left))
	  y (+ y (tv:sheet-inside-top)))
    (if (not (or (< x (tv:sheet-inside-left))
		 ( x (tv:sheet-inside-right))
		 (< y (tv:sheet-inside-top))
		 ( y (tv:sheet-inside-bottom))))
	(setf (ar-2-reverse tv:screen-array x y)
	      (boole alu value 
		     (ar-2-reverse tv:screen-array x y))))))

This method takes its arguments in inside coordinates, and so it first converts them to outside coordinates. Then it compares them with the boundaries of the inside of the window, and does nothing if they are outside those boundaries. This is how it does clipping. Finally, if everything is OK, it reads out the current value of the point, combines it with the new value using the specified alu function (which defaults to the char-aluf of the window), and stores it back into the array.

8.3.1 Subprimitives for Drawing

In addition to using as-2-reverse yourself, you can use these subprimitives, mostly microcoded. They are equivalent in principle to using as-2-reverse many times, but they are much faster and have much less error checking.

Some of these primitives will accept a sheet or an array. In window-system applications the argument is usually a sheet, but any suitable two-dimensional numeric array will do. (Suitable usually means that the width, times the number of bits per element, is a multiple of 32.) If an array is used, there is no need to worry about tv:prepare-sheet. If you are doing the output on a window, you should pass the window, not its screen array.

Function: sys:%draw-rectangle width height x-bitpos y-bitpos alu-function sheet-or-array

Draws a rectangle of size width by height with its upper left corner at x-bitpos, y-bitpos. Alu function alu-function is used, so you can draw, erase or complement the rectangle with the same function. sheet-or-array is usually the sheet to be drawn on. There is no clipping or error checking.

Function: tv:%draw-rectangle-clipped width height x-bitpos y-bitpos alu-function sheet

This is a little smarter, clipping to the edges of sheet. It does not work on arrays.

Function: tv:draw-rectangle-inside-clipped width height x-bitpos y-bitpos alu-function sheet

This clips to the inside edges of sheet.

Function: sys:%draw-line x0 y0 x y alu draw-end-point-p sheet-or-array

Draws a line from (x0,y0) to (x,y), all relative to the outside edges of the sheet, or indices in the array. The point at (x,y) is not drawn if draw-end-point-p is nil. No clipping or error checking is done.

Function: sys:%draw-triangle x1 y1 x2 y2 x3 y3 alu sheet-or-array

Draws a triangle with the specified corners. No clipping or error checking is done.

Function: tv:draw-char font char x y alu sheet-or-array

Draws the character with code char in font with its upper left corner at position (x,y) in outside coordinates. alu is used as the alu function, so you can either draw or erase. There is no clipping or error checking.

Function: sys:%draw-char font char x y alu sheet-or-array

This is the actual microcoded primitive. It does not take into account the indexing table of a wide font, so when used on a wide font char is not the character code that the user actually wants to output. It is best to use tv:draw-char.

Function: sys:%color-transform n17 n16 n15 n14 n13 n12 n11 n10 n7 n6 n5 n4 n3 n2 n1 n0 width height array start-x start-y

This function operates on a rectangular portion of an art-4b array. It examines each element of the array, and replaces the value of that element with n0 if its previous value was 0, n1 if its previous value was 1, and so on. The upper-left hand corner of the array is specified by start-x and start-y, and its size is specified by width and height. array must be an art-4b array and the specified rectangle must be within the bounds of the array.

Function: bitblt alu width height from-array from-x from-y to-array to-x to-y

Copies or merges a rectangular portion of from-array to a congruent portion of to-array. from-x and from-y specify one corner of the rectangle in from-array, and to-x and to-y specify the corresponding point in to-array. The opposite corner is found by adding width and height to either of those two positions. The copying is done starting at the specified corner and proceeding toward the opposite one.

The width of each array, times the number of bits per element in that array, must be a multiple of 32.

When used in window system applications, one of the arrays will frequently be a window’s screen array. Then the window must be prepared using tv:prepare-sheet.

The operation is not simply one of copying; the bits coming from from-array can be merged with those of to-array. This is controlled by the alu argument. Each pair of bits is combined according to that argument to get the new bit to put in to-array. If alu is tv:alu-seta, the old bits in to-array are ignored. If alu is tv:alu-ior, then the old bits and the incoming bits are or’ed together. And so on. bitblt is careful never to change bits in to-array outside the specified rectangle, which is why it is safe to use tv:alu-seta, whereas it is not safe to use it in the other subprimitives.

Function: tv:make-sheet-bit-array window x y &rest make-array-options

This function creates a two-dimensional bit-array useful for bitblting to and from windows. It makes an array whose first dimension is at least x but is rounded up so that bitblt’s restriction regarding multiples of 32. is met, whose second dimension is y, and whose type is the same type as that of the screen array of window (or the type it would be if window had a screen array). make-array-options are passed along to make-array (see (make-array-fun)) when the array is created, so you can control other parameters such as the area.

9 Blinkers

Each window can have any number of blinkers. The kind of blinker that you see most often is a blinking rectangle the same size as the characters you are typing; this blinker shows you the cursor position of the window. In fact, a window can have any number of blinkers. They need not follow the cursor (some do and some don’t); the ones that do are called following blinkers; the others have their position set by explicit operations.

Blinkers are instances of flavors, like windows, but they are different flavors, and support a different set of standard operations. The window system provides several kinds of blinkers, which differ in the way they appear on the screen.

.defflavor tv:blinker All flavors of blinkers incorporate this one. .end_defflavor

Blinkers need not actually blink; for example, the mouse arrow does not blink. A blinker’s visibility may be any of the following:

Usually only the blinkers of the selected window actually blink; this is to show you where your type-in will go if you type on the keyboard. This is because the blinker’s visibility is generally controlled based on another attribute, the deselected visibility, combined with whether the window is selected. While the current visibility is frequently changed by hand by the program that is using the blinker, the deselected visibility is usually fixed and says something about how the blinker is generally used. Here are its possible values and their meanings:

When the window is deselected, each blinker’s visibility is initialized from its deselected visibility. When the window is selected, visibilities of :on or :off are changed to :blink. Blinkers whose visibility is t or nil or :blink are not affected.

Blinkers are used to add visible ornaments to a window; a blinker is visible to the user, but while programs are examining and altering the contents of a window the blinkers all go away. The way this works is that before characters are output or graphics are drawn, the blinker gets turned off; it comes back later. This is called opening the blinker. tv:prepare-sheet ((tv:prepare-sheet-fun)) is responsible for doing this. You can see this happening with the mouse blinker when you type at a Lisp Machine. To make this work, blinkers are always drawn using exclusive ORing (see tv:alu-xor, (tv:alu-xor-var)).

Every blinker is associated with a particular sheet (window or screen). The blinker is displayed on this sheet, so that its image can appear only within the sheet. When characters are output or graphics are drawn on a sheet, only the blinkers of that sheet and its ancestors are opened (since blinkers of other sheets cannot possibly be occupying screen space that might overlap this output or graphics). The mouse blinker is free to move all over whatever screen it is on; it is therefore associated with the screen itself, and so must be opened whenever anything is drawn on any window on the screen.

A blinker has a position which gives the location of the blinker’s upper left corner relative to the blinker’s sheet. The blinker’s lower right corner is controlled by the blinker’s size together with its position. The blinker position is constrained to be within the sheet’s area. This does not force the blinker’s lower right corner to be within the sheet’s area, but if it is not, the blinker’s image will probably be truncated and the part outside the sheet will not appear.

.defmetainitoption windows :blinker-p t-or-nil .defmetainitoption1 windows :blinker-flavor flavor-name .defmetainitoption1 windows :blinker-deselected-visibility visibility These init options specify whether a cursor-following should be created for this window, and what its flavor and visibility should be. The defaults are t, tv:rectangular-blinker, and :on.

Any other blinkers you want for a window must be created manually in an :init method or elsewhere. .end_defmetainitoption

.defmetainstvar "windows and screens" tv:blinker-list The list of all blinkers associated with this window or screen. .end_defmetainstvar

.defmetamethod "windows and screens" :blinker-list Returns the list of blinkers associated with this window or screen. .end_defmetamethod

Function: tv:sheet-blinker-list sheet

Accessor defsubst for the instance variable.

9.1 Blinker Functions and Operations

Function: tv:make-blinker window &optional (flavor 'tv:rectangular-blinker) &rest options

Creates and returns a new blinker. The new blinker is associated with the given window, and is of the given flavor. Other useful flavors of blinker are documented below. The options are initialization-options to the blinker flavor. All blinkers include the tv:blinker flavor, and so init-options taken by tv:blinker will work for any flavor of blinker. Other init-options may only work for particular flavors.

.definstvar tv:blinker tv:x-pos .definstvar1 tv:blinker tv:y-pos The current position of the blinker on its window, or nil if the blinker should follow the window’s cursor. .end_definstvar

.definitoption tv:blinker :x-pos x .definitoption1 tv:blinker :y-pos y Set the initial position of the blinker within the window. These init-options are irrelevant for blinkers that follow the cursor. The initial position for non-following blinkers defaults to the current cursor position. .end_definitoption

.defmethod tv:blinker :read-cursorpos Returns two values: the x and y components of the position of the blinker within the inside of the window. .end_defmethod

.defmethod tv:blinker :set-cursorpos x y Sets the position of the blinker, relative to the inside of the window. If the blinker has been a following blinker (that is, one which follows the window’s cursor) then it ceases to be one, and from this point on moves only when :set-cursorpos is done. .end_defmethod

.defmethod tv:blinker :size Returns the width and height of the blinker area occupied by the blinker, in pixels, as two values. Each flavor of blinker implements this differently. .end_defmethod

.defmethod tv:blinker :set-size new-width new-height Sets the size of the blinker’s displayed pattern. Not all blinker flavors actually do anything, but they will all allow the operation. For example, character blinkers have no way to change their size because there is no mechanism for automatically scaling fonts. .end_defmethod

.definitoption tv:blinker :follow-p t-or-nil Sets whether the blinker follows the cursor; if this option is non-nil, it does. By default, this is nil, and so the blinker’s position gets set explicitly. .end_definitoption

.defmethod tv:blinker :set-follow-p new-follow-p Sets whether the blinker follows the cursor. If this is nil, the blinker stops following the cursor and stays where it is until explicitly moved. Otherwise, the blinker starts following the cursor. .end_defmethod

.definstvar tv:blinker tv:visibility The blinker’s current visibility. .end_definstvar

.defmethod tv:blinker :visibility .defmethod1 tv:blinker :set-visibility new-visibility Get or set the visibility of the blinker. The specified visibility should be one of :on, nil, :off, t, or :blink; their meanings are described above. .end_defmethod

.definitoption tv:blinker :visibility visibility Initializes the visibility. .end_definitoption

.definstvar tv:blinker tv:deselected-visibility The blinker’s deselected visibility. .end_definstvar

.definitoption tv:blinker :deselected-visibility symbol Sets the initial deselected visibility. By default, it is :on. .end_definitoption

.defmethod tv:blinker :deselected-visibility .defmethod1 tv:blinker :set-deselected-visibility new-visibility Examine or change the deselected visibility of the blinker. .end_defmethod

.definstvar tv:blinker tv:half-period The time interval in 60ths of a second between successive blinks of the blinker. This is relevant only if the visibility is :blink. .end_definstvar

.defmethod tv:blinker :half-period .defmethod1 tv:blinker :set-half-period new-half-period Get or set the half-period of the blinker. The argument is in 60ths of a second. .end_defmethod

.definitoption tv:blinker :half-period half-period Initialize the half-period. The default is 15. .end_definitoption

.definstvar tv:blinker tv:sheet The window or screen this blinker moves on. .end_definstvar

.defmethod tv:blinker :sheet Gets the window or screen that the blinker moves on. .end_defmethod

.defmethod tv:blinker :set-sheet new-sheet Sets to new-sheet the window or screen on which the blinker moves. If the old window is an ancestor or descendant of new-sheet, adjusts the (relative) position of the blinker so that it does not move. Otherwise, moves it to the point (0,0). .end_defmethod

.definstvar tv:blinker tv:time-until-blink The time interval in 60ths until the next time this blinker should blink. For a blinking blinker, this controls the next turning on or off.

A non-blinking blinker will not necessarily change its state at the specified time, but it will be checked at that time and displayed if it is supposed to be visible but is not. This is how blinkers reappear after being opened so that output can be done. .end_definstvar

.defmethod tv:blinker :defer-reappearance This operation is invoked whenever a blinker is opened in order to prepare a sheet, if the visibility is not :blink and if the blinker is scheduled to reappear in less than 25/60 sec. By default, it is defined to delay the blinker’s reappearance until 1/2 sec after the present. .end_defmethod

.definstvar tv:blinker tv:phase t when the blinker is present on the screen, nil when it is not. .end_definstvar

.defmethod tv:blinker :phase Returns t if the blinker is now displayed on the screen. .end_defmethod

.defmetamethod blinkers :blink Draws or erases the blinker. Since the blinker is always drawn by xor’ing, drawing it and erasing it are usually exactly the same. The method can examine the instance variable tv:phase to tell which one is happening, but usually there is no need to know. The :blink operation may assume that the blinker’s sheet is prepared for output. It is always called with interrupts disabled. .end_defmetamethod

Special Form: tv:with-blinker-ready do-not-open body...

This macro is useful in writing methods of blinkers that change the size, position, shape or anything else that affects how the blinker appears. It executes body after preparing to remove the blinker self from the screen. If do-not-open is nil, the blinker is actually opened before body is executed. Otherwise, body may call tv:open-blinker if it wants the blinker open. Interrupts are disabled by this macro in any case, so that if the blinker is opened it remains open for the duration of body.

Once the blinker is opened, its instance variables may be set without special care.

Function: tv:open-blinker blinker

Clears blinker off from the screen if it is currently drawn. This does not change blinker’s visibility. Blinkers that are supposed to be visible but are not on the screen are put back on the screen by the scheduler, every so often. Thus, a blinker can be relied on to stay open only as long as interrupts are disabled.

Function: tv:sheet-following-blinker window

Returns a blinker that follows window’s cursor, or nil if that window has no such blinker. If there is more than one, it returns the first one it finds (it is pretty useless to have more than one, anyway).

Function: tv:turn-off-sheet-blinkers window

Sets the visibility of all blinkers on window to :off.

9.2 Blinker Flavors

All the flavors in this section depend on tv:blinker.

For other blinker flavors and related considerations for use of a blinker for mouse tracking, see the section on mouse blinkers, (mouse-blinkers).

.defflavor tv:rectangular-blinker This is one of the flavors of blinker provided for your use. A rectangular blinker is displayed as a solid rectangle; this is the kind of blinker you see in Lisp listeners and editor windows. The width and height of the rectangle can be controlled. .end_defflavor

.definitoption tv:rectangular-blinker :width n-pixels .definitoption1 tv:rectangular-blinker :height n-pixels Set the initial width and height of the blinker, in pixels. By default, they are set to the font-blinker-height and font-blinker-width (see (tv:font-blinker-height-fun)) of the zeroth font of the window associated with the blinker. .end_definitoption

.defmethod tv:rectangular-blinker :set-size new-width new-height Sets the width and height of the blinker, in pixels. .end_defmethod

.defmethod tv:rectangular-blinker :set-size-and-cursorpos new-width new-height x y Sets the width and height of the blinker, in pixels, and also its position, at once. This avoids any chance that the blinker will appear on the screen with its old size and new position, or vice versa. .end_defmethod

.defflavor tv:hollow-rectangular-blinker (tv:rectangular-blinker) This flavor of blinker displays as a hollow rectangle; the editor uses such blinkers to show you which character the mouse is pointing at. This flavor includes tv:rectangular-blinker, and so all of tv:rectangular-blinker’s init-options and operations work on this too. .end_defflavor

.defflavor tv:box-blinker (tv:rectangular-blinker) This flavor of blinker is like tv:hollow-rectangular-blinker except that it draws a box two pixels thick, whereas the tv:hollow-rectangular-blinker draws a box one pixel thick. This flavor includes tv:rectangular-blinker, and so all of tv:rectangular-blinker’s init-options and operations work on this too. .end_defflavor

.defflavor tv:stay-inside-blinker-mixin This mixin makes a rectangular blinker, or any modified version thereof, keep all of its corners inside the blinker’s sheet. Normally a blinker only makes sure that its position (its upper left corner) is within the sheet. Trying to position this sort of blinker in a bad place positions it against the edge of the sheet, as near as possible to the requested place. .end_defflavor

.defflavor tv:ibeam-blinker This flavor of blinker displays as an I-beam (like a capital I). Its height is controllable. The lines are two pixels wide, and the two horizontal lines are nine pixels wide. .end_defflavor

.definitoption tv:ibeam-blinker :height n-pixels Sets the initial height of the blinker. It defaults to the line-height (see (char-width-and-line-height)) of the window. .end_definitoption

.defflavor tv:character-blinker This flavor of blinker draws itself as a character from a font. You can control which font and which character within the font it uses. .end_defflavor

.definitoption tv:character-blinker :font font Sets the font in which to find the character to display. This may be anything acceptable to the :parse-font-specifier operation (see (tv:screen-parse-font-specifier-method)) of the window’s screen. You must provide this. .end_definitoption

.definitoption tv:character-blinker :character ch Sets the character of the font to display. You must provide this. .end_definitoption

.defmethod tv:character-blinker :character Returns the character that this blinker is displaying as. .end_defmethod

.defmethod tv:character-blinker :set-character new-character &optional new-font Sets the character to be displayed to new-character. Also, if new-font is provided, set the font to new-font. new-font may be anything acceptable to the :parse-font-specifier operation (see (tv:screen-parse-font-specifier-method)) of the window’s screen. .end_defmethod

.definstvar tv:character-blinker tv:character .definstvar1 tv:character-blinker tv:font The character being displayed, and the font it is displayed in. .end_definstvar

.defflavor tv:bitblt-blinker (tv:mouse-blinker-mixin) A blinker that displays by copying a two-dimensional array of pixels onto the screen. The array’s size must be at least the blinker’s size. As it happens, this flavor also includes the ability to be the mouse blinker. .end_defflavor

.definitoption tv:bitblt-blinker :array array This option specifies the array of pixels to be used to display the blinker. Use make-pixel-array to create the array. If you do not specify this option, you must specify both the :height and :width options, which will be used to create an array. .end_definitoption

.definitoption tv:bitblt-blinker :width n-pixels .definitoption1 tv:bitblt-blinker :height n-pixels Set the initial width and height of the blinker, in pixels. .end_definitoption

.defmethod tv:bitblt-blinker :size Returns the width and height of the blinker. If this is less than the size of the blinker’s array, then only part of the array, starting at the upper left corner, is used. .end_defmethod

.defmethod tv:bitblt-blinker :set-size width height Sets the size of the blinker, making a new array if the old one is not as big as the new size. .end_defmethod

.defmethod tv:bitblt-blinker :array .defmethod1 tv:bitblt-blinker :set-array array Get or set the array of pixels to be used to display the blinker. .end_defmethod

.definstvar tv:bitblt-blinker tv:array .definstvar1 tv:bitblt-blinker tv:height .definstvar1 tv:bitblt-blinker tv:width These instance variables hold the special information of bitblt blinkers. .end_definstvar

.defflavor tv:magnifying-blinker (tv:bitblt-blinker) A kind of bitblt blinker which automatically displays a "magnified" version of some of the dots underneath it. A small square of screen pixels is magnified by replacing each pixel with an n by n square of identical pixels, where n is the blinker’s magnification factor.

The x-offset and y-offset which the blinker has by virtue of tv:mouse-blinker-mixin (see (tv:mouse-blinker-mixin-flavor)) help determine the center of magnification. The position of the magnifying blinker is, as always, the position of its upper left corner. However, the cursor positions plus the offsets give the point which the blinker is indicating (this is the place where the mouse position would be, if this blinker were the mouse blinker). The magnification is done so as to keep that point on the screen fixed. .end_defflavor

.definitoption tv:magnifying-blinker :magnification factor Specifies the magnification factor of the magnifying blinker. 3 is a good value to use. The height and width of the blinker should be multiples of the magnification. So should the offsets. .end_definitoption

.defmethod tv:magnifying-blinker :magnification .defmethod1 tv:magnifying-blinker :set-magnification factor Get or set the magnification factor of the blinker. .end_defmethod

.definstvar tv:magnifying-blinker tv:magnification The magnification factor of the blinker. .end_definstvar

.defflavor tv:reverse-character-blinker (tv:bitblt-blinker) This flavor of blinker appears as a solid rectangle with a character removed from it. That is, a solid rectangle and the character are both drawn, and xor with each other. This flavor of blinker proved to be very confusing in the use for which it was originally implemented, but there seems no point in deleting it entirely.

All the operations and init options of tv:character-blinker are provided, though this flavor does not depend on that one.

The position of the blinker is at the upper left corner of the rectangle. The position of the upper left corner of the character with respect to the rectangle is specified with the init options :character-x-offset and :character-y-offset. .end_defflavor

.definitoption tv:reverse-character-blinker :character-x-offset n-pixels .definitoption1 tv:reverse-character-blinker :character-y-offset n-pixels Specify the offset of the character’s upper left corner to the right and down from the blinker position (the rectangle’s upper left corner). .end_definitoption

10 The Mouse

Programs and windows can use the mouse as an input device. The functions, variables, and flavors described below allow you to use the mouse to do some simple things. To get advanced mouse behavior in your own programs, like the way the editor gets the mouse to put a box around the character being pointed at, you have to define new methods for various window operations described in this chapter. Alternatively, you can invoke the built-in choice facilities, such as menus and multiple-choice windows; these high-level facilities are described later.

At any time the mouse is considered to be indicating a certain position on the screen, called the mouse cursor position. The mouse cursor is a conceptual entity which we think of as what moves, inside the machine, when the user moves the mouse.

The mouse cursor position is indicated on the screen by a blinker called the mouse blinker, an actual Lisp object of the sort described in the chapter on blinkers. Different blinkers can be the mouse blinker at different times, since each window can decide what to use as the mouse blinker when that window owns the mouse.

There can be more than one screen, but the mouse cursor position is limited to one screen, called the mouse sheet (it does not have to be a screen, but it normally is). Mouse cursor positions are usually represented relative to the outside of the mouse sheet, though in operations on windows they are sometimes represented relative to the particular window. The Terminal  command can be used to set the mouse sheet to another screen if your Lisp Machine has more than one screen; there is also a system menu option for this.

Variable: tv:mouse-x
Variable: tv:mouse-y

These variables give the position of the mouse, in pixels, measured from the outside upper-left corner of the mouse sheet. They are maintained by the process handling the mouse, normally the mouse process.

Function: tv:mouse-set-sheet sheet

Makes sheet be the mouse sheet, the one on which the mouse cursor moves. Only inferiors of the mouse sheet (to any number of levels) can own the mouse.

Variable: tv:mouse-sheet

The mouse sheet.

Function: tv:mouse-set-sheet-then-call sheet function &rest args

Applies function to args with sheet as the mouse-sheet.

Usually the mouse cursor moves only if the user moves the mouse. However, the program can move the mouse cursor, and change the logical position of the mouse, at any time. This is called warping the mouse. For example, double-click-left in the editor warps the mouse to where the editor cursor is currently located. Since there is no fixed association between positions of physical mouse on the table and spots on the screen, warping the mouse does not result in any inconsistency.

Function: tv:mouse-warp x y

Warps the mouse to be at positions x, y with respect to the mouse sheet.

Tracking the mouse means examining the hardware mouse interface, noting how the mouse is moving, and adjusting the mouse cursor position and the mouse blinker accordingly. Mouse tracking is done by microcode within a window, and by a process called the mouse process when moving between windows. The mouse process also keeps track of which window owns the mouse at any time. For example, when the mouse enters an editor window, the editor window becomes the owner, and to indicate this, the blinker changes to a northeast arrow instead of a northwest arrow; this is all done by the mouse process.

In general, the mouse process decides how to handle the mouse based on the flavor of the window that owns the mouse. Some flavors handle the mouse themselves, running in the mouse process, in order to be able to put little boxes and such around things, usually to indicate what would happen if you were to click a button. The editor, the inspector, menus, and other system facilities do this. The flavor of the window owning the mouse is also what usually controls the effect of clicking the mouse buttons.

10.1 Encoding Mouse Clicks as Characters

Clicks on the mouse are sometimes encoded into characters. Such characters are normally forced into input buffers of windows (see (tv:stream-mixin-force-kbd-input-method)), and so they are distinguished from regular keyboard characters by having the %%kbd-mouse bit turned on. Encoding of clicks is done with tv:mouse-button-encode (see (tv:mouse-button-encode-fun)). See (characters) for full information the fields of such a character.

Note that "mouse clicks" can also be done on the keyboard. See the variables tv:use-kbd-buttons and tv:*mouse-incrementing-keystates*, in (mouse-parameters).

These standard mixins handle mouse clicks by forcing keyboard input describing the click:

.defflavor tv:kbd-mouse-buttons-mixin Handles mouse clicks by encoding them as characters which are forced into the window’s input buffer. In more detail: if it is a double-click on the right button, the system menu is called forth. Otherwise, the encoded character representation of the click is forced into the input buffer of the window. Furthermore, if it is a single-click on the left button, the window is selected.

The state of the Control, Meta, Super and Hyper keys at the time of the click is included in the character, in the %%kbd-control, etc., fields (see (%%kbd-control-var)). .end_defflavor

.defflavor tv:list-mouse-buttons-mixin This is just like tv:kbd-mouse-buttons-mixin except that a blip goes in the input buffer rather than just an encoded click. The blip looks like:

  (:mouse-button encoded-click window x y)

This is more useful than just the encoded click: it tells you where the mouse was (relative to the outside part of the window), and which window the mouse was over (this is useful primarily if several windows are sharing the same input buffer).

The state of the Control, Meta, Super and Hyper keys is included in the encoded click, in the %%kbd-control, etc., fields. .end_defflavor

The following subtle point may explain some difficulties you may have with the above flavors. It is a tricky point, and you can ignore it if you don’t understand it. The characters (or blips) created by the flavors above go straight into the window’s input buffer. Under some circumstances they may bypass pending characters that have been typed ahead at the keyboard. So if you type something and then mouse-click at something in rapid succession while your program is busy, the program may see the mouse-click before it sees the character from the keyboard. [This may be fixed in the future.] See (typeahead-explanation), for further discussion of these issues.

10.2 Ownership of the Mouse

Usually the mouse is handled according to the window that it is positioned over. We say that this window owns the mouse. The window that owns the mouse is the one that will receive the :handle-mouse, :mouse-moves and :mouse-click messages. So the usual case is that the window under the mouse owns the mouse.

Since windows are arranged in a hierarchy, generally a window, its superior, its superior’s superior, and so on, are all under the mouse at the same time. So the window that owns the mouse is really the lowest window in the hierarchy (farthest in the hierarchy from the screen) that is visible (it and all its ancestors are exposed). If you move the window to part of the screen occupied by a partially-visible window, then one of its ancestors (often the screen itself) becomes the owner. The screen handles single-clicking on the left button by selecting the window under it; this is why you can select partially-visible windows with the mouse.

A greedy window can keep ownership of the mouse even if the mouse moves outside of it, by setting tv:window-owning-mouse to that window. This should be done only when that window has come by the mouse by legitimate means, inside a :handle-mouse operation on that window or one of the other operations invoked by it. Inferiors of the greedy window can still own the mouse when it is over them. Greediness ends when tv:window-owning-mouse is set back to nil (its normal state). Then the mouse goes back to being owned by whichever window is under it. While a window is being greedy, mouse tracking continues to use the methods of the owning window, but the way of determining the owning window is changed.

The mouse can also be grabbed, which means that some process has taken it away from all windows. This state is represented by tv:window-owning-mouse being t. See (grabbing-mouse).

Usurping the mouse is an even more drastic method of taking over control. It turns the mouse process off, so you have to do the tracking yourself. See (usurping-mouse).

Variable: tv:window-owning-mouse

If this is nil, the mouse is owned by the window under it. If this is t, the mouse is grabbed. If this is a window, the mouse is owned by that window.

Function: tv:window-owning-mouse

Returns the window that now owns the mouse, either because it is being greedy or because the mouse is over it. If the mouse has been grabbed, the value is t.

Variable: tv:mouse-window

The window that is currently handling the mouse. This is the window that tv:window-owning-mouse returned the last time the mouse process called it.

Function: tv:mouse-wakeup

Informs the mouse process that the screen layout has changed. Anything which may change which window is under any point where the mouse might be should call this function.

.defflavor tv:hysteretic-window-mixin This mixin makes a window continue to own the mouse (by being greedy) for a small distance beyond the edges of the window. This distance is called the hysteresis, and you can specify it. This mixin is used by momentary menus, so that if you accidentally slip a bit outside the menu, the menu won’t vanish; you have to get well away from it before it vanishes. .end_defflavor

.definitoption tv:hysteretic-window-mixin :hysteresis n-pixels Sets the initial value of the hysteresis, in pixels. It defaults to 25. (decimal). .end_definitoption

.defmethod tv:hysteretic-window-mixin :hysteresis .defmethod1 tv:hysteretic-window-mixin :set-hysteresis new-hysteresis Examine or set the hysteresis of the window. .end_defmethod

10.2.1 Grabbing the Mouse

Normally mouse clicks and motion are interpreted by a window that owns the mouse. Some applications, such as Edit Screen, use the mouse for choosing a window to be operated on. Then it is necessary to make sure that control of the mouse remains with the program that is doing this (e.g. Edit Screen) rather than going to whatever window the user wants to choose. This is done by grabbing the mouse.

When the mouse is grabbed, the mouse process gets told that no window owns the mouse, and it changes the mouse blinker back to the default (a northeast arrow). The mouse process will continue to track the mouse, and your process can now watch the position and the buttons by using tv:mouse-x and tv:mouse-y, and the variables and functions described below.

Special Form: tv:with-mouse-grabbed

A tv:with-mouse-grabbed special form just has a body:

   (tv:with-mouse-grabbed
      forms...)

The forms inside are evaluated with the mouse grabbed.

Variable: tv:mouse-last-buttons

This variable contains a mask describing the mouse buttons, as of the last time the process handling the mouse looked at them. The numbers 1, 2, and 4 represent the left, middle, and right buttons respectively, and the value of tv:mouse-last-buttons is the sum of the numbers representing the buttons that were being held down.

Variable: tv:mouse-speed

The speed the mouse has been moving recently, in units approximately like inches per second.

Function: tv:mouse-wait &optional (old-mouse-x tv:mouse-x) (old-mouse-y tv:mouse-y) (old-mouse-buttons tv:mouse-last-buttons)

This function waits for any of the variables tv:mouse-x, tv:mouse-y, or tv:mouse-last-buttons to become different from the values passed as arguments. To avoid timing errors, your program should examine the values of the variables, use them, and then pass in the values that it examined as arguments to tv:mouse-wait when it is time to wait for the mouse to move again. It is important to do things in this order, or else you might fail to wake up if one of the variables changed while you were using the old values and before you called tv:mouse-wait.

Function: tv:mouse-button-encode bd

When a mouse button has been pushed, and you want to interpret this push as a click, call this function. It watches the mouse button and figures out whether a single-click or double-click is happening. It returns nil if no button is pushed, or an encoded character describing the click (see (characters)).

You should call tv:mouse-button-encode only when a button has just been pushed; that is, when you see some button down that was not down before. You have to pass in the argument, bd, which is a bit mask saying which buttons were pressed down: which are down now that were not down "before". The form (logand (logxor old-buttons -1) new-buttons) will compute this mask, where old-buttons is a mask of the buttons that were down before and new-buttons is a mask of the ones that are down now.

Function: tv:merge-shift-keys char

Modifies char by setting the bits corresponding to all the shift keys currently pressed down on the keyboard. This is useful on the result returned by tv:mouse-button-encode, if you wish to record the state of the shift keys in the description of a mouse click so that the shift keys can alter the meaning of the click.

Variable: tv:who-line-mouse-grabbed-documentation

When grabbing or usurping the mouse, you should explain what is going on in the mouse-documentation line at the bottom of the screen. with-mouse-grabbed and with-mouse-usurped bind this variable to nil, which makes the mouse-documentation line blank. Inside the body of one of these special forms, you may setq this variable to a string, which will be displayed in the mouse-documentation line. If your program has "modes" which affect how the mouse acts, each part of the program should setq this variable to its own documentation.

Function: tv:window-under-mouse &optional operation active-condition x y

Returns the window that is seen at the point where the mouse is (or at (x,y) in the mouse sheet, if they are non-nil). This is the window that is partially visible at that point. If operation is non-nil, only windows that handle that operation are considered at all. active-condition is another way of filtering among windows; it can be :active or :exposed, to select among active or exposed windows.

This is used by the mouse process in deciding which window owns the mouse, and can also be used by you when you have grabbed the mouse.

Function: tv:mouse-specify-rectangle &optional left top right bottom (sheet mouse-sheet) (minimum-width 0) (minimum-height 0) abortable

Grabs the mouse and asks the user to specify a rectangle by clicking at two corners. This is how the system menu Create option works. Four values are returned, the left, top, right, and bottom of the rectangle, all relative to sheet.

left and top, if non-nil, are where to position the mouse initially when asking for the upper left corner. If right and bottom are also non-nil, then when asking for the lower right corner the mouse is positioned initially so as to make a rectangle of the same size as the arguments specify. In other words, what matters about the argument right is how much bigger it is than left.

minimum-width and minimum-height constrain the values that may be returned.

If abortable is non-nil, the user is permitted to abort by clicking the middle button. Then the function returns nil.

It is often useful to call this function via tv:mouse-set-sheet-then-call ((tv:mouse-set-sheet-then-call-fun)).

Function: tv:mouse-set-window-size window &optional (move-p t)

Grabs the mouse and asks the user for new edges for window, returns them, and (unless inhibited) sets the edges of window to them as well. window’s edges are set unless move-p is nil.

The values are the new edges, suitable for the :set-edges operation, or nil if the user aborted.

Function: tv:mouse-set-window-position window &optional (move-p t)

Grabs the mouse and asks the user for a new position for window. The new position is returned as two values, and window is moved to that position unless move-p is nil.

The values are the new position of the upper left corner, suitable for the :set-position operation, or nil if the user aborted.

10.2.2 Usurping the Mouse

For high real-time performance, you can usurp the mouse. Then the mouse process steps aside and lets you do everything related to tracking the mouse until you return control of it. The variables tv:mouse-x and tv:mouse-y are not updated while the mouse is usurped. The mouse blinker disappears, and if you want any visual indication of the mouse to appear, you have to do it yourself.

Special Form: tv:with-mouse-usurped

A tv:with-mouse-usurped special form just has a body:

   (tv:with-mouse-usurped
      forms...)

The forms inside are evaluated with the mouse usurped.

Function: tv:mouse-input &optional (wait-flag t)

Waits until something happens with the mouse, and then returns saying what happened. Four values are returned. The first two are delta-x and delta-y, which are the distance that the mouse has moved since the last time tv:mouse-input was called. The second two are buttons-newly-pushed and buttons-newly-raised, which are bit masks (using the bit assignment used by tv:mouse-last-buttons; see above) saying what buttons have changed since the last time tv:mouse-input was called.

You may call this function only with the mouse usurped; otherwise you will get in the way of the mouse process, which calls this function itself, and mouse tracking won’t work correctly.

The variables tv:mouse-x and tv:mouse-y are not maintained by this function; you must do it yourself if you want to keep track of a cumulative mouse position. tv:mouse-last-buttons is maintained.

The buttons-newly-pushed value is suitable for being passed as an argument to tv:mouse-buttons-encode, which can be used with the mouse usurped as well as with the mouse grabbed.

If wait-flag is nil, then the function will not wait; it may return with all zeroes, indicating that nothing has changed.

Function: tv:mouse-buttons

Returns the current state of the mouse buttons, in the format used by the tv:mouse-last-buttons variable, by examining the hardware mouse registers.

10.3 How Windows Handle the Mouse

The mouse is rarely grabbed or usurped. Normally it is owned by a window (or a screen). Then, mouse handling works through various flavor operations on the owning window. There are several operations, used at various points in mouse handling, to give you convenient hooks for modifying a window’s behavior.

The outermost loop of mouse handling determines the owning window and then invokes its :handle-mouse method. When this method returns, the owning window is recalculated.

.defmetamethod windows :handle-mouse This operation is invoked by the mouse process to handle the mouse while it is on this window. It should return only when the mouse moves out of the window, or if the mouse is grabbed.

The default definition is to call tv:mouse-standard-blinker followed by tv:mouse-default-handler. .end_defmetamethod

Function: tv:mouse-default-handler window scroll-bar-p

The guts of the :handle-mouse operation. :handle-mouse methods typically set up the desired sort of mouse blinker and then call this function. window is the window the mouse is being handled for, and scroll-bar-p is t to provide a scroll bar (see (scroll-bar)), if the window implements one. Generally the :enable-scrolling-p operation is used to compute the second argument.

A second argument of :in is used for handling the scroll bar itself. Values other than nil, t and :in should be avoided.

This function invokes the :mouse-moves operation to inform the window about mouse motion, and the :mouse-buttons operation to inform it about buttons going down. They are the most convenient hooks to use for implementing simple new mouse behaviors.

.defmetamethod windows :set-mouse-cursorpos x y .defmetamethod1 windows :set-mouse-position x y Move the mouse instantaneously to the specified position. The effect is as if the user had moved the mouse over to that spot, without the user actually touching it. For :set-mouse-position, x and y are relative to the outside edges of the window. For :set-mouse-cursorpos, they are relative to the inside edges (as in the :set-cursorpos operation). .end_defmetamethod

.defmetamethod windows :mouse-moves x y This operation is invoked in the mouse process every time the mouse moves either into, within or out of this window. x and y are the current position of the mouse, relative to the outside edges of this window.

:mouse-moves handlers should always call tv:mouse-set-blinker-cursorpos to make the mouse blinker move. In addition, they frequently move other blinkers or turn them on or off. This is how menus arrange to outline the item the mouse is over.

tv:mouse-default-handler is what invokes this operation.

When this window ceases to own the mouse, for whatever reason, the :mouse-moves method will always be called one final time, so that it can turn off extra blinkers, etc. .end_defmetamethod

Function: tv:mouse-set-blinker-cursorpos &rest ignore

Moves the current mouse blinker to the current mouse position. :mouse-moves methods typically call this function.

.defmetamethod windows :mouse-buttons mask x y This operation is invoked in the mouse process when a button is pressed. mask is a mask of the buttons pressed, and x and y are the mouse position (in the mouse sheet).

By default, this calls tv:mouse-button-encode to check for double clicks, then brings up the system menu for double-click-right; otherwise, it invokes the :mouse-click operation.

tv:mouse-default-handler is what invokes this operation. .end_defmetamethod

.defmetamethod windows :mouse-click mouse-char x y This operation is where most handling of mouse clicks actually goes on. It is invoked in the mouse process. mouse-char is a character code describing the button pressed and how many times; such as, #\mouse-l-2. x and y are the position of the mouse at the beginning of the click. It is preferable to use this position rather than the current one, because the user positioned the mouse accurately before clicking and motion during the click was probably accidental.

Any window selection desired should be done in another process, using process-run-function or tv:mouse-select. It is unrobust to do something so error-prone in the mouse process.

:or method combination is used, so that all the methods are run until one of them returns non-nil. So each mixin can define a way of handling the mouse under certain circumstances, and it can decline to handle the click by returning nil. For example, tv:margin-choice-mixin defines a :mouse-click method which handles the click if the position is inside a margin choice box, and returns nil otherwise so that the window’s primary way of handling clicks can be run.

tv:kbd-mouse-buttons-mixin and tv:list-mouse-buttons-mixin work by defining :mouse-click methods. .end_defmetamethod

.defmetamethod windows :who-line-documentation-string This operation should return a string describing what the mouse would do if clicked on this window in its current position. For example, menus return a string describing the menu item that the mouse is over. If different buttons do different things, or if multiple clicks are in use, the string should describe all the possibilities. .end_defmetamethod

Function: tv:mouse-select window

Selects window, and safe to use in the mouse process because it creates a temporary process to do the work in that case. Used by :mouse-click methods.

Function: tv:mouse-call-system-menu

Brings up the system menu, and designed to be safe to use in the mouse process. Used by :mouse-click methods.

10.4 Mouse Blinkers

At any time one blinker is the mouse blinker, which follows the motion of the mouse. It is not always the same blinker. Each window can set up the kind of mouse blinker it wants or change the blinker, as long as that window owns the mouse.

The mouse blinker’s sheet is the mouse sheet, not the window that owns the mouse and wants this blinker to be used. This avoids problems with displaying the blinker at points near the edge of the owning window which require parts of the blinker to be outside that window.

Note that mouse blinkers are not following blinkers; the mouse cursor position is independent of the cursor position of the owning window and also independent of the cursor position of the mouse sheet.

The recommended way to make a window flavor use a special form of mouse cursor is to give the flavor a :mouse-standard-blinker method which alters the mouse blinker using tv:mouse-set-blinker or tv:mouse-set-blinker-definition (see below).

Usually there is only one form of mouse blinker used for any given window. If you want the mouse blinker’s appearance to vary while the mouse remains in the same window, a good technique is to have the :mouse-standard-blinker method know how to set up whichever blinker appearance is right at the moment it is called, and then call tv:mouse-standard-blinker after every event that might necessitate changing the blinker.

Variable: tv:mouse-blinker

The blinker now following the mouse. It should not be changed by the user directly.

Function: tv:mouse-set-blinker blinker &optional x-offset y-offset

Makes blinker the new mouse blinker. If x-offset and y-offset are non-nil, blinker’s offsets (see below) are also set.

blinker can be a defined blinker type instead of a blinker. Then this function is equivalent to tv:mouse-set-blinker-definition with only three arguments specified ((tv:mouse-set-blinker-definition-fun)).

This function is typically called from :mouse-standard-blinker methods.

Function: tv:mouse-standard-blinker &optional (window (tv:window-owning-mouse))

Sets the mouse blinker to the standard kind for window, by invoking the :mouse-standard-blinker operation on it. This is called by the window system at appropriate times.

.defmetamethod windows :mouse-standard-blinker This should use tv:mouse-set-blinker or tv:mouse-set-blinker-definition to set up the right kind of mouse blinker to use when the mouse is on this window. By default, it is defined to pass on the message to the superior window; finally, the screen handles the operation by making the blinker an upward-left arrow. .end_defmetamethod

.defflavor tv:mouse-blinker-mixin Not all blinkers can serve as mouse blinkers. This mixin makes a blinker suitable for use as the mouse blinker.

A mouse blinker has two offsets which relate the blinker position to the mouse position. Remember that the blinker position is where the upper left corner of the blinker is displayed. The upper left corner is not always what you want to place at the precise spot the mouse is pointing to. For example, if you are using a character blinker with the character X, probably the center of the X rather than its corner should be "the spot". .end_defflavor

.defmethod tv:mouse-blinker-mixin :offsets Returns the x and y offsets of the blinker as two values. The values give the position of the mouse cursor relative to the blinker; that is, in order to locate the cursor within the area of the blinker’s display, the offsets must be positive. .end_defmethod

.defmethod tv:mouse-blinker-mixin :set-offsets x y Sets the offsets of the blinker. .end_defmethod

.defflavor tv:mouse-character-blinker .defflavor1 tv:mouse-rectangular-blinker .defflavor1 tv:mouse-hollow-rectangular-blinker .defflavor1 tv:mouse-box-blinker .defflavor1 tv:mouse-box-stay-inside-blinker These are versions of popular blinker flavors described in (blinker-flavors), which can be used as the mouse blinker. tv:mouse-box-stay-inside-blinker incorporates tv:stay-inside-blinker-mixin. .end_defflavor

The flavors tv:bitblt-blinker and tv:magnifying-blinker are already suited to be mouse blinkers.

10.4.1 Reusable Mouse Blinker Types

Normally you do not create mouse blinkers yourself. Instead, each screen keeps a list of mouse blinkers of various sorts, and you reuse one of them. This is done by means of mouse blinker type keywords. A mouse blinker type keyword is given a meaning, which is a function for creating a blinker. The first time someone wants a blinker of that type on a given screen, one is created and remembered, and reused every time a blinker of that type is wanted. A blinker type keyword serves a purpose similar to that of a resource.

Predefined type keywords include :character-blinker, :rectangle-blinker, :box-blinker and :box-stay-inside-blinker.

You do not have to use this mechanism, but it saves creation of blinkers to do so.

Function: tv:mouse-define-blinker-type type creation-function

Defines type as a mouse blinker type, with creation-function as a function to create one. The function will receive a screen as argument and should call make-blinker.

Function: tv:mouse-get-blinker type sheet

Returns a blinker of type type whose sheet is sheet. The same blinker will be automatically reused for different sheets on the same screen. In fact, the blinker’s sheet will be the screen, not sheet.

Function: tv:mouse-set-blinker-definition type x-offset y-offset visibility operation &rest args

Sets the mouse blinker to be a blinker of type type, and sets its offsets and visibility as specified; then sends the blinker a message of operation and args if operation is non-nil. operation is typically used to initialize other aspects of the blinker. For example, the :set-character operation is useful with character blinkers.

This function can be used in the :mouse-standard-blinker method of a window to specify a different appearance of the mouse blinker while the mouse is in that window.

.definstvar tv:screen tv:mouse-blinkers A list of mouse blinkers, examples of various reusable mouse blinker types, created so far for this screen. .end_definstvar

10.5 Mouse Scrolling

Some windows have the ability to scroll. They display only a portion of a virtual window which is (or may be) too big to be shown all at once. Scrolling means moving the actually-shown portion up or down through the entire display.

10.5.1 Scrolling Protocol

There are several ways the mouse can be used to scroll a window. Each is implemented by a mixin. They all communicate with the window using the same protocol. For the sake of this protocol, the contents of the window are considered to be divided vertically into "lines". A position for scrolling is expressed as the number of lines that are above the top of the window. These do not have to be actual lines of text, though usually they are, but they must all have the same height. Usually this common height is the window’s line-height, but that is not required.

.defmetamethod "scrolling windows" :enable-scrolling-p The various mouse-scrolling features use this operation to decide whether they should be active at any given time. If this operation returns nil, the scrolling facilities do not react to the mouse. .end_defmetamethod

.defmetamethod "scrolling windows" :scroll-position Returns four values:

  • top-line-num The line-number of the line currently at the top of the window.
  • total-lines The total number of lines available to scroll through.
  • line-height The height (in pixels) of a line.
  • n-items The number of lines that the window has room for.

.end_defmetamethod

.defmetamethod "scrolling windows" :scroll-to to &optional (type ':absolute) type is one of:

  • :absolute Places the line numbered to at the top of the window.
  • :relative Adjusts the line displayed at the top of the window by to lines. If to is positive, text moves upward on the screen.

Since to is not guaranteed to be legal, both types of scrolling must error check their arguments. .end_defmetamethod

.defmetamethod windows :new-scroll-position This operation is used by the program managing the window to tell the mouse scrolling facilities that the contents of the window have changed under program control. It should be invoked whenever either the total number of lines to scroll through or the line number at the top of the window is changed by anything except the mouse scrolling facilities.

Mouse scrolling facilities put daemons on this operation in order to update their displays when the situation changes. .end_defmetamethod

10.5.2 Scroll Bars

If you move the mouse to the left edge of an editor window from the inside, eventually the mouse cursor changes to a thick up-and-down arrow. Simultaneously, a thin vertical line appears next to and outside of the left border of the window. This is called entering the scroll bar, and the thin vertical line, which indicates the portion of the total text that is now on the screen, is the scroll bar itself.

The vertical position of the top and bottom of the thin vertical line, as proportions of the height of the window, are the same as the positions of the first and last lines of text on the screen, as proportions of the total number of lines.

While the mouse is in the scroll bar, clicks have special meanings:

  • single left Moves this line (the one the mouse points at) to the top of the window.
  • single right Moves the line at the top of the window to where the mouse points.
  • double left Moves this line (the one the mouse points at) to the bottom of the window.
  • double right Moves the line at the top of the window to where the mouse points.
  • middle Scrolls so that the scroll bar moves to where the mouse is. The mouse vertical position on the window thus controls where in the display to scroll to; the top of the window requests the beginning of the available display, and the bottom requests the end.

.defflavor tv:basic-scroll-bar This mixin gives a window the ability to have a scroll bar. It defines three instance variables:

  • tv:scroll-bar When the window provides margin space for a scroll bar, this is a list describing the rectangle allocated. Otherwise, it is nil.
  • tv:scroll-bar-always-displayed If this is non-nil, the bar will be displayed whenever margin space is provided for it, even if the mouse is not there.
  • tv:scroll-bar-in This is non-nil when the mouse is actually in this window’s scroll bar.

.end_defflavor

.definitoption tv:basic-scroll-bar :scroll-bar spec Specifies whether to have a scroll bar, how big to make it, and where. spec can be nil for no scroll bar, t for a default scroll bar, or a small positive number, which requests a scroll bar of that width. The scroll bar occupies space in the margins of the window. .end_definitoption

.defmethod tv:basic-scroll-bar :set-scroll-bar spec Sets whether this window has a scroll bar, or how wide it is. spec is the same as in the :scroll-bar init option. This can change the inside size of the window, since it can change the amount of space needed in the margin. .end_defmethod

.defmethod tv:basic-scroll-bar :enable-scrolling-p This mixin defines this operation to return t when the window has a scroll bar. See (scrolling/ windows-enable-scrolling-p-method) for a description of this operation. .end_defmethod

.definitoption tv:basic-scroll-bar :scroll-bar-always-displayed t-or-nil Non-nil to say that the bar of the scroll bar should appear on the screen all the time, not just when the mouse is "in" it. .end_definitoption

.defmethod tv:basic-scroll-bar :scroll-bar-always-displayed .defmethod1 tv:basic-scroll-bar :set-scroll-bar-always-displayed t-or-nil Get or set this flag in an existing window. Setting it updates the screen. .end_defmethod

.defmethod tv:basic-scroll-bar :scroll-more-above .defmethod1 tv:basic-scroll-bar :scroll-more-below t if there is text to scroll up (down) to. The default definition uses the :scroll-position operation; some flavors redefine it for greater efficiency. .end_defmethod

.defmethod tv:basic-scroll-bar :mouse-buttons-scroll mouse-char x y This operation is invoked when the mouse is clicked in the scroll bar. mouse-char is a character with %%kbd-mouse set, identifying the button clicked and how many times. x and y are the position at the time of the click, relative to this window’s outside edges. The default definition provides the standard scrolling commands; you can redefine it. .end_defmethod

.defmethod tv:basic-scroll-bar :scroll-relative from to Scrolls the window to move what is now at the y-position from to the y-position to. The arguments can be numeric vertical cursor positions, or the symbols :top or :bottom. The :scroll-position and :scroll-to operations are used to accomplish the scrolling. .end_defmethod

10.5.3 Margin Scrolling

The scrolling mixins described here require that the window have tv:basic-scroll-bar as well, because they make use of operations defined by that flavor. If you do not want to have a scroll bar, you can specify nil for the :scroll-bar init option.

.defflavor tv:flashy-scrolling-mixin This mixin provides the ability to scroll the window a line at a time by pushing the mouse against the top or bottom edge. The mouse blinker changes to a thick up or down arrow when it is in the right place to do this.

This sort of scrolling is provided in the editor and the inspector. This flavor does not cause the text "more above" to appear, the way it does in the inspector; that is done by tv:margin-scrolling-mixin. .end_defflavor

.definitoption tv:flashy-scrolling-mixin :flashy-scrolling-region spec spec specifies where in the window the regions should go in which the mouse can cause scrolling. It looks like this:

((top-height top-left top-right)
 (bottom-height bottom-left bottom-right))

Each region always abuts the top or bottom edge of the window, overlapping the window’s margin, but possibly extending into the inside of the window. Each height is a number of pixels in height for the specified region. Each left and right give the sides of the region. left and right can be fixnums (positions relative to the window left edge), flonums (fractions of the width of the window, with zero at the left), or :left for the left edge or :right for the right edge. .end_definitoption

.defflavor tv:margin-scroll-mixin This mixin (which requires tv:margin-region-mixin as well) provides for mouse-sensitive regions in the top and bottom margins which say "more below" or "more above" if there is something to scroll to. A mouse click on the region scrolls an entire windowfull. .end_defflavor

.definitoption tv:margin-scroll-mixin :margin-scroll-regions region-list Each element of region-list describes what to do with one of the two scrolling regions. An element looks like

(keyword at-end-message more-message font-specifier)

keyword is :top or :bottom, and says which region this element describes. at-end-message is an expression evaluated to get the string to display in the region when there is no room for more scrolling in that direction. If nil or omitted, it defaults to "Top" or "Bottom". more-message is another expression which is supposed to evaluate to a string to print when there is room for more scrolling. "More above" and "More below" are the defaults.

Most commonly one just uses a string for the at-end-message and the more-message.

font-specifier specifies the font to use. It defaults to tr10i if it is nil or omitted. .end_definitoption

.defflavor tv:flashy-margin-scrolling-mixin This mixin provides both flashy scrolling and margin scrolling, with the flashy scrolling areas overlying the margin scrolling regions. You don’t need anything else except tv:basic-scroll-bar. .end_defflavor

Here are two ways of controlling when margin scrolling regions appear or disappear:

.defflavor tv:margin-scroll-region-on-and-off-with-scroll-bar-mixin This mixin, when combined with tv:margin-scroll-mixin, makes the margin scroll regions disappear if the :scroll-bar init option or the :set-scroll-bar operation is used to make the scroll bar disappear, and reappear if a scroll bar is created again. .end_defflavor

.defflavor tv:scroll-stuff-on-off-mixin This mixin provides a scroll bar, flashy scrolling and margin scrolling, and makes them appear or disappear according to the value returned by the :enable-scrolling-p operation. .end_defflavor

.defmethod tv:scroll-stuff-on-off-mixin :decide-if-scrolling-necessary Makes the scroll bar and margin regions appear or disappear if appropriate, using the :enable-scrolling-p to decide whether they should be present. The goal is to avoid displaying scrolling features, and using up screen space for them, when there is no place to scroll to.

This operation is invoked automatically at certain times. It should be invoked also whenever the number of lines to scroll through has been changed, but before doing any associated redisplay (since the redisplay to be done may be different after this operation finishes).

If the scroll bar and margin regions must be added or removed, then either the inside size of the outside size of the window must change. The :adjustable-size-p operation is used to decide which. If it returns non-nil, the inside size is preserved and the outside size is changed; otherwise, the outside size is preserved.

Changing the inside size may affect the window’s redisplay calculations, and for some windows it may cause a redisplay within this operation. You may want to invoke it inside of a tv:with-sheet-deexposed to avoid letting the user see gratuitous double redisplays, or to suppress the redisplay entirely if there is no bit-save-array.

If the outside size is to be changed, and if changing the number of displayable items changes the height of the window, that should be done before invoking this operation. .end_defmethod

.defmethod tv:scroll-stuff-on-off-mixin :adjustable-size-p This operation is used to decide how to adjust the window margin size. If it returns non-nil, the inside size is preserved; otherwise, the outside size.

tv:scroll-stuff-on-off-mixin does not define this operation, but it requires users to define it. .end_defmethod

10.6 Mouse Parameters

Variable: tv:use-kbd-buttons

If this is non-nil, the Roman numeral keys I through III on the keyboard are treated as mouse clicks when the Mode-Lock key is down. The default is t.

Variable: tv:mouse-bounce-time

The delay in microseconds after a change in a mouse button status before the system begins to look for another change. The default is 2000. microseconds.

Variable: tv:mouse-double-click-time

The delay in microseconds after which the system gives up checking for an additional mouse click. The default is .2 seconds.

Function: tv:mouse-discard-clickahead

Clears out the microcode buffer in which the mouse-tracking microcode records mouse clicks.

Variable: tv:*mouse-incrementing-keystates*

This is a list of keys (valid arguments for tv:key-state). When the mouse is clicked, each of these keys that is held down adds one to the "number of clicks". The default value is

(:control :shift :hyper)

Thus, if you do a single click with the Control key down, it is treated as a double click.

11 Margins, Borders, and Labels

In previous sections, we have mentioned the distinction between the inside and outside parts of the window. The part of the window that is not the inside part is called the margins. There are four margins, one for each edge. The margins sometimes contain a border, which is a rectangular box drawn around the outside of the window. Borders help the user see what part of the screen is occupied by which window. The margins also sometimes contain a label, which is a text string. Labels help the user see what a window is for.

A label can be inside the borders or outside the borders (usually it is inside). In general, there can be lots of things in the margins; each one is called a margin item. Borders and labels are two kinds of margin items. In any flavor of window, one of the margin items is the innermost; it is right next to the inside part of the window. Each successive margin item is outside the previous one; the last one is just inside the edges of the window. Each margin item is created by a flavor’s being mixed in. You can control which margin items your window has by which flavors you mix in, and you can control their order by the order in which you mix in the flavors. Margin item flavors closer to the front of the component flavor list are further toward the outside of the margins. The tv:window flavor has as components tv:borders-mixin and tv:label-mixin, in that order, and so the label is inside the border. The scroll bar, in windows that have one, is also a margin item (see (scroll-bar)).

.defmetamethod windows :margins Returns four values: the sizes of the left, top, right, and bottom margins, respectively. Each value includes the contributions of borders, labels, and anything else, to that one margin. For a window with no margins, all four values are zero. .end_defmetamethod

.defmetamethod windows :left-margin-size .defmetamethod1 windows :top-margin-size .defmetamethod1 windows :right-margin-size .defmetamethod1 windows :bottom-margin-size Return the size of one of the margins. .end_defmetamethod

.defmetainstvar windows tv:left-margin-size .defmetainstvar1 windows tv:top-margin-size .defmetainstvar1 windows tv:right-margin-size .defmetainstvar1 windows tv:bottom-margin-size These hold the four values returned by the :margins operation. There are no operations to set these variables or init options to initialize them, because the margin sizes are always supposed to be computed from the labels, borders and other margin items as described below. .end_defmetainstvar

Function: tv:sheet-left-margin-size window
Function: tv:sheet-top-margin-size window
Function: tv:sheet-right-margin-size window
Function: tv:sheet-bottom-margin-size window

Return the value of the corresponding instance variable of window. These are accessor defsubsts created by the :outside-accessible-instance-variables option of defflavor.

Function: tv:sheet-inside-left &optional (window self)
Function: tv:sheet-inside-top &optional (window self)
Function: tv:sheet-inside-right &optional (window self)
Function: tv:sheet-inside-bottom &optional (window self)

Return the positions of the inside edges, relative to the top left outside corner of the window. If used with no argument, these defsubsts expand into direct references to instance variables, and therefore may be used only within methods or (declare (:self-flavor ...)) functions.

11.1 Borders

.defflavor tv:borders-mixin The tv:borders-mixin margin item creates the borders around windows that you often see when using the Lisp Machine. You can control the thickness of each of the four borders separately, or of all of them together. You can also specify your own function to draw the borders, if you want something more elaborate than simple lines.

The borders also include some whitespace left between the borders and the inside of the window. The thickness of this white space is called the border margin width. The space is there so that characters and graphics that are up against the edge of the inside of the window, or the next-innermost margin item, do not "merge" with the border. .end_defflavor

.definitoption tv:borders-mixin :borders argument This option initializes the parameters of the borders. argument may have any of the following values:

  • nil There are no borders at all.
  • a symbol or a number A specification (see below) that applies to each of the four borders.
  • a list (left top right bottom) Specifications (see below) for each of the borders at the four edges of the window.
  • a list (keyword1 spec1 keyword2 spec2...) Specifications (see below) for the borders at the edges selected by the keywords, which may be among :left, :top, :right, :bottom.

Each specification for a particular border may be one of the following. It specifies how thick the border is and the function to draw it.

  • nil This edge should not have any border.
  • t The border at this edge should be drawn by the default function with the default thickness.
  • a number The border at this edge should be drawn by the default function with the specified thickness.
  • a symbol The border at this edge should be drawn by the specified function with the default thickness for that function.
  • a cons (function . thickness) The border at this edge should be drawn by the specified function with the specified thickness.

The default (and currently only) border function is tv:draw-rectangular-border. Its default width is 1.

To define your own border function, you should create a Lisp function that takes six arguments: the window on which to draw the label, the "alu function" (see (aluf)) with which to draw it, and the left, top, right, and bottom edges of the area that the border should occupy. The returned value is ignored. The function runs inside a tv:sheet-force-access (see (tv:sheet-force-access-fun)). You should place a tv:default-border-size property on the name of the function, whose value is the default thickness of the border; it will be used when a specification is a non-nil symbol.

Note that setting border specifications to ask for a border width of zero is not the same thing as giving nil as the argument to this option, because in the former case the space for the border margin width (see the previous page) is allocated, whereas in the latter case it is not. .end_definitoption

.defmethod tv:borders-mixin :set-borders new-borders Redefines the borders. new-borders can be any of the things that can be used for the :borders init option (see above). .end_defmethod

.definitoption tv:borders-mixin :border-margin-width n-pixels Sets the width of the white space in the margins between the borders and the inside of the window. The default is 1. If some edge does not have any border (the specification for that border was nil) then that border won’t have any border margin either, regardless of the value of this option; that is the difference between border specifications of 0 and nil. .end_definitoption

.defmethod tv:borders-mixin :border-margin-width .defmethod1 tv:borders-mixin :set-border-margin-width new-width Return or set the value of the border margin width. .end_defmethod

.definstvar tv:borders-mixin tv:border-margin-width The current border margin width. .end_definstvar

.definstvar tv:borders-mixin tv:borders A description of the currently specified borders. It is nil for no borders. Otherwise its format is complicated and internal in nature. .end_definstvar

.defflavor tv:full-screen-hack-mixin This mixin is included in many system flavors, such as Lisp listeners, Supdup, and Zmacs frames. It offers the user the option of requesting that these windows have no borders when they occupy the full screen. .end_defflavor

Function: tv:flush-full-screen-borders flush-p

With an argument of t, eliminates the borders of all windows which are full-screen-sized and have tv:full-screen-hack-mixin.

With an argument of nil, reinstates the normal borders of all such windows.

11.2 Labels

.defflavor tv:label-mixin The tv:label-mixin margin item creates the labels in the corners of windows that you often see when using the Lisp Machine. You can control the text of the label, the font in which it is displayed, and whether it appears at the top of the window or the bottom. .end_defflavor

.defmetainitoption windows :name The value is the name of the window, which should be a symbol. All windows have names; note that this is an init option of tv:minimum-window. It is mentioned here because the main use of the name is as the default string for the label, if there is a label (see below). .end_defmetainitoption

.defmetamethod windows :name Returns the name of the window, which is a symbol. See above. .end_defmetamethod

.definitoption tv:label-mixin :label specification Sets the string displayed as the label, the font in which the label is displayed, and whether the label is at the top or the bottom of the window. Anything you don’t specify will default; by default, the string is the same as the name of the window, the font is the screen’s standard font for the purpose :label (see (font-purposes)), and the label is at the bottom of the window.

specification may be any of:

  • nil There is no label at all.
  • t The label is given all the default characteristics.
  • :top The label is put at the top of the window.
  • :bottom The label is put at the bottom of the window.
  • a string The text displayed in the label is this string.
  • a font The label is displayed in the specified font.
  • a list (keyword1 arg1 keyword2 ...) The attributes corresponding to the keywords are set; the rest of the attributes default. Some keywords take arguments and some do not. The following keywords may be given:
    • :top The label is put at the top of the window.
    • :bottom The label is put at the bottom of the window.
    • :centered The label is printed horizontally centered, rather than starting at the left edge.
    • :string string The text displayed in the label is string.
    • :font font-specifier The label is displayed in the specified font. font-specifier may be any font specifier (see (font-specifier)).
    • :vsp vsp If the label is multiple lines, lines will be separated by vsp rows of pixels.

.end_definitoption

.defmethod tv:label-mixin :label-size Returns the width and height of the area occupied by the label. .end_defmethod

.defmethod tv:label-mixin :set-label specification Changes some attributes of the label. specification can be anything accepted by the :label init option. Any attribute that specification doesn’t mention retains its old value. .end_defmethod

.definstvar tv:label-mixin tv:label The value of this variable describes the label of the window. It is either nil for no label or a list of length eight, whose elements are

  • tv:label-left
  • tv:label-top
  • tv:label-right
  • tv:label-bottom The rectangle allocated to the label. All four edges are relative to the window’s outside upper left corner.
  • tv:label-font The font to use for the label.
  • tv:label-string The string to display in the label.
  • tv:label-vsp The separation between lines in the label.
  • tv:label-centered Non-nil if the label text should be horizontally centered.

.end_definstvar

.defflavor tv:top-label-mixin Causes the label to appear in the top margin of the window by default instead of at the bottom. The mixin does not override an explicit specification of the label position. .end_defflavor

.defflavor tv:box-label-mixin Makes the label appear to be in a box, by drawing a line just on the inside of the label. This combines with the window’s borders, which surround the other three sides of the label, to make a box. The extra line is present only if the label is turned on. Menus use this mixin, so from any menu that has a label, such as the one you get from Split Screen in the system menu, you can see what it looks like. .end_defflavor

.definitoption tv:box-label-mixin :label-box-p t-or-nil If this option is nil, the box around the label is inhibited. .end_definitoption

.defflavor tv:centered-label-mixin Makes the label string appear by default horizontally centered in the width of the window. .end_defflavor

.defflavor tv:delayed-redisplay-label-mixin This flavor adds the :delayed-set-label and :update-label operations to your window. You send a :delayed-set-label message to change the label in such a way that it will not actually be displayed until you send an :update-label message. This is especially useful for programs that suppress redisplay when there is typeahead; the user’s commands may change the label several times, and you may want to suppress the redisplay of the changes in the label until there isn’t any typeahead. .end_defflavor

.defmethod tv:delayed-redisplay-label-mixin :delayed-set-label specification This is like the :set-label method, except that nothing actually happens until an :update-label is done. .end_defmethod

.defmethod tv:delayed-redisplay-label-mixin :update-label Actually does the :set-label operation on the specification given by the most recent :delayed-set-label operation. .end_defmethod

.definstvar tv:delayed-redisplay-label-mixin tv:label-needs-updating Non-nil if a :delayed-set-label has been done but not displayed yet. .end_definstvar

11.3 Margin Regions

Margin regions are a general facility for allocating space in a window’s margin for specific purposes. Each region can display text or graphics and can be mouse sensitive. Margin choices (see (margin-choice)) are implemented using margin regions.

.defflavor tv:margin-region-mixin This mixin gives a window the ability to have margin regions. .end_defflavor

.definstvar tv:margin-region-mixin tv:region-list A list of margin region descriptors. Each descriptor specifies one margin region and is a list of this form:

(function margin size left top right bottom)

The list may be longer than seven. The meaning of the extra elements is up to you. Here is what the seven standard elements mean. We list the names of the defsubsts provided to access them.

  • tv:margin-region-function A function to handle various operations on the margin region. It is called with an operation name as the first argument, so it could be a flavor instance, but no flavors are predefined for the purpose and usually the function is a defselect. The margin region descriptor itself is always one of the arguments, to identify the region being operated on.
  • tv:margin-region-margin The name of the margin that this region lives in; either :left, :top, :right or :bottom.
  • tv:margin-region-size The thickness in pixels of the margin region, perpendicular to the edge it is next to. (The other dimension is controlled by the size of the window, possibly diminished by space already reserved for other margin items.)
  • tv:margin-region-left
  • tv:margin-region-top
  • tv:margin-region-right
  • tv:margin-region-bottom The edges of the rectangle assigned to the margin region. If positive, they are relative to the outside upper left corner of the window. If negative, they are relative to the outside lower right corner.

    You do not specify these; they are computed by the :redefine-margins operation which divides up the margin space, and recorded here so that the margin region can be displayed and found by the mouse.

The margin region descriptor may be longer than seven. Additional elements are not used by tv:margin-region-mixin itself and therefore may be used by higher-level facilities to record their own information with each margin region. .end_definstvar

.defmethod tv:margin-region-mixin :set-region-list new-region-list Sets the list of margin regions. The new list should be a list of margin region descriptors as described above, but only the first three elements of each descriptor need be filled in. The rest will be set up automatically. .end_defmethod

These are the operations that the function of a margin region is expected to handle:

  • :refresh descriptor This operation should draw this region on the screen in the position specified by the margin region descriptor.
  • :mouse-enters-region descriptor This operation is invoked whenever the mouse moves into this region.
  • :mouse-leaves-region descriptor This operation is invoked whenever the mouse moves out of this region.
  • :mouse-moves x y descriptor This operation is invoked when the mouse moves within a region. It is also invoked, following the :mouse-enters-region operation, when the mouse moves into a region. x and y are the new mouse position, relative to the outside of the window.
  • :mouse-click x y descriptor mouse-char This operation is invoked when the mouse is clicked on this region, except for double click right. If the operation does nothing, the mouse click has no effect. The argument mouse-char is like that of the :mouse-click window operation ((windows-mouse-click-method)).
  • :who-line-documentation-string descriptor This operation is invoked to get who line documentation to be used when the mouse is in this region. It should return a string describing the meaning of mouse clicks on the region.
Function: tv:margin-region-area descriptor

Returns the four edges of the rectangle allocated to descriptor’s margin region, all relative to the window’s outside upper left corner. This may only be used inside of methods of the window whose margin region is being operated on.

11.3.1 Margin Region Example

This is a simplification of the function used to handle the margin regions made by tv:margin-scroll-mixin. These regions display strings such as "More above" and respond to a mouse click by scrolling a full page. The margin regions used have additional nonstandard elements beyond the seventh:

  • tv:margin-scroll-region-more-p Non-nil if there is more text to scroll to past this edge.
  • tv:margin-scroll-region-empty-msg The string to display when there is no more to scroll to past this edge.
  • tv:margin-scroll-region-more-msg The string to display when there is more to scroll to.
  • tv:margin-scroll-region-msg-font The font to display the strings in.
(declare-flavor-instance-variables (tv:margin-scroll-mixin)
(defselect margin-scroll-region
  (:refresh (region &optional old-valid
		    &aux more-p left top right bottom)
   (multiple-value (left top right bottom)
     (tv:margin-region-area region))
   ;; Is there anything more to scroll to past this edge?
   (setq more-p 
	 (send self
	       (if (eq (tv:margin-region-margin region) ':top)
		   ':scroll-more-above ':scroll-more-below)))
   ;; Redisplay string in the region unless already right.
   (when (or (not old-valid) 
	     (neq more-p (margin-scroll-region-more-p region)))
     (setf (margin-scroll-region-more-p region) more-p)
     (tv:sheet-force-access (self)
       ;; Erase the region.  Sheet has just been prepared.
       (tv:%draw-rectangle (- right left) (- bottom top)
			   left top tv:erase-aluf self)
       ;; Print the string.
       (send self ':string-out-centered-explicit
	     (if more-p (margin-scroll-region-more-msg region)
	       (margin-scroll-region-empty-msg region))
	     left top right nil
	     (margin-scroll-region-msg-font region) tv:char-aluf
	     0 nil nil))))
  ((:mouse-enters-region :mouse-leaves-region :mouse-moves)
   (&rest ignore))
  (:mouse-click (ignore ignore region ignore)
   (if (margin-scroll-region-more-p region)
       (let ((from (tv:margin-region-margin region)))
	 (send self ':scroll-relative
	       from (if (eq from ':top) ':bottom ':top)))
       (beep)))
  (:who-line-documentation-string (ignore)
    "Any button to scroll one page.")))

11.4 Defining Margin Item Flavors

Let us assume that you want to define a thing called a mumble that goes in a window’s margins, the way labels and borders do. You create a flavor mumble-margin-mixin that implements the feature.

This flavor should have certain instance variables, which will be used only by the methods of mumble-margin-mixin so their precise format is up to you.

  • current-mumbles Some sort of specification of what mumbles this window should have. It might record text to display for the mumbles, a font to use, etc.
  • mumble-margin-area Records the rectangle within the window where the mumbles should go. Everything that deals with the location of the mumbles on the screen should act based on the value of this variable.

    It is recommended to use a list of four values: the left, top, right and bottom edges of the rectangle, all relative to the upper left outside corner of the window.

Some margin mixins have just a single variable whose value is a list containing both the contents and the position of the margin item.

Example:
(defflavor mumble-margin-mixin 
	   ((current-mumbles nil) mumble-margin-area)
	   ()
  (:required-flavors tv:minimum-window)
  (:inittable-instance-variables current-mumbles))

(defmethod (mumble-margin-mixin :before :init) (ignore)
  (setq current-mumbles 
	(canonicalize-and-validate-mumble-spec 
	  current-mumbles)))

Now you must at the minimum create methods for two standard operations for margin computation and display, to interface mumble-margin-mixin to the rest of the system. These operations are :compute-margins and :refresh-margins.

.defmetamethod windows :compute-margins lm tm rm bm :compute-margins is used by the system to find out how much space is needed in each margin of the window by borders, labels, and anything else. Each flavor that implements a kind of margin item must define a method for it. This operation uses :pass-on method combination, so that the values from one method become the arguments to the next. These arguments are interpreted as the amount of space allocated so far in each margin. Each method increments one or more of them by the amount of space needed by that mixin. .end_defmetamethod

.defmetamethod windows :refresh-margins Redraws all the contents of the window’s margins. Each flavor of margin item must add a daemon method to this operation. The method may assume that its own margin area is completely erased to begin with. .end_defmetamethod

For example:

(defmethod (mumble-margin-mixin :compute-margins) 
	   (lm tm rm bm)
  (let ((wid (mumble-margin-width current-mumbles)))
    (setq mumble-margin-area
	  (list lm tm (+ lm wid) (- tv:height bm)))
    (values (+ lm wid) tm rm bm)))

Here we assume that the mumbles always go in the left margin. So it is always the left margin’s width that is incremented, and the others are returned just as they were passed. We also assume that mumble-margin-width is a function you have defined that computes the width of space that the mumbles need.

In addition to returning modified versions of its arguments, the method also sets up the value of mumble-margin-area. This is the only place it is necessary to set that variable. By recording the position of each margin item this way, we take into account how one margin item affects the position of the others. For example, the mumbles might come inside the borders, and then the lm, tm, rm and bm values will already contain the width of the borders. Then margin-mumble-area will describe a rectangle that is within the borders.

Usually an additional mixin-specific operation is introduced into this method, as follows:

(defmethod (mumble-margin-mixin :compute-margins)
	   (lm tm rm bm)
  (send self ':recalculate-mumble-margins lm tm rm bm))

(defmethod (mumble-margin-mixin :recalculate-mumble-margins) 
	   (lm tm rm bm)
  (let ((wid (mumble-margin-width current-mumbles)))
    (setq mumble-margin-area
	  (list lm tm (+ lm wid) (- tv:height bm)))
    (values (+ lm wid) tm rm bm)))

This way, other mixins can be defined to modify where the mumbles go by replacing the :recalculate-mumble-margins method.

The one other thing you must do is provide a method for :refresh-margins, to draw the mumbles in the rectangle recorded: You can assume that that rectangle is clear to start with.

(defmethod (mumble-margin-mixin :after :refresh-margins) ()
  (tv:sheet-force-access (self)
    (draw-mumbles current-mumbles mumble-margin-area)))

You may wish to provide the user with an operation to change the window’s mumbles. This operation should use the :redefine-margins operation.

.defmetamethod windows :redefine-margins This operation recomputes how much margin space is needed for all of the margin items, by invoking the :compute-margins operation, and then actually changes the window margin sizes if necessary.

If the margin sizes have changed, then the window is erased and :refresh-margins is done; the instance variable tv:restored-bits-p (present in all windows) is left set to nil. If the margin sizes have not changed, no output whatever is done, and tv:restored-bits-p is left set to t. All this is done using the :refresh operation. .end_defmetamethod

Here is an example of how to use it:

(defmethod (mumble-margin-mixin :set-mumbles) (new-mumbles)
  (setq current-mumbles
	(canonicalize-and-validate-mumble-spec new-mumbles))
  (send self ':redefine-margins)
  (when tv:restored-bits-p
    (tv:sheet-force-access (self)
      (erase-mumble-area mumble-margin-area)
      (draw-mumbles current-mumbles mumble-margin-area))))

The explicit erasure and drawing of the mumbles is done in the case where the total sizes of the margins have not changed (and therefore no screen updating has been done), in case the contents of the mumbles have changed.

12 Frames

A frame is a window that is divided into sub-windows, using the hierarchical structure of the window system (discussed in (window-hierarchy)). The sub-windows are called panes. The panes are the inferiors of the frame, and the frame is the superior of each pane. Several heavily-used systems programs use frames. For example, inspector windows are frames. The default inspector window has six panes: the interaction pane on top, the history pane and command menu pane below it, and three inspect panes below that. The window debugger and Zmacs also use frames. In Zmacs, each new editor window is a pane of the Zmacs Frame. ZMail uses several different frames, even frames within other frames.

From these examples, you can see some of the things that frames are good for. In general, by using a frame as a user interface to an interactive subsystem, you get a convenient way to put many different things on the screen, each in its own place. Generally you can split up the frame into areas in which you can display text or graphics, areas where you can put menus or other mouse-sensitive input areas, and areas to interact with, in which keyboard input is echoed or otherwise acknowledged.

It is usually best for a frame and its panes to be treated as a unit by the system menu Select menu and by the Terminal and System keys. The mixins tv:inferiors-not-in-select-menu-mixin ((select-menu)) and tv:alias-for-inferiors-mixin ((previously-selected-windows-section)), respectively, in the frame’s flavor bring this about. Then selection of panes within the frame is done by making the chosen pane the selection substitute of the frame ((selection-substitutes)). The program managing the frame can maintain a "selected pane within the frame" this way, while letting the user decide when to select the frame as a whole.

It is also common for all of the panes to use the same input buffer so that the program can always do its input in the same fashion and collect keyboard and mouse input from all the panes. See (shared-input-buffers).

It is also possible to have frames with less coupling between their panes. For example, the frame you get from requesting a frame in the system menu Split Screen option does make its panes share an input buffer, and allows them to be individually represented in the Select menu and for Terminal and System commands. It also lets the panes be selected in their own right and not as substitutes for the frame. This is done because typically each window in the split screen frame is managed by its own process.

One kind of frame is the constraint frame, which adjusts the shapes of its panes automatically as its own shape is changed. These frames are described first since they are a ready-to-use facility. More basic frame flavors can be built upon to create frames which manage their panes’ exposure and shapes in other ways. The editor, for example, does this.

.defflavor tv:basic-frame All frame flavors are built on this one. tv:frame-forwarding-mixin (see (tv:frame-forwarding-mixin-flavor)) mixed with this provides a non-constraint frame to which you need only add code to decide when to expose the panes and how big to make them.

tv:basic-frame is nearly the same as tv:minimum-window; it does not have all the mixins that go into the tv:window flavor. In particular, it does not provide for borders or a label, and it cannot be the selected window. It also has tv:delay-notification-mixin (see (tv:delay-notification-mixin-flavor)) as a component. .end_defflavor

12.1 Constraint Frames

If you use Edit Screen to change the shape of an inspector or debugger window frame, the shapes of the panes are all changed so that the proportions come out looking as they are supposed to. If you play around with Edit Screen enough, you can even see the menus reformat themselves (changing their numbers of rows and columns) in order to keep all of their items visible. The way all this works is that the positions and shapes of the panes, instead of being explicitly specified in units of pixels, are specified symbolically. When the window changes shape, the symbolic description is elaborated again in light of the new shape, and the panes are reshaped appropriately.

This set of symbolic descriptions is called a set of constraints. When you make a constraint frame, you specify the configuration of panes within the frame by creating list structure to represent the layout. The format of this list structure is called the constraint language. It lets you say things like "give this pane one third of the remaining room, then give that pane 17 pixels, and then divide what remains between these two panes, evenly." The constraint language is fairly complex, and is described in full detail later. In general, a frame can have many different configurations. Each configuration is described in the constraint language, and each specifies one way of splitting up the frame. While the program is running, it can switch a frame from one configuration to another. Some panes may appear in more than one configuration, but other panes may be left out of one configuration, and may only be visible when the frame is switched to another configuration. For example, in ZMail, when you click on Profile, the frame changes to a new configuration whose panes include a profile editor window and another frame, the profile button frame.

12.1.1 Constraint Frame Flavors

The processing of constraints is actually implemented by a frame mixin called tv:basic-constraint-frame.

The flavor of the frame itself might be any of several flavors. The simplest thing for it to be is tv:constraint-frame.

.defflavor tv:constraint-frame This flavor is the basic kind of constraint frame. The rest of this section describes its behavior in detail. This flavor, like tv:basic-frame, does not provide for borders, a label, or for being selected. .end_defflavor

.defflavor tv:bordered-constraint-frame This flavor is just tv:constraint-frame with tv:borders-mixin (see (tv:borders-mixin-flavor)) mixed in at the right place. It will have a border around the edge. By default (using the :default-init-plist option of the flavor system), the :border-margin-width is zero, so the panes at the edges of the frame are right next to the border itself. .end_defflavor

Bordered constraint frames are used most often. Usually, each of the panes has borders, and the frame does too. A reason for this is that when two of the panes are right next to each other, which they usually are, their borders are side by side, and so look like a double-thick line. In order to make the edges of the panes that are at the edge of the frame (rather than up against another pane) look like they are the same thickness, the frame has a border itself.

A convenient way to make all the panes of a constraint frame use the same input buffer is to use one of the following flavors:

.defflavor tv:constraint-frame-with-shared-io-buffer This is like tv:constraint-frame, but all the panes of the frame share the same input buffer used by the frame itself. See (shared-input-buffers). .end_defflavor

.defflavor tv:bordered-constraint-frame-with-shared-io-buffer This is just like tv:constraint-frame-with-shared-io-buffer except that it has tv:borders-mixin mixed into it at the right place, so that the frame has a border around it. .end_defflavor

.definitoption tv:constraint-frame-with-shared-io-buffer :io-buffer io-buffer If this option is present, io-buffer is used as the input buffer for the frame and the panes. Otherwise, a default input buffer is created. (See (io-buffer) for a discussion of I/O buffers.) .end_definitoption

12.1.2 Examples of Specifications of Panes and Constraints

The full description of how to use constraint frames, including the full constraint language, is rather complicated. The complete specifications are given in the next section; this section gives some common examples, in order to show the general idea of how the specifications work.

The following form creates a constraint frame with two panes, one on top of the other, each of which takes up half of the frame.

(make-instance 'tv:constraint-frame
	       ':panes
		  '((top-pane tv:window)
		    (bottom-pane tv:window))
	       ':constraints
		  '((main . ((top-pane bottom-pane)
			     ((top-pane 0.5))
			     ((bottom-pane :even))))))

Two initialization options were given to the tv:constraint-frame flavor: the :panes option and the :constraints option. The meaning of the :panes specification is: "This frame is made of the following panes. Call the first one top-pane; its flavor is tv:window. Call the second one bottom-pane; its flavor is tv:window". The meaning of the :constraints specification is: "There is just one configuration defined for this pane; call it main. In this configuration, the panes that appear are, in order from top to bottom, top-pane and bottom-pane. top-pane should use up 0.5 of the room. bottom-pane should use up all the rest of the room."

This example demonstrates some more features:

(make-instance 
  'tv:bordered-constraint-frame
  ':panes
    '((graphics-pane tv:window
	 :label nil :blinker-p nil)
      (message-pane tv:window
         :label "Message Pane" :blinker-p nil)
      (interaction-pane tv:window))
  ':constraints
    '((main . ((interaction-pane graphics-pane message-pane)
	       ((message-pane 4 :lines))
	       ((graphics-pane 400))
	       ((interaction-pane :even))))))

This frame has a border around the edges (because of the flavor of the frame itself), and it has three panes. The panes are given some initialization options themselves. The topmost pane is interaction-pane, graphics-pane is in the middle, and message-pane is on the bottom. message-pane is four lines high, graphics-pane is 400 pixels high, and interaction-pane uses up all remaining space.

Here is a window that has two possible configurations. In the first one, there are three little windows across the top of the frame and a big window beneath them; in the second one, the same big window is at the top of the frame, and underneath it is a strip split between a menu and another window.

(make-instance
 'tv:bordered-constraint-frame
 ':panes
   '((huey tv:window)
     (dewey tv:window)
     (louie tv:window)
     (main-pane tv:window)
     (random-pane tv:window)
     (menu tv:command-menu
	   :item-list ("Foo" "Bar" "Baz")))
   ':constraints
     '((first-config . ((top-strip main-pane)
			((top-strip :horizontal (.3)
			  (huey dewey louie)
			  ((huey :even)
			   (dewey :even)
			   (louie :even))))
			((main-pane :even))))
       (second-config . ((main-pane bottom-strip)
			 ((bottom-strip :horizontal (.2)
			   (random-pane menu)
			   ((menu :ask :pane-size))
			   ((random-pane :even))))
			 ((main-pane :even))))))

In this example, the frame has two different configurations. When the frame is first created, it is in the first of the configurations listed, namely first-config. In this configuration, the top three-tenths of the frame are split equally, horizontally, between three windows, and the rest of the frame is occupied by main-pane. The frame can be switched to a new configuration using the :set-configuration message (see (tv:basic-constraint-frame-set-configuration-method)). If we switch it to second-config, then main-frame will appear on top of a strip one-fifth of the height of the window. This strip will contain a menu on the right that is just wide enough to display the strings in the menu’s item list, and another pane using up the rest of the strip. When the configuration of the window is switched, main-pane must be reshaped.

Another thing to notice is that the list of items in the menu was present in the :panes option, rather than a form to be evaluated. If the list had been in a variable, it would have been necessary to write the :panes option using backquote, like this:

  ':panes
     `((huey tv:window)
       (dewey tv:window)
       (louie tv:window)
       (main-pane tv:window)
       (random-pane tv:window)
       (menu tv:command-menu 
	     :item-list ,the-list-of-items))

Menus and how to use them are explained later; see (menu).

In this example, the window is divided into two windows, side by side.

(make-instance
  'tv:bordered-constraint-frame
  ':edges '(100 100 600 600)
  ':panes
    '((left tv:window)
      (right tv:window))
  ':constraints
    '((main . ((whole-thing)
	       ((whole-thing :horizontal (:even)
			     (left right)
			     ((left :even) 
			      (right :even))))))))

This example also points out that constraint frames are windows too, and you can use init-options acceptable to tv:minimum-window with them. In this case, we give the edges of the frame as a whole, in absolute numbers. Remember that frames are not built out of tv:window; see (frame-flavor-components).

In actual practice, panes are usually made out of more interesting flavors than tv:window.

12.1.3 Specifying Panes and Constraints

This section gives the complete rules for specifying the panes of a constraint frame, and for the constraint language. It should help explain any of the above examples that were unclear, and tell you all the things you can do with the constraint language.

When you create a constraint frame, you must supply two initialization options. The :panes option specifies what panes you want the frame to have, and the :constraints option specifies the set of constraints for each of the configurations that the window may assume. For the purposes of these two options, windows are given internal names, which are Lisp symbols, used only by the flavors and methods that deal with constraint frames. These names are not used as the actual names of the windows (as in the :name message (see (windows-name-method)).

.definitoption tv:constraint-frame :panes pane-descriptions This initialization option is required for all flavors of constraint frames. The argument, pane-descriptions, is a list of pane descriptions. Every pane description looks like this:

(name flavor . options)

name is the internal name (a symbol). flavor is the flavor of which the pane should be an instance. options is a list to be appended to the initialization plist for the pane when it is created. When the frame is first created, it will create all of its panes, using flavor and options. The frame will add some of its own options to control the position and shape of the window; you should not pass any such options in the options list. .end_definitoption

.definitoption tv:constraint-frame :constraints configuration-description-list This initialization option is required for all flavors of constraint frames. The argument, configuration-description-list, is a list of configuration descriptions. The format of configuration descriptions is explained below. .end_definitoption

Both init options work by initializing instance variables which are then looked at by the :init methods of constraint frames. Instead of using the init options, you can set the instance variables yourself in a :before :init method.

.definstvar tv:constraint-frame tv:panes .definstvar1 tv:constraint-frame tv:constraints The instance variables in which the constraint frame mechanism looks to find the lists of panes and constraints. .end_definstvar

A configuration-description-list is a list of configuration-descriptions. There is one configuration-description in the list for each of the possible configurations that the frame can assume. Each configuration is named by a symbol, called the configuration-name. A configuration-description-list is an alist that associates the configuration-descriptions with the names. It looks like this:

((configuration-name-1 . configuration-description-1)
 (configuration-name-2 . configuration-description-2)
 ...)

Each configuration-description describes the layout of the panes in a single configuration. The description has two parts. The first part specifies the order in which the windows appear, and the second part specifies how the sizes are computed. Actually, in addition to windows, there can also be dummies in the configuration-descriptor. A dummy is used either to hold empty space that is not used by any window, or it can reserve a region of space to be divided up by another configuration-description.

A configuration-description splits up one of the dimensions of a rectangular area into many parts. Such an area is called a section. Which of the two dimensions is being split up is determined by the stacking. If the stacking is :vertical then the section is being split up vertically; that is, the parts are stacked on top of each other. If the stacking is :horizontal then the section is being split up horizontally; that is, the parts are side-by-side. The stacking of the top-level configuration-descriptions in the :constraints option is always :vertical, but there can be more configuration-descriptions nested inside of them, and these can have either stacking.

Each part has a name, represented as a symbol. A part may hold either an actual pane, or other things; in the latter case, it is called a dummy part. Dummy parts can be further subdivided into more panes and dummies using another constraint-description, or their pixels can be blank or filled with some pattern.

A configuration-description looks like this:

(ordering . description-groups)

ordering is a list of names of panes and of dummies, each represented by a symbol; the order of this list is the order that the panes and dummies appear in the space being split up by the configuration-description. For vertical stacking the list goes top to bottom. For horizontal stacking the list goes left to right. A description-group is a list of descriptions. Each description describes either exactly one pane or one dummy. A configuration-description must have one description for each element of the ordering list.

All of the descriptions in a description-group are processed together ("in parallel"); each of the description-groups is processed in turn, starting with the first one. By grouping the descriptions this way, you can control which constraints are elaborated together and which are elaborated at different times; when two constraints are elaborated at different times you can control which one is elaborated first. The reason that the ordering-list in the configuration-description is separate from the description-groups is so that the order in which the panes and dummies appear in the frame can be independent of the order in which their constraints are elaborated.

Each description describes one pane or one dummy. We’ll get back to dummies later. A description that describes a pane looks like this:

(pane-name . constraint)

pane-name is the name of the pane being described; constraint is the constraint that describes the pane. We will return later to what descriptions of dummies look like. The constraint will be elaborated, and will yield a size in pixels; this size will be used for the width or height being computed.

Finally we get to constraints themselves. The basic form of a constraint is as follows:

(key arg-1 arg-2 ...)

key may be a fixnum, a flonum, or one of various keyword symbols. Each type of constraint may take arguments, whose meaning depends on which kind of constraint this argument is passed to.

While descriptions of panes do not have the same format as descriptions of dummies, the same kind of constraints are used in both of them. So all the formats given below may be used inside the descriptions of either panes or dummies.

Any constraint may, optionally, be preceded by a :limit clause. If a constraint has a :limit clause, the constraint looks like:

(:limit limit-specification key arg-1 arg-2 ...)

The :limit clause lets you set a minimum and a maximum value that will be applied to the size computed by the constraint. If the constraint returns a value smaller than the minimum, then the minimum value will be used; if it returns a value larger than the maximum, then the maximum value will be used. The limit-specification is normally a two-element list, whose elements are fixnums giving the minimum and maximum values in pixels. If the list has a third element, it should be one of the symbols :lines or :characters, and it means that the fixnums are in units of lines or characters, computed by multiplying by the line-height or char-width of the pane (see (char-width-and-line-height)). If there is a fourth element, it should be the name of a pane, and that pane’s line-height or char-width is used instead of that of the pane being constrained. (If this constraint applies to a dummy instead of a pane, and the third element of the list is present, then the fourth must be present as well, since dummies do not have their own line-height nor char-width.)

The following Lisp objects may be used as values of key in a constraint. Note: the :funcall and :eval constraints are rarely used and you probably don’t need to worry about them. The other kinds are used frequently.

  • fixnum This lets you specify the absolute size. The value computed by the constraint is simply this fixnum. Optionally, an argument may be given: it may be the symbol :lines or the symbol :characters, meaning that the fixnum is in units of lines or characters, and should be computed by multiplying by the line-height or char-width of the window. If a second argument is also present, it should be the name of a pane, and that pane’s line-height or char-width is used instead of that of the pane being constrained. (If this constraint applies to a dummy instead of a pane, and the first argument is given, then the second must be present as well, since dummies do not have their own line-height nor char-width.)
  • flonum This lets you specify that a certain fraction of the remaining space should be taken up by this window. Optionally, an argument may be given: it may be :lines or :characters, and it means to round down the size of the pane to the nearest multiple of the pane’s line-height or char-width. A second argument may be given; it is just like the second argument when key is a fixnum (see above).

    The distinction between descriptors in the same group and descriptors in different groups is important when you use this kind of constraint. If you have one descriptor group with two descriptors, each of which requests .2 of the remaining space, then both panes will get the same amount of space. However, if you have the same two descriptors but put them in successive descriptor groups, then the first one will get .2 of the remaining space, and then the second one will get .2 of what remains after the first one was allocated; thus the second pane will be smaller than the first. In other words, the amount of space remaining is recomputed at the end of each descriptor group, but not at the end of each descriptor.

  • :even "constraint frames" This constraint has a special restriction: you can only use it for descriptors in the last descriptor group of a configuration. Furthermore, if any of the descriptors in that group use :even, then all of the descriptors in the group must use :even. The meaning is that all of the panes in the last descriptor group evenly divide all of the remaining space.

    It is usually a good idea to use :even for at least one pane in every configuration, so that the entire frame will be taken up by panes that all fit together and extend to the borders of the frame. :even is careful to choose exactly the right number of pixels to fill the frame completely, avoiding roundoff errors that might cause an unsightly line of one or a few extra pixels somewhere.

    Remember that just because the :evens must be in the last descriptor group does not mean that the panes that they apply to must be at the bottom or right-hand end of the frame! The ordering of the panes in the frame is controlled by the ordering list, not by the order in which the descriptors appear.

  • :ask "constraint frames" This constraint lets you ask the window how much space it would like to take up. The format of a constraint using :ask is as follows:
    (:ask operation arg-1 arg-2 ...)
    

    A message with operation operation and arguments composed of some extra arguments passed by the constraint mechanism followed by arg-1, arg-2, etc. is sent to the pane; its answer says how much space the pane should take up. Note that arg-1, etc., are not forms: they are the values of the arguments themselves (i.e. they are not evaluated; if you want to compute them, you must build the constraint language description at run-time. This is usually written using a backquoted list).

    The arguments that are actually sent along with the message are the same as the arguments passed when you use the :funcall option except that the constraint-node is not passed; see below.

    Various different flavors of windows accept some messages suitable for use with :ask. By convention, several kinds of windows, such as menus, accept a message called :pane-size. For example, the :pane-size method for menus figures out how much space in the dimension controlled by the :ask constraint is needed to display all the items of the menu, given the amount of space available in the other dimension. No arguments are specified in the constraint. Other useful operations, handled by all windows, are :square-pane-size (also with no additional arguments), which makes the window take up enough room to be square including its borders, and :square-pane-inside-size, which makes the window be square inside its borders.

  • :ask-window "constraint frames" This constraint is a variation on :ask. Its format is:
    (:ask-window pane-name message-name arg-1 arg-2 ...)
    

    It works like :ask except that the message is sent to the pane named pane-name instead of the pane being described. This is primarily used for dummies, when the size of a dummy wants to be controlled by the needs of a pane inside it.

  • :funcall "constraint frames" This constraint lets you supply a function to be called, which should compute the amount of space to use. The format is:
    (:funcall function arg-1 arg-2 ...)
    

    The specified function is called. It is first passed six arguments from inside the workings of constraint frames, and then the arg-1, arg-2, etc. values. The six arguments are:

    • constraint-node This is an internal data structure. [Not yet documented; you should not need to look at this anyway.]
    • remaining-width The amount of width remaining to be used up at the time this description is elaborated, after all of the panes in previous description groups and all of the earlier panes in this description group are allocated.
    • remaining-height Like remaining-width, but in the height direction.
    • total-width The amount of width remaining to be used up by all of the parts of this description group. This is the amount of room left after all of the panes in previous description groups have been allocated but none of the panes in this description group have been allocated.
    • total-height Like total-width, but in the height direction.
    • stacking Either :vertical or :horizontal, depending on the current stacking.
  • :eval "constraint frames" This is like :funcall, but instead of providing a function and arguments, you provide a form. The format is:
    (:eval form)
    

    The six special values that are passed as arguments when the :funcall constraint-type is used can be accessed by form as the values of the following special variables:

    tv:**constraint-node**
    tv:**constraint-remaining-width**
    tv:**constraint-remaining-height**
    tv:**constraint-total-width**
    tv:**constraint-total-height**
    tv:**constraint-stacking**
    

This finishes the discussion of descriptions of panes. Descriptions of dummies are different; they may be in any of several formats, identified by the following keywords:

  • :blank "constraint frames" This description is used if you want this part of the section to be filled up with some constant pattern. The format of the description is:
    (dummy-name :blank pattern . constraint)
    

    The constraint is used to figure out the size of the part of the section, in the usual way. pattern may be any of the following:

    • :white "constraint frames" The part is filled with zeroes.
    • :black "constraint frames" The part is filled with the maximum value that the pixels can hold (if the pixels are one bit wide, as on a black-and-white TV, this value is 1).
    • an array The part is filled with the contents of the array, using the bitblt function (see (bitblt-fun)).
    • a symbol The symbol should be the name of a function of six arguments. The function is expected to fill up the rectangle that has been allocated to this part of the section with some pattern. The following values are passed to the function:
      • constraint-node This is an internal data structure. [Not yet documented; you should not need to look at this anyway.]
      • x-position
      • y-position
      • width
      • height These four arguments tell the function the position and size of the rectangle that it should fill.
      • screen-array This is a two-dimensional array into which the function should write the pattern it wants to put into the window.
    • a list This is similar to the case in which pattern is a symbol, but it lets you pass extra arguments. The first element of the list is the function to be called, and that function is passed all of the objects in the rest of the list, after the six arguments enumerated above.
  • :horizontal or :vertical This description is used if you want to subdivide the part into more panes and dummies, using a configuration-description. If you use :vertical, it will be split up with vertical stacking, and if you use :horizontal, it will be split up with horizontal stacking. [Currently, you are required to use the opposite kind of stacking from the kind currently happening; that is, successive levels of configuration-description must use alternating kinds of stacking. This restriction may be lifted in the future.] The format is as follows:
    (dummy-name :horizontal constraint . configuration-description)
    or
    (dummy-name :vertical constraint . configuration-description)
    

    constraint, as usual, specifies the size of this part; it can be in any of the formats given above. Note that in this format, constraint appears as an element of a list rather than as the tail of a list, and so the printed representation of the list will include a pair of parentheses around the constraint. configuration-description tells how this part is subdivided into parts of its own.

.defmetamethod windows :pane-size remaining-width remaining-height total-width total-height stacking This operation is invoked by constraints of the form (:ask :pane-size). It should return the size in pixels to give the pane, in the current stacking direction. The meanings of the arguments as they will be passed by the constraint manager are described above under the :funcall constraint (see (:ask-arguments)). .end_defmetamethod

.defmetamethod windows :square-pane-size remaining-width remaining-height total-width total-height stacking .defmetamethod1 windows :square-pane-inside-size remaining-width remaining-height total-width total-height stacking These operations are invoked by constraints of the form (:ask :square-pane-size) and (:ask :square-pane-inside-size). They return the size required to make the pane square. For horizontal stacking, they returns a width equal to the specified height; for vertical stacking, they returns a height equal to the available width.

The difference between the two operations is that :square-pane-size makes the outside size of the window square, whereas :square-pane-inside-size makes the inside of the window (not including the borders) square. .end_defmetamethod

12.1.4 Constraint Frame Operations

.defmethod tv:basic-constraint-frame :get-pane pane-name Returns the pane (the inferior window itself) that was named by the symbol pane-name in the :panes specification of this frame. .end_defmethod

.defmethod tv:basic-constraint-frame :pane-name pane Returns the symbol that was used to name pane in the :panes specification of this frame. If pane is not one of the panes, return nil. .end_defmethod

.defmethod tv:basic-constraint-frame :create-pane name flavor &rest options Creates and returns a window, to serve as a pane of this frame, made from flavor flavor and init options options. name is the pane name to be used. By default, it is not used here.

The panes of the frame are created from their specification using this operation, the arguments being taken from the elements of the specification. It may be useful to redefine this operation. .end_defmethod

.defmethod tv:basic-constraint-frame :send-pane pane-name message &rest arguments Sends the specified message with the specified arguments to the pane that was named by the symbol pane-name in the :panes specification of this frame. .end_defmethod

.defmethod tv:basic-constraint-frame :send-all-panes message &rest arguments Sends the specified message with the specified arguments to all of the panes of this frame, including the non-exposed ones. .end_defmethod

.defmethod tv:basic-constraint-frame :send-all-exposed-panes message &rest arguments Sends the specified message with the specified arguments to all of the exposed panes of this frame. .end_defmethod

.definitoption tv:basic-constraint-frame :configuration configuration-name Makes the initial configuration of the frame be the one named by the symbol configuration-name. .end_definitoption

.defmethod tv:basic-constraint-frame :configuration Returns the symbol naming the current configuration of the frame. .end_defmethod

.defmethod tv:basic-constraint-frame :set-configuration configuration-name Sets the configuration of the frame to the one named by the symbol configuration-name. .end_defmethod

.defmethod tv:basic-constraint-frame :get-configuration configuration-name Returns the internal ("parsed") data structure that describes what is specified for configuration configuration-name. This describes which windows are supposed to be included, and the constraints for them. .end_defmethod

.defmethod tv:basic-constraint-frame :redefine-configuration config-name new-config &optional (parsed-p t) Redefines the meaning of configuration config-name according to new-config. If parsed-p is t, new-config is expected to be in parsed form, such as the value returned by the :get-configuration operation. If parsed-p is nil, new-config is treated as a configuration-description such as you would use to define the configuration when initially specifying the constraints of the frame (see (configuration-description)). .end_defmethod

12.2 Pane-Frame Interaction

Several fundamental window operations actually ask the window’s superior what to do. This has no effect for a top-level window but becomes important when the window’s superior is a frame. The superior can decide whether the operations should actually go ahead as requested. These operations are :expose, :deexpose, :bury, :select and :set-edges. Here is how they are handled:

  • :expose, :deexpose, :bury, :select These operations first send a message to the superior with operation :inferior-expose, :inferior-deexpose, :inferior-bury or :inferior-select. The pane itself is passed as the argument.

    If the message sent to the superior returns non-nil, the operation is performed on the pane as usual. Otherwise, it is skipped.

  • :set-edges An :inferior-set-edges message is sent to the superior, its arguments being the pane followed by the arguments of the :set-edges message. If the operation’s first value is non-nil, the pane’s edges are changed as requested. Otherwise, the pane’s edges are not changed, and the remaining values from the :inferior-set-edges operation are returned from the :set-edges.

    Of course, the frame can change the pane’s edges in some other way and then return nil.

tv:basic-frame defines only the :inferior-select operation to do anything nontrivial; it makes the pane be the frame’s selection substitute and then sends a :select to the frame. The others operations do nothing but return non-nil. Thus, there is minimal interaction between the frame and its inferiors. tv:frame-forwarding-mixin defines :inferior-expose, :inferior-deexpose and :inferior-bury so that the frame and panes are all exposed together.

.defflavor tv:frame-forwarding-mixin Defines :inferior-expose, :inferior-deexpose, and :inferior-bury methods for a frame that normally cause :expose, :deexpose or :bury operations on panes to expose, deexpose or bury the frame rather than the pane.

An :inferior-set-edges method is also defined, for internal reasons only. Its purpose is to avoid a user-visible change in behavior rather than to provide one.

This flavor is part of tv:constraint-frame and the other standard instantiable flavors of constraint frame. .end_defflavor

tv:basic-frame has an instance variable tv:recursion which is used to distinguish between :expose, etc. operations sent by the frame’s code to its panes, and those sent by other programs. When an outside program sends a :expose, :deexpose, or :bury message to one of the panes, the :inferior-expose, etc. operation on the frame simply exposes, deexpose or buries the frame itself, and does not allow the operation on the pane to proceed. When the frame’s code itself exposes a pane, it does so with tv:recursion temporarily non-nil so that when the :inferior-expose is done it will return t and let the pane be exposed.

.defmetamethod frames :pane-types-alist This should return a menu item list to be used for the handling of the Create item in the screen editor, when editing the panes of this frame. The value of the menu item should be a flavor of window to create, or a list to be evaluated to return a flavor.

The menu item’s value (or the result of evaluating it) can also be t, which directs the screen editor to read a flavor name from the user. .end_defmetamethod

12.2.1 The Selected Pane

A frames is normally operated with one of its inferiors as a selection substitute. The selection substitute of a frame is also called the "selected pane", as this feature used to be available only in frames. Unless you mix tv:select-mixin into your frame flavor, the frame itself cannot be the selected window. Therefore, it is important to provide a selection substitute when the frame is created. This can be done by doing :set-selection-substitute in an :after :init method:

(defmethod (my-frame :after :init) (ignore)
  (send self ':set-selection-substitute
	(send self ':get-pane 'interaction-pane)))

Explicitly selecting a pane with the :select operation actually works by setting the frame’s selection substitute, by means of the forwarding mechanism described above.

In a constraint frame, or any other frame which has tv:frame-forwarding-mixin, you should not attempt to select a pane which is not already exposed, because of the effects of forwarding on the :expose operation.

.definitoption tv:basic-constraint-frame :selected-pane pane-name In a constraint frame, you can initialize the selected pane with this handy init option. Instead of fishing out the pane, just give its name. .end_definitoption

.defmethod tv:basic-frame :select-pane inferior-window This is another, older name for the :set-selection-substitute operation, before it was generalized to apply to windows other than frames. .end_defmethod

.defmethod tv:basic-frame :selected-pane This is another, older name for the :selection-substitute operation, before it was generalized to apply to windows other than frames. .end_defmethod

.defflavor tv:interaction-pane (tv:preemptable-read-any-tyi-mixin tv:notification-mixin tv:autoexposing-more-mixin tv:window) This flavor is often useful for a pane for reading and echoing multi-character commands in a system which uses a frame. This pane would typically be the selected pane. .end_defflavor

13 Miscellaneous Features

13.1 Notifications

Notifications are asynchronous messages that come from something other than the selected window. For example, when an interactive message from another user comes in (which was sent with the qsend function), it is printed as a notification. You may have noticed that sometimes a notification is printed out immediately, while sometimes all that happens is a message in the who line. The selected window is responsible for deciding what to do with the notification.

Function: tv:notify window-of-interest format-string &rest format-args
Function: tv:careful-notify window-of-interest careful-p format-string &rest format-args

Make a notification. format-string and format-args are passed to format to print the text of the notification. Where this text is printed, and how, is under the control of the selected window, as described below.

window-of-interest is a window that should be selected if the user clicks the mouse on the notification window (if the notification happens to use its own window). For example, a notification about a message from another user will supply the Converse window as this argument. This window can also be selected with the Terminal 0 S command.

tv:careful-notify is different in that if careful-p is non-nil and the notification cannot be printed now because of windows being locked, it returns immediately. The value is non-nil if the notification was printed successfully.

.defmetamethod windows :print-notification time string window-of-interest The system invokes this operation on the selected window to ask it to make a notification. time will be a time to mention in the notification. string is the text to print. window-of-interest should be set up for the user to select in some convenient fashion, if possible. .end_defmetamethod

.defflavor tv:notification-mixin This mixin causes a window to handle notifications which happen while it is selected by printing them out on the window itself, if the window is big enough. Lisp listeners and typeout windows of all sorts use this mixin. .end_defflavor

.defmethod tv:notification-mixin :print-notification-on-self time string window-of-interest This operation does the actual work of printing a notification on the window itself, once it has been decided definitely to do so. It is sometimes useful for window flavors incorporating tv:notification-mixin to redefine this. .end_defmethod

.defflavor tv:delay-notification-mixin tv:delay-notification-mixin implements the default way of handling notifications: to make them wait. It is a component of tv:window, and also of anything that contains tv:select-mixin. tv:notification-mixin works by overriding it.

If a notification arrives while a window of this sort is selected, it is put on a list called tv:pending-notifications. All that happens immediately is a beep. But the presence of a non-nil value for this variable causes the mouse documentation line to display a message that there are notifications waiting, with blinking asterisks at each end of the line.

As soon as a window that can print the notifications is selected, they will be printed. For example, selecting a Lisp listener will do it. If you are in the editor, selecting the typeout window by typing Break will do it. There is also a command, Terminal N, which selects a window that just prints the notifications.

Alternatively, Terminal 2 N can be used to make the mouse documentation line go back to its normal function. This works by transferring everything on tv:pending-notifications onto another list, tv:deferred-notifications. These deferred notifications will still be printed if you switch to a suitable window. .end_defflavor

Another way a window can handle a notification is to ask some other window to do so. For example, editor windows (zwei:zmacs-window-pane) ask the containing Zmacs frame to do the job, and it in turn asks the echo area window to do it. This window displays the notification itself if the notification fits.

Function: tv:find-process-in-error

Returns a process that has got an error and is waiting, having made a notification, for a window to be selected so the debugger can be run. If no such process is waiting, returns nil. If there are several such processes, the most recent one to make its notification is returned. The window the process is waiting for selection of is returned as the second value.

Function: tv:choose-process-in-error

Similar to tv:find-process-in-error but asks the user about each candidate process. When the user answers Y, that process is returned. If the user answers N to each candidate, the value is nil. The window the process is waiting for selection of is returned as the second value.

Function: tv:print-notifications

Prints on standard-output all the notifications that have happened in this session.

.defmetamethod windows :notice event The :notice operation is used to report certain events so that flavors can redefine what to do when they happen. The argument to :notice is an event name, a keyword. Additional arguments are allowed but have no meaning for any of the events yet defined. Here are the defined events:

  • :input :notice
  • :output :notice The window is being used for input (output) and is not exposed, and its deexposed input (output) action is :notify. The default action is to make a notification and wait.
  • :input-wait :notice The window is being used for input and the process is waiting because no input is available now. The default action is to adjust the vertical position at which the next **MORE** will happen.
  • :error :notice The window is being used for the debugger and is not exposed. The default action is to make a notification and wait, or to get another window if this one is too small.

The :notice operation uses :or method combination: all the methods are run until one returns non-nil. Aside from that, the value returned is not meaningful. .end_defmetamethod

13.2 Lisp Listeners

.defflavor tv:lisp-listener This flavor of window is used for the window initially selected when the system starts up, and for windows created when you ask to create a "Lisp" window with any of the system menu commands. .end_defflavor

Variable: tv:initial-lisp-listener

The Lisp listener window that is selected when you boot.

Function: tv:idle-lisp-listener &optional screen.

Returns a Lisp listener which is waiting for input at top level, and is the full size of the specified screen. The screen defaults to tv:default-screen ((tv:default-screen-var)).

.defflavor tv:lisp-interactor This flavor of window works just like a Lisp listener, but System L will not select this kind of window, nor will tv:idle-lisp-listener return one. .end_defflavor

The mixin primarily responsible for making a Lisp listener behave the way it does is tv:listener-mixin-internal.

.defflavor tv:listener-mixin-internal This contains tv:process-mixin, and arranges by default for the process to be initialized to run the Lisp top level read-eval-print loop si:lisp-top-level1. .end_defflavor

.defmethod tv:listener-mixin-internal :package .defmethod1 tv:listener-mixin-internal :set-package package Get or set the package being used by the read-eval-print loop. These work by interfacing with some complicated code in tv:lisp-top-level1. The value from :package can be nil. When you set the package, either a package or a package name is acceptable.

These operations communicate with the process running the read-eval-print loop to access that process’s own binding of package. .end_defmethod

.defflavor tv:listener-mixin This flavor inherits its entire definition from tv:listener-mixin-internal. The only difference is that System L is defined to look for windows with this flavor, and not the other. .end_defflavor

13.3 Editor Windows

.defflavor zwei:zmacs-frame This is the flavor of the window you get when you type System E. It has its own process, and can select any Zmacs buffer. Generally none of the editor-specific operations should be invoked on this window; that should be left up to the window’s own process. Requests to this process, which generally ask the process to select a buffer, are passed to it as blips of the form

(:execute zwei:edit-thing spec)

where spec is anything valid as the argument to ed. .end_defflavor

A Zmacs frame is useful for providing the user an opportunity to edit whatever he likes. Sometimes it is useful for a program to offer the user specific text to edit for its own purposes.

.defflavor zwei:standalone-editor-window This is a window with no panes that serves as an editor. It has a minibuffer and type-in window that pop up as its inferiors when they are needed. This window has no process of its own; use the :edit operation in any process to do editing in the window. .end_defflavor

.defflavor zwei:standalone-editor-frame Another kind of standalone editor window, but this one is a frame with a permanently visible mode line and typein-window or mini buffer, just as a Zmacs frame is. .end_defflavor

.defmetainitoption "standalone editor windows" :comtab comtab Specifies the comtab to use in editing in this frame. The default is zwei:*standalone-comtab*. .end_defmetainitoption

.defmetamethod "standalone editor windows" :edit Invokes the editor command loop on this window. The End command will return. .end_defmetamethod

.defmetamethod "editor windows" :interval-string Returns a string giving the current text in the window. .end_defmetamethod

.defmetamethod "editor windows" :set-interval-string string Sets the text in the window to string. .end_defmetamethod

.defmetamethod "editor windows" :interval Returns the interval which is being edited in the window. If the window is a Zmacs frame, this is the selected buffer. Standalone editor windows have their own nonshared intervals which they edit; many of the editor primitives that work on Zmacs buffers also work on these intervals. .end_defmetamethod

.defmetamethod "editor windows" :set-interval interval Sets the interval that this window is displaying and editing to interval. On a Zmacs window, interval must be a Zmacs buffer; then this will actually tell the window to select the new buffer. .end_defmetamethod

.defflavor zwei:pop-up-standalone-editor-frame A temporary window form of zwei:standalone-editor-frame. .end_defflavor

.defresource zwei:pop-up-standalone-editor-frame &optional (superior tv:mouse-sheet) A resource of such windows, used by the following function. .end_defresource

Function: zwei:pop-up-edstring string &optional (near-mode '(:mouse)) mode-line-list min-width min-height initial-message (comtab zwei:*standalone-comtab*)

Pops up an editor window containing string and let the user edit it. When he types End, returns a string giving whatever he left in the editor buffer. If he types Abort, the value is nil.

near-mode specifies how to position the window before popping it up. It is passed to tv:expose-window-near.

mode-line-list is a list to be used to drive the mode line.

min-width and min-height are minimums for the size of the window. The window is larger than that if string requires more space to display.

initial-message, if non-nil, is displayed in the typein window immediately after the frame pops up.

comtab is the comtab to be used for editing.

.defflavor zwei:editor-top-level This is the flavor used by the Lisp (Edit) window which you can create with the system menu Create option. It is a kind of Lisp listener in which both the input and the output are recorded in an editor interval and can be edited. It is based on zwei:standalone-editor-window. .end_defflavor

.defresource zwei:temporary-mode-line-window-with-borders-resource &optional (superior tv:mouse-sheet) A resource of such windows, used by the following functions. .end_defresource

.defflavor zwei:temporary-mode-line-window-with-borders The temporary mode line window contains just a mode line and a mini buffer. It is a way for a program to request a small piece of input while allowing the user to edit with Zwei.

This is the flavor of window that you get in ZMail if you click right on Select in the ZMail command menu and then click on Find File in the Select menu. .end_defflavor

Function: zwei:typein-line-readline-near-window window format-string &rest format-args

Pops up a temporary mode line window near window, displaying its mode line by passing format-string and format-args to format, and lets the user edit. Return terminates editing. The user’s input is returned as a string. window may be any window on the screen, or :mouse, meaning pop up near the mouse.

Function: zwei:read-defaulted-pathname-near-window window prompt defaults special-type

Pops up a temporary mode line window near window, displaying the string prompt as the mode line, and lets the user edit text which (when the user types Return) is parsed into a pathname using defaults and special-type. window may be any window on the screen, or :mouse, meaning pop up near the mouse.

.defmethod zwei:temporary-mode-line-window-with-borders :call-mini-buffer-near-window window function &rest args Pops up this window near window, then uses function to read the input and returns the value it returns. function should be an editor function which invokes the mini buffer using zwei:edit-in-mini-buffer. The first argument to function is a stream reading from the text the user edited. args are passed to function as additional arguments. .end_defmethod

13.4 Window Flavors for Other Programs

.defflavor tv:peek-frame This flavor of window is a self-contained Peek display with its own process to update it. .end_defflavor

.defflavor tv:inspect-frame This flavor of window is a self-contained inspector with its own process to update it. .end_defflavor

.defresource tv:inspect-frame-resource &optional (superior tv:mouse-sheet) A resource of inspector frames which are created in a slightly special way so that they do not have their own processes, but instead are to be invoked in some other process by the function inspect. .end_defresource

.defflavor supdup:supdup A self-contained Supdup window with its own pair of processes to transfer data to and from the network. .end_defflavor

.defflavor supdup:telnet A self-contained Telnet window with its own pair of processes to transfer data to and from the network. .end_defflavor

.defresource supdup:supdup-windows &optional (superior tv:mouse-sheet) .defresource1 supdup:telnet-windows &optional (superior tv:mouse-sheet) Resources of Supdup and Telnet windows, for use by the functions supdup and telnet when operating in the mode of substituting for another window. .end_defresource

.defflavor tv:pop-up-text-window A temporary window, otherwise like tv:window. .end_defflavor

.defresource tv:pop-up-text-window &optional (superior tv:mouse-sheet) A resource of such windows. .end_defresource

.defflavor tv:truncating-pop-up-text-window A temporary window which truncates lines of output, otherwise like tv:window. .end_defflavor

.defflavor tv:truncating-pop-up-text-window-with-reset Like tv:pop-up-text-window but truncates lines and resets the associated process when deexposed. This is the kind of window that Terminal F uses to print its output, and it is good for many similar applications. .end_defflavor

.defresource tv:pop-up-finger-window &optional (superior tv:mouse-sheet) A resource of such windows. .end_defresource

13.5 The Who Line

The who line is the pair of lines at the bottom of the main Lisp Machine screen which display the current status of the machine. The first of the two lines displays documentation what mouse clicks would do at the present time, based on the actual position of the mouse. The second line displays the time, your login name, the current process’s package and run state, and file or net server information. The term "who line" is sometimes used to refer to this line alone.

The window system treats the who line as a separate screen, thus preventing windows on the rest of the screen from being moved or reshaped to overlap the who line. The mouse documentation line is displayed by a window of its own, and so is each field of the second line.

The documentation displayed by the mouse documentation line is obtained by sending the window under the mouse a :who-line-documentation-string message (see (windows-who-line-documentation-string-method)), or from the variable tv:who-line-mouse-grabbed-documentation when the mouse is grabbed (see (tv:who-line-mouse-grabbed-documentation-var)).

Function: tv:who-line-documentation t-or-nil

Turns the who line display of mouse documentation on or off.

The package name and run state displayed in the who line describe only one process. They normally describe the process associated with the selected window, which is a different process if a new window is selected. However, the who line can be fixated on a particular process, independent of the selected window.

Variable: tv:who-line-process

The process to describe in the who line, or nil meaning to display the one associated with the selected window. In the latter case, the :process operation on the window is used to get the process to display.

Variable: tv:last-who-line-process

The process most recently described in the who line, regardless of why that process was chosen. May be nil if there was no process to describe (for example, if the who line was supposed to describe the selected window but there was no selected window or the window had no process).

The user can set tv:who-line-process using the Terminal W command (see "Operating the Lisp Machine").

Function: tv:who-line-clobbered

Informs the who line that it must redisplay everything.

Recording open file streams for display:

Function: tv:who-line-file-state-sheet

This who line window displays the status of an open stream or active network server. It can also display the idle time if there is no stream or server.

This window is also responsible for maintaining the lists of streams and servers that could be displayed. New streams and servers are reported to it with operations described here.

.defmethod tv:who-line-file-sheet :add-stream stream update-p Adds stream to the list of open streams recorded by the file state sheet. If update-p is non-nil, the who line field is updated immediately. .end_defmethod

.defmethod tv:who-line-file-sheet :delete-stream stream Removes stream from the list of streams for the who line. .end_defmethod

.defmethod tv:who-line-file-sheet :delete-all-streams Clears out the list of streams for the who line. .end_defmethod

.defmethod tv:who-line-file-sheet :open-streams Returns the list of streams recorded for the who line. .end_defmethod

When the who line describes an open file, the name to display for it is obtained with the :string-for-wholine pathname operation. See (fs:pathname-string-for-wholine-method).

.defmethod tv:who-line-file-sheet :add-server conn contact-name process function Adds a entry to the list of active network servers recorded by the file state sheet. conn should be the network connection of this server, contact-name the contact name it responded to, process the process the server is running in. .end_defmethod

.defmethod tv:who-line-file-sheet :delete-server conn Removes the entry for connection conn from the list of servers for the who line. Note that this happens automatically if the connection is broken or closed. .end_defmethod

.defmethod tv:who-line-file-sheet :delete-all-servers Clears out the list of servers for the who line. .end_defmethod

Function: tv:close-all-servers &optional (reason "Foo on you")

Closes the connections of all network servers, giving reason (a string) as the reason in the CLS packet.

Function: tv:describe-servers

Prints descriptions of all active network servers.

13.6 The Color Screen

The usual color screen on a Lisp Machine has 454. lines of 576. pixels each, and each pixel has four bits. This allows sixteen different colors to be displayed at once. There are far more than sixteen possible colors. A color map controls the meaning of each of the sixteen pixel values. Each of the sixteen color map slots specifies an eight-bit red intensity, an eight-bit green intensity, and an eight-bit blue intensity. Thus there are about 16 million different colors that can appear, but only sixteen can be displayed at once.

Variable: color:color-screen

The screen object that represents the color screen. This object is always present whether the machine has a color screen or not.

Function: color:color-exists-p

t if this machine actually has a color screen.

13.6.1 Color Map Functions

Function: color:write-color-map slot r g b &optional synchronize screen

Writes the color map contents for slot, a fixnum from 0 to 17, with the three intensities r, g and b, all fixnums from 0 to 377 octal.

If synchronize is non-nil, the change is delayed until the vertical retrace time, so that it will take effect between frames. screen is the screen to operate on, in case you have more than one. It defaults to the normal color screen.

Function: color:write-color-map-immediate slot r g b &optional screen

Like color:write-color-map, but faster. It performs no synchronization at all, and is intended for use when you have already waited for vertical retrace.

Function: color:blt-color-map array &optional screen

Copies the contents of array, a 16 by 3 array, into the color map of screen (which defaults to the normal color screen). This function always waits for vertical retrace to do its work.

Function: color:read-color-map slot &optional screen

Returns three values, the red, green and blue intensities from the color map from slot slot. This does not actually read the hardware color map, as there is no way to do that. Instead, color:write-color-map maintains a copy for this purpose.

Function: color:fill-color-map r g b &optional (start-slot 1) screen

Writes multiple slots in the color map, starting with start-slot and ending with slot 17, from r, g and b. Note that the default omits slot 0, which is normally left as black (all three intensities zero). This function always waits for vertical retrace to do its work.

Function: color:random-color-map &optional (start 1) synchronize screen

Sets the contents of the color map to sixteen randomly chosen colors. The slots modified are start through 17, by default omitting slot 0. synchronize is the same as in color:write-color-map.

Function: color:spectrum-color-map

Sets the color map to a spectrum, leaving color 0 as black.

Function: color:colorize &optional (delay 4)

Sets the color map (except for slot 0) randomly over and over again, waiting delay 60ths of a second in between.

Function: color:colorate &optional (delay 4) (steps 1000.)

Repeatedly chooses two colors (numbers from 1 to 17) randomly and moves their color map values gradually towards and through each other, so that ultimately the two slots exchange colors. A delay of delay 60ths of a second elapses between exchanges.

13.6.2 Operating on Pixels

One way to draw on the color screen is to store into its screen array with as-2-reverse. The screen array of the color screen can be obtained with tv:sheet-screen-array, and it is an array of type art-4b. You can also use these functions:

Function: color:clear

Fills the whole color screen with color 0.

Function: color:rectangle x y width height color &optional aluf screen

Sets the contents of a rectangle on the color screen to pixel value color. x and y are the coordinates of the upper left corner, and width and height are the size.

aluf is an alu function to apply to each pixel, combining the specified color with the old pixel contents to get the new contents. The default is tv:alu-seta, which ignores the old contents. This alu function is used only on the pixels of the rectangle, which is different from what is done by the drawing primitives for the black and white screen; this is why tv:alu-seta does not produce incorrect results as it normally would.

Function: color:color-draw-line x1 x2 y1 y2 &optional (color 17) aluf screen

Sets a line from (x1,y1) to (x2,y2) on the color screen to color color. aluf is used as in color:rectangle.

Function: color:color-draw-char font char x y &optional (color 0) screen

Draws character char in font font at position (x,y) in color color. font is an ordinary black-and-white font.

Color fonts can also be created. A color font is composed of four-bit pixels just like the color screen. Using a color font, characters can be drawn with the normal character drawing primitives. When this is done, each bit of the color font pixel is combined with the corresponding bit of the screen pixel using the alu function. The alu function operates bit by bit just as it does on black-and-white screens, and is applied to many pixels in the neighborhood of the character, so tv:alu-seta should not be used.

Function: color:make-color-font bw-font &optional bit-list name-suffix

Creates a color font from black-and-white font bw-font. bit-list is a list of four numbers, zero or one, which specifies the bits of the pixels of the color font that correspond to ones in the original font. Pixels that are zero in the original font remain all zero in the color font. bit-list defaults to (1 1 1 1).

The name of the resulting font is color- followed by the name of the original font, followed by the value of name-suffix.

Windows can be created on the color screen in the ordinary manner by specifying color:color-screen as the superior. When fonts are specified for such windows, if the font specifier names a black-and-white font, a color version of it is found or created. This color font is created with bit list (1 1 1 1). This is done by the :parse-font-specifier method of the color screen.

13.7 The System Menu

This section describes how to interface with and customize the system menu which pops up when you click twice on the right mouse button.

The system menu is an instance of flavor tv:dynamic-multicolumn-momentary-window-hacking-menu (see (tv:dynamic-multicolumn-momentary-window-hacking-menu-flavor)), which means that its menu items are grouped by columns, and each column’s items come from the value of a corresponding variable which is examined each time the menu is popped up in case more items have been added. This is to enable you to add items to the menu and control where they go. The most common column to add to is the third one, which lists various kinds of windows to select (somewhat like the System command), so a special interface is provided for adding to it.

Function: tv:add-to-system-menu-programs-column name form documentation &optional after

Adds an item named name to the third column of the system menu. form is what to execute if the user clicks on the item, and documentation is the mouse documentation string.

after is the name of an item to add after (a string), or t to add at the top, or nil to add at the bottom.

Variable: tv:*system-menu-windows-column*

A menu item list which forms the first column of the system menu.

Variable: tv:*system-menu-this-window-column*

A menu item list which forms the second column of the system menu. By convention this is used for things that operate on the window that the mouse was pointing at when the system menu was brought up. They are implemented with :window-op menu items.

The Select item in the system menu pops up a momentary menu with a list of windows that the user might want to select. Not all the visible windows are included; usually a team of windows belonging to a single program is represented by a single entry since selection among the team is controlled by the program rather than the user. See (select-menu), for full details.

The Create item in the system menu pops up a menu for the user to choose a flavor of window to create.

Variable: tv:default-window-types-item-list

A menu item list that is used by the system menu Create option, and by Create in the screen editor when operating on a screen.

In general, the screen editor can operate on the inferiors of any window. Then, the :pane-types-alist operation on that window is used to get the item list for possible flavors to create; see (frames-pane-types-alist-method). On a screen, the operation returns the value of this variable.

13.8 Window Resources

A resource is a pool of interchangeable objects that are available to be used temporarily and then returned to the pool (see (defresource-fun). Read that before you continue here).

Resources whose objects are windows are often useful. For example, there is a resource of windows of the right flavor to serve as "the system menu"; when you invoke "the" system menu, a window is allocated from the resource, and it is returned to the resource’s pool when it is deactivated.

Normally one defines a resource with defresource. If the objects in the resource are windows, it is better to use instead a different function, tv:defwindow-resource. Allocating windows from resources, and returning them, is just like working with any other resources, and is documented in the Lisp Machine manual.

All the names described in this manual as resources are defined in this way.

Special Form: tv:defwindow-resource name parameters &rest options

Defines a resource of windows, named name. parameters are parameters on which the object can depend. Following the parameters specified is one additional parameter that is always defined: the window’s superior. When you allocate a window from the resource, this parameter defaults to tv:mouse-sheet.

options is a list of alternating keywords and values. Neither the keywords nor the values are evaluated at the time that tv:defwindow-resource is executed, but sometimes the value becomes part of an expression that will be executed later (when a window is allocated from the resource).

The allowed keywords are

  • :initial-copies The value is the number of windows to create in the resource when the resource is defined. The default is one. The initial copies are made inferiors of tv:default-screen. Creating an initial copy is just a way of saving time the first time a window needs to be allocated from the resource.
  • :constructor See the definition of defresource. If it is not specified, tv:defwindow-resource provides a default, which calls make-instance with arguments taken from the :make-window option.
  • :make-window The value should be a list of a flavor name followed by keyword arguments. This list will be consed into a make-window form to get the constructor for the resource.
  • :reusable-when The value should be :deexposed, :deactivated. If this keyword is not specified, then windows of the resource can be allocated to requesters if they have been explicitly returned to the pool and are not locked. :deexposed means that any window that is not exposed is considered to have been returned to the pool. :deactivated means that any window that is not active is considered to have been returned to the pool.
Variable: tv:window-resource-list

A list of the names of all window resources defined with tv:defwindow-resource.

Example: the system menu is created thus:

;Resource of system menus
(defwindow-resource system-menu ()
  :make-window
  (dynamic-multicolumn-momentary-window-hacking-menu
    :column-spec-list
    '(("Windows" *system-menu-windows-column*
       :font fonts:hl12i)
      ("This window" *system-menu-this-window-column*
       :font fonts:hl12i)
      ("Programs" *system-menu-programs-column*
       :font fonts:hl12i))
    :save-bits t)
  :reusable-when :deexposed)

13.9 The Cold Load Stream

User programs that make use of the screen organization and standardization facilities provided by the window system are frequently in a somewhat difficult position. If that interface to the window system does not work, there seems to be no way at all to find out what is going on. Similarly, debugging code associated with switching between windows can be difficult since there may be no place to print debugging output at the time such code is executing.

One way to debug such problems is to use the cold load stream. This is the stream used in constructing the initial Lisp Machine environment, before the window system itself has been loaded. It has the advantage that it does not attempt to interface with the rest of the window system, or vice versa. It will never deexpose any windows or lock any locks. It types out one character at a time, by calling the microcode directly, and has very simple-minded ideas about end of line exceptions and more breaks.

Variable: tv:cold-load-stream

The cold load stream is the value of this variable.

When the cold load stream is "waiting" for type-in, it does not actually wait; in fact, it loops until a character appears, with scheduling turned off, blinking its own special blinker by hand. The who line is not updated. Also, the chaosnet processes do not get to run. If the machine stays in this state too long, all chaosnet connections will be lost.

Whenever the system gets an error in the keyboard process, the scheduler or the mouse process, the debugger uses the cold-load-stream rather than terminal-io. You also have the option of requesting this if there is an error in a process whose terminal-io is a window that is not exposed and cannot be exposed because of locked windows. (You will be queried, using the cold load stream, to choose between this and a couple of other possibilities.)

When you exit from the debugger after it was using the cold load stream for one of these reasons, it will ask you whether to "restore the screen". Normally you should say Yes; then the screen contents will go back to what they were before the debugger was entered.

It is often preferable to use the cold load stream for debugging window problems even when the normal alternatives are available. This is because the operation of the debugger using a window for I/O may interfere with the window phenomena being debugged. Use of the cold load stream will avoid these problems. You can request use of the cold load stream by setting debug-io to the value of tv:cold-load-stream before you run your test. Once this has been done, not only errors but breakon and Meta-Break as well will use the cold load stream. To turn off use of the cold load stream for all debugger invocations, set debug-io back to nil.

You can also force trace output into the cold load stream by setting trace-output. Note that you must not set trace-output to nil when done; you must save its original value and set it back to that.

When the cold load stream is used because you have set one of the stream variables to it, you do not get the chance to restore the screen. It is not so easy to define how to do that "right" in this case; if it were done after each exit from the debugger, you would not get to see the history of multiple entries to the debugger.

The program can invoke a break loop using the cold load stream by calling tv:kbd-use-cold-load-stream. Type Resume to continue. Note that when the break is entered, the package you are typing into is typed out, because the package in the who-line is not going to be correct for this break loop.

You the user can request such a break loop by typing Terminal Call or by clicking on Emergency Break item in the system menu. You can get your program into the debugger using the cold load stream, without having made advance preparation, by getting a break loop in this fashion, setting debug-io to the cold load stream, exiting, and typing Meta-Break.

Also, it is often useful to get a cold load stream break loop and call eh on various processes or stack groups.

13.10 The Window-Based Debugger

The window-based debugger is an alternative to the usual debugger; it performs the same functions but displays graphically rather than using sequential stream I/O. You invoke the window-based debugger by typing Control-Meta-W while in the usual debugger. You can switch back and forth between the two debuggers any number of times while handling a single error.

The debugger window is divided into six panes. At the bottom is a Lisp-listener-like window, which ordinarily provides a read-eval-print loop similar to the regular keyboard debugger. More commands are available by using the mouse in the other windows as described below.

At the top is a display of the disassembled or ground code for the currently selected stack frame, depending on whether or not it is compiled. It has a scroll-bar, but is otherwise not sensitive to the mouse.

Next are the args and locals windows, side by side, displaying the names and values of the arguments to the current stack frame and its local variables; they are grayed out if there are none. They also have scroll bars. Clicking the mouse on the name of an argument will print the name and the value in the Lisp window. Clicking on just the value will print it in the Lisp window. The mouse will highlight any relevant quantity that you are pointing to.

Next is the stack window, which displays in a pseudo-list format the functions and arguments on the stack. Clicking on a function or argument or sublists of them will cause them to be printed in the Lisp window as in the argument or local windows. Also, clicking the mouse to the left of a line containing a particular stack frame will make the debugger select that frame, changing what the above three windows show.

Below this, and above the Lisp window, is the command menu for the debugger window. The available commands are:

  • What error Reprints the error message for the current error, in the Lisp window.
  • Exit Window EH Exits the debugger window, returning to the regular debugger.
  • Abort Program Like Abort in the regular debugger.
  • Arglist Asks for the name of a function, which can be typed on the keyboard, or moused if it is on the screen. Picking an actor or a closure will ask for the message name to that actor and print the arguments to its method for that message. Picking a line of a stack frame from the stack window will try to align the printout of the arguments with what value was supplied in that position in that frame.
  • Edit Reads a name of a function in the same fashion as the Arglist command and invokes the editor on that function.
  • Retry Attempts to restart the current frame, like the Control-Meta-R command in the regular debugger.
  • Return a Value Asks for the name of a value (which can be selected with the mouse) and returns it from the current frame, like Control-R in the regular debugger.
  • Proceed Proceeds from the error. Clicking left on Proceed is like typing Resume in the regular debugger. Clicking right on Proceed gets you a menu of available proceed types, from which you can select one. This is equivalent to using one of the available Super commands in the regular debugger. If proceeding asks for an object to return, you can specify it with keyboard input or by pointing to a value with the mouse.
  • Set arg Select an argument or local with the mouse and type or mouse a new value to be substituted in.
  • Search Like the Control-S command, except that the mouse can be used.
  • Throw Like Control-T in the regular debugger, it asks for a tag and a value and throws there. The mouse can be used to specify the tag and value.
  • T
  • NIL Ordinarily just supply those symbols as arguments or values for other commands. These can also be used to answer yes-or-no questions.

14 Choice Facilities

The window system contains several facilities to allow the user to make choices. These all work by displaying some arrangement of choices in a window. By pointing to one with the mouse the user can select it. The details (how the choices are specified, what the user interaction looks like, and what happens when a choice is selected) vary widely, which is why there are several separate facilities.

Each choice facility is implemented as a family of window flavors, providing several variations on the basic facility. For those who don’t want to create their own window, each facility provides an easy-to-use function interface that temporarily pops up a window of the appropriate flavor. The function interfaces will be described first in each section. Following the function interfaces there is documentation on how to create and use a window which has the facility.

This document does not cover how to modify these facilities to provide your own specialized versions, except in the simplest ways. That is certainly a reasonable thing to want to do. In order to do it you will need to read some of the code that implements the facility in question, for instance to learn about window instance variables and about internal operations that you might want to redefine or put daemons on.

Some portions of these facilities execute in the process that calls them, while other portions execute in the mouse process. All Lisp evaluation with which the user is concerned takes place in the user’s process when using the facilities described in this document, with a very few exceptions which are noted when they occur. Thus the user may freely use side-effects (both special variables and *throw) and need not worry that an error in his program will interfere with mouse tracking.

14.2 Multiple Choice Facility

The multiple choice facility provides a window containing a bunch of items, one per text line, and several choices about each item. To see an example of its use, invoke the editor command Meta-X Kill Or Save Buffers.

For each item, there can be several yes/no choices for the user to make. There is the same set of choices for each item (though some items may omit some choices). For example, in Kill Or Save Buffers, there is an item (a line) for each buffer, and each line offers choices "Save", "Kill" and "Unmod". The choices of the same kind for different items form a column, with a heading at the top saying what that choice is for. The leftmost column contains the text naming each item. The remaining columns contain small boxes (called choice boxes). A "no" box has a blank center, while a "yes" box contains an "X".

Pointing the mouse at a choice box and clicking the left button turns it on or off. Each choice can be initialized by the program to "yes" or "no" as appropriate for a default.

There can be constraints among the choices for an item. For example, if you want the choices to be mutually exclusive, you can set up constraints so that clicking one choice box to "yes" will automatically set the other choice boxes on the same line to "no".

A multiple choice window may have more lines of choices to offer than the window has lines. In this case, the user can scroll, as the multiple choice window is a kind of text scroll window (see (text-scroll-windows)).

There are several parameters associated with a multiple-choice window:

The item-name is a string, the column heading for items. In the editor example, it is "Buffers".

The item-list is a list of representations of items. Each element is a list, (item name choices). item is any arbitrary object, such as an editor buffer. name is a string which names that object; it will be displayed on the left on the line of the display devoted to this item. choices is a list of keywords representing the choices the user can make for this item. Each element of choices is either a symbol, keyword, or a list, (keyword default). If default is present and non-nil, the choice is initially "yes"; otherwise it is initially "no". This is how the editor initializes the "Save" choice to be "yes" for a modified buffer.

The keyword-alist is a list defining all the choice keywords allowed. Each element takes the form (keyword name). keyword is a symbol, the same as in the choices field of an item-list element. name is a string used to name that keyword. name is used as the column heading for the associated column of choice boxes.

An element of keyword-alist can have up to four additional list elements, called implications. These control what happens to other choices for the same item when this choice is selected by the user. Each implication can be nil, meaning no implication, a list of choice keywords, or t meaning all other choices. The first implication is on-positive; it specifies what other choices are also set to "yes" when the user sets this one to "yes". The second implication is on-negative; it specifies what other choices are set to "no" when the user sets this one to "yes". The third and fourth implications are off-positive and off-negative; they take effect when the user sets this choice to "no". The default implications are nil t nil nil, respectively. In other words the default is for the choices to be mutually exclusive.

If a keyword-alist element does not contain implications, the default implications are rplacd’ed into it.

Kill Or Save Buffers specifies the implications as

((:save "Save" nil (:not-modified) nil nil)
 (:kill "Kill" nil (:not-modified) nil nil)
 (:not-modified "UnMod" nil (:save :kill) nil nil)
 (:compile "QC-FILE" nil nil nil nil))

so that "Unmod" cannot be chosen together with either "Save" or "Kill".

The finishing-choices are the choices to go in the bottom margin. When the user clicks on one of these he is done. The variable tv:default-finishing-choices contains a reasonable default for this, providing Do It and Abort choices.

14.2.1 Functional Interface

This is the easy interface to the multiple choice facility:

Function: tv:multiple-choose item-name item-list keyword-alist &optional near-mode maxlines

Pops up a multiple-choice window and allows the user to make choices with the mouse. The dimensions of the window are automatically chosen for the best presentation of the specified choices. If there are too many choices, more than maxlines, scrolling of the window is enabled.

item-name, item-list, and keyword-alist are as described above. finishing-choices cannot be specified and is always the default.

When the user clicks on one of the two finishing choices in the bottom margin (Do It and Abort) the window disappears and tv:multiple-choose returns. If the user finishes by choosing Abort the returned value is nil, and the second returned value is :abort. If the user chooses Do It, the returned value is a list with one element for each item. Each element is a list whose car is the item (that arbitrary object which the user passed in in the item-list argument) and whose cdr is a list of the keywords for the "yes" choices selected for that item.

near-mode tells the window where to pop up. It is a suitable argument for tv:expose-window-near. The default is the list (:mouse). maxlines, which defaults to twenty, is the maximum number of choices allowed before scrolling is used.

Here is an example:

(tv:multiple-choose "Word"
   '((:eat "Eat" (:add :make-permanent))
     (:drink "Drink" (:forget :make-permanent)))
   '((:add "Add" nil nil nil (:make-permanent))
     (:forget "Forget" nil (:make-permanent) nil nil)
     (:make-permanent "Make Permanent" (:add) (:forget) nil nil)))

offers the possibilities of :add or :make-permanent for :eat and the possibilities of :forget or :make-permanent for :drink. Presumably this would be done because :drink has already been "added" and :eat has not been.

The implications say that making permanent is incompatible with forgetting when forgetting is possible, and requires adding when adding is possible.

The value returned might be

((:eat :add :make-permanent)
 (:drink))

In this example, the items are keywords (symbols), but that is not significant. The system never looks inside them; it just compares them with eq and puts them in the returned value.

14.2.2 Flavors and Operations

These are the grubby details:

.defflavor tv:basic-multiple-choice (tv:displayed-items-text-scroll-window tv:margin-choice-mixin ...) This is the basic flavor that makes a window implement the multiple-choice facility. Like most basic mixins, it is not itself instantiable but it does commit any window that incorporates it to being a multiple-choice rather than any different sort of window. .end_defflavor

.definstvar tv:basic-multiple-choice tv:item-name The window’s item name. .end_definstvar

.definstvar tv:basic-multiple-choice tv:choice-types The window’s keyword alist. .end_definstvar

.defflavor tv:multiple-choice (tv:basic-multiple-choice tv:top-box-label-mixin tv:window) This is a reasonable window with the multiple-choice facility in it. It has borders and a label area on top which is used for the column headings. .end_defflavor

.defflavor tv:temporary-multiple-choice-window (tv:temporary-window-mixin tv:multiple-choice) This is a multiple-choice window which is equipped to pop up temporarily. .end_defflavor

.defresource tv:temporary-multiple-choice-window &optional (superior tv:mouse-sheet) This is a resource of temporary multiple-choice windows, used by the tv:multiple-choose function. .end_defresource

The following operations are provided by multiple choice windows.

.definitoption tv:basic-multiple-choice :item-list item-list Initializes the window’s item list to item-list. .end_definitoption

.defmethod tv:multiple-choice :setup item-name keyword-alist finishing-choices item-list &optional maxlines This operation sets up all the various parameters of the window. Usually it is used while the window is deexposed. The window decides what size it should be and whether all the items will fit or scrolling is required, then draws the display into its bit-array. Thus when the window is exposed the display will appear instantaneously.

maxlines is the maximum number of lines the window may have; if there are more items than this only some of them will be displayed and scrolling will be enabled. maxlines defaults to 20.

The finishing-choices are a list of choices for tv:margin-choice-mixin (see (tv:margin-choice-mixin-flavor)). When one of these finishing choices is clicked on, it should set the instance variable tv:choice-value of self to either a symbol (for an abnormal exit) or a list for the :choose operation to return. .end_defmethod

.definstvar tv:basic-multiple-choice tv:choice-value When the mouse process sets this non-nil, the :choose operation returns. .end_definstvar

.defmethod tv:multiple-choice :choose &optional near-mode Moves the window to the place specified by near-mode, which defaults to the list (:mouse), and exposes it. Then waits for the user to make a finishing choice and returns the window to its original activate/expose status before the :choose. This operation returns the same value as the function tv:multiple-choose. .end_defmethod

14.3 Choose-Variable-Values Facility

This facility presents the user with a display of a bunch of Lisp variables and their values. The user may change the value of some of the variables. When the values are to his liking he may indicate that he is done.

The choose-variable-values window is a kind of text scroll window, so each line of the display corresponds to one variable. The name of the variable, a colon, and the value of the variable are displayed. Pointing the mouse at the value causes a box to appear around it. Clicking the left mouse button at that point allows the value to be changed.

For an example of a choose-variable-values window, try the Frame option of the Split Screen item in the system menu. ZMail profile mode is also a good example.

14.3.1 Specifying the Variables

When you use a choose-variable-values window, you must specify one or more variables with a list of specifiers. You pass the list as an argument to tv:choose-variable-values.

Each variable has a type which controls what values it may take on, the way the value is displayed and the way the user enters a new value. The type mechanism is extensible and is described in detail later. The types fall into two categories, those with a small number of legal values and those with a large or infinite number of legal values. The first kind of type displays all the choices, with the one which is the current value of the variable in bold-face. Pointing at a choice and clicking the mouse sets the variable to that value. Those types with a large number of legal values display the current value. Pointing at the value and clicking the mouse allows a new value to be entered from the keyboard. Rubbing out more characters than typed in restores the original value instead of changing it.

The variables themselves can be either symbols, which are effectively examined and set as special variables in the calling program’s process, or locatives, whose contents are examined and set. The syntax for input and output is controlled by the binding of base, ibase, *nopoint, prinlevel, prinlength, package, and readtable as usual.

Each line of the display is specified by an item, which can be one of the following:

  • a string The string is simply displayed. This is useful for putting headings and blank separating lines into the display.
  • a symbol The symbol is a variable whose type is :sexp; that is, its value may be any Lisp object. The name of the variable on the display is simply its print-name, and the value is stored as the value of the symbol.
  • a list (variable name type args...) This is the general form. variable is the variable whose value is being chosen. It is either a symbol or a locative. If name is supplied it can be a string, which is displayed as the name of the variable, or it can be nil, meaning that this line should have no variable name, but only a value. name is optional; if it is omitted it defaults to the print-name of variable, or to nil if variable is a locative.

    type is an optional keyword giving the type of variable; if omitted it defaults to :sexp. args are possible additional specifications dependent on type.

    It is possible to omit name and supply type since one is always a string or nil and the other is always a non-nil symbol.

For clarification of this, refer to the examples on (choose-variable-values-examples).

14.3.2 Predefined Variable Types

The following are the types of variables supported by default, along with any args that may be put in the item after the type keyword:

  • :sexp "choose variable values"
  • :any "choose variable values" The value is any Lisp expression (sometimes called an S-expression), printed with prin1, read with read.
  • :princ "choose variable values" Same as :sexp except that the value is printed with princ rather than prin1.
  • :string "choose variable values" The value is a string, printed with princ, read with readline.
  • :number "choose variable values" The value is any type of number. It is printed with prin1 and read with read, but only a number is accepted as input.
  • :number-or-nil "choose variable values" The value may be either a number or nil.
  • :date "choose variable values" The value is a universal date-time. It is printed with time:print-universal-time and read with readline-trim and time:parse-universal-time.
  • :date-or-never "choose variable values" The value is either a universal date-time or nil. nil is printed as "never", and a number is printed using time:print-universal-time. Input is read with readline-trim; if the string is not "never" it is passed to time:parse-universal-time.
  • :interval-or-never "choose variable values" The value is either nil or a number of seconds. It is printed with time:print-interval-or-never and new values are read using time:read-interval-or-never.
  • :character "choose variable values" The value is a character code. It is printed as the character name (using the ~:@C format operator), and is read as a single keystroke.
  • :character-or-nil "choose variable values" Like :character but nil is also allowed as the value. nil displays as "none" and can be input via the Clear Input key.
  • :string-list "choose variable values" The value is a list of strings, whose printed representation for input and output consists of the strings separated by commas and spaces.
  • :pathname "choose variable values"
  • a list (:pathname defaults) The value is a pathname (see chapter 22 of the Lisp Machine manual). It is printed with princ and read with readline, fs:parse-pathname, and fs:merge-pathname-defaults. If defaults is provided, it is a pathname or a defaults-alist to pass to fs:merge-pathname-defaults. It can also be a symbol whose value should be used. If it is the same variable this item is setting, then each typed-in value is merged with the previous setting.
  • :pathname-or-nil "choose variable values" Like :pathname but nil is also allowed as a value. It is read and printed as a blank line.
  • :pathname-list "choose variable values" The value is a list of pathnames. In the printed representation they are separated by commas.
  • :choose values-list print-function The value of the variable must be one of the elements of the list values-list. Comparison is by equal rather than eq. All the choices are displayed, with the current value in boldface. A new value is input by pointing to it with the mouse and clicking. print-function is the function to print a value; it is optional and defaults to princ.
  • :assoc values-list print-function Like :choose but car of each element of values-list is what to display, while cdr is the value that goes in the variable.
  • :menu-alist item-list Like :choose, but instead of a list of values there is item-list, which is a list of menu items (see (menu)). The usual menu mechanisms for specifying the string to display, the value to return, and the mouse documentation work with this.
  • :boolean "choose variable values" The value of the variable is either t or nil. The choices are displayed as yes and no.
  • :documentation doc type args... This is not really a variable type, but goes in the place where a type would normally be expected. The real type is type; it and its args are optional as usual. doc is a string which is displayed in the mouse documentation line when the mouse is pointing at this item. The default if no documentation is supplied in this way depends on the type, and generally is something like "Click left to input a new value from the keyboard."

14.3.3 Functional Interface

Function: tv:choose-variable-values variables &rest options

This is the easy-to-use function interface to the choose-variable-values facility. It pops up a window displaying the values of the specified variables and permits the user to alter them. One or more choice boxes (as in the multiple-choice facility) appear in the bottom margin of the window. When the user clicks on the Exit choice box the window disappears and this function returns. The value returned is not meaningful; the result is expressed in the values of the variables.

The system chooses the dimensions of the window, and enables scrolling if there are too many variables to fit in the chosen height.

variables is a list whose elements can be special variables or the more general items described above. See the examples below.

options is the usual list of alternating option keywords and argument values. The following option keywords are allowed:

  • :label The argument is a string that is the label to be displayed at the top of the window. The default is "Choose Variable Values".
  • :function The function to be called if the user changes the value of a variable. The default is nil (no function). The use of this function is described below ((c-v-v-function)).
  • :near-mode Where to position the window. This is a suitable argument for tv:expose-window-near. The default is the list (:mouse).
  • :width Specifies how wide to make the window. This can be a number of characters, or a string (it is made just wide enough to display that string). The default is to make it wide enough to display the current values of all the variables, provided that isn’t too wide to fit in the superior.
  • :extra-width When :width is not specified, this specifies the amount of extra space to leave after the current value of each variable of the kind that displays its current value (rather than a menu of all possible values). This extra space allows for changing the value to something bigger. The extra space is specified as either a number of characters or a character string. The default is ten characters. If :width is specified, then :extra-width is ignored.
  • :margin-choices The argument is a list of specifications for choice boxes to appear in the bottom margin. Each element can be a string, which is the label for the box which means "done", or a cons of a label string and a form to be evaluated if that choice box is clicked upon. Since this form is evaluated in the user process it can do such things as alter the values of variables or *throw out. The default for :margin-choices is ("Exit").
  • :superior The argument is the window to which the pop-up choose-variable-values window should be inferior. The default is the value of tv:mouse-sheet, or the superior of w if near-mode is (:window w).
  • :reverse-video-p The argument is used to control whether the window displays white-on-black or black-on-white. It is used as the argument of the :set-reverse-video-p operation.

A choose-variable-values window optionally may have an associated function, which is called whenever the user commands the window to change the value of one of the variables.

This function can implement constraints among the variables. It is called with arguments window, variable, old-value, and new-value. The function should return nil if just the original variable needs to be redisplayed, or t if no redisplay is required; in this case it would usually setq several of the variables, then perform a :refresh operation on the window.

Here are some examples of how to call tv:choose-variable-values. The simplest sort of thing you can do is:

(tv:choose-variable-values '(base ibase *nopoint)
			   ':label "Number format parameters")

which displays the three variables’ names and values and lets the user change them. The same example can be done with nicer formatting with:

(tv:choose-variable-values
	'((base "Output Base" :number)
	  (ibase "Input Base" :number)
	  (*nopoint "Decimal Point"
		    :assoc (("Yes" . nil)
			    ("No" . t))))
	':label "Number format parameters")

The entry for *nopoint would have been simply

	 (*nopoint "No Decimal Point" :boolean)

except that we wanted to reverse the sense of t and nil. We might even have used

	 (*nopoint :boolean)

if we wanted to use the name of the variable as the label rather than spelling it out.

For a hokier example, consider a grocery store. Suppose we have variables *cuts-of-beef*, *cuts-of-pork*, *cuts-of-lamb*, and *lettuce-types*, which contain lists of strings indicating what is available, *squash-type*, which indicates whether we stock summer squash or winter squash, and *milk-price*, which contains a floating-point number that is the current price of a gallon of milk. Then the following expression would display the inventory and allow it to be modified, using several different kinds of items:

(tv:choose-variable-values
	'("Meat Department"
	  (*cuts-of-beef* "Beef" :string-list)
	  (*cuts-of-pork* "Pork" :string-list)
	  (*cuts-of-lamb* "Lamb" :string-list)
	  ""
	  "Produce"
	  (*lettuce-types* "Lettuce" :string-list)
	  (*squash-type* "Squash" :choose ("Summer" "Winter"))
	  ""
	  "Dairy"
	  (*milk-price* "Milk" 
		:documentation
		    "Click left to raise the price of milk"
		:number)))

Note the use of strings to provide labels for the sections, and null strings to separate the sections with blank lines.

14.3.4 Defining Your Own Variable Type

.defmethod tv:basic-choose-variable-values :decode-variable-type kwd-and-args The system uses this operation on a choose-variable-values window when it needs to understand an item. kwd-and-args is a list whose car is the item’s type keyword and whose remaining elements, if any, are the arguments to that keyword. Six values are returned; these values are described below. The default method for :decode-variable-type looks for two properties on the keyword’s property list:

  • tv:choose-variable-values-keyword The value of this property is a list of the six values described below. Unnecessary values of nil may be omitted at the end.
  • tv:choose-variable-values-keyword-function The value of this property is a function that is called with one argument, kwd-and-args. The function must return the six values.

You may add a new variable type to the standard set by putting one of the above properties on the keyword. You may define your own flavor of choose-variable-values window and give it a :decode-variable-type method to make it not use the standard variable types. This method must take care of implementing the :documentation keyword, which can appear in an item where a variable type would normally appear. .end_defmethod

The six magic values are:

  • print-function A function of two arguments, object and stream, to be used to print the value. prin1 is acceptable.
  • read-function A function of one argument, the stream, to be used to read a new value. read is acceptable. If nil is specified, there is no read-function and instead new values are specified by pointing at one choice from a list. If the read-function is a symbol, it is called inside a rubout-handler, and over-rubout will automatically leave the variable with its original value. If read-function is a list, its car is the function, and it will be called directly rather than inside a rubout-handler.
  • choices A list of the choices to be printed, or nil if just the current value is to be printed. The choices are printed using the print-function, just as the current value is.
  • print-translate If there are choices, and this function is supplied non-nil, it is given an element of the choice list and must return the value to be printed using the print-function.
  • value-translate If there are choices, and this function is supplied non-nil, it is given an element of the choice list and must return the value to be stored in the variable.
  • documentation A string to display in the mouse documentation line when the mouse is pointing at this item. This string should tell the user that clicking the mouse will change the value of this variable and give any special information (e.g. that the value must be a number).

    Alternatively, this can be a symbol that is the name of a function. It will be called with one argument, which is the current element of choices or the current value of the variable if choices is nil. It should return a documentation string or nil if the default documentation is desired. This can be useful when you want to document the meaning of a particular choice, rather than simply saying that clicking the mouse on this choice will select it. Note that the function should return a constant string, rather than building one with format or other string operations, because it will be called over and over as long as the mouse is pointing at an item of this type. The function is called by the who-line updating in the scheduler, not in the user process.

For example, :boolean is defined thus:
(defprop :boolean 
         (choose-variable-values-boolean-print nil (t nil))
	 choose-variable-values-keyword)
(defun choose-variable-values-boolean-print (value stream)
  (funcall stream ':string-out (if value "Yes" "No")))

The type :any is defined with

(defprop :any (prin1 read) tv:choose-variable-values-keyword)

14.3.5 Making Your Own Window

The function tv:choose-variable-values may not be adequate if you wish to keep the window permanently exposed or if you wish to alter its behavior. Then you must create a window yourself. Here are the pertinent flavors.

.defflavor tv:basic-choose-variable-values (tv:mouse-sensitive-text-scroll-window-without-click) This is the basic flavor which makes a window implement the choose-variable-values facility. It is not instantiable. .end_defflavor

.defflavor tv:choose-variable-values-window (tv:basic-choose-variable-values tv:window ...) This is a choose-variable-values window with a reasonable set of features, including borders, a label at the top, stream I/O, the ability to be scrolled if there are too many variables to fit in the window, and the ability to have choice boxes in the bottom margin. .end_defflavor

.defflavor tv:choose-variable-values-pane (tv:choose-variable-values-window) A tv:choose-variable-values-window designed to be a pane of a constraint frame. It redefines the :adjustable-size-p operation to return nil always, on the assumption that the window’s size has been specified by the frame and cannot be changed except by the frame. .end_defflavor

.defflavor tv:temporary-choose-variable-values-window (tv:choose-variable-values-window tv:temporary-window-mixin) A tv:choose-variable-values-window that is equipped to pop up temporarily. .end_defflavor

.defresource tv:temporary-choose-variable-values-window &optional (superior tv:mouse-sheet) This is a resource of such windows, from which tv:choose-variable-values gets a window to use. .end_defresource

There are two main styles of use: to create a window giving all of the parameters in the init-plist, or to create a window without specifying the parameters, and then use the :setup operation (see below) to set the parameters before using the window. But in any case, you must specify the list of variable-specifiers (see (c-v-v-specs)) and the stack group to evaluate variables in before you can use the window.

The following init options are available:

.definitoption tv:basic-choose-variable-values :variables specifier-list Initializes the list of variable-specifiers, telling the window which variables to display and how to read and print the values. .end_definitoption

.definitoption tv:basic-choose-variable-values :function fcn Initializes the associated function (see (c-v-v-function)), the function called when the window changes the value of one of the variables it displays. The default is nil (no function). .end_definitoption

.definstvar tv:basic-choose-variable-values tv:function The window’s associated function. .end_definstvar

.definitoption tv:basic-choose-variable-values :stack-group sg The stack group in which the variables whose values are to be chosen are bound. The window needs to know this so that it can get the values while running in another process, for instance the mouse process, in order to update the window display when it is refreshed or scrolled. If you do not specify the stack group at this time, you must specify it with the :setup operation, before you can use the window. .end_definitoption

.definstvar tv:basic-choose-variable-values tv:stack-group The stack group in which variables’ values should be evaluated. .end_definstvar

.definitoption tv:basic-choose-variable-values :name-font font The font in which names of variables are displayed. The default is the system default font. .end_definitoption

.definitoption tv:basic-choose-variable-values :value-font font The font in which values of variables are displayed. The default is the system default font. .end_definitoption

.definitoption tv:basic-choose-variable-values :string-font font The font in which items that are just strings (typically heading lines) are displayed. The default is the system default font. .end_definitoption

.definitoption tv:basic-choose-variable-values :unselected-choice-font font The font in which choices for a value, other than the current value, are displayed. The default is a small distinctive font. .end_definitoption

.definitoption tv:basic-choose-variable-values :selected-choice-font font The font in which the current value of a variable is displayed, when there is a finite set of choices. This should be a bold-face version of the preceding font. The default is the bold-face version of the default unselected-choice font. .end_definitoption

.definitoption tv:choose-variable-values-window :margin-choices choice-list The default is a single choice box, labeled "Done". See (margin-choice) for the details of what you can put here. Note that specifying nil for this option will suppress the margin-choices entirely. .end_definitoption

If no dimensions are specified in the init-plist, the width and height will be automatically chosen according to the other init-plist parameters. The height is dictated by the number of variables to be displayed. Specifying a height in the init-plist, using any of the standard dimension-specifying init-plist options, overrides the automatic choice of height.

Choose-variable-values windows provide these operations:

.defmethod tv:choose-variable-values-window :setup items label function margin-choices Changes the list of items (variables), the window label, the constraint function, and the choices in the bottom margin, and sets up the display. Also remembers the current stack-group as the stack-group in which the variables are bound. If the window is not exposed (more generally, if the :adjustable-size-p operation on the window returns non-nil), this reshapes the window to a good size based on the specified items. .end_defmethod

.defmethod tv:choose-variable-values-window :set-variables item-list &optional dont-set-height Sets the list of variable-specifiers which controls the variables displayed in the window, then redisplays the window.

Unless dont-set-height is supplied non-nil, the height of the window will be adjusted according to the number of lines required. If more than 25. lines would be required, 25. lines will be used and scrolling will be enabled. The :setup operation uses :set-variables to do part of its work. .end_defmethod

.defmethod tv:choose-variable-values-window :adjustable-size-p If this returns non-nil, :setup will reshape the window. By default, this operation returns non-nil when the window is deexposed. .end_defmethod

.defmethod tv:choose-variable-values-window :appropriate-width &optional extra-space Returns the inside-width appropriate for this window to accommodate the current set of variables and their current values. Use this operation after a :setup and before a :expose, and use the result to do a :set-inside-size. The returned width will not be larger than the maximum that will fit inside the superior.

If extra-space is supplied, it specifies the amount of extra space to leave after the current value of each variable that displays its current value (rather than a menu of all possible values). This extra space allows for changing the value to something bigger. The extra space is specified as either a number of characters or a character string. The default is to leave no extra space. .end_defmethod

.defmethod tv:choose-variable-values-window :redisplay-variable variable Redisplays just the value of that variable. .end_defmethod

In the simplest mode of operation, you call the tv:choose-variable-values function, which takes care of creating the window and all necessary communication with it. When you make your own choose-variable-values window, you need to handle the communication yourself, using the information given below. An example of a situation in which this is necessary is when you have a frame, some panes of which are choose-variable-values windows.

A choose-variable-values window handles mouse clicks by putting blips (lists) in its input buffer. These blips are generated by the mouse process and are supposed to be read in the controlling process. There are two types of blip, both used for specific purposes, and your program must be able to take the appropriate actions when it reads them. The easy way for you to do this is to call the function tv:choose-variable-values-process-message, which is provided just for this purpose.

.definitoption tv:choose-variable-values-window :io-buffer io-buffer The I/O buffer to be used for blips and for ordinary input from the window. .end_definitoption

The following forms of list are inserted as blips into the input buffer:

  • (:variable-choice window item value line-no button) Indicates that the user clicked on the value of a variable, expressing the desire to change it. The controlling process should read keyboard input as necessary and set the variable.
  • (:choice-box window box) Indicates that the user clicked on one of the choice boxes in the bottom margin. The controlling process may wish to deexpose the window if the box was the "Done" box.
Function: tv:choose-variable-values-process-message window blip

This function implements the proper response to the above blips. It should be called in the process and stack-group in which the variables being chosen are bound. window should be the choose-variable-values window and blip should be the object read as input.

This function returns nil except in the case where blip indicates a click on a "Done" choice box.

If blip says that the user clicked on a variable, this function reads user input from the window as necessary and sets the variable.

If blip is a :choice-box blip, the action depends on the box in it. If the sixth element of box is nil, which is normally the case for the "Done" box, this function returns t. Otherwise, the sixth element of box is evaluated, but this function returns nil.

If blip is actually a character rather than a blip, it is ignored unless it is a Clear-screen, in which case the choose-variable-values window is refreshed. Therefore, it is reasonable to use this function with a loop like this:

(do ()
    ((tv:choose-variable-values-process-message
       c-v-v-window
       (progn
	 (process-wait "Choose" c-v-v-window ':listen)
	 (send c-v-v-window ':any-tyi)))))

14.3.6 User Option Facility

There is a facility, based on the choose-variable-values facility, for keeping track of options to a program of the sort that a user would specify once and keep in his init file. Special forms are provided for defining options, and there are functions for putting all the options into a choose-values window so that the user can alter them, for writing the current state of the options into an init file, and for resetting all the options to their default initial values.

Special Form: define-user-option-alist name constructor documentation

Defines name a special variable whose value is a "user option alist", something which may be used by the other functions below. This alist will keep track of all of the option variables for a particular program.

The simplest usage is (define-user-option-alist name), which just defines name.

(define-user-option-alist name constructor) specifies in addition the name of a constructor macro to be defined, which provides a slightly different way of defining an option variable from defvar-user-option. The form (constructor option default name type args...) will define an option in this user-option-alist. The arguments are the same as the similarly-named arguments to defvar-user-option.

A third argument may be used to specify a documentation string for the variable name. To specify a documentation string and no constructor, give nil for the constructor.

Special Form: defvar-user-option

Defines an option and adds it to a user option list.

(defvar-user-option option default documentation
  alist name type args...)

defines the special variable option to be an option in the alist, which must have been previously defined with define-user-option-alist. The variable is declared and initialized via (defvar option default documentation). The value of the form default is remembered so that the variable can be reset back to it later.

type is the type of the variable for purposes of the choose-variable-values facility. It is optional and defaults to :sexp. args, which are evaluated (at the time the definition is done), are the arguments for the type keyword used.

name is the name of the variable to be displayed in the choose-variable-values window. If it is omitted or nil, the default is (string-capitalize-words (get-pname option)); except that when the first and last characters of the print-name are asterisks, they are removed. E.g. the default name for sowq:*sunny-side-up* would be "Sunny Side Up".

Example:
(defvar-user-option preferred-radix 8 
  "Radix to use for files that don't specify one."
  my-program-option-alist "Preferred radix"
  :assoc '(("8" 8) ("10" 10.)))
Special Form: defvar-site-user-option

This is like defvar-user-option, except that instead of an initial value a site option keyword is specified. Instead of a default value, you specify the name of a site option (a keyword). The actual default value is the value of that site option in the current site table. Loading a new site table resets the option.

Special Form: defvar-site-alist-user-option

Defines a user option whose possible values are controlled by site options.

(defvar-site-alist-user-option option default documentation
  alist name menu-alist)

defines option as a user option on alist, like defvar-site-user-option. The type for tv:choose-variable-values is always :menu-alist, and the list of menu items to be used is determined from the site table according to menu-alist.

menu-alist is a symbol whose value is a menu alist, a list of menu items. These items are the alternatives offered to the user, as in the :menu-alist type of variable. However, each menu item specifies a site option keyword, and that alternative is available to the user only if that site option currently has a non-nil value.

The menu item can specify the controlling site keyword using the modifier keyword :site-keyword, as in

("Foo" :value :foo :site-keyword :foo-present)

If this is not done, the menu item’s value-to-return is also the site keyword.

default is the name of a site keyword whose value specifies the default. This site option’s value is matched against each menu item, comparing it against the value of the modifier keyword :default-site-keyword, or, if that is not present, against the menu item’s site keyword name. The first match is the default alternative. Thus "Foo" will be the default alternative if the default site option’s value is :foo-present.

If default is nil, then the first available menu alist item is also the default.

Function: choose-user-options alist &rest options

Displays the values of the option variables in alist to the user and allows them to be altered. The options are passed along to tv:choose-variable-values. Note that alist is an actual alist, not a symbol whose value is an alist.

Function: reset-user-options alist

Each of the option variables in alist is reset to its default initial value.

Macro: tv:restrict-user-option option restriction-type site-options...

Specifies that the user option variable option is significant only if the site tables for your site do (or, if they do not) contain one of the specified site-options.

restriction-type is either :if or :unless. If it is :if, the option should be mentioned in the choose-variable-values window only if one of the specified site options is present in the currently loaded site table. :unless means that the option should be offered only if none of the specified site options is loaded.

Each option may have an :if restriction and an :unless restriction.

Elimination of options from an alist according to their restrictions is done by tv:prune-user-option-alist, calling which is up to you.

restriction-type may also be :never. Then the option is never offered to the user to change, but it will still be reset and written with the other options.

Function: tv:prune-user-option-alist alist

Returns an alist containing only some of the elements of alist, lacking those that are suppressed by restrictions, or that offer only a single alternative. (The latter is likely to happen with a site-menu-alist user option if a given site allows only one of the possible alternatives.)

Function: write-user-options alist stream

For each option variable in alist whose current value is not equal to its default initial value, a form is printed to stream that will set the variable to its current value. The form uses login-setq so it is appropriate for putting into an init file.

14.4 Mouse-Sensitive Type Out

The mouse-sensitive items facility is a feature somewhat related to the choice facilities described above. It is similar in its appearance to the user, but quite different in the way it is interfaced to by a program. Mixing tv:basic-mouse-sensitive-items into a window flavor equips the window with mouse-handling according to the paradigm described in this section. Mouse-sensitive items are something you use when defining your own window, rather than a complete, stand-alone facility, and consequently do not have an "easy to use" functional interface.

For an example of mouse-sensitive items, try the C-X C-B (List Buffers) command in the editor. Try moving the mouse over the list of buffers and clicking the right-hand button.

The word "typeout" appears here and there in the mouse-sensitive items facility for historical reasons. Often mouse-sensitive items are typed out on top of some other display, such as an editor buffer. However, the mouse-sensitive-item facility has nothing to do with the typeout-window facility. At this point it would be a fairly big incompatible change to fix this.

.defflavor tv:basic-mouse-sensitive-items Mixing this flavor into a window provides for areas of the screen which are sensitive to the mouse. Moving the mouse into such an area highlights the area by drawing a box around it. At this point clicking the mouse performs a user-defined operation. This flavor is called basic because it fixes the handling of the mouse by the window; it will not work to mix it with another flavor that expects to define some other kind of mouse handling. However it is less basic than many basic flavors in that it does not do anything special with the displayed image of the window. .end_defflavor

A mouse-sensitive item has a type, which is a keyword which controls what you can do to it, an item, which is an arbitrary Lisp object associated with it, and a rectangular area of the window. Typically something is displayed in that area at the same time as a mouse-sensitive item is created, using normal stream output to the window. Unlike things such as menu items, these mouse-sensitive items are not a permanent property of the window; they are just as ephemeral as the displayed text and go away if you clear the window or if typeout wraps around and types over them. Of course, if you don’t type out more items and text than fit in the window, and never clear the window, then they will be permanent.

Associated with each type is a set of operations that are legal to perform on items of that type. One of these operations is selected as the default. The tv:item-type-alist instance variable is an alist that defines these. This alist is composed of elements of the following form:

(type left-button-alternative
 documentation
 (string . alternative)       ;A menu item
 (string :value alternative)  ;Another menu item
 menu-item...)		    ;More of them

documentation is the string to be displayed in the who line while the mouse is pointing at an item of this type. The menu items may also have documentation strings in them. documentation may also be a list of the form

(doc-function label-function)

where doc-function is a function that, when applied to a mouse-sensitive item, returns a documentation string, and label-function is a similar function that returns a string to use as the menu label, to identify the item that the menu is going to apply to.

Here is part of the item type alist used in typeout windows of editor windows:

((zwei:directory zwei:directory-edit-1
  "Left: Run DIRED on this directory.  Right: menu of View, Edit."
  ("View" :value zwei:view-directory 
   :documentation "View this directory")
  ("Edit" :value zwei:directory-edit-1
   :documentation
   "Run DIRED on this directory."))
 (zwei:file zwei:find-defaulted-file
  "Left: Find file this file.  Right: menu of Load, Find, Compare."
  ("Load" :value zwei:load-defaulted-file
   :documentation "LOAD this file.")
  ("Find" :value zwei:find-defaulted-file
   :documentation "Find file this file.")
  ("Compare" :value zwei:srccom-file
   :documentation
   "Compare this file with the newest version."))
 (zwei:flavor-name zwei:edit-definition-for-mouse
  "Left: Edit definition.  Right: menu of Describe, Edit."
  ("Describe" :value zwei:describe-flavor-internal
   :documentation "Describe this flavor.")
  ("Edit" :value zwei:edit-definition-for-mouse
   :documentation "Edit definition.")))

When an item is clicked on with the mouse, a blip which is a list of the form

(:typeout-execute alternative item)

is placed in the window’s input buffer. item is the datum supplied when the item was constructed, whose purpose is to identify which item was clicked on, and alternative is obtained by looking up the type of the item in the window’s item-type-alist.

If the item is clicked on with the left mouse button, the left-button-alternative is used in the :typeout-execute blip. If the item is clicked on with the right button, the menu items are put into a menu, and the user chooses one. The value returned by the :choose operation is used as the alternative in the :typeout-execute blip. Clicking on an item of a type that is not one of the alternatives in the item-type-alist just beeps.

For the Load alternative on a file item in the editor, the blip might be

(:typeout-execute zwei:load-defaulted-file
 #fs:logical-pathname "SYS: SYS; QFCTNS LISP")

.defmethod tv:basic-mouse-sensitive-items :item-type-alist .defmethod1 tv:basic-mouse-sensitive-items :set-item-type-alist new-item-type-alist Return or set the item type alist of the window. .end_defmethod

.definitoption tv:basic-mouse-sensitive-items :item-type-alist alist Initializes the item type alist of the window. .end_definitoption

Special Form: tv:add-typeout-item-type

The special form

(tv:add-typeout-item-type alist type name function
			  default-p documentation)

is used to declare information about a mouse-sensitive item type by adding an entry to an alist kept in a special variable. This alist can then be put into the item-type alist of a mouse-sensitive window, for instance using the :item-type-alist init-plist option. Note that each possible alternative for a particular mouse-sensitive item type is defined with a separate tv:add-typeout-item-type form; this allows each alternative to be defined at the place in the program where it is implemented, rather than collecting all the alternatives into a separate table. It also allows new alternatives to be added in a modular fashion.

alist is the special variable containing the alist. You should defvar it to nil before defining the first item type. Each program that uses mouse-sensitive items has its own alist of item types, so that there is no conflict in the names of the types. type is the keyword symbol for the type being defined. name is the string that names the operation and alternative is the representation of the alternative (the object to be put in the second element of the :typeout-execute blip). default-p is optional; if it is supplied and non-nil, it means that this operation is the default performed when you click the left button on an item of this type. documentation is optional but highly recommended; it is a string that documents what function does. When the user points the mouse at an item of this type, the documentation line at the bottom of the screen will give the documentation for the default function (reachable by the left button) and a list of the functions in the menu (reachable by the right button). If the user clicks right, calling for a menu, then the documentation for whichever function in the menu he points the mouse at will be displayed.

alist, type, and function are not evaluated. name, default-p, and documentation are evaluated.

In the editor, alternative is interpreted (when a :typeout-execute blip is read) as a function to be called, and the tv:add-typeout-item-type form is typically placed right before the function definition of alternative.

These are the operations used to print items on a window.

.defmethod tv:basic-mouse-sensitive-items :item type item &rest format-args A new item item of type type is printed, either by calling format with format-args, or by princ’ing item if format-args is nil.

The mouse-sensitive area of the item is whatever space is used up by printing it, as judged by the motion of the cursor.

The arguments item and type is not necessarily used in printing the item, but they are used in handling a click on the item. type is used to look up a function in the item type alist, and item is placed directly into the :typeout-execute blip.

Example:
(send standard-output ':item 'zwei:file pathname)

in the editor, where standard-output is a window that supports mouse-sensitive items, will princ the value of pathname and make an item of type zwei:file whose datum is that pathname. .end_defmethod

.defmethod tv:basic-mouse-sensitive-items :primitive-item type item left top right bottom .defmethod1 tv:basic-mouse-sensitive-items :primitive-item-outside type item left top right bottom This operation is used to define a mouse-sensitive item without printing it. (Presumably you print it yourself, either before or after.) The type and item are used as in the :item operation. The remaining arguments are coordinates that describe the four edges of the mouse-sensitive rectangle.

In :primitive-item, the four coordinates are relative to the inside top left corner of the window (that is, they are cursor positions such as :read-cursorpos would return). In :primitive-item-outside, they are relative to the outside corner of the window (like values of the instance variables tv:cursor-x and tv:cursor-y). .end_defmethod

.defmethod tv:basic-mouse-sensitive-items :item-list type list Several items are printed, arranged neatly in columns, one for each element of list. An element of list can be either a string or a list (name . item). In the latter case, name (typically a string) is printed with princ, and item is used as the datum for the item. If the element is an atom, that atom serves both to be princ’d and used as the datum. All the items are of type type. .end_defmethod

.defmethod tv:basic-mouse-sensitive-items :mouse-sensitive-item x y Returns a list describing the mouse-sensitive item found at cursor position x, y in the window, or nil if there is none there.

The list looks like this:
(type item left top right bottom)

The type and item are as specified in the :item operation and the coordinates are cursor positions (that is, relative to the outside top left corner of the window). .end_defmethod

14.5 Margin Choices

A window can be augmented with choice boxes (see (choice-box)) in its bottom margin using the flavor tv:margin-choice-mixin. These give the user a few labeled mouse-sensitive points that are independent of anything else in the window.

Margin choices are not a complete, stand-alone choice facility and consequently do not have an "easy to use" functional interface.

For an example of a window with margin choices (as well as choice boxes in its interior), try the editor command Meta-X Kill or Save Buffers.

.defflavor tv:margin-choice-mixin Puts choice boxes in the bottom margin, according to a list of choice-box descriptors which can be specified with the :margin-choices init-plist option or the :set-margin-choices operation. A choice-box descriptor is a list, (name state function x1 x2). It is legal to use a longer list as a choice-box descriptor and store your own data in the additional elements.

name is a string that labels the box. state is t if the box has an "X" in it, nil if it is empty. x1 and x2 are used internally to remember where the choices boxes are; they are always spread out evenly in the available width.

function is a function that is called in a separate process if the user clicks on the choice box. It receives three arguments: the choice-box descriptor for the choice box, the "margin region" that contains the choice boxes, and the y-position of the mouse relative to this window. You probably want to ignore the last two arguments. When function is called, self is bound to the window, so function may use (declare (:self-flavor flavor)) to access the window’s instance variables. The structure access functions tv:choice-box-name and tv:choice-box-state may be of use inside function (they are just more specific names for car and cadr). If function changes the state of the choice box, it will need to refresh the choice boxes by doing

(funcall (tv:margin-region-function region) ':refresh region)

where region is its second argument, which is why that argument is passed.

tv:margin-choice-mixin contains tv:margin-region-mixin as an included flavor; this means approximately that tv:margin-region-mixin will appear in any combination right after tv:margin-choice-mixin if it is not explicitly specified to appear somewhere else. The position of tv:margin-region-mixin controls where the choice boxes appear in relation to the other margin items (borders, labels. etc). See (margins). .end_defflavor

.definitoption tv:margin-choice-mixin :margin-choices choices choices is a list of choice-box descriptors, described above. A line of choice-boxes will appear in the bottom margin of the window. If choices is nil, there will be no choice boxes and no space for them in the bottom margin; however, the window will still be capable of accepting the :set-margin-choices operation to create a line of choice boxes later. .end_definitoption

.defmethod tv:margin-choice-mixin :set-margin-choices choices Changes the set of margin choices according to choices, which is nil to turn them off or a list of choice-box descriptors, described above. If the choice boxes are turned on or off, the size of the window’s bottom margin will change accordingly. .end_defmethod

.definstvar tv:margin-choice-mixin tv:margin-choices A list of margin choices, or nil. .end_definstvar

To get a menu with margin choices, it is best to use tv:menu-margin-choice-mixin ((tv:menu-margin-choice-mixin-flavor)), which goes to a little extra trouble to interface the margin choices to the menu.

15 Typeout Windows

Typeout windows are a facility provided to make it easier for a program that normally displays a single updating picture to print a stream of unrelated output from time to time.

For example, Zmacs windows normally present a continuously updated display of an editor buffer. But some editor commands are designed to print output, such as a directory listing from Control-X Control-D or a list of buffers from Control-X Control-B. This output cannot conveniently be printed on the editor window itself, since that window is set up to maintain its standard display of an editor buffer and is no longer suitable for displaying anything else. Instead, the output is printed on a special kind of window called a typeout window, which exists as an inferior of the editor window. Other programs that maintain updating displays, such as the inspector and Peek, also use typeout windows for this purpose.

A typeout window is an inferior of another window such as the editor or Peek display window, and "grows" over its superior as output is done on it. The output starts at the top of the typeout window, which is also the top of its superior, and proceeds downward. The typeout window always keeps track of how far down output has proceeded, so that the superior window can eventually find out how much of its permanent display has been clobbered by the typeout window and therefore needs to be redisplayed. A horizontal line or "window shade" appears just below the point of lowest output, to enable the user to separate the typeout from the remains of the permanent display. If output to the typeout window proceeds far enough, it wraps around to the top of the screen. Then the typeout window records that the entire superior has been clobbered and no longer displays any horizontal line.

.defflavor tv:basic-typeout-window This is the base flavor for all kinds of typeout windows. It is actually just a mixin, not instantiable by itself. .end_defflavor

.defflavor tv:typeout-window (tv:basic-typeout-window tv:notification-mixin tv:window) This is the flavor normally used for actual typeout windows. .end_defflavor

.defflavor tv:typeout-window-with-mouse-sensitive-items (tv:basic-mouse-sensitive-items tv:typeout-window) This flavor of typeout window also provides the :item operation, for including mouse-sensitive rectangles among the typeout. See (tv:basic-mouse-sensitive-items-flavor). .end_defflavor

.defmethod tv:basic-typeout-window :bottom-reached Returns the greatest y-position clobbered by the typeout window. This is a cursor position, relative to the typeout window. The horizontal line (typeout window border), when enabled, appears at this position, provided it is not zero or equal to the inside bottom of the window.

The value is nil when the typeout window is not active.

The typeout window has an instance variable tv:bottom-reached, but this method does not simply return the value of the instance variable. .end_defmethod

Variable: tv:*enable-typeout-window-borders*

When this variable is non-nil, a horizontal line is used to indicate the bottom of the area used by the typeout window. No line appears when the typeout window has used its entire area (if it has wrapped around or done a :clear-screen). When this variable is nil, the horizontal line does not appear. The default value is t.

15.1 Activation and Deactivation

A typeout window is deactivated when not in use. Any attempt to output to it automatically activates and exposes it because its deexposed-typeout-action is (:expose-for-typeout).

.defmethod tv:basic-typeout-window :expose-for-typeout Sent in order to prepare the typeout window to be typed out on. The typeout window marks itself "exposed" while leaving the bits of its superior on the screen. It initializes itself as "empty" and its bottom-reached as zero. It also finds a suitable ancestor and makes itself that ancestor’s selection substitute. In normal use, this typically causes the typeout window to become selected. .end_defmethod

.defmethod tv:basic-typeout-window :active-p Returns non-nil if the typeout window is active, which is the case if and only if typeout is currently visible in it. .end_defmethod

Exposing the typeout window automatically causes it to become the selection substitute of one of its ancestors (see (selection-substitutes)). Just which ancestor is determined according to the situation; it is the nearest ancestor in the existing path of selection substitutes. This is the nearest ancestor that can be used for the purpose and actually make the typeout window be selected. It is the typeout window’s direct superior only if that superior is selected. For example, if you type Meta-X in Zmacs and then type Help, the help message will print on the main editor window’s typeout window, but that editor window is not selected (the minibuffer is). So the typeout window will substitute for the editor frame rather than for the nonselected editor window immediately above it.

When the program wants to make the typeout go away and put back its standard display, it must first deactivate the typeout window with the :deactivate operation.

When the typeout window is deactivated, it sends a :remove-selection-substitute message to whichever ancestor it had decided to substitute for. As a result, if the typeout window is still that ancestor’s selection substitute, the substitute is set back to what it had been before the typeout window was exposed. If the ancestor’s substitute has been changed since then, it is left alone.

The purpose of making the typeout window a selection substitute is primarily to make its cursor blinker blink. A typeout window by default shares the input buffer of its superior, so which of them is selected has no effect on reading keyboard input. A separate feature of typeout windows turns the superior’s blinkers off completely while the typeout is exposed.

15.2 Superiors of Typeout Windows

To make a window possess an inferior typeout window, include the flavor tv:essential-window-with-typeout-mixin in it. This causes a typeout window to be created and provides the methods to handle communication with the typeout window.

.defflavor tv:essential-window-with-typeout-mixin This is the basic mixin that gives a window the ability to manage a typeout window as its inferior. .end_defflavor

.defflavor tv:window-with-typeout-mixin (tv:no-screen-managing-mixin tv:essential-window-with-typeout-mixin) This is what you typically use, rather than tv:essential-window-with-typeout-mixin, because it prevents screen management of this window’s inferiors from getting in the way of the operation of the typeout window. .end_defflavor

.definstvar tv:essential-window-with-typeout-mixin tv:typeout-window This window’s typeout window. .end_definstvar

.defmethod tv:essential-window-with-typeout-mixin :typeout-window Returns the value of the instance variable tv:typeout-window, which is the typeout window associated with this window. .end_defmethod

.definitoption tv:essential-window-with-typeout-mixin :typeout-window (flavor-name options...) This init option specifies what kind of typeout window to create. The car of the value is the name of flavor of typeout window to use, and the cdr is a list of alternating options and values to pass to make-instance.

If the option is not specified, or is nil, no typeout window is actually created. .end_definitoption

The tv:basic-typeout-window flavor provides for daemons and wrappers that cause the :mouse-moves and :mouse-buttons messages to get passed either to the typeout window or to its superior, depending on whether the typeout window has grown down to where the mouse is.

.defmethod tv:essential-window-with-typeout-mixin :turn-on-blinkers-for-typeout Sent to the superior of a typeout window when the mouse moves into an area that the typeout window is not using, this operation should make visible any blinkers that are associated with the use of the mouse. The definition actually provided by the flavor tv:essential-window-with-typeout-mixin does nothing; this operation exists so that you can add daemons to it. .end_defmethod

.defmethod tv:essential-window-with-typeout-mixin :turn-off-blinkers-for-typeout Sent to the superior of a typeout window when the mouse moves into the area used by the typeout window, this operation should turn off any blinkers that were turned on by :turn-on-blinkers-for-typeout. The definition actually provided by the flavor tv:essential-window-with-typeout-mixin does nothing; this operation exists so that you can add daemons to it. .end_defmethod

A typeout window does **MORE** processing if and only if that is enabled for its superior. The usual motivation for using a typeout window is that the superior is to be used for something other than sequential output; therefore, **MORE** processing is usually not desired on the superior. However, it is not desirable to simply disable **MORE** processing for the superior because this disables it for the typeout window as well and because the user could reenable it for both windows with Terminal M.

.defmethod tv:basic-typeout-window :more-p .defmethod1 tv:basic-typeout-window :set-more-p new-more-p These operations are passed along to the superior, so that the user who types the Terminal M command need not be aware of the distinction between the typeout window and its superior. .end_defmethod

.defflavor tv:intrinsic-no-more-mixin This mixin, intended for use in superiors of typeout windows, prevents **MORE** processing unconditionally without saying that it is "disabled". Programs and the user can think they can enable and disable **MORE** processing for the window using the the :more-p and :set-more-p operations, and the Terminal M command, but only the typeout window is affected.

An alternative way to accomplish this is as follows:

(defmethod (my-display-window-with-typeout-window
             :more-exception)
	   ()
  (setf (tv:sheet-more-flag) 0))

.end_defflavor

15.3 Delaying Redisplay After Typeout

The typeout window superior must know how to check before redisplaying to find out whether part of its last display has been overwritten by the typeout window and therefore must be redisplayed. To find out how much screen height the typeout window has used, use the :bottom-reached operation on it. The typeout window must also be deactivated so that more typeout, happening after the redisplay, will work properly.

Here is an example which is how general scroll windows do this:

(defmethod (tv:scroll-window-with-typeout-mixin
	     :before :redisplay)
	   (&rest ignore)
  (when (funcall tv:typeout-window ':active-p)
    (let ((br (min tv:screen-lines
		   (1+ (truncate (send tv:typeout-window
				       ':bottom-reached)
				 tv:line-height)))))
      ;; br is the number of lines of our display
      ;; that were clobbered by typeout.
      (funcall tv:typeout-window ':deactivate)
      (dotimes (l br)
	;; Mark lines as clobbered
	(aset nil tv:screen-image l 0)
	(aset -1 tv:screen-image l 1)
	(aset -1 tv:screen-image l 2))
      ;; Erase the clobbered area.
      (send self ':draw-rectangle
	    (tv:sheet-inside-width)
	    (* br tv:line-height)
	    0 0
	    tv:alu-andca))))

The editor normally updates its display after each command. But after a command that prints typeout, it is important not to update the permanent display right away, because that would make the typeout disappear almost as soon as it appeared. The same consideration applies to other programs that use typeout windows.

The convention in this situation is that after a command that has produced typeout, redisplay should be delayed until the user types another input character. If that character is a space, it is discarded. Otherwise, it is interpreted as a command.

The way the program should decide whether to wait before redisplaying is to invoke the :incomplete-p operation on typeout window. This reads a flag that is set whenever output is done on the typeout window and can be cleared by the program’s command loop between commands. Thus, the flag indicates whether the typeout window was used during the last command.

Here is a sample piece of code that illustrates this technique:

(let ((standard-output typeout-window))
  (do-forever
    ;; Clear the flag.
    (send standard-output ':make-complete)

    ;; Read and execute one command.
    (process-command (send standard-input ':tyi))

    (when (send standard-output ':incomplete-p)
      ;; If this command printed some typeout,
      ;; delay redisplay by waiting for next input char.
      (let ((ch (send standard-input ':tyi)))
	(unless (eq ch #\sp)
	  ;; Anything but Space, execute as a command.
	  ;; Since Space is not untyi'd, it allows
	  ;; immediate redisplay.
	  (send standard-input ':untyi ch))))

    ;; Here is where we redisplay after each command.
    (unless (send standard-input ':listen)
      ;; Normal redisplay must deactivate the typeout window;
      ;; see the previous example.
      (redisplay-normal-display))))

Note that this command loop follows the editor’s practice of not redisplaying when there is input available. As a result, when the character read is not a Space, the :untyi causes redisplay to be prevented by the presence of input. Then the same character is read again at the top of the loop and processed as a command. If this command too prints typeout, its typeout will add on to that already on the typeout window. If this command does not print typeout, the old typeout will be erased after it is done.

.defmethod tv:basic-typeout-window :incomplete-p Returns the window’s incomplete-flag: t if the command loop should wait for the next character before deactivating the typeout window. .end_defmethod

.definstvar tv:basic-typeout-window tv:incomplete-p The window’s incomplete-flag: t if the command loop should wait for the next character before deactivating the typeout window. .end_definstvar

.defmethod tv:basic-typeout-window :make-complete Clears the incomplete-flag. The command loop can use this to clear the flag after examining it.

Certain functions such as fquery perform this operation on the I/O stream to tell the program not to wait before redisplaying, as it normally would do. The idea is that the fquery question is not worth preserving on the screen once the user has answered it. .end_defmethod

.defmethod tv:basic-typeout-window :make-incomplete Sets the incomplete-flag. All the standard output stream operations also do this. .end_defmethod

16 Text Scroll Windows

Text scroll windows provide a simple means of maintaining a display of a number of lines of the same type with scrolling. For example, they are used by the inspector to display the slots of a structure. (See (scroll-windows) for a more general kind of scroll window.)

.defflavor tv:text-scroll-window This is the base flavor for all kinds of text scroll windows. It is not instantiable by itself. .end_defflavor

A text scroll window updates its display based on a sequence of items. Each item generates one line of display. An item can be any Lisp object, and how it displays is controlled by how you define the :print-item operation. For example, you could define this operation to do a :string-out; then the items would have to be strings. By default, :print-item uses the function prin1, so each item is a Lisp object to be printed.

.defmethod tv:text-scroll-window :print-item item line-no index Displays item, which should be the indexth item of those currently displayed, at the current cursor position in the window, which should be on line number line-no of the window.

This operation is the primitive used by all other text scroll window operations to do output of items. As defined by tv:text-scroll-window, it just does prin1 of item, ignoring the other arguments. Other flavors built on tv:text-scroll-window are expected to redefine this operation.

In any case, no item may print out as more than one line. This is enforced by truncating output at the margin. .end_defmethod

16.1 Specifying the Item List

In simple use, you specify an array of items to be displayed, or a list of items (which is converted into an array). Items are referred to sometimes by their indices in the array. A more sophisticated technique is to specify an item generator, which is a function that simulates the effect of a possibly very large array of items without requiring you to actually create the array.

.definstvar tv:text-scroll-window tv:items The array whose elements are the items to be scrolled through. The index of an item in this array is called the index of the item. This array contains the entire set of items to be scrolled through, not just those that are on the screen at any time. .end_definstvar

.definstvar tv:text-scroll-window tv:top-item The index of the first item currently being displayed (on the first line of the window). This is how the current scroll position is remembered. .end_definstvar

The flavor tv:text-scroll-window provides these operations:

.defmethod tv:text-scroll-window :items Returns the window’s array of items. .end_defmethod

.defmethod tv:text-scroll-window :set-items new-items Sets a new array of items. new-items may be a suitable array (it should have a fill pointer), or a list of items (an array is made from it), or a number of items (the array is made that long, but initially empty).

The item-generator of the window is set to nil, turning off that feature, so that the array of items will actually be used. .end_defmethod

.defmethod tv:text-scroll-window :top-item .defmethod1 tv:text-scroll-window :set-top-item new-top-item The top-item is the index of the item to be displayed on the first line of the window. .end_defmethod

.defmethod tv:text-scroll-window :number-of-items Returns the number of items this window is currently scrolling through. .end_defmethod

.defmethod tv:text-scroll-window :number-of-item item Returns the item number (index) of item. .end_defmethod

.defmethod tv:text-scroll-window :item-of-number index Returns the item at index index. .end_defmethod

.defmethod tv:text-scroll-window :last-item Returns the value of the last item to be scrolled through (that is, the one whose index is one less than the number of items). .end_defmethod

.defmethod tv:text-scroll-window :put-item-in-window item .defmethod1 tv:text-scroll-window :put-last-item-in-window Scroll the window so that the specified item, or the last item, appears on the screen. The argument item is an item value, not an index. .end_defmethod

.defmethod tv:text-scroll-window :delete-item index Modifies the list of displayable items, removing the item at index, and updates the screen if that index is within the portion currently displayed. .end_defmethod

.defmethod tv:text-scroll-window :insert-item index item .defmethod1 tv:text-scroll-window :append-item item Add a new item item to the list of items to be displayed, either at index index (before the item currently at that index) or at the end. .end_defmethod

The following auxiliary operations are also defined.

.defmethod tv:text-scroll-window :redisplay start end This is the internal function that causes a :print-item message to get sent for each line in the range start to end, which are screen line indices. It should not be redefined, but daemons may be placed on it to note changes in the screen layout. .end_defmethod

.defmethod tv:text-scroll-window :scroll-redisplay new-top delta This is the internal scrolling function that causes partial redisplay with bitblting and then sends a :redisplay message for the rest. new-top is the new tv:top-item, and delta the number of lines actually to be scrolled. This operation should not be redefined, but daemons may be placed on it. .end_defmethod

The operations :scroll-bar-p, :scroll-position, :scroll-to, and :new-scroll-position are also defined for interface with the scroll bar. Other scrolling commands can also use them.

16.2 Bells and Whistles

Function text scroll windows provide for you to change dynamically the function used to display items. These windows have an instance variable which holds the function to be used. The inspector uses this feature so that each data type you can inspect can be handled in an independent manner, with its own conventions for what an item means.

.defflavor tv:function-text-scroll-window (tv:text-scroll-window) An instantiable function text scroll window. .end_defflavor

.definstvar tv:function-text-scroll-window tv:print-function This is the function to be called to display an item. See (print-function-example) for an example of a print function, taken from the inspector. .end_definstvar

.definstvar tv:function-text-scroll-window tv:print-function-arg This is an additional argument to be passed to the print function. The print-function’s complete list of arguments are the item itself, the value of tv:print-function-arg, the window, and the item number. .end_definstvar

.definitoption tv:function-text-scroll-window :print-function function .definitoption1 tv:function-text-scroll-window :print-function-arg arg Initialize the corresponding instance variable. .end_definitoption

.defmethod tv:function-text-scroll-window :print-function .defmethod1 tv:function-text-scroll-window :print-function-arg .defmethod1 tv:function-text-scroll-window :set-print-function function .defmethod1 tv:function-text-scroll-window :set-print-function-arg arg Get or set the corresponding instance variable. .end_defmethod

.defmethod tv:function-text-scroll-window :setup list list is a list of the form

(print-function print-function-arg
 (item...)
 top-item-number
 label
 item-generator)

As you can see, it specifies everything relevant to telling the window what items to display and how to display them. label is passed to the :set-label operation.

It is not useful to specify both a list of items and a non-nil item-generator, since the list of items is not used if the item-generator is non-nil.

The display is updated by this operation. .end_defmethod

Since a text scroll window updates a display according to a fixed pattern, it is often useful for it to have an inferior which is a typeout window, for the sake of occasional output that is not part of the standard display (such as, the output for Help in the inspector).

.defflavor tv:text-scroll-window-typeout-mixin (tv:window-with-typeout-mixin) This can be added to a flavor containing tv:text-scroll-window and provides a typeout window. It also arranges for proper interaction with the typeout window and partial redisplay over the area it clobbers. .end_defflavor

.defmethod tv:text-scroll-window-typeout-mixin :flush-typeout If the typeout window is active, this deexposes it, and makes sure that redisplay knows that the lines have been clobbered. .end_defmethod

.defflavor tv:text-scroll-window-empty-gray-hack This is a mixin that goes with tv:text-scroll-window. When windows of this type have an empty array for tv:items, or an item generator that says the number of items is zero, the interior of the window becomes gray.

This is used in some panes of the window-based debugger frame. .end_defflavor

16.3 Item Generators

The item generator feature is how the inspector can scroll through the elements of a large array without having to cons up another equally large array of items.

.definstvar tv:text-scroll-window tv:item-generator The item generator function, or nil if no item generator is in use. The item generator is a function which simulates the effect of an array of items. It overrides any explicit array of items; the value of tv:items will still be an array, but it will not affect the display. .end_definstvar

.defmethod tv:text-scroll-window :item-generator .defmethod1 tv:text-scroll-window :set-item-generator new-item-generator Get or set the window’s item-generator. .end_defmethod

The :set-items operation sets the item generator to nil, since if you want to use an explicit list of items, you must not want the item generator to cause them to be ignored.

The item generator function should expect its first argument to be an item generator operation keyword. These are the keywords defined:

  • :number-of-items item-generator Returns the number of items to scroll through (the equivalent of the fill pointer in an actual array of items).
  • :number-of-item item Returns the index of the specified item. If an actual array were in use, this would be the index in the array where item is found.
  • :item-of-number index Returns the item at index index. If an actual array were being used, this would be the index’th element of the array.
  • :insert-item index item Insert a new item item, before the one at index index. If an actual array were in use, this would be done by moving the following elements down. The item generator need support this only if you wish to use the :insert-item or :append-item operation on the window.
  • :delete-item index Delete the item at index index. The following items move to lesser indices. The item generator need support this only if you wish to use the :delete-item operation on the window.

The inspector uses an item generator to display the elements of an array, so that it does not have to create another array of items as big as the array being displayed. If l is the length of the array’s leader, then item numbers 0 through l-1 correspond to the leader, and item number l+i corresponds to array element i (multidimensional arrays being treated as one-dimensional).

The value of the item at item number n is just n. In other words, the virtual array of items that the item generator simulates is an array of consecutive integers, independent of the data being displayed. This may seem to be a weird way of doing things, but consider this: we do not want the line for the ith element to print out as simply that element. We want it to contain the number i as well. So the item value is simply l+i, and the :print-item operation is redefined to "print" such a number by printing i followed by the ith array element.

Here is a simplified version of the item generator used by the inspector. Note that the array whose elements are being displayed is found as (car print-function-arg), and (cadr print-function-arg) is non-nil if the leader should be displayed. tv:print-function-arg is an instance variable from the flavor tv:function-text-scroll-window; see (tv:function-text-scroll-window-tv:print-function-arg-instvar).

(defselect inspect-array-item-generator
  (:number-of-items ()
    (declare (:self-flavor tv:basic-inspect))
    (+ (if (cadr tv:print-function-arg)
	   (or (array-leader-length (car tv:print-function-arg)) 0)
	 0)
       (array-length (car tv:print-function-arg))))
  (:number-of-item (item)
    item)	;; The item's number is the item!
  (:item-of-number (number)
    number))	;; The number's item is the number!

:insert-item and :delete-item are not supported, since the inspector does not try to insert or delete items.

The inspector uses a tv:function-text-scroll-window (see (tv:function-text-scroll-window-flavor)) so :print-object is handled by calling a dynamically changeable print-function. Here is a simplified version of the print-function used by the inspector when displaying an array.

(defun inspect-array-printer
       (item arg window
	&aux (array (car arg))
	(leader-length-to-mention
	  (or (and (cadr arg) (array-leader-length array)) 0)))
  ;; arg is the value of tv:print-function-arg.
  ;; (car arg) is the array.
  ;; (cadr arg) is t to display the leader.
  ;; item is a number, as described above.
  (cond ((< item leader-length-to-mention)
	 (format window "Leader ~D" item)
	 (format window ":~12T ")
	 (tv:print-item-concisely
	   (array-leader array item) window))
	(t
	 (let ((item (- item leader-length-to-mention))
	       (rank (array-rank array))
	       indices)
	   (or (= rank 1)
	       (setq indices 
		     (array-indices-from-index array item)))
	   (format window "Elt ~D" 
		   (if (= rank 1) item indices))
	   (format window ":~9T ")
	   (tv:print-item-concisely 
	     (ar-1-force obj item) window)))))

16.4 Mouse Sensitive Text Scroll Windows

.defflavor tv:mouse-sensitive-text-scroll-window Windows of this flavor allow the lines to contain mouse-sensitive items just like those of tv:basic-mouse-sensitive-items (see (tv:basic-mouse-sensitive-items-flavor)) though the implementation is different.

Note that the word "item" in "mouse-sensitive item" is completely unrelated in meaning to the items of the text scroll window itself. .end_defflavor

.defmethod tv:mouse-sensitive-text-scroll-window :item type item &rest format-args All output to text scroll windows is done with the :print-item operation, which is responsible for printing a single item. This operation can include mouse-sensitive items in the output by using the :item operation, which is compatible with that of tv:basic-mouse-sensitive-items (see (tv:basic-mouse-sensitive-items-item-method)).

Note that the item argument here is the datum to identify the mouse-sensitive item, not the text scroll window item being displayed on this line. .end_defmethod

The :item-list and :primitive-item operations are not provided, since in this context they are not really useful.

.defmethod tv:mouse-sensitive-text-scroll-window :item1 item type print-function &rest args This is another way of outputting a mouse-sensitive item. item and type have the same meanings as for the :item operation, but the output is done by calling print-function with item, the window, and the elements of args as arguments.

The :item operation used to do this, but it was changed for compatibility, and the old functionality renamed to :item1. .end_defmethod

In a typical tv:basic-mouse-sensitive-items window, mouse-sensitive items are output on specific occasions, and only because they are supposed to be present and mouse-sensitive at that time. In a text scroll window, typically a single display is maintained at all times, but the parts that should be sensitive to the mouse may need to depend on other things. For example, in the inspector, normally the values of slots are sensitive, but when you are specifying a slot to store into, the names of the slots are sensitive instead.

.definstvar tv:mouse-sensitive-text-scroll-window tv:sensitive-item-types The list of sensitive item types. A mouse sensitive item is sensitive to the mouse if its type (as specified in the :item operation) is a member of this list.

t can also be used instead of a list; then all mouse sensitive items actually are sensitive. t is the default value, so that this feature does not get in the way if you do not use it. .end_definstvar

.defmethod tv:mouse-sensitive-text-scroll-window :sensitive-item-types .defmethod1 tv:mouse-sensitive-text-scroll-window :set-sensitive-item-types new-item-types Get or set the list of sensitive item types. .end_defmethod

.definitoption tv:mouse-sensitive-text-scroll-window :sensitive-item-types item-types Initializes the set of sensitive item types. .end_definitoption

The inspector’s print function shown in the previous section really does its output using the :item1 operation so that the output becomes mouse-sensitive. Here is the real code for the cond-clause that handles leader elements:

	((< item leader-length-to-mention)
	 (funcall window ':item1 item 'leader-slot
		  #'(lambda (item window)
		      (format window "Leader ~D" item)))
	 (format window ":~12T ")
	 (funcall window ':item1 (array-leader array item)
		  ':value #'tv:print-item-concisely))

leader-slot and :value are item types which the inspector makes mouse sensitive at various times.

When the mouse is clicked on a mouse sensitive item, a blip is placed in the window’s input buffer. The blip looks like

(type item window mouse-character)

type is the item type, such as leader-slot or :value, and item is the actual item value specified in the :item or :item1 operation. window is the text scroll window itself. (This is how the inspector can tell which inspect pane you click on.) mouse-character is a character whose %%kbd-mouse bit is 1. It tells the program which button was clicked.

.defflavor tv:line-area-text-scroll-mixin This mixin, when added to tv:text-scroll-window, creates a "line area" near the left edge where the mouse cursor changes to a rightward arrow and a click means something different. The line area is an additional part of the left margin and does not overlap the space used for displaying the items.

You must also include the flavor tv:margin-region-mixin in the flavor combination you instantiate.

A mouse click in the line area puts a blip into the input buffer that looks like this:

(:line-area item window button-mask)

button-mask is a mask of bits corresponding to mouse buttons; see tv:mouse-last-buttons, (tv:mouse-last-buttons-var), for how to interpret it. .end_defflavor

.definitoption tv:line-area-text-scroll-mixin :line-area-width number Specifies the width of the line area in pixels as number. .end_definitoption

.defmethod tv:line-area-text-scroll-mixin :line-area-mouse-documentation This operation should return a string to display in the mouse documentation line while the cursor is in the line area. .end_defmethod

.defflavor tv:line-area-mouse-sensitive-text-scroll-mixin This flavor should be used instead of tv:line-area-text-scroll-mixin if tv:mouse-sensitive-text-scroll-window is in use. .end_defflavor

.defflavor tv:current-item-mixin This flavor, when added to tv:line-area-text-scroll-mixin, identifies one of the items with an arrow in the line-area. .end_defflavor

.definstvar tv:current-item-mixin tv:current-item The item to be marked with an arrow, or nil if none. An arrow will mark this item if it is on the screen, no matter where it scrolls to. .end_definstvar

.defmethod tv:current-item-mixin :current-item .defmethod1 tv:current-item-mixin :set-current-item item Get or set the value of tv:current-item. .end_defmethod

These flavors are part of the implementation of tv:mouse-sensitive-text-scroll-window.

.defflavor tv:mouse-sensitive-text-scroll-window-without-click This is a component of tv:mouse-sensitive-text-scroll-window that provides everything but the :mouse-click method. Since this operation uses :or method-combination, it is not possible to override a method once it is present. .end_defflavor

.defflavor tv:displayed-items-text-scroll-window This flavor records additional information about the items that are actually displayed. It provides an instance variable, tv:displayed-items, which is an array indexed by line number. In this array, the :print-item operation can store any relevant information about what was displayed on the line.

The meaning of elements of the array is not defined by this flavor. The :print-item operation is responsible for storing whatever information is useful into the appropriate slot of the array. However, this flavor does move elements of the array when scrolling is done, and set them to nil when parts of the window are cleared, or when they are about to be redisplayed.

This flavor is essentially a subroutine of tv:mouse-sensitive-text-scroll-window, which uses each element of tv:displayed-items to hold information on the mouse-sensitive items for the line. .end_defflavor

.definstvar tv:displayed-items-text-scroll-window tv:displayed-items The array of information about lines on the screen. .end_definstvar

Despite all this hair, no window yet devised is as mouse-sensitive as my mother.

17 General Scroll Windows

General scroll windows are used to put up a continuously-maintained display of items, each of which can vary in size. They are used by Peek. General scroll windows (from now on called simply scroll windows) are not a generalization or a building block of text scroll windows, but rather an independent facility.

The scroll window’s display is made up of items. These items are not the same as items in text scroll windows; the same term is used because they fit in a similar place in the scheme of things.

An item in a scroll window always occupies an entire line or several entire lines. An item can be composed of sub-items which are juxtaposed vertically, each sub-item occupying and filling up some number of lines. The sub-items can in turn be composed of more items. New sub-items can be dynamically added or deleted at any level, and the display is updated automatically to match by moving lines around on the screen.

Eventually this process of subdivision must come to an end, with lowest-level items made up of entries, which are juxtaposed in a horizontal sequence.

An entry displays a single string or quantity, updating its display if the value changes. The entry must record how to obtain a value to display, how to tell when the value has changed since the screen was updated, and how to output the new value. A single entry can wrap around at the right margin just like ordinary output. Entries can be added to and removed from an item dynamically.

In Peek’s Active Processes display, there is a single item that displays the entire set of processes. It is composed of sub-items, one for each process. If a new process appears, a new sub-item is created to display it. The sub-item for a single process is a lowest-level item. Each of the things displayed about a process–its name, its run state, its priority, its percentage use of the cpu–is displayed by a single entry in that item.

The line of column headings at the top of the display is also a lowest-level item; its entries display constant strings.

Every character displayed on a scroll window comes from an entry. The items serve only to group entries, and to control the automatic insertion and deletion of entries.

Entries can be either fixed or variable width. A variable width entry takes up as much space as is needed to print its data; this can change when the window is redisplayed. When that happens, the remaining entries in the item all have to move left or right. A fixed width entry specifies an amount of horizontal space and always occupies that much space. As a result, it can be redisplayed without redisplaying the rest of the item afterward. The entries used in the Active Processes display are all fixed-width so that they will line up in columns

Note: if the entry specifies a fixed width and the printing of its contents goes past that width, the window redisplay algorithm will be confused.

The data structure that represents an item is either a list or an array. If it is a list, its cdr is a list of component items, and its car contains information on how to update the list (add or remove component items). Then the item is displayed simply as the concatenation of its components. If it is an array, then it is a lowest-level single-line item, and the elements of the array represent entries on the line. The array also has leader slots whose meanings are described below.

17.1 Specifying Items and Entries

You do not generally create an array item or an entry yourself. They are made by calling the function tv:scroll-parse-item, which is given a descriptive data structure made out of lists. Examples of its use are at the end of this section.

The arguments to tv:scroll-parse-item are entry descriptors, each of which specifies how to create one entry. The entries thus specified all go together into one item.

Here are the possible kinds of entry descriptors:

  • a string string
  • a list (:string string [width]) The entry is displayed by printing string. A string entry never varies, since it always displays precisely the specified string, and is always fixed width. The width can be specified as a means of controlling the position of the following entry; otherwise, the actual width needed to print the string is the width of the item.

    Example: either (:string "Foobar" 10.) or "Foobar " specifies an entry that prints as Foobar followed by 4 spaces.

  • a list (:symeval symbol [width-or-nil] [format-string]) The entry is displayed by printing the value of symbol, by passing it to format together with format-string. If format-string is omitted, the value is printed with princ. This type of entry is automatically updated when the value of symbol changes.

    width-or-nil may be a number of pixels, to specify a fixed-width entry, or nil to specify a variable-width entry.

    Example:

    (:symeval base nil " ~D. ")
    

    specifies an entry that prints the value of base in decimal with a following period and a space in front and in back. It is variable-width so the space it takes up is three plus however many digits are needed to print the value of base.

  • a list (:function function list-of-args [width-or-nil] [format-string]) The value to display is obtained by applying function to list-of-args. If this value has changed since the last time it was checked, it is displayed by passing it to format together with format-string. If format-string is nil, the value is simply princ’d.

    width-or-nil may be a number of pixels, to specify a fixed-width entry, or nil to specify a variable-width entry.

    Example:

    `(:function si:process-quantum-remaining 
      (,process) 5. ("~4D//"))
    

    is an expression that creates an entry descriptor which specifies an entry that will call si:process-quantum-remaining on some process and print the result in decimal, followed by a slash, in a field 5 characters wide.

  • an interpreted function (lambda ...)
  • an interpreted function (named-lambda ...)
  • a compiled function An entry descriptor which is either a compiled function (a FEF) or a list starting with lambda or named-lambda is considered a function. It is treated as an abbreviation for (:function function), which specifies no arguments, variable width, and no format string (the value is printed with princ).
  • a list (:value index [width-or-nil] [format-string]) The value to be displayed is found at index in the window’s value-array.

Two other keywords can be used in an entry descriptor to make the entry mouse sensitive. They can be used only in scroll windows which have tv:essential-scroll-mouse-mixin (see (scroll-window-mouse)). To use these keywords, first you construct an entry descriptor to specify how the entry should print, according to the preceding table. Then you add one of these keywords and a value to go with it at the front of the list. The mouse keyword gives the entry mouse sensitivity but has no effect on how the entry appears on the screen.

  • :mouse The keyword :mouse is used in an entry descriptor that looks like
    (:mouse mouse-data . another-entry-descriptor)
    

    Such an entry descriptor is handled by creating an entry from another-entry-descriptor, and then modifying it by recording mouse-data as the mouse sensitivity of the entry. The resulting entry will print according to another-entry-descriptor but will be mouse sensitive as well.

  • :mouse-item The keyword :mouse is used in an entry descriptor that looks like
    (:mouse-item mouse-data . another-entry-descriptor)
    

    :mouse-item is like :mouse except that the symbol tv:item is replaced throughout mouse-data with item, the item this entry is going to become part of. mouse-data better be a list.

    There is no way to cause the entry itself to be inserted into its own mouse sensitivity datum because this is not useful when scroll windows are used in the intended manner.

Function: tv:scroll-parse-item &rest keyword-args-and-entry-descriptors

Creates and returns an array item containing entries constructed according to keyword-args-and-entry-descriptors.

keyword-args-and-entry-descriptors begins optionally with some alternating keywords and values. They are followed by entry descriptors, one for each entry you want in the item. The keywords and values at the beginning specify information that applies to the item as a whole. Keywords and entry descriptors are distinguished by the fact that an entry descriptor is never a symbol.

The keywords defined are

  • :mouse The value is stored as the mouse-sensitivity of the entire item. This is meaningful only if the window flavor includes tv:essential-scroll-mouse-mixin (see (scroll-window-mouse)).
  • :mouse-self The value is stored as the mouse-sensitivity of the entire item, but first the symbol self is replaced wherever it appears by the item itself (the array that this function is constructing). This is meaningful only if the window flavor includes tv:essential-scroll-mouse-mixin (see (scroll-window-mouse)).
  • :leader This keyword requests extra slots to be allocated in the array leader of the item array. It is either a number, the number of extra slots desired, or a list, whose length is the number of extra slots and whose contents are used to initialize them.
Function: tv:scroll-interpret-entry entry-descriptor item

Creates and returns an entry according to entry-descriptor for use in the array item item. You do not normally call this function yourself; it is used as a subroutine of tv:scroll-parse-item.

Function: tv:scroll-string-item-with-embedded-newlines string

Returns an item that will display the string string. This item is composed of one item for each line making up string.

Here is an example taken from Peek; it makes the item for a process (the value of process) in Active Processes mode. The entries that use the process as a function work because the process is a flavor object; the argument given to the process is a flavor operation. Note that tv:peek-process-menu is a function in Peek which asks for a choice with a momentary menu.

(tv:scroll-parse-item
  ;; The first entry is mouse-sensitive.
  `(:mouse-item
     (nil :eval (peek-process-menu ',process 'item 0)
	  :documentation
	  "Menu of useful things to do to this process.")
     :string ,(process-name process) 30.)
  `(:function ,#'peek-whostate ,(ncons process) 25.)
  `(:function ,process (:priority) 5. ("~D."))
  `(:function ,process (:quantum-remaining) 5. ("~4D//"))
  more entries...)

17.2 Using a Scroll Window

.defflavor tv:basic-scroll-window All flavors of scroll window are built on this flavor, which provides all the facilities specific to scroll windows. It is not instantiable by itself. .end_defflavor

.defflavor tv:scroll-window (tv:flashy-scrolling-mixin tv:basic-scroll-window tv:borders-mixin tv:basic-scroll-bar tv:window) This is an instantiable scroll window flavor. It provides for a scroll bar and margin scrolling, and for borders and labels. .end_defflavor

In addition to being able to create a tree of items and entries, you must tell the scroll window to display them. At the highest level, the entire display is grouped into a single item, the root item. Switching modes in Peek works by switching to a new root item.

.definstvar tv:basic-scroll-window tv:display-item The root item of the window. The window’s display is precisely whatever comes from this item, and nothing more. Usually the root item contains some number of subitems which do the real work. .end_definstvar

.defmethod tv:basic-scroll-window :display-item .defmethod1 tv:basic-scroll-window :set-display-item item Get or set the root item of the window. Setting the root item redisplays the window. .end_defmethod

.definitoption tv:basic-scroll-window :display-item item Initializes the root item. .end_definitoption

.definstvar tv:basic-scroll-window tv:truncation If this is nil, entries can wrap around at the right margin. Otherwise, each item can occupy only one line. .end_definstvar

.defmethod tv:basic-scroll-window :truncation .defmethod1 tv:basic-scroll-window :set-truncation flag Get or set the truncation flag. Setting the flag redisplays the window. .end_defmethod

.definitoption tv:basic-scroll-window :truncation flag Initializes the truncation flag. .end_definitoption

A scroll window has a value array whose elements may be used to hold arbitrary data to be displayed by entries using the keyword :value. Such an entry specifies the index of a slot in the value array whose contents are the data to display. Putting appropriate data in the value array is up to you. One technique is to have an automatically updating item whose update function stores data into the value array, and have entries in the item look in those slots. There can be many such items, all using the same value array slots. See (automatically-updating-items).

.definstvar tv:basic-scroll-window tv:value-array The window’s value array. .end_definstvar

.defmethod tv:basic-scroll-window :value-array Returns the window’s value array. .end_defmethod

.definitoption tv:basic-scroll-window :value-array array-or-length Initializes the window’s value array, or specify how long to make it. .end_definitoption

The :redisplay operation updates the display based on the current root item, automatically reprinting the entries whose contents have changed. :redisplay will be done automatically by the window system at certain times (such as when the window size is changed, or the screen is refreshed), but if you want it to happen simply because some of the displayed data has changed, you must send a :redisplay message yourself.

:redisplay-selected-items is another way to request display updating, which allows you to control which items will be checked.

.defmethod tv:basic-scroll-window :redisplay &optional full-p force-p Redisplays the contents of the scroll window. If full-p is nil, the window assumes that its screen bits contain the result of the last redisplay that was done, and only items and entries whose contents are different from last time are actually output. If full-p is non-nil, everything that is supposed to be on the screen is redrawn.

force-p non-nil means update the contents of the window even if it is not exposed. Normally, this operation will wait if the window is not exposed. .end_defmethod

.defmethod tv:basic-scroll-window :redisplay-selected-items list-of-items Redisplays the items in list-of-items, if they are present on the screen. Other items in the current item hierarchy are not even considered for redisplay. .end_defmethod

Since a scroll window shows a constantly updated display, it is often useful to have a typeout window in it for occasional output that is not part of the display that is usually shown.

.defflavor tv:scroll-window-with-typeout-mixin This mixin should be used in addition to tv:window-with-typeout-mixin on any scroll window that is to have a typeout window. It handles interfacing between typeout window output and redisplay of the scroll window. .end_defflavor

.defflavor tv:scroll-window-with-typeout A scroll window that has an inferior typeout window. See (windows-with-typeout). .end_defflavor

17.3 Inserting and Deleting Items

Scroll windows provide operations for replacing, inserting and deleting items explicitly. Since the items form a multilevel hierarchy, the position at which to replace, insert, or delete the item must be specified as a list of numbers. For example, (1 3 0) as a position means item number 0 within item number 3 within item number 1 (within the root item, tv:display-item). nil as a position refers to the root item itself.

.defmethod tv:basic-scroll-window :get-item position Returns the item at position. .end_defmethod

.defmethod tv:basic-scroll-window :set-item position item Stores item into the hierarchy at position. .end_defmethod

.defmethod tv:basic-scroll-window :insert-item position item Inserts item at position, before the item that was at position. .end_defmethod

.defmethod tv:basic-scroll-window :delete-item position Deletes the item at position, so that the following item moves to that position. .end_defmethod

These operations also update the window on the screen as necessary.

17.4 Automatically Updating Items

Just as an entry automatically updates the value it displays, sometimes one wants an item to update automatically the list of items it contains. For example, the Active Processes display contains one item that displays a list of all active processes. This item contains a list of component items, one item per process. Just before the displayed entries for each process are updated if necessary, additional items should be created and inserted in the list if there are any newly active processes, and items should be removed if processes have become inactive.

The first element of an item that is a list is used to store the data of a property list for the item. Two properties are given standard meanings:

  • :pre-process-function "scroll items" The value of this property is a function to be called whenever it is time to display this item. Its sole argument is the item itself. The function can modify the item. The value it returns is ignored.
  • :function "scroll items" The value of this property is a function to update an individual component of this item. This function is called each time any component item is about to be displayed or otherwise thought about.

    The arguments given to the function are the component item, the reverse of the position of that item (a list of integers), and the location of the property list of the containing item, the same property list on which this :function property appears (this can be passed directly to get). To repeat, the second argument is the reverse of the position as would be passed to the :get-item operation or related operations. This is because it is easier to implement that way without consing.

    The function should return an updated component item, perhaps the same one as it was passed, perhaps a new one.

Other properties can be used for any purpose. Some of the commonly used pre-process functions use other properties for their internal state information and additional parameters.

Function: tv:scroll-maintain-list init-fun item-fun &optional per-elt-fun stepper compact-p pre-proc-fun

Returns an item which maintains a list of component items, one for each element of a driving list. The item updates automatically so that component items appear and disappear as elements of the driving list do.

init-fun should be a function of no arguments that returns the current value of the driving list. item-fun should be a function that, given an element of the list, returns a component item to use to display for that element. item-fun is called each time a new element appears in the driving list. The item created starts out with no component items. The appropriate set of component items is created by adding them one by one in this way, the first time the item is updated.

This item works because it is given a suitable pre-process function. The other arguments to tv:scroll-maintain-list are also stored on the property list of the item created. In particular, per-elt-fun becomes the :function property. (That is all per-elt-fun is used for.)

Normally the value from init-fun is a list, and the objects that the items are made from are the elements of this list, but it is possible to extract the objects in other ways. If stepper is not nil, it should be a function to step through a "kind of list". stepper is called with one argument, a "kind of list", and returns three values:

  • the first element extracted from it
  • a "kind of list" of the remaining elements
  • non-nil to say there are no more elements nil as the "kind of list" is always recognized as being empty, regardless of the third value.

stepper is first called with the value returned by init-fun. The first value goes (if it is new) to the item-fun; the second is fed back to stepper unless either it is nil or the third value is non-nil.

A stepper function that could step through the properties in a property list might be:

(defun plist-stepper (plist-tail)
  (values (car plist-tail) (cddr plist-tail)))

compact-p non-nil says to recopy the list each time an element is inserted or deleted, so that the list remains compact and localized.

Here is how Peek, in Window Hierarchy mode, recursively creates a tree of automatically updating items:

;; Make an item to describe the entire window hierarchy.
(defun peek-window-hierarchy (ignore)
  (tv:scroll-maintain-list
    ;; The init-fun.  When called, it returns a current list of screens.
    #'(lambda () tv:all-the-screens)
    ;; The item-fun, which makes an item for a screen.
    #'(lambda (screen)
	(list ()
	      (tv:scroll-parse-item 
		(format nil "Screen ~A" screen))
	      (peek-window-inferiors screen 2)
	      (tv:scroll-parse-item "")))))
;; No per-elt-fun is needed.  Also, the default stepper works
;; because our "list" really is a list.

;; Make an item to describe window and its inferiors.
;; indent is an indentation to print with.
(defun peek-window-inferiors (window indent)
  (declare (special window indent))
  (tv:scroll-maintain-list
    (closure '(window) #'(lambda () (tv:sheet-inferiors window)))
    (closure '(indent)
      #'(lambda (sheet)
	  ;; Make an item with two subitems
	  (list ()
		;; One for this window,
		(tv:scroll-parse-item
		  (format nil "~VX" indent)
		  `(:mouse
		    (nil :eval (peek-window-menu ',sheet)
		     :documentation
		     "Menu of useful things to do to this window.")
		    :string ,(send sheet ':name)))
		;; and one with subitems for its inferiors.
		(peek-window-inferiors sheet (+ indent 4)))))))

And here is how it makes the item that displays a chaosnet connection’s packets.

(tv:scroll-maintain-list
  `(lambda () (chaos:read-pkts ',conn))
  `(lambda (x)
     (peek-chaos-packet-item x ,(+ indent 2)))
  nil
  #'(lambda (state)
      (values state (chaos:pkt-link state)
	      (null (chaos:pkt-link state)))))

Note that instead of a list of packets there is a chain, with each packet pointing to the next one. Therefore, an explicit stepper is required. chaos:pkt-link is the function which, given one packet, returns the next one in the chain (or nil at the end).

Function: tv:scroll-maintain-list-unordered init-fun item-fun &optional per-elt-fun stepper

Returns an item which maintains an unordered list of component items, one for each element of a driving list. The item updates automatically so that component items appear and disappear as elements of the list do.

This function is very much like tv:scroll-maintain-list. The difference is that new component items are always added at the front of the combined item, no matter where they appear in the driving list. Changes in the order of that list have no effect at all. This is why this function is called "unordered".

Function: tv:scroll-maintain-list-update-states elements window &optional item

Redisplays some of the component items of item, an item of the sort created by tv:scroll-maintain-list or tv:scroll-maintain-list-unordered.

elements is a list that specifies which component items to update. If the element of the driving list from which a component item was made is memq of elements, then the component item is updated.

17.5 Representation of Items

An item is either a list or an array. A list item contains other items, while an array item contains entries.

List items have these accessor functions:

  • tv:scroll-item-component-items Returns the list of component items of this item.
  • tv:scroll-item-plist Returns the contents of the property list of this item.

Array items have these accessor functions, which refer to array leader slots. (The array elements themselves hold the entries in the item.)

  • tv:scroll-item-size Returns the number of entries in the item.
  • tv:scroll-item-mouse-items Returns a list of mouse-sensitive areas of entries in this item.
  • tv:scroll-item-line-sensitivity Returns what was specified for mouse sensitivity of the item as a whole (using the :mouse or :mouse-self keyword in tv:scroll-parse-item).
Variable: tv:scroll-item-leader-offset

The number of standardly-defined slots in an item’s array leader. The slot with this number and beyond can be used by applications for their own purposes.

Entries are also arrays. They have a lot of components, all managed internally, and users should probably not access them directly. Peek never needs to do so.

17.6 Mouse Sensitive Scroll Windows

.defflavor tv:essential-scroll-mouse-mixin This mixin gives a scroll window the ability to make either items or entries mouse sensitive. .end_defflavor

.defflavor tv:scroll-mouse-mixin This mixin in addition defines the :execute operation to be the same as on menus. .end_defflavor

tv:scroll-parse-item provides syntax, described above, for associating a mouse sensitivity to any item or entry. The mouse sensitivity is a list whose purpose is to identify which mouse-sensitive area was clicked on, and also specify what to do when that happens.

If the car of the mouse sensitivity is nil, then the mouse sensitivity is interpreted as a menu item. When the sensitive area is clicked on, the menu item is executed by means of the :execute operation–but this is done in the mouse process. Unfortunately, there is no way to avoid this, since mouse clicks on scroll windows are expected to be able to happen "at any time", and no other process has expressed its willingness to handle them with a :choose operation.

If the car of the mouse sensitivity is non-nil, a click is handled by putting a blip into the scroll window’s input buffer. The blip has the form

(blip-type sensitivity window mouse-character)

sensitivity is the mouse sensitivity list. blip-type is the car of that list. window is the scroll window itself, and mouse-character is a character such as #\mouse-l-1 which indicates which button was clicked.

The reason that the blip-type is extracted and put at the front is that programs that use scroll windows may need to handle blips from many sources. By specifying the car of each mouse sensitivity, the program can arrange to distinguish these blips from blips coming from menus, typeout windows, etc. and process each one in the correct fashion.

Often a scroll window displays many similar items that describe different data objects. These items will all have the same patterns of mouse sensitivity. One way for the program to tell which item the user clicked on is to set up the mouse sensitivity using the :mouse-self keyword (for an item) or :mouse-item (for an entry). This inserts the item itself into the sensitivity in place of the symbol self or tv:item, respectively.