The C64 OS Programmer's Guide is being written

This guide is being written and released and few chapters at a time. If a chapter seems to be empty, or if you click a chapter in the table of contents but it loads up Chapter 1, that's mostly likely because the chapter you've clicked doesn't exist yet.

Discussion of development topics are on-going about what to put in this guide. The discusssions are happening in the C64 OS Community Support Discord server, available to licensed C64 OS users.

C64 OS PROGRAMMER'S GUIDE


Chapter 4: Using the KERNAL → Toolkit (module)

A Brief Explanation of Toolkit

The Toolkit is an object-oriented user interface toolkit, loosely inspired by OpenStep, but greatly simplified and adapted for the low memory and CPU speed of the C64. It is designed to be used from 6502 assembly language, which may sound confusing because 6502 assembly is not inherently an object-oriented language.

In this context, "object-oriented" is a design philosophy. It is a description of how the memory is structured and linked together, and it is a system of conceptual constraints that would ordinarily be enforced by a compiler. Rather than being enforced, in 6502 the constraints are strongly encouraged. Using Toolkit as it was intended provides many benefits and allows you to design richer, more flexible user interfaces.

For a more complete discussion of how object-orientation works in 6502, see the blog posts, Object Orientation in 6502 (Take 2) and Toolkit Class Hierarchy.

Definition of a Class

A class consists of two 16-bit data words followed by a jump table defining an ordered set of routines. A class's first word is a pointer to its superclass. TKObj has a superclass pointer of $0000 (i.e., null,) which identifies it as the root class. Thus, a subclass is established by pointing to its superclass, which points to its superclass, and so on, all the way back to the root class, TKObj, from which all other classes inherit.

A class defines a set of routines; in OO terminology, these are its methods. A subclass must have the same set of routines as its parent class, plus it may optionally add more routines. If a subclass does not need to modify the behavior of any given routine, it can point its own jump table entry for that routine to the jump table entry for that same routine in its parent class. If a subclass needs to have custom behavior for a given routine, its jump table entry points to its own implementation. Powerfully and usefully, a subclass may call its superclass's implementation within its own implementation of a routine. It may then modify the result generated by its parent implementation. This saves work and memory by not having to reimplement the complete behavior of the parent to get a result that is only slightly different.

A class's routines always perform their work on the current object of focus. The current object of focus is pointed to by a zeropage pointer called "this". Thus, the object's own class, or any of its superclasses all the way back to the root class, always operate on whatever object is pointed to by "this".

Definition of an Object

The second word in a class definition is the byte size of an object of this class. When an instance of a class is created, the size of the object is read from the class definition and that much space is allocated from heap memory.

An object is a data structure, consisting of an ordered set of properties. At the heart of every object, its first property, is the ISA pointer. (This is pronounced "is a", it should not be pronounced "eyes-ah".) The ISA pointer identifies an object as an instance of whatever class definition it points to. For example, if the ISA pointer of an object points to the class defintion of a TKList, we can say, this object "is a" TKList.

A class also defines the set of properties, their sizes and offsets, that every object of that class must have. An object of a class must have exactly the same properties, in exactly the same order, as an object of its superclass. But just as a class can add more routines than its superclass has, an object of a class can have more properties than an object of its superclass. This congruence of class routines (i.e., methods) and object properties (i.e., instance variables) between a class and its superclass allows any object to substitute in wherever an object of one of its superclasses is expected.

TKList is a subclass of TKView, therefore any object that is a TKList is also a kind of TKView. Similarly, a TKSplit is a subclass of TKView. Now let's look at an example of how this is relevant when building a user interface using Toolkit.

The job of TKSplit is to organize a horizontal or vertical split on the screen, which resizes a TKView on either side of the split. A TKSplit needs one TKView for each of its sides. Since a TKList is a subclass of TKView, a TKList may be appended directly to one of the TKSplit's sides. What's more, a TKSplit also inherits from TKView. This means another instance of TKSplit may be appended to one of the sides of the first TKSplit. See the example diagram and explanation that follows.

A visual example of TKView subclass nesting.

In the example above, the outer view shown in red is a TKSplit configured as a vertical split. This manages two TKViews which it positions side-by-side. On the left side, shown in green, is a TKList. This is permissible because a TKList is a subclass of TKView. On the right side, shown in blue, is another TKSplit. This is permissible because TKSplit is also a subclass of TKView. In this case, the nested TKSplit is configured as a horizontal split, and it can manage two more TKViews positioned one above the other.

Job of the Toolkit KERNAL Module

Most of the behavior of Toolkit is found its classes. The Toolkit KERNAL module provides a set of (non-object-oriented) routines which support the fundamental behaviors of the Toolkit environment. For example, creating new objects, linking runtime loaded classes to their parent class, looking up and calling methods, managing some low-level behaviors of the draw context, and a handful of supporting routines to help build and traverse nested object hierarchies.


Draw Context Management

The following set of five routines are used to manage the draw context. TKView implements a draw method. Subclasses of TKView override the draw method to perform custom drawing behavior. When an object's draw method is called, there is already a draw context configured specifically for the object being drawn. Thus, the only thing the draw routine needs to do is draw itself using the context drawing routines found in the Screen module. (E.g., setlrc, setdprops, ctxclear and ctxdraw.)

If your class is a container view, designed to contain and organize child views, then part of your class's drawing behavior must be to forward the drawing calls to its child views. Before calling a child view's draw method, the draw context must be reconfigured to be appropriate to the position, bounds and scroll offsets required by the child. However, except for in extraordinarily rare situations, it is almost never necessary to do this work manually. Most of the work is implemented by TKView's draw method. Typically, therefore, you only need to call the superclass's draw method first, and then perform any necessary custom drawing.

TKView makes calls to pushctx, pullctx, setctx, recontext and boundschk for you, based upon the anchoring, offset, and scroll properties of each child immediately appended to your view.

pushctx

Purpose Push the current draw context to the stack.
Module offset 0
Communication registers None
Stack requirements 2 bytes (+11 bytes, even after this routine returns.)
Zero page usage $07, $08
Registers affected A, X
Input parameters None
Output parameters None

Description: This routine is used to push the non-volatile draw context to the stack. The non-volatile draw context consists of the first 11 bytes. The draw context is defined by //os/s/:ctxdraw.s. After this routine returns, 11 bytes of stack are occupied more than were occupied before this routine was called.

The stack is manipulated internally to allow this routine to be called and returned from correctly, all while it adds new content to the stack. This routine is called prior to manipulating the draw context to make it suitable for a child view to be drawn.

Although this routine is critical to how Toolkit works, it can also be used when doing sophisticated manual drawing using the draw context.

pullctx

Purpose Pull the draw context from the stack to restore it after a manipulation.
Module offset 3
Communication registers None
Stack requirements 2 bytes (-11 bytes, after this routine returns.)
Zero page usage $07, $08
Registers affected A, X
Input parameters None
Output parameters None

Description: This routine is used to pull the non-volatile draw context from the stack. The non-volatile draw context consists of the first 11 bytes. The draw context is defined by //os/s/:ctxdraw.s. After this routine returns, 11 bytes that were occupied on the stack are no longer occupied.

The stack is manipulated internally to allow this routine to be called and returned from correctly, all while it removes content from the stack. This routine is called after pushctx to restore the draw context to how it was before it underwent some manipulation.

Although this routine is critical to how Toolkit works, it can also be used when doing sophisticated manual drawing using the draw context.

setctx

Purpose Copy an in-memory draw context to workspace memory for use by the context drawing system.
Module offset 6
Communication registers X, Y
Stack requirements 2 bytes
Zero page usage $39, $3a
Registers affected A, X, Y
Input parameters RegPtr → Pointer to a draw context structure to install.
Output parameters None

Description: On the screen at any given time may be multiple regions each managed by a different draw context. Each process, typically each screen layer, must maintain a draw context structure in memory. This can be either in static memory (allocated at the time the code is assembled) or it can be in heap memory (allocated at runtime.) When a screen layer is preparing to draw itself, it must first install its own local draw context structure into the workspace memory version of the draw context that the context drawing routines operate on.

Load a pointer to the draw context to install into a RegPtr, and call setctx. Setctx is called implicitly by settkenv.

recontext

Purpose Modifies the current draw context, shrinking it for the offset and size of this view.
Module offset 9
Communication registers None
Stack requirements 4 bytes
Zero page usage $2b, $2c, $2d, $2e, $2f, $30, $39, $3a, $3b, $3c, $3d, $3e, $61, $62, $63, $64, $65, $66, $67, $68
Registers affected A, X, Y
Input parameters this → Points to the child view of a view for whom the draw context is current configured.
Output parameters None

Description: This routine is used when the draw context is currently configured for a view and that view has nested child views. First the draw context is backed up by calling pushctx, then one of the child views is selected and made the new this by calling ptrthis. Finally, recontext is called, and takes no parameters.

This routine takes the current draw context and modifies it. It contracts and offsets the current context in accordance with how the new this view is set within its parent view.

When the this is restored to the parent view, any manipulations that were performed by recontext can be undone by calling pullctx. The cycle can then be repeated to recontext for the next child view, until all child views have been handled. It is almost never necessary to call recontext manually, since it is called by TKView.

boundschk

Purpose Checks if the current view falls at least partially inside the current draw context's visible region.
Module offset 12
Communication registers None
Stack requirements 4 bytes
Zero page usage $2b, $2c, $61, $62
Registers affected A, Y
Input parameters this → Points to the child view of a view for whom the draw context is current configured.
Output parameters C ← Set if this view is out of bounds.

Description: A container class may be sized or scrolled such that one or more of its nested child views is not visible in any way on the screen. A view, for which no part of it is visible on the screen, is said to be out of bounds. A view that is out of bounds does not need to have the draw context manipulated using recontext; it does not need to have its draw routine called, nor does it need to process mouse events.

When the draw context is currently configured for a container view, just after one of its children is made the new this object, a call to boundschk determines if this view is in bounds. If the carry is returned clear, the view is in bounds. If the carry is returned set, the view is out of bounds. It is almost never necessary to call boundschk manually, since it is called by TKView.



Toolkit Environment Management

The following set of four routines are used to manage the Toolkit environment. At any given moment, there may be multiple Toolkit environments present on the screen at the same time. For example, an Application's main screen layer may have a Toolkit environment with a view hierarchy for its user interface. While at the same time, a Utility panel may be open on another screen layer, which also has a Toolkit environment and a complete view hierarchy of its own. Although both of these view hierarchies use the KERNAL's Toolkit routines, they are separate and managed by different processes.

Similar to how there may be more than one draw context on screen at the same time. Each process maintains its own local draw context, which it must install (with setctx) into workspace memory, before it can be used by the KERNAL's context drawing routines. Each process also maintains its own local toolkit environment, which it must install (with settkenv) into workspace memory, before it can use other Toolkit-related KERNAL calls.

Toolkit is a complex subject and is covered in Chapter 6: Using the Toolkit found later in this guide.

settkenv

Purpose Sets the Toolkit environment prior to instantiating new classes or calling the methods of existing objects.
Module offset 15
Communication registers X, Y
Stack requirements 4 bytes
Zero page usage $39, $3a, $ef, $f0
Registers affected A, X, Y
Input parameters RegPtr → Pointer to Toolkit environment structure
Output parameters this ← Points to Toolkit environment's root view.
class ← Points to the class of this, read from this.isa
tkenvptr ← Points to the local Toolkit environment structure

Description: This routine is used to configure the Toolkit environment from a local Toolkit environment structure. This must be called prior to instantiating new classes, or calling the methods of any object. Load a pointer to the Toolkit environment structure into a RegPtr and call settkenv. There are no parameters returned directly, however the this pointer is pointed to the Toolkit environment's root object. The class pointer is pointed to the class definition of the this object. And the tkenvptr, used for accessing and manipulating properties of the current Toolkit environment, is pointed to the local Toolkit environment structure.

settkenv is called implicitly by each of the following routines: tkupdate, tkmouse, tkkcmd and tkkprnt. Therefore, it is safe (and recommeded) to use each of these routines without first calling settkenv manually.

tkupdate

Purpose Checks for and clears the dirty flag in the toolkit environment, then calls update on the root view.
Module offset 18
Communication registers X, Y
Stack requirements 6 bytes +
Zero page usage $39, $3a, $ef, $f0, Custom
Registers affected A, X, Y
Input parameters RegPtr → Pointer to Toolkit environment structure
Output parameters None

Description: This routine is used to allow a Toolkit's view hierarchy to be updated (i.e., redrawn) during a screen layer's low-level draw cycle. Individual objects in a view hierarchy maintain their own flags indicating whether they need to be redrawn. In order to prevent the need to walk the entire view hierarchy searching for dirty views on each screen layer redraw cycle, the Toolkit environment maintains a dirty flag for the entire view hierarchy.

Typically, the way to use tkupdate is as follows: If a screen layer uses Toolkit, then in the screen layer's draw routine, first load a pointer to the Toolkit environment structure into a RegPtr and call tkupdate. If the Toolkit environment's dirty flag is set, tkupdate calls the update method on the root view. This propagates through the view hierarchy allowing dirty views to redraw themselves. The dirty flag of the environment is cleared. When tkupdate returns, the screen layer's draw routine may then proceed with any required manual drawing.

Zero page and stack usage depend on the depth of the view hierarchy, and which objects need to be drawn.

tkmouse

Purpose Hit tests the view hierarchy, and calls the hit view's responder method that handles the low-level mouse event type.
Module offset 21
Communication registers X, Y
Stack requirements 6 bytes +
Zero page usage $39, $3a, $ef, $f0, Custom
Registers affected A, X, Y
Input parameters RegPtr → Pointer to Toolkit environment structure
Output parameters C ← CLR if the event was handled.
C ← SET if the event was not handled.

Description: This routine is used to hit test the Toolkit's view hierarchy. Hit testing recursively walks the view hierarchy to find the topmost view, which is not transparent to mouse events, which the mouse pointer is above. A pointer to the hit view is saved in the Toolkit environment as the first mouse view. The low-level mouse event's type is used to lookup a corresponding method. That method is then called on the hit view.

Some mouse events, such as ltrack (left mouse button held down, while the mouse is moved around the screen) bypass the hit testing stage, and call the responder method on the first mouse view, which was established by some previous mouse event.

Typically, the way to use tkmouse is as follows: If a screen layer uses Toolkit, then in the screen layer's mouse events routine, first load a pointer to the Toolkit environment structure into a RegPtr and call tkmouse. After tkmouse returns, the low-level mouse event is still queued. If tkmouse returns with the carry clear, the event was handled by the Toolkit and you will typically return immediately. If the carry was set, the Toolkit did not handle the event. The low-level event can be read and used to perform manual mouse event handling for anything on the screen layer that is not part of Toolkit.

Zero page and stack usage depend on the depth of the view hierarchy, and on which object is hit and how its responds to the mouse event.

tkkcmd

Purpose Calls the dokeyeqv method on the Toolkit environment's first responder object.
Module offset 24
Communication registers X, Y
Stack requirements 6 bytes +
Zero page usage $39, $3a, $ef, $f0, Custom
Registers affected A, X, Y
Input parameters RegPtr → Pointer to Toolkit environment structure
Output parameters C ← CLR if the event was handled.
C ← SET if the event was not handled.

Description: This routine is used to allow keyboard command events to be processed by views in the view hiearchy of a Toolkit environment.

Some views are capable of responding to key command events. When such a view is interacted with by the mouse, typically in response to a left click (though not necessarily), that view may take first responder status. When a view takes first responder status, the Toolkit environment is checked to see if some other view is the current first responder. That view has a method called on it to inform it that it is losing first responder status. A pointer to the view taking first responder status is then installed in the Toolkit environment. TKView (and its subclasses) have a property that indicates if it is the current first responder, and this allows a first responder to draw itself differently, to indicate this special status to the user.

Typically, the way to use tkkcmd is as follows: If a screen layer uses Toolkit, then in the screen layer's key command events routine, first load a pointer to the Toolkit environment structure into a RegPtr and call tkkcmd. The Toolkit environment is checked for a first responder object. The dokeyeqv method is called on that object. If there is no first responder, the dokeyeqv method is called on the root view instead. If tkkcmd returns with the carry clear, the event was handled by the Toolkit and you will typically return immediately. If the carry was set, the Toolkit did not handle the event. The low-level event can be read and used to perform manual key command event handling for anything that is not part of Toolkit.

Zero page and stack usage depend on the depth of the view hierarchy, and on which object responds to the key command event.

tkkprnt

Purpose Calls the keypress method on the Toolkit environment's first responder object.
Module offset 27
Communication registers X, Y
Stack requirements 6 bytes +
Zero page usage $39, $3a, $ef, $f0, Custom
Registers affected A, X, Y
Input parameters RegPtr → Pointer to Toolkit environment structure
Output parameters C ← CLR if the event was handled.
C ← SET if the event was not handled.

Description: This routine is used to allow printable keyboard events to be processed by views in the view hiearchy of a Toolkit environment.

Some views are capable of responding to printable key events. When such a view is interacted with by the mouse, typically in response to a left click (though not necessarily), that view may take first responder status. When a view takes first responder status, the Toolkit environment is checked to see if some other view is the current first responder. That view has a method called on it to inform it that it is losing first responder status. A pointer to the view taking first responder status is then installed in the Toolkit environment. TKView (and its subclasses) have a property that indicates if it is the current first responder, and this allows a first responder to draw itself differently, to indicate this special status to the user.

Typically, the way to use tkkprnt is as follows: If a screen layer uses Toolkit, then in the screen layer's printable key events routine, first load a pointer to the Toolkit environment structure into a RegPtr and call tkkprnt. The Toolkit environment is checked for a first responder object. The keypress method is called on that object. If there is no first responder, the keypress method is called on the root view instead. If tkkprnt returns with the carry clear, the event was handled by the Toolkit and you will typically return immediately. If the carry was set, the Toolkit did not handle the event. The low-level event can be read and used to perform manual printable key event handling for anything that is not part of Toolkit.

Zero page and stack usage depend on the depth of the view hierarchy, and on which object responds to the printable key event.



Toolkit Class and Object Support

The following set of eight routines are used to support the existence and fundamental behaviors of object-oriented entitites, such as classes and objects. All of these routines require that the Toolkit environment first be set. This may be set by calling settkenv directly, or indirectly via tkupdate, tkmouse, tkkcmd or tkkprnt.

Part of the process of setting the Toolkit environment establishes the low-level memory pool out of which memory allocations will be made; malloc is used implicitly by calls to tknew, and free is called implicitly when any object's delete method is called. These memory routines need a memory pool, and that is provided by the Toolkit environment.

Class Linking

The following is a diagram of how classes and objects are structured. The arrows show how class definitions are linked to their superclass, and how objects use the ISA pointer to establish the connection to their class.

In the yellow box, which represents memory dynamically allocated from heap, there are arrows indicating a parent/child relationship between the two objects. These properties are how TKViews (and TKView subclasses) are connected together to form the view heirarchy. This is discussed in the section below, Toolkit View Hierarchy Support.

A diagram of loaded classes linking to their superclass.
A diagram of loaded classes linking to their superclass.

classlnk

Purpose Links a dynamically loaded class to its superclass.
Module offset 30
Communication registers X, Y
Stack requirements 2 bytes
Zero page usage $2b, $2c, $2d, $2e
Registers affected A, Y
Input parameters RegPtr → Pointer to unlinked class.
class → Pointer to superclass.
Output parameters class ← Linked class.

Description: This routine is used to link a dynamically loaded class to its superclass.

Some classes are built in; they are loaded by the booter to fixed addresses in memory and they stay in memory all the time. Other classes are available in //os/tk/ to be loaded at runtime. Any class that gets loaded at runtime has to be linked to its superclass. The superclass of a class you're loading in might be one of the build-in classes, or it might be another runtime loaded class. For example, the inheritance chain for TKTable looks like this:

TKObjTKView ← TKList ← TKTable

The first two, shown in italics, are built in. The latter two have to be loaded and linked at runtime. In order to have access to TKTable, you would first load the TKList class and link it to the built-in TKView. Now that TKList is available, you can load in the TKTable class and link it to TKList. Note that linking TKList to TKView, and linking TKTable to TKList is not arbitrary. TKView is the required superclass of TKList. And TKList is the required superclass of TKTable. If you attempted to load TKTable and link it directly to TKView, your code would not run.

To link a class, first load and relocate a class from disk using loadreloc. The address of the loaded class is the start of the first page of relocation. If loadreloc returns $72, then the loaded class's address is $7200. This address should be saved to a variable or a pointer store. Next, this class needs to be linked to its superclass. Copy the address of the superclass into the class pointer. (The class pointer is a zeropage pointer defined by //os/s/:toolkit.s) Then load the address of the class being linked into a RegPtr, and call classlnk. The class is linked to its superclass, and the class pointer (in zeropage) is updated to point to the linked class.

The following sample code shows how to load and link TKList and TKTable.

classptr

Purpose Fetch a pointer to a built-in class by ID
Module offset 33
Communication registers X, Y
Stack requirements 2 bytes
Zero page usage None
Registers affected A, X, Y
Input parameters X → Class ID
Output parameters RegPtr ← Pointer to class of Class ID.
This routine raises exceptions.

Description: This routine is used to get the address of a built-in class. In different versions of C64 OS, the addresses of the built-in classes will move about. Therefore you must not assume the address of a built-in class.

Load the class ID into the X register, and call classptr. The address of that class is returned as a RegPtr. If the class ID is not valid, an exception is raised. The class IDs of all built-in classes are defined by //os/tk/h/:classes.h. Below is the list of built-in classes in C64 OS v1.0.

Class ID Super Class Class
0 root class TKObj
1 TKObj TKView
2 TKView TKSplit
3 TKView TKTabs
4 TKView TKScroll
5 TKView TKLabel
6 TKView TKCtrl
7 TKCtrl TKButton
8 TKCtrl TKSBar

tknew

Purpose Create a new instance of a Toolkit class.
Module offset 36
Communication registers X, Y
Stack requirements 8 bytes
Zero page usage $2b, $2c, $2d, $2e
Registers affected A, X, Y
Input parameters RegPtr → Pointer to a class to instantiate.
Output parameters RegPtr ← Pointer to new instance of class.
this ← Pointer to new instance of class.
class ← Pointer to class of new instance.
This routine raises exceptions.

Description: This routine is used to create a new object. Load a pointer to the class to instantiate into a RegPtr, and call tknew. The Toolkit environment must already be set. Malloc is called internally to allocate memory of the correct size for an object of this class. If malloc fails to allocate the memory an exception is raised.

The allocated memory has its ISA pointer set to point to its class. Once the ISA pointer is set on a block of memory of the correct size, that block of memory is an uninitialized instance of that class. The init method must be called on the new object in order to initialize its properties to valid default values.

The this and class pointers are set, such that the new object is the this object, ready to have its methods called and its properties set. The following is an example of how to create a new TKView and assign it as the rootview of its Toolkit environment.



getprop16

Purpose Get a 16-bit property from this object.
Module offset 39
Communication registers X, Y
Stack requirements 2 bytes
Zero page usage $2b, $2c, $2d, $2e
Registers affected A, X, Y
Input parameters this → The current object to operate on.
Y → Object property offset.
Output parameters RegWrd ← 16-bit value read from this->property.

Description: This is a simple convenience routine that saves memory by implementing an extremely common pattern. When an object is already configured as the this object, this routine can be used to read a 16-bit property from that object.

Load the property offset into the Y register. Call getprop16, the 16-bit property is returned as a RegWrd. Although it seems simple, calling this routine takes just 3 bytes, but implementing it inline takes 7 bytes. If it has to be called 25 times, using this routine shortens your code and saves half a page of memory.

ptrthis

Purpose Change the this context to a new object.
Module offset 42
Communication registers X, Y
Stack requirements 2 bytes
Zero page usage $2b, $2c, $2d, $2e
Registers affected A, X, Y
Input parameters RegPtr → Pointer to an object to make this.
Output parameters this ← Points to the new object.
class ← Points to the class of this.
This routine raises exceptions.

Description: This routine is used to change the this context to a new object. Setting the this context is the first stage of calling an object's methods.

Load a pointer to an object into a RegPtr, and call ptrthis. If the pointer is null, an exception is raised. Otherwise, the this and class zeropage pointers are configured to make the object the this context. The following is a simple example of how to call an object's method by first setting it as the this object.



setclass

Purpose Change the class pointer to this's class.
Module offset 45
Communication registers None
Stack requirements 2 bytes
Zero page usage $2b, $2c, $2d, $2e
Registers affected A, Y
Input parameters this → Pointer to the current object.
Output parameters class ← Points to the class of this.

Description: When an object is first established as the this object, using ptrthis, the class pointer points to the object's class. The isa pointer is read from the object and stored in the class pointer. As the object is used, sometimes the class pointer gets changed to point to one of its superclasses, such as by calling setsuper. Setclass restores the class pointer to the this object's own class.

This routine takes no parameters and returns no parameters. Simply call setclass and the class pointer is restored to the class of this. This routine is typically only needed in the implementation of a Toolkit class; it is not generally needed to use existing classes.

setsuper

Purpose Change the class pointer to the class's superclass.
Module offset 48
Communication registers None
Stack requirements 2 bytes
Zero page usage $2d, $2e
Registers affected A, Y
Input parameters class → Pointer to a class.
Output parameters class ← Pointer to the superclass of the initial class.

Description: When an object is first established as the this object, using ptrthis, the class pointer points to the object's class. The isa pointer is read from the object and stored in the class pointer. As the object is used, it is sometimes necessary to treat the object as an instance of one of its superclasses. To do so, call setsuper. This routine takes no input parameters and returns no parameters. It updates the class pointer to the superclass of the current class pointer.

It is almost never necessary to call this routine when using existing classes. This routine is used in the implementation of a Toolkit class, for calling superclass methods on the existing object. It is possible to call setsuper more than once, on each call the class pointer moves up the class inheritance chain by one superclass. To restore the class pointer to the proper class of the this object, call setclass.

getmethod

Purpose Prepare to call a class's method on this object.
Module offset 51
Communication registers Y
Stack requirements 2 bytes
Zero page usage $2d, $2e
Registers affected A, Y
Input parameters Y → method offset into class.
class → Pointer to a class.
Output parameters jmpvec ← Pointer to method of class.
RegPtr ← Pointer to method of class.

Description: This routine is used to prepare a method to be called. Load the method offset into the Y register, and call getmethod. Method offsets are defined by the header files of Toolkit classes found at //os/tk/h/. The address of the method routine is looked up for the class pointed to by the class pointer, and stored in jmpvec. A pointer to the routine is also returned in a RegPtr.

The reason for separating getting a method into jmpvec and calling that method is to be able to use all of the registers, including the status register, as input parameters to the method. To call an object's method, first establish that object as the this object using ptrthis. That sets the class pointer to the object's own class. Load the method offset into Y, call getmethod. Then load the parameters to pass to the method into the registers. Finally, call sysjmp to jump through the method vector.

Note that getmethod does not look up the method for the current object, per se. It looks up the method on the current class pointer. Thus, if the class pointer has been changed with setsuper then the method that is looked up is that superclass's method.





Toolkit View Hierarchy Support

The following set of six routines are used to help manage the view hierarchy. A user interface doesn't consist only of loading and linking classes, instantiating, initializing and setting the properties of objects. To build a user interface, those objects need to be connected together.

Virtually all Toolkit objects descend from TKView, and TKViews allow other TKViews to be nested within them. The process of nesting one view within another is called appending. Once several TKViews are appended together, the result is a view hierarchy, sometimes called a node tree.

The view hierarchy always consists of a single root view, which is pointed to by the Toolkit environment, at property te_rview. (The Toolkit environment properties are defined by //os/s/:toolkit.s.) Toolkit objects are able to form a node tree, because TKView defines node-linking properties: parent, child, and sibling.

instanceof

Purpose Check if a class descends from a given class.
Module offset 54
Communication registers X, Y
Stack requirements 7 bytes
Zero page usage $2d, $2e
Registers affected A, X, Y
Input parameters this → Pointer to an object.
RegPtr → Pointer to a class.
Output parameters C ← CLR if this is an instance of class.
C ← SET if this is not an instance of class.

Description: This routine is used to check if an object is an instance of a class. With this already configured for an object to check, load a pointer to a class into a RegPtr, and call instanceof. If the carry is returned clear, this is an object of that class. If the carry is set, this is not an instance of that class.

This is typically used by class implementations when their objects are allowed to embed other objects of a specific class. For example, a TKScroll manages a content view plus two TKSBars. In theory the TKSBars could be custom subclasses of TKSBar. If TKScroll wanted to confirm that its scroll bar is a kind of TKSBar, it could use instanceof to make that check. This is only needed when a class is able to have a custom object passed in as one of its children. It can first check that it's an instanceof the required class, and if it isn't, it can raise an exception.

walk

Purpose Recursively walk the node tree, calling a callback for each node.
Module offset 57
Communication registers A
Stack requirements 4 bytes +
Zero page usage $2b, $2c, $2d, $2e, Custom
Registers affected A, X, Y
Input parameters A → $4c (JMP instruction) = walk full tree.
A → $2c (BIT instruction) = walk visible tree. (Skip invisible nodes.)
this → Pointer to an object.
jmpvec → Pointer to a callback routine.
Output parameters C ← CLR when recursive walk is complete.

Description: This routine is used to recursively walk a section of the view hierarchy (a section of the node tree), and call a provided callback routine on each node. There are two modes, specified by a value passed in the accumulator. $4c (JMP instruction) puts walk into a mode that will have it walk all nodes. $2c (BIT instruction) puts walk in a mode where it skips nodes that are invisible, and thus skips all of their children too.

There are times when it is useful to recursively traverse part or all of the node tree in order to find a specific node, or to be able to perform some work on every instance or kind of node. For example, the routine viewwtag searches the node tree for an object with a given tag, and returns a pointer to that object.

To search the entire view hierarchy set the root view as this. To walk only part of the view hiearchy set a different object as this. Point jmpvec (defined by //os/s/:service.s) to a custom callback routine. Decide whether you want to walk all child nodes of this ($4c, a JMP instruction), or only its visible children ($2c, a BIT instruction), and set the approprate mode into the accumulator. Then call walk.

The amount of stack required by this routine depends on the depth of the view hierarchy. The zero page usage depends on the behavior of the custom callback.

The visibility of a node is determined by the df_visib bit in the display flags (dflags) property of TKView and its subclasses. Only TKViews can form part of the view hierarchy, because they also provide the parent, child and sibling node properties.

On each node, the routine pointed to by jmpvec is called. The current node being processed is configured as the this object. The callback routine can do anything it wants to the this node, including checking what kind of class it is, read or change its properties, etc. The callback can also control the continuation of the walk. If it finds a node it's looking for, it can return with the carry clear. This causes all recursion to unroll and the walk to end. The node that was found remains the this object. If the callback returns with the carry set, the walk continues. If the callback never returns with the carry clear, the walk continues until the recursion through the tree naturally concludes.

isdescof

Purpose Searches up the view hierarcy to see if this is a nested child of a view.
Module offset 60
Communication registers X, Y
Stack requirements 2 bytes
Zero page usage $2b, $2c, $2f, $30, $fd, $fe
Registers affected A, Y
Input parameters this → Object to check for descendency.
RegPtr → Pointer to potential ancestor object.
Output parameters A ← 1 means descends
A ← 0 means does not descend.
Z ← CLR if this descends from RegPtr object.
Z ← SET if this does not descend from RegPtr object.

Description: This routine is used to check if the this object descends, i.e., is a child node, of a reference node passed in the RegPtr. This routine must not be confused with instanceof. Instanceof checks to see if an object's class is a subclass of some other class. isdescof relates to the view hierarchy, the node tree. TKViews and their subclasses are appended to each other creating a tree, where parent nodes point at their children, which point at their children, etc. And child nodes point at their parent node, which point at their parent node, etc., all the way back to the root node. Isdescof checks to see if some parent, or parent of parent, etc., of this is the reference node.

This can be useful in a number of ways. For example, if the user clicked a button, you can use isdescof to check if that button is nested within some other known view. If it is, you can take some action. This allows different controls to share the same action routine, but still have subtly different behavior. This is just an example. How isdescof gets used is up to the creative imagination of the programmer. It just adds some easy-to-use flexibility for analyzing the view hierarchy.

opaqancs

Purpose Returns a pointer to the nearest opaque ancestor of the this view.
Module offset 63
Communication registers X, Y
Stack requirements 4 bytes
Zero page usage $2b, $2c
Registers affected A, X, Y
Input parameters this → Object to start at.
Output parameters RegPtr ← Pointer to first opaque ancestor.
RegPtr ← null if there is no opaque ancestor.

Description: This routine is used to find the first opaque ancestor in the view hierarchy of the this object. The opaqueness of a view is determined by the df_opaqu bit in the display flags (dflags) property of TKView and its subclasses. An opaque view clears its visible area when it gets redrawn. By default, TKView is not opaque. This is because, if multiple TKView's are nested within each other, it is inefficient to have the most distant ancestor clear its draw context, only to have its children completely cover its view area, and to have them clearn their draw contexts, only to have their children completely cover them, etc.

The opaqueness of certain key views can be turned on when necessary. The effect of a view being transparent can be the ghosting of one of its child views that's moved within it. This routine is used in special circumstances when a view has its anchor or offset properties changed, and it gets marked dirty, requring a redraw. To prevent ghosting, call opaqancs to find the nearest opaque ancestor. That view should also be marked dirty. It is possible to prevent the ghosting problem by marking the rootview dirty, but it is more efficient to only require a redraw starting from the nearest opaque ancestor.

viewwtag

Purpose Returns a pointer the first view found with a match tag.
Module offset 66
Communication registers A, X, Y
Stack requirements 4 bytes +
Zero page usage $03, $04, $2b, $2c, $2d, $2e
Registers affected A, X, Y
Input parameters A → Tag to search for.
this → Object to start at.
Output parameters RegPtr ← Pointer to first view with matching tag.

Description: This routine is used to find the first view with a tag matching the one passed in via the accumulator. TKView has a tag property. A tag is a 1-byte arbitrary identifier. It has no pre-defined purpose. It can be used to mean anything. For example, you could tag every node of a certain type with the same value. Then when using the walk routine, you could check the tag of every node, and if it matches that special tag, you could do something with those nodes.

The routine viewwtag (View With Tag) is a convenience routine, it uses walk with a special tagchk callback to compare the tag of each node to the one passed in, and returns the first match, ending the walk. Tags are not required to be unique, but viewwtag can only find the first match. Because viewwtag uses walk, it can search only part of the node tree. This allows the same tag to be reused for different parts of the node tree. An example of this might be to tag a common control that is used within different tabs of a TKTabs container view. Starting with the content view of the tab currently in focus will find the one control with that tag that is within that tab.

appendto

Purpose Append a view as a child of another view.
Module offset 69
Communication registers X, Y
Stack requirements 6 bytes
Zero page usage $03, $04, $2b, $2c
Registers affected A, X, Y
Input parameters this → View to be appended.
RegPtr → Pointer to a view to which this should be appended.
Output parameters this ← The view that was appended.

Description: This routine is a convenience that can save memory and make the task of building the view hierarchy more streamlined. In addition to the node properties, parent, child, and sibling, TKView also provides two node methods: addchild and unparent. In order to append a view as the child of another view, it is necessary to call the addchild method on the parent. The parent manipulates its own child property, if this is its first child, it sets itself as the parent property of the child, and it manipulates the sibling chain of the child if this is not the parent's first child.

Typically, when building a user interface, you start with the root view. Next, you'll instantiate another view and then append it to its parent. The problem is that in order to append the current view to its parent, it is necessary to change the this context to the parent view, prepare to call addchild, and then pass in a pointer to the new child. Immediately thereafter it is then necessary to change the this context back to the child if you want to continue to modify its properties or call its methods. All this juggling is inconvenient and bloats up the user interface construction code. The appendto routine streamlines this process. It starts with this as the child to append, and takes the parent in a RegPtr. When appendto returns, this is still the child object.

The following is an example of appending a button to a root view, with a fair amount of surrounding context.