My Own System (MOS) Application Program Interface (API)

2014 January 30

This document specifies the current API for My own OS, which is the same as the framework glue I use for cross-platform support of MacOS and Win32. I considered Qt for a while, but this is smaller, faster, and easier to maintain, which also makes it more portable -- for example, it also runs on the (classic) MacOS -- and (unlike Qt) it is not subject to the whim of whoever currently owns it. Actually it is, but I have only myself to blame for it. My TAG compiler is also built over this framework, so it fully compiles itself.

MOS and application programs built over it are written in Turkish Demitasse (T2, "A stronger brew than Java"), but it is similar enough to C++ and Java that the APIs here can mostly be read as if written in one of those more popular languages.

Separately compiled T2 programs are imported (linked) as "packages". This library API is segmented by package. When you

import SomePackage;
all the (non-private) declared functions and data structures of SomePackage become available to your program. The package System has a great many functions, so they are grouped by function here. Other packages are generally imported only if you need those functions.

Dangerous -- For systems programming only, avoid importing this!
System -- Most programs need to import this
Utilities -- Some non-essential, but sometimes useful functions
UnDragPkg -- Undo, drag, copy/paste and scripting support
ImDlogPkg -- Widget-based window support, such as dialogs
BigTxtPkg -- Large text files
Sortex -- Text sorting
TextAry -- Some utilities for faster text manipulation
Processes -- Multiple processes

System Events -- This is a listing of the predefined events


This package contains unsafe operations needed for systems programming. The T2 compiler knows about this package and also enables certain unsafe operators only if you have previously imported Dangerous. You should avoid using this package if at all possible. Package K68codeOps has numerous additional functions and data types and constants, which can only be used if Dangerous is also imported (first).

X1NT, X1NT X0NT(int x)
Sometimes it is necessary to export an unsafe function from a package, so it can be used in other packages where the programmer knows its dangers. Normally this requires importing Dangerous. To enforce that requirement, you can substitute opaque type X1NT for an integer parameter. Nobody can do anything with this type except convert it to or from integer, and then only with function X0NT() imported from Dangerous and built-in function ORD().

Unary &
The prefix & operator gets the machine address of a variable or function. You have no business doing that unless you want to poke around dangerously in memory.

int PEEK(int addr), void POKE(int addr, int valu)
Given a memory address addr, you can PEEK at the data there or POKE a different valu into it. This obviously can corrupt all sorts of things. Don't do it unless you know what you are doing. Requiring these functions to be imported from Dangerous prevents their wanton use without giving notice that the code is (ahem) Dangerous.

void C0DE(int val, ...); someType C0DE(:someType, int val, ...)
It's too hard to make a compiler generate all possible useful machine code sequences. This is a way to get arbitrary code in sysmem environments without involving a separate assembly language. It also permits type-casting to arbitrary types, using the second form. Any parameter values given are pushed onto the virtual (compiler) stack, and then zero or more special integer codes preceded by a prefix # specify (68K now, eventually x86) machine instructions and/or compiler pseudo-ops. The details of these operations are presently only embedded in the Turk68 compiler. Sorry about that. When T2 is compiled to C++, a final quoted string parameter to this function gives the C++ text value to be generated for it; the string parameter is ignored for 68K.

void RAW_CODE(String txt)
When T2 is compiled to C++, this function transfers the literal txt to the output code file, as a way to accomplish essentially the same thing that C0DE does when generating machine code; the Turk68 compiler ignores it.

Note also T2 predefined constant _Is_In_C_ is true when compiling to C++ and false otherwise; if/else statements that depend on compile-time constants like _Is_In_C_, when the constant is false, the C code is not generated at all. Turk68 similarly removes code controlled by false constants. In all cases the code must still be valid T2, so this is not as flexible as C macros.

RAW__C0DE "some C++ text"
This is not a statement or function, but more like a preprocessor macro for placing C++ code in the output text file created by T2C. If the text is the empty string, then the immediately following RAW__C0DE specifies the name of a C++ namespace directive (or if again empty, the end of the previous one). I use this mostly for inserting the beginning or end of a block comment, which removes the enclosed generated C++ code, so that the same source code files can be used for both 68K and C++ outputs. There are other hacks for controlling the C++ output, which I hope to document soon, so that you can get decent C++ code files with nothing more than hacked-up T2 code.


This package contains most of what you need to do ordinary programs. I have grouped its functions into these categories:


boolean: true, false
float, double
Object, Throwable, this, super
null, void
int ORD(scalar x)
char CHR(int x)
Char4 CHR4(int x)
int ABS(int x), int MAX(int x), int MIN(int x)
int PackText(char a, char b, char c, char d), char TextChar(int x, int i)
_XCH_(someType x, someType y)
int LENGTH(someType x)
subClass DownCast(superType x,subClass)

Type Changing Functions
Data & Types
Array Functions
String Functions
File I/O Functions
Debugging Functions
Widget & Window Functions (including text and fonts)
Menu Management
Event Functions & User Interaction
Miscellaneous Functions


These data types and values and functions are built into the T2 language and do not need to be imported from any package:

This is an ordinary 32-bit integer, like C++ or Java. We do not (yet) contemplate 64-bit integers, because they tend to be slower than 32-bit integers except for very large programs that need access to more than 2 billion bytes of data. Such programs are still very rare.

boolean: true, false
This is its own data type, not compatible with numbers. If you want to convert a boolean to integer, use the ORD() function; if you want to convert integer to boolean, use the ODD() function, or else compare it to 0. All scalar data is 32 bits. Memory is big enough to not justify wasting time and effort packing small data into small spaces -- unless the programmer wants to do that.

For now, T2 only supports 7-bit ASCII characters, but the other 128 8-bit characters can be used within comments and as data. This is its own data type, not compatible with numbers. If you want to convert a character to integer, use the ORD() function; if you want to convert an 8-bit integer to character, use the CHR() function.

This is a new data type in T2, inspired by the 4-byte character codes of the MacOS. This is a scalar value that fits in a 32-bit word (and uses integer hardware operators), but is not compatible with numbers. If you want to convert a Char4 to integer, use the ORD() function; if you want to convert an integer to Char4, use the CHR4() function. There is automatic coercion between Char4 and String (both ways). Compares are bitwise and case sensitive.

float, double
These types are not yet implemented.

This is a native data type designed to be easy and safe to use. The T2 compiler automatically coerces all of the above data types to string when needed in expressions, and there are functions for parsing out each of the above types from string data. Unlike C, string compares are not case sensitive. There are also numerous handy functions for working with string chunks.

String references are stored in  a single 32-bit word. Although there is no obvious distinction in the source code and they are completely compatible with each other, there are four underlying representations. Null strings are stored as zero. Most strings are dynamically allocated with a reference count, and the pointer to the opaque allocated block is stored. Literal (quoted) strings are encoded by the compiler as Mac-ish "P-strings" preceded by a 1-byte length code, and the (odd) pointer to the string is stored. However, small numbers are cheaper to work  with in the original MOS and IBSM, so the compiler encodes the string literals as small positive integer indexes into a string table. All system routines that operate on string values know to convert the small integer index into a P-string, and know about the differences between P-strings and dynamic strings. In Win32, everything except empty strings is converted to dynamic strings. That's probably why C is so much slower than (emulated) 68K code on a computer with a clock 5x slower.

Object, Throwable, this, super
T2 classes may be declared as subclasses of Object or Throwable or some other class, or they may be declared without reference to a superclass, in which case they are not subclasses of any other class (except in T2C, where everything is a subclass of Object). Exceptions can only throw and catch objects of type Throwable. Within all class methods, this always points to the current object, and within all methods of a subclass, super points to its superclass for purposes of accessing the overridden methods. I don't think you can reach more than one level back. Objects can be allocated dynamically by new, or statically declared using final. References to final objects are fully compatible with dynamic objects, except of course you cannot re-assign them.

You can make arrays of any data type above, and (unlike C++ or Java) the array type is an honest data type, not compatible with any other data type. Arrays can be dynamically allocated (like objects) by new someType[size] or statically where they are declared. References to arrays are pointers to the first element, as in C++ and Java, but (like Java and unlike C++) you cannot do arithmetic on array pointers. Each dynamic object and array is stored with its own reference count and size, and the language both automatically deletes the storage when it goes out of scope, and prevents you from exceeding its bounds.

All dynamic object and array variables are initially set to null where they are declared (unless initialized otherwise), and can be re-assigned to null or other (compatible) references from time to time. References can be compared to other (compatible) references or to null. Arrays are compatible if their element type is the same. Objects are compatible if they are the same class or one is a subclass of the other, except that you cannot directly assign to a variable declared as the subclass of the type of reference being assigned to it; you must use DownCast() to coerce it (see below).

Although you cannot have variables of type void, it is a true data type and not compatible with any other data type. In particular, statements are type void, and you cannot return a non-void function value to a void statement, nor (obviously) a void function result to an expression that wants some other type.

int ORD(scalar x)
This function casts boolean, char, Char4 or X1NT data to integer. No code is generated, it just lets the compiler know that you wanted a different type.

char CHR(int x)
This casts the low byte of x into a character. No error is possible, the excess bits are simply discarded.

Char4 CHR4(int x)
This casts an integer into Char4. No code is generated, it just lets the compiler know that you wanted the other type.

int ABS(int x), int MAX(int x, int y), int MIN(int x, int y)
This returns the obvious result.

int PackText(char a, char b, char c, char d), char TextChar(int x, int i)

This is a hardware-neutral way to pack and unpack text bytes. PackText builds an integer from the four characters in such a way that a is first and d is last in the normal byte ordering for the underlying hardware. TextChar conversely extracts the ith byte from the integer x, where i is an index from 0 to 3 in the normal byte ordering of the underlying hardware. Only the low two bits from i are used, any other bits are disregarded.

_XCH_(someType x, someType y)

All dynamic T2 objects and arrays are reference-counted, which takes a certain amount of time. If x and y are two (32-bit) variables of the same type, it is always safe to exchange them without concern for their current reference counts; this function does that very efficiently.

int LENGTH(someType x)
Objects, arrays, and strings have a length; this function returns it.

subClass DownCast(superType x,subClass)

It is not always safe to down-cast a value declared to be a supertype into a variable declared to be its subtype. If the source value is not the same or a subclass of the destination type, DownCast returns null, which you can test. The approved way to do this in Java seems to require introspection, or an exception if it can't be done. I think this is faster and simpler.

Data & Types

final boolean _Mac_OS_ = true, _Wintel_ = false, _Is_In_C_ = false;

The first of these is true if compiling to the MacOS, the second is true if compiling to Windows, and the third is true if the compiler is generating C++.

type IntArray = int[];
type ArrayArray = IntArray[];
type StringArray = String[];

These are convenience names for common array types.

type Process;   type File;   type Window;   type MenuBar;
type Point;   final Point Nowhere = &0x80008000;

These are opaque types, that is, application programs are not allowed to look inside. Point is actually two signed 16-bit integers packed into 32 bits and representing a position on a visible screen or window, with casts to convert Point to and from integer, and a predefined constant representing no valid position.

final int HiBit = 0x80000000;

This is a convenient name for an integer constant I use a lot to represent something like an invalid value.

final Char4 Null4 = CHR4(0), NoName = "    ";

These are convenient names for common Char4 constants.

final char TabKey = CHR(9), BSkey = CHR(8), DelKey = CHR(127);
final char EnterKey = CHR(10), EscKey = CHR(27), LfArKy = CHR(28);
final char RtArKy = CHR(29), UpArKy = CHR(30), DnArKy = CHR(31);

These are convenient names for some control characters.

final int CapsLockKey = 0x10000, ShiftKey = 0x20000, ControlKey = 0x40000;

These bits are defined for the "KeyC" event, and represent the state of the specified modifier key when the key was pressed. You can get the current state of these keys from the function GetButtons, but you should not depend on GetButtons to accurately represent the modifier state at the event time, because other things may be happening and delaying the delivery of events. Unlike Windows, there are no events to notify the client program of key release or when the modifier keys are actually pressed. This is not a gaming API.


A Widget is some kind of visible display element on a window, often using the native display elements of the host operating system if that makes sense. The Widget class is methodless (for historical reasons), but its fields are generally public. Mostly you do not need to look at its internals, except maybe the scrollbar. The window itself is also a Widget, but is hidden and its fields not directly accessible. I have a few complex Widgets used for dialog and text windows, but there is code to support them, so their internal structure is explained in connection with those access functions in those packages.

class RectAngle {int rTop, rLeft, rBotm, rRite;}~RectAngle
class Widgit extends RectAngle {
  Char4 wKind, wName;  int wFlags; // see: 'wFlags bit masks'
  Widgit wLink, wParent, wChild; // wLink is siblings, in Z-order
  Window wWindo;  int FoBgColo;  String theText;
  int MoInfo;  X1NT HostInfo;}~Widgit

The RectAngle is the (methodless) base class for a Widgit. AWidgit has not only a RectAngle (frame), but also what wKind of Widgit it is, a unique (for the window) wName, and some binary wFlags detailing various aspects of this Widgit. These are the bit values for the flags:

final int wiVisble = 1; // =0 if hidden
final int wiHilit = 2; // visibly active
final int wiHasFocus = 4; // takes keystrokes
final int wiActive = 8; // takes/reports clicks
final int wiClikIn = 64; // chkbx checked; cursor blink in text
final int wiRollOver = 128; // reports rollovers
final int wiClicker = 256; // reports clicks to owner
final int wi2clicks = 512; // reports 2-clicks
final int wiDragble = 1024; // dragable
final int wiDropble = 2048; // takes drops
final int wiOwnDraw = 0x1000; // owner draws it
final int wiDefalt = 0x2000; // default [btn] auto-close [win]
final int wiTxRight = 0x4000; // right-just text
final int wiTxLeft = 0x8000; // left-just text
final int wiTxCenter = 0xC000; // centered text; =0 if not Texty
final int wiAutoSizeM = 0xF0000; // auto-size bits: TBLR
final int wiAutoFill = 0x50000; // all 4 edges track their edge
  // 00 no mods (stay with TL), 01 fill win, 11 stay with BR
final int wiUserBits = 0x3F00000; // specific to the wigit
  final int wiUsrBitShft = 20; // for positioning wiUserBits
final int wiEscape = 0x8000000; // default [btn] response to esc key
The same bits apply to windows (with function access). Widgits can be linked into a tree structure, with children, siblings, and a parent. Every Widgit also points to its parent window (wParent in  the root Widgit is null). Some Widgits have meaningful text, and most of them have meaningful foreground and background colors. Widgit color is defined using six levels of brightness in each of three primary colors (sometimes called HTML colors), for a total of 216 distinct colors, which fits in one byte. Some of these colors have been given predefined names, and you can do arithmetic to form others:
final int minRed = 1, minGreen = 6, minBlue = 36, minGray = 43;
final int MaxColor = 5, black = 0, white = 215, winGray = minGray*4;
final int purple = minBlue*3+minRed*3, green = minGreen*3;
final int dkRed = minRed*4+minBlue, red = minRed*5;
final int ltBlue = minRed*4+minGreen*4+minBlue*5, blue = minBlue*5;
final int brown = minGreen+minRed*3, amber = red+minGreen*4+minBlue;
An example of a specific Widgit is the scrollbar:
class Dial extends Widgit { // pcnt = # scroll units in visible page
  int value, base, limit, pcnt;  X1NT prior, thumb;}~Dial
The wKind of a scrollbar can be either "ScrV" or "ScrH". Additional fields tell the display (and application program) what the current value is, its minimum and maximum values, and what percent of the range is "visible" -- if that makes sense; for example, if the scrollbar represents lines of text on a window, the base would be 0 (top line showing), the limit would be the maximum number of lines that can be scrolled off the top, and pcnt would be how many lines are visible at any one time. The two opaque fields are used in drawing. Another example is the text label:
class Texty extends Widgit {int TxFace;}~Texty
where wKind is "Labl".  Other common Widgits include "Butn" button and "ChBx" checkbox, with their obvious functions. However, these Widgits are being deprecated for the somewhat more robust dialog window.

Type Changing Functions

The functions in this group do safe conversions between different types.

void IgnoreInt(int xx);
void IgnoreBool(boolean xx);
void IgnoreStr(String str);
void IgnoreWin(Window theWin);
void IgnorePtr(IntArray thePtr);

These functions convert their respective parameter types to void by discarding the value (typically a function result). Note that C and Java do this implicitly.

boolean Is4chars(int valu);
int ValidAsC4(int num);

The Char4 type does not forbid any combination of control characters and what-not, but the type is more useful if restricted to printable ASCII characters. The function Is4chars() tests each of the four characters and returns true if they all conform. The  function ValidAsC4 corrects (to space) any non-ASCII, non-printing characters. Both functions are compiled inline (for the Mac) and do all four characters in parallel for speed.

int Point2int(Point pt);
Point Point4int(int pt);

These are codeless typecasts.

Point MakePoint(int V, int H);
int PointV(Point pt);
int PointH(Point pt);

These functions construct a Point from two short integers, or else extract one or the other (signed) values from a Point. See also Pack2ints()/HiWord()/LoWord()/LoHalf() in Miscellaneous Functions below for the same operations on ints.

String Num2Str(int theValu);

T2 does not let you directly compare a number to a string (do you want the number converted to string? Or the string converted to a number?) so you can use this function to explicitly convert an integer to a string for such compares. Use ParseInt (below) to convert the other way.

String IffyStr(boolean whom, String tru, String fls);
String FormDateTime(int timDt, int pts);

Given the number of seconds since midnight January 1, 2000, FormDateTime can format it as a short (3-letter month) date, the time, or both, depending on whether pts is -1, +1, or zero. There are also several functions (see Debugging Functions) for preparing debugging text where the converted data is often surrounded by text separators and/or labels, which can also be used in isolation. They generate smaller code because the type conversion is in the function instead of inline everywhere.

char CharAt(int offs, String str);
boolean ParseBool(String str);
int ParseInt(String str);
Char4 Chr4Str(String str);
float ParseFloat(String str);

These functions convert strings to the specified type. The objective is to allow as much format lattitude as possible, so most of these conversions keep going as long as the data makes sense, then return what it found. If the source string is shorter than 4 characters, Chr4Str fills out with spaces. If there are no digits at the front of the source string, ParseInt returns zero. ParseBool accepts '1' and 'T' and 't' as true, and everything else (including the empty string) as false. CharAt checks for offs within the string length, and returns the null character if not.

Dial Dial4wgt(Widgit wgt);
Texty Texty4wgt(Widgit wgt);

Because DownCast() does not work on methodless classes, these functions give a type-safe way to access the extra fields in these subclasses.

boolean ODD(int valu);
This efficiently tests the low bit.

Array Functions

void ZeroArray(IntArray theData);
Arrays are normally allocated pre-zeroed, but sometimes it's useful to do it again. This function is faster than a for-loop.

IntArray ResizeAry(IntArray theAry, int sz);

Unlike the MacOS, where "handle" data can be moved and resized without altering the "master pointer," C and Java and T2 all allocate a fixed position in memory. Maybe this can be grown, but probably not, so this function recopies the data shared by the old and new sizes, and zeroes any added elements. If the array shrinks (but not too much) the array bounds may be reset on the same allocation. See also PartOfArray in Utilities.

String Functions

In all cases where a string is returned that may be different from the source, a copy is always made unless the result is the same; the source string is never altered. There is no legitimate way to alter the characters of a string, so you don't need to worry about it. The string storage is automatically reclaimed when it goes out of scope. Lines in T2 strings are always separated by the newline character '\n' 0x0A as in Java/unix, not the return character '\r' 0x0D as in the MacOS. File data is converted on input and output when the file type is specified as TextFile.

int Offset(String seek, String instr);
String Substring(int offs, int len, String str);
String Replace(String nu, int offs, int len, String str);
String ReplacAll(String nu, String olds, String str);

These are essentially the same as their Java counterparts, where the offset offs is zero-based. Replace replaces len characters of the source string with nu, whereas ReplacAll is a search-and-replace that replaces every occurrance of olds with nu. If new instances of olds are formed by or included in the substitution, they are not replaced unless you make a second call on the function.

int nLines(String str);
int nWords(String str);
int nItems(char sep, String str);
String LineOf(int ordn, String str);
String WordOf(int ordn, String str);
String ItemOf(char sep, int ordn, String str);
String PutLineOf(String nu, int orn, String str);
String PutWordOf(String nu, int ordn, String str);
String PutItemOf(String nu, char sep, int orn, String str);
String InsertB4Line(String nu, int orn, String str);
String InsertB4Word(String nu, int orn, String str);
String InsertB4Item(String nu, char sep, int orn, String str);
String DeleteLineOf(int orn, String str);
String DeleteWordOf(int orn, String str);
String DeleteItemOf(char sep, int orn, String str);
int n0Lines(String str);
int n0Items(char sep, String str);

The T2 library has a rich set of string operations. I borrowed from HyperCard the notion of chunks, which can be lines or words or items separated by some kind of delimiter (comma in HC). The chunks are numbered from one, not zero. In the case of words, excess whitespace (spaces, tabs, newlines) have no effect on the word count; any non-white characters count as the first word. Otherwise, when counting chunks, an empty string has zero chunks, but a single delimiter at the end is not counted. If you care about it, there's an alternate form (with '0' in its name) that counts the trailing delimiter, if any.

int Len2LastLine(String str, int len);
int Offs2ItemOf(char sep, int itm, String str);
String AllItemsFrom(char sep, int itm, String str);
String LastItem(String str, char sep);
String ShortFiName(String str);

These are some additional chunk-like functions I found useful. When doing operations on files greater than what can be conveniently held in memory, Len2LastLine measures all but the last line (which might be partial, if reading a fixed block size). Similarly, Offs2ItemOf measures the amount of text before a particular item, which lets you pick out multiple items in a single chunk. AllItemsFrom is a multiple chunk extractor, but extracts everything after (including) the specified item. Substring(0,Offs2ItemOf(sep,itm,str),str) #AllItemsFrom(sep,itm,str) would return (most likely a copy of) the original string. You can pick out only the last item of a string using LastItem, which is equivalent to ItemOf(sep,nItems(sep,str),str); but slightly more convenient. You can use either of these two functions with lines by choosing '\n' as the separator. ShortFiName is the same as LastItem, where the separator is the host file system file path delimiter, ':' for MacOS, '\' for Windows, or '/' for unix.

Array Text Functions

void Str2TxtBlok(String st, int bg, int ln, IntArray ary);
String TxtBlok2Str(int bg, int ln, IntArray ary);
String Array2Str(IntArray ary);
int FindStTxBlk(String st, int bg, int ln, IntArray ary);

Strings can be packed into a binary data structure with integers using these functions (see also package TextAry). This is useful for storing data into resources (see FileI/O Functions) or other binary blocks of data. Accessing the data as integers avoids the overhead of string allocation, and if you use a static array, also the overhead of reference counting. Given an existing array, Str2TxtBlok inserts (part of) a string into it, replacing whatever is already there, beginning at character offset bg for ln bytes. If ln is zero, the string length is used, up to (but not past) the end of the array. TxtBlok2Str reverses the process, extracting a string of the specified length (or less, if it reaches a null); a zero ln gets whatever is there up to a null or the end of the array. Array2Str is equivalent to TxtBlok2Str(0,0,ary); it gets the whole (first) string there. FindStTxBlk searches the array for the first occurrance of the specified string after offset bg, but not beyond bg+ln and returns the offset or -1 if not found. Unlike Offset, you can search for additional hits after the first one found.

void InitPstr(IntArray ary);
void AppChar(IntArray ary, char theCh);
void AppCh4(IntArray ary, Char4 theCh);
void AppHex(IntArray ary, int nm, int nd);
void AppNum(IntArray ary, int nm);
void AppPstr(IntArray ary, String st);
boolean IsntPstr(String st);
int GetPstrLn(IntArray ary);
void TrimPstr(IntArray ary, int tab2);
String GetPstr(IntArray ary, boolean same);

Much more useful when dealing with small strings in inner loops, are these utilities for manipulating Mac-like "P-strings" stored in a user-supplied array. If the array is statically allocated, these are much faster than dynamically allocated strings. However, the programmer is responsible for making sure the strings fit into the given array (maximum P-string length is 255 characters, but it is further limited by the array size, if less than 64). If you use GetPstr with same=true the "string" returned is actually a pointer into the array (in the Mac only), so you cannot let it go out of scope or get reused until you are done with the string. If same=false the returned string is a dynamically allocated copy with no such restrictions; if it is constructed from multiple small parts, it's still much faster than vanilla strings. InitPstr sets the length byte to zero; usually I just do ary[0]=0; which does the same thing faster. Each of the five append functions appends one item of the specified type (if it fits); you get to specify how many hexadecimal digits to append. AppPstr was originally specified to accept only other P-strings (use IsntPstr to determine that) and string literals (which are also encoded as P-strings), but it has been extended to accept any string now, although P-strings are still much faster. Different hardware stores integers differently with respect to strings, so the length byte might be at different ends of ary[0], but GetPstrLn always returns the correct length byte. TrimPstr cuts the length as specified, or else inserts spaces to extend it.

File I/O Functions

A file can be in one of two states, open and active, or else merely a reference (file name to the underlying host system). The two file dialogs return a reference, which can then be opened using OpenFileRef. Alternatively, the file can be opened from a full path name using FileOpen. If a simple file name is given, it will be opened in whatever directory the underlying file system designates. We do not yet have a way to specify a directory to open it in, other than by building a full path name. The native MOS file path delimiter is '\' like Windows; if you use it in a pathname, it will be converted to whatever the underlying system wants to see. However, the file name returned by FullPath has the host delimiters, so you need to use ShortFiName to extract component parts in a platform-independent manner.

File NewFileDlog(String msg, File dir, String fnm);
File FileDialog(String msg, File dir, Char4 fty);
File OpenFileRef(File ref, int opt);
File FileOpen(String name, int opt);
File CloneFileRef(File fi, Process frm);
File Close2Ref(File fi);
void FileClose(File fi);
void FileDelete(File fi);

To open a file, you must specify opt as a combination of these bits:

final int ReadWrite = 0, ReadOnly = 1, WriteOnly = 2,
    CreateFi = 0, ExistingFi = 4,
    TextFile = 0, BinaryFile = 8,
    Directory = 16, ResFileTy = BinaryFile+Directory,
    DirecOnly = ResFileTy, Executable = 32,
    MacResFi = Executable+ResFileTy,
    MakeFiRef = 64, SharedFile = 128,
    SharedResFile = ResFileTy+SharedFile;
Zero is the default value, if none is specified in that bit position. If you open a Directory, then reading from it (you cannot write to it) reads all the file names in that directory. Resource files (ResFileTy) are a random-access binary file type similar to MacOS resource forks, a sort of mini file system in one host file, which lets you save and retrieve "resource" data, typically strings or arrays of integers, using functions optimized for that purpose.

If any of the File-returning functions fails, it returns null. If you have several programs running and interacting, they can send File references to each other, but (in MOS) a program can only open its own files unless they are SharedFile (usually SharedResFile) Therefore you need to use CloneFileRef to create a file reference owned by the current process as a copy of the given reference owned by the specified process frm. File references can be extracted from events and their replies by  EventFile and FileFrom. Note also that the file must be closed to a reference for FileDelete to delete it from the physical file system.

int GetFileErr();
int GetFileFlags(File fi);
int GetFilePosn(File fi);
int GetSizeFile(File fi);
boolean FileAtEOF(File fi);
String FullPath(File fi);
String GetFileName(File fi);
String ProgFileName();
int GetFiRefNum(File fi);
File FileFromID(int fi);
void GetFileInfo(File fi, IntArray ary);
void SetFileInfo(File fi, IntArray ary);

These functions return information about the file, or in the case of SetFileInfo, change it. GetFileErr returns 0 if the most recent file operation completed without error. Otherwise it will be one of these codes (all negative):

final int FileErr = -256,    FileEOF = FileErr+1,
    NoSuchFile = FileErr+2,  FileNotOpen = FileErr+3,
    CantAccess = FileErr+4,  OpenOther = FileErr+5,
    CantWrite = FileErr+6,   TooManyFiles = FileErr+7,
    NoSuchRes = FileErr+8;
GetFileFlags returns the bits used to open the file, or if it's not a valid file, zero. A TextFile open as ReadWrite has another (higher) bit added to make the result non-zero. Do not depend on unspecified bits, always mask off what you wish to test. FileAtEOF(fi) returns true exactly when GetFilePosn(fi)=GetSizeFile(fi). GetFileName returns just the short file name; it's equivalent to ShortFiName(FullPath(fi)). ProgFileName returns the full path name of the running program. I use it to extract the partial path of the directory in which the program file is, as a default location for support files, but that probably wouldn't work in a unixy file system with all kinds of restrictions.

int ReadByte(File fi);
int ReadData(File fi, IntArray ary);
String ReadText(File fi, int size);
String ReadLine(File fi);
int WriteData(File fi, IntArray ary);
void WriteBytes(File fi, IntArray ary, int bgn, int ln);
void WriteText(File fi, String str);
void WriteChar(File fi, char ch);
void WriteChr4(File fi, Char4 ch);
void WriteLn(File fi);

These functions read and write data of various types. ReadData reads up to the size of the given array, and ReadText reads up to the specified number of (file) characters; either will be less if the file ends early, or if the file is opened as TextFile, if there are return-newline character pairs which are replaced internally by newline alone. Regardless of the host or file encoding of text line ends, all such delimiters are converted to newlines for internal processing, then converted back to the host encoding when written to a TextFile.

void Seek(File fi, int offs);
void SetEOF(File fi);

Files opened as ReadWrite or ReadOnly can (if the host permits it) be restarted at an arbitrary byte offset. Use GetFilePosn to determine where you wish to come back to, or SetEOF to shorten the file after it has been written beyond that point. Files normally written sequentially automatically set the end of file after the last byte written.

void InitResFile(File fi);
void NewResource(File fi, Char4 ty, int id, String nm, IntArray ary);
void ReplaceRes(File fi, Char4 ty, int id, String nm, IntArray ary);
void NewStrRes(File fi, Char4 ty, int id, String nm, String st);
void DeleteResource(File fi, Char4 ty, int id, String nm);

Once a file has been opened as ResFileTy, you can use InitResFile to clear out any old resources. Consistent with the MacOS, resources have a Char4 type and a short ingeter reference number and an optional (but not yet implemented) text name. A resource of the same type and reference number as an existing resource replaces it in the file, so ReplaceRes is just another name for NewResource. Resources are limited at this time to a maximum size of 1024 integers or 4096 characters.

int CountResTypes(File fi);
Char4 GetIxResType(File fi, int ix);
int NumResources(File fi, Char4 ty);
int GetIxResource(File fi, Char4 ty, int ix, IntArray ary);
int GetIxResID(File fi, Char4 ty, int ix);
String GetStrRes(File fi, Char4 ty, int id);
IntArray GetResource(File theFi, Char4 rty, int rno);
IntArray SeeResource(File fi, Char4 ty, int id);
int RawGetRes(File fi, Char4 ty, int id, String nm, IntArray ary);

To inspect a resource file, you can CountResTypes to learn how many types there are, then GetIxResType to look at each type in sequence. For each type, you can ask for NumResources of that type, then GetIxResource to get the data, or GetIxResID to learn only its reference number. Normally you already know what's there, so you would use GetStrRes or GetResource to get the data directly. These functions cache the data in memory to minimize file access time for repeated access to resources near each other. If you know you only need to access it once, you can use SeeResource, which bypasses the cache. For lower-level access, RawGetRes and GetIxResource tell you how big the resource is (pass it ary=null), so you can allocate an array of an appropriate size for a second access call.

void SaveRegistryItem(String da, String nm, String itm);
String GetRegistryItem(String nm, String itm);

Like Windows, the Registry is the approved place to store program-specific configuration data. In the MacOS it is implemented as MacOS resources in a file in the "Preferences" folder. In Windows it is stored in the system Registry. Only text strings may be stored, but you can use EncodeIntArray from package TextAry to convert arrays of integers to text.

Debugging Functions

Some of these do not exist in T2C.

void SystemDebugLog(String str);
boolean SysDebugLogging();

The basic cross-platform debugging tool supported by the MOS framework is a system log file, to which you can write various diagnostics. The framework itself also logs what is going on at various debugging levels, so your information can be seen in context. In a production program no such file is open, and calls to SystemDebugLog harmlessly do nothing; however, formatting the text for display takes time, and you can test SysDebugLogging() which returns true if the file is active, so to omit the formatting if not.

String Int2Log(String before, int whom, String after);
String Dec2Log(String before, int whom, String after);
String Str2Log(String before, String theStr, int theLen, String after);
String Hex2Log(String before, int whom, int theLen, String after);
String Chr2Log(String before, char whom, String after);
String Boo2Log(String before, boolean whom, String after);
String FormWinID(String before, Window theWn, String after);
String Tim2Log(String before, int whom, String after);

Each of the common data types has a formatting function here. The converted string is sandwiched between the before and after parameters, which can be nested with multiple calls to make a continuous string like this:

SystemDebugLog(Int2Log("Info: ",aNum,Boo2Log(", ",aCond,Str2Log(" '",aStr,35,"'"))));
This is approximately the same as
SystemDebugLog(("Info: "#aNum#", "#aCond#" '"#aStr#"'");
except that Int2Log automatically formats large numbers as hexadecimal (unless they make sense as a pair of small integers or a Char4 or certain colors), and Str2Log converts newlines and tab characters to something printable ('\' and '`'), and cuts string values down to the specified maximum (0 does not cut it). Chr2Log reports most characters outside the normal ASCII range as numbers. Tim2Log formats the current time according to the parameter used by FormDateTime; alternatively, it will accept a time in whom and formate it as date and time, or if you add 0x80000000 to it, only the time.

String Safe2Log(String theStr);

The Mac version uses Array Text Functions for the intermediate and result strings; if you are not using the result immediately by concatenating it into a larger string by strictly left- or right-nesting as in the example above, you probably should isolate it with a wrapper call to Safe2Log (uneeded in T2C at this time, unless I get around to implementing P-strings in C).

You can use the xx2Log functions to build up text strings for other purposes than logging, but if there's a chance that another of these functions will be used before a previous result ceases to be useful, you should always wrap that previous value in a Safe2Log call. The main reason for the xx2Log functions is to eliminate the unsightly long runs of nested "_cat_(" calls in the C code, and (in the Mac) to reduce the system calls through (A4) which cannot be single-stepped over in the debugger. If you don't need these benefits, long strings of concatenation operators # is slightly faster and much safer.

String PosTime(String prefx);
String DateLog();
String ArrayDumpLine(IntArray ary, int nw, int pos);
String LogCameFrom(String prefix, int deep);

In addition to the type-changing functions mentioned above, you can also use  these functions to build debugging strings of useful data. Most of these let you specify a text prefix, so that concatenating the items is simple and takes minimal code in the application program. LogStr converts newlines and tab characters to something printable ('\' and '`'), and limits the length to theLen, do that the line in the log file is not excessively long. ArrayDumpLine returns as text part or all of an integer array, where nw is the number of words you want (but limited to the array size), and pos is the length of the line in front of where this will display, so to fold the line before it gets too long. If pos is negative, it stops when a couple of zeros are encountered.

LogCameFrom returns a string representation of the line that called this function, where the low four bits of deep specifiy how far back to look, zero meaning the function that called LogCameFrom, bit 4 (=16) specifies that you want a source line number (if it can be found in the Mac object code; always given in the PC), and if negative, it adds a timestamp. Because C does not specify a way to get traceback information, the T2C compiler surrounds each function call with coming_from("name")...retnd_from() calls to record this information, but only if you called the BoundsNullError() function which otherwise does nothing in T2C.

void GoodDump();

This dumps out everything that is known by the framework concerning the running program. It responds to a magical menu command "GSD!" and puts this directly into the debug log without troubling the application program, if selected.

int CompilerVersion();
int StakDepth();

In case you care, CompilerVersion() returns the compiler version as a hexadecimal number yyyymmdd, the date the compiler was compiled. While I'm making changes to the compiler, this can help identify down-rev bugs. Not available in T2C, StakDepth is the maximum depth of the function stack so far, which can help in determining sufficient stack space to allocate when starting up a sublaunch (see NewProgram in package Processes). You probably need at least 1000 words more than the highest this ever gets. On the PC, StakDepth only counts functions that called BoundsNullError.

void DEBUGGER();
void Stop4Debug(Char4 msg);
void HeapCheck(int why);

These invoke low-level debug facilities in the host (MacOS) system.

String Stx(String theStr);

This is a work-around for a compiler bug. Some MacOS framework String functions mess up the reference count in a variable declaration, but not if enclosed in a function call like Stx.

Widget & Window Functions

Graphics Operations

After seeing the chaotic situation in Win32, I decided that MOS should use its own fonts and text rendering engine. This gives me a consistent way to deal with the precalculation of word-wrap parameters in cross-platform software. It also limits the user to the fonts I deemed worth preparing. That's not a problem for me at this time, but it needs to be addressed if this is going to get wider usage. These are the eight fonts currently available:
0 Mono -- Derived from MacOS Monaco 9, but altered to distinguish all characters
1 Text -- Derived from MacOS Times 12, but with whitespace between characters
2 Ital -- Derived from somebody's Times 12 Italic, not the usual Mac algorithmic slant
3 Tiny -- My own hand-crafted 6-pt font
4 Bold -- Derived from MacOS Helvetica 10 Bold
5 Head -- Derived from MacOS Helvetica 18 Bold
6 Grek -- Greek, derived from MacOS Symbol 10
7 Nano -- My own hand-crafted 4-pt font, almost readable
The user can extend this list by subclassing IconFontFetch.

Back when the Mac first came out there was some debate about copyright on pixel fonts, and as I recall, the pixels themselves on the screen are not subject to copyright, but the algorthims for shaping them (first Adobe, then for obvious reasons, Apple+Microsoft TrueType) could be protected. Like the original Mac, these fonts are just pixels.

int FontStrWidth(String str, Char4 fnt);
int StringWidth(String str);
int IxStrWidth(String str, int fo);
int FontChrWidth(char aCh, Char4 fnt);
void GetCharWidths(Char4 fnt, IntArray tos);

Given the name or index number of a font, this calculates the width in pixels of a string or character. These framework calls use some significant overhead, so it is also possible to download the character width table for a given font. A string width is just the sum of the character widths, unless there are control characters included. We don't do kerning yet.

RectAngle NewRect(int tp, int lf, int bt, int rt);
void StuffRect(RectAngle rc, int tp, int lf, int bt, int rt);
void OffsetRect(RectAngle rc, int ver, int hor);
boolean InsetRect(RectAngle rc, int ver, int hor);
boolean EmptyRect(RectAngle rc);
boolean SectRect(RectAngle a, RectAngle b, RectAngle c);
boolean PtInRect(Point pt, RectAngle rcx);

These operations on RectAngle are essentially the same as the MacOS operations similarly named.

Window Operations

Window WindowFromID(Char4 id);
Window theFrontWindow();
Window FindNameWin(String stz);
Window NextWindow(Window theWn);
boolean WinOnScreen(Window theWn);

The public ways to create a new window are exported from their respective packages (below). Given that windows exist, these calls access them. Each window has a (hopefully unique) 4-character "name" which identifies it in various logging operations, and which can be used to find it. You can also find a window from its title, if it has one. If you pass null to NextWindow, it will return the first in the window list (which I think is the most recent created); it returns null if the given window is the last of the list.

void CloseWindow(Window theWn);
void HideWindow(Window theWn);
void ShowWindow(Window theWn);
void Win2Behind(Window theWn, Window whom);
Widgit GetMyWidgits(Window theWn);

CloseWindow removes the window from existence, while HideWindow merely makes it invisible. ShowWindow always brings it to the front, while Win2Behind moves it to behind the second parameter window; neither of these affect its position in the window list, only the order on screen. GetMyWidgits returns the root Widgit of this window, from which you can use the link and child pointers to access additional Widgits.

Char4 GetWindID(Window theWn);
void SetWindID(Window theWn, Char4 val);
void GetWindRect(Window theWn, RectAngle rc);
void SetWinTitle(Window theWn, String stz);
String GetWinTitle(Window theWn);
void MoveWind(Window theWn, Point loc) ;
Point GetWinLoc(Window theWn);
void SizeWind(Window theWn, Point loc);
Point GetWinSize(Window theWn);
void SetDocScroll(Window theWn, int loc);
int GetDocScroll(Window theWn);
void SetWinColor(Window theWn, int val);
int GetWinColor(Window theWn);
void SetWinDatum(Window theWn, int val);
int GetWinDatum(Window theWn);
void SetWindFlags(Window theWn, int val);
int GetWindFlags(Window theWn);
void SetWinFlag(Window theWn, int val, int flg);
int GetWinFlag(Window theWn, int flg);

You can get or set each of these window properties. If you pass null to GetWinSize, it returns the size of the main screen; otherwise null is ignored (or returns zero or null). If the window itself (possibly in its top Widgit) has scrollbars, you can get or set the scroll. The window color refers to the background and text colors, if that makes sense. The "datum" can be used for anything you like. You can get or set all the flags at once, or else a single numbered bit.

void InvalRect(Window theWn, RectAngle rc);
void ValdRect(Window theWn, RectAngle rc);
void DrawWindNow(int why, Window theWn);
void SetCursor(int whom, Window theWn, RectAngle rc);

InvalRect forces all or part of a window to be redrawn, and ValdRect cancels that. On the Mac update events don't happen while dragging or the mouse is otherwise held down, so if you want the window redrawn in those circumstances, you need to call DrawWindNow to do it. Bad Things Happen in the managed windows if this is interrupted and re-enters in a different process, so MOS detects this situation and sends a "DraW" event to the main process, which should call DrawWindNow when it arrives. If you are using AutoEvent, it will do this for you.

Each window has a default cursor to show when the cursor is within the specified rectangle (or the whole window, if null), which SetCursor sets; if theWn is null, then the cursor applies generally, except over an active window with its own specified cursor. These cursors are defined:

-1  Spinning beachball
 0  Default pointer
 1  I-beam text cursor
 4  Wait cursor (hour-glass)
The beachball cursor must be continually refreshed to spin; otherwise it reverts to the default for the window or screen.

Menu Management

MenuBar NewMenuBar(String stz);

Originally I imagined operations on a MenuBar, but it turns out we really want the program to set up its menubar and just leave it there forever. So all the other calls operate on the current (unnamed) menubar. The specification string is a list of menu items with a blank line between each major menu. The first line in each major menu is the menu title (shown in the menubar). Each item has a Char4 command that is sent to the program (see events) when that menu is selected, and an optional shortcut key code (distinguished by its prefix '^' or '`' if disabled). The first four characters of the menu name is its command, unless you give an explicit command separated by a tab from the rest of the line. A single hyphen '-' on a line is an inactive separator line. The first four characters of the menu title should be unique, and identifies it for the following operations:

void KillMenu(Char4 mID);
void AddMenu(Char4 mID, Char4 aft, String stz);
void SetMenuItem(Char4 mID, Char4 msg, String stz, char cky, int pos);
void DeleMenuItemsFrom(Char4 mID, int pos);

A whole major menu can be removed from the menubar, or a new one inserted after (to the right of) the specified one. You can also alter (or insert at the end) individual menu items, given the major menu title code, and the line number (zero is the title itself). A single item can be deleted by giving it an empty item name, or all the menu items below a certain point can be deleted at once.

void MenuDisable(Char4 mID, int pos, boolean dsb);
void CheckMenu(Char4 mID, int pos, boolean ckd);

Without otherwise altering a menu item, it can be disabled or enabled, and a checkmark added or removed.

int PopupMenu(Point pt, String stz, int pre);

Generally in response to a mouse click (usually a context menu event), the program can choose to pop up a menu under the mouse and get back a 0-base number representing which line was selected, or -1 if none. The point is in screen coordinates where the menu is to pop up, which should be near the mouse location at the time of the click, and you can specify which line comes up there under the mouse, usually the default selection (if that makes sense) or the previous value selected (if this pops up from a selection item).

Event Functions & User Interaction

The fundamental interaction between the host system and the application program is by means of events. An Event is a final (methodless) class with these components:
final class Event {
  Char4 EventNo;
  int EvInfo;  // file reference or click time or other info
  int EvLocn;  // mouse loc or other value
  int EvDatum; // relevant window ID or other data
  Process EvSend;}~Event
void GetEvent(Event theEv);
void SendEvent(Event theEv, Process toProc);
int SendMsg(Char4 EvNo, int eInfo, int EvLoc, int EvDat, Process toProc);
void AsyncMsg(Char4 EvNo, int eInfo, int EvLoc, int EvDat, Process toProc);

The main program is expected to loop more or less continuously through a call to GetEvent, which returns only when something happens. When there are multiple processes, you can use SendEvent to send an event to another process; the sending process waits for the event to be accepted and possibly changes made to the Event object sent. Similarly, you can SendMsg to that program with specified values for the individual fields of the Event, and it returns the result from EvInfo. Obviously you cannot SendEvent nor SendMsg to yourself (so SendMsg must be imported from the Processes package). AsyncMsg posts the event to the other process then returns immediately without waiting for it to be accepted. Use MySelf() to send a message to yourself, which is queued and then delivered in sequence to GetEvent. If you use null for the toProc parameter, MOS will try to determine an appropriate destination from whichever process is willing to accept it (see ProcEvent) or whichever process owns the Window whose ID is in EvDat (if so, see GetWindID), or else the main process if none other.

void DelayedEvt(Char4 EvNo, int eInfo, int EvLoc, int EvDat, int msec);

It is also useful to sent yourself messages that are delivered at a later time, the delay specified in milliseconds. This allows programmed delays and allows for something to happen at a later time without tying up the computer on unnecessary idle events.

void AsyncFiMsg(Char4 EvNo, File fi, int EvLoc, int EvDat, Process toProc);
void ReplyFile(Event theEv, File fi);
File EventFile(Event theEv);

File references can be sent between processes using these calls. AsyncFiMsg is the same as AsyncMsg, but sends a file reference number in EvInfo (see GetFiRefNum above). The recipient can use EventFile to extract the file reference from the received event, and CloneFileRef to localize the reference. EventFile uses FileFromID to convert a file ID to a valid file reference, if any.

Window EventWindow(Event ev);
Widgit ClickedWidgit(Event theEv);

Similarly, many events are relevant to a particular window, and its Char4 name is sent in EvDatum. EventWindow extracts the window reference from that ID. Some events include a Widgit name in EvLocn, from which ClickedWidgit can recover the Widgit pointer. If there is not an actual Widgit with that ID in the event window, it returns null.

int MakeEventStr(String str);
String GetEventStr(int str);

Although only integers can be sent in event objects, there is a way to send an arbitrary text string to another process, by encoding it using MakeEventStr. This returns a small integer, which any process can send to GetEventStr to recover a copy of the original string. Use EncodeIntArray (in package TextAry) to convert an array to a string. This is a temporary storage, and the strings so encoded are not preserved past the first call to GetEventStr. You are also limited to a maximum of 30 outstanding strings. Maybe in the future we can build a more comprehensive solution.

boolean AutoEvent(Event theEv);

Some specialized windows manage their own events. You should call this function early in your event loop, and immediately continue to GetEvent if it returns true (the event was handled). It's an explicit call so you can override any events in your own program before letting the defaults act, if you so choose. You can also set up your own event handler(s) to be called by AutoEvent by subclassing EventBroadcast:

class EventBroadcast {
  EventBroadcast link;
  boolean HandleEvent(Event theEv) {return false;} // true if event is taken
  void DeInstall() {/* remove this from event path */}~DeInstall
  void ReInstall() {/* install this in event path */}~ReInstall
  EventBroadcast() {ReInstall();}~EventBroadcast}~EventBroadcast
AutoEvent maintains a linked list of EventBroadcast objects, and the HandleEvent method of the each object in this list is called for each event, until one of them returns true.

When you have multiple processes with their own windows and their own event handlers, sometimes these events get sent to whatever process owns the top window, which may be a different process than the event should be handled by. The ProcEvent class in the Processes package is a subclass of EventBroadcast, and it maintains lists of which events should go to which processes.

void SetClip(String theTxt);
String GetClip(Event theEv);

Usually in response to a menu event, the program can access text in the system clipboard. There are other functions in the UnDragPkg package for doing fancier stuff with clipboard data.

Separate processes must be managed using the Processes package, but these functions are useful also in a stand-alone program:

void Quit();

Quit unconditionally terminates the running process. You are responsible for making sure user data is saved before asking to Quit. When a sublaunched process Quits, a "Term" event is sent to the parent process. When the root process Quits, everything is unconditionally terminated. These are some other useful functions for accessing process information:

Process MySelf();
Process Proc4ch(Char4 pnam);
Char4 ProcID(Process pnam);
Char4 WhoAmI();

Point GetMouse(Window theWn);
int GetButtons();
  // mods: lok:0, shft:1, ctrl:2, cmd:3, opt:4; mouse:31,30,29, frnt:28
boolean ForeGround();
boolean AbortMe();

These are some functions for accessing the computing environment. The mouse location is relative to the specified window, if any, or else to the main screen if null. The low five bits returned by GetButtons represent the various (Mac) modifier keys; its upper bits represent the mouse button state(s). ForeGround is true if the program has an active window (its menubar is showing on the Mac); it could also be true if a sublaunched program is active. AbortMe is only true if the running program is in the foreground, and the user has pressed the system-defined key combination for aborting whatever is running; on the Mac that is command-period. You should call it often in computationally intense loops, and terminate the operation if true.

int GetTime();
int GetMilliSecs();

The system time is returned in seconds since midnight 2000 January 1. On the Mac the milliseconds is not particularly accurate relative to the seconds, it's just a way to get a finer gradation of time.

void SpinBeachBall();
void TurnOffBlak();
void Beep();

These are some non-event ways of interacting with the user. SpinBeachBall spins a beachball cursor to show that the computer is busy doing something long computation; you need to call it often for the ball to spin. If the computer runs continuously with no user activity for several minutes, a screen-blanker automatically kicks in; use to TurnOffBlak to disable this for the current process. Beeping is bad manners, but it can be used to call attention to something that might not otherwise be noticed. I use it to signal the end of a very long run, because it can be heard across the room.

String AskText(String msg, String txt, int opt);

This asks for short text input, giving a prompt and a default initial value. If it is cancelled, it returns the empty string. The option number was originally intended for specialized processing, like hiding password typing, or filtering the data, but I never got around to it; just use zero.

int MsgBox(String msg, Char4 icn, int opt);

This presents a short text message, one of three icons ("Qstn", "Bang", "Stop"), and up to three buttons for the user to respond with. HyperCard let the program specify the exact text of the buttons, but Windows is less generous with their system message box, so I have numbered the allowed choices thus:

final int MsgOK = 1, MsgCancel = 2, MsgNo = 3, MsgYes = 4, MsgSave = 5,
          MsgDiscard = 6, MsgYesAll = 7, MsgResume = 8;
Each four bits of the opt parameter designates one of these values, with the default being the low four bits. These constants are defined to assemble the various values, with a common one as an example:
final int MsgBtn1 = 1, MsgBtn2 = 16, MsgBtn3 = 256;
final int MsgOKCancel = MsgCancel*MsgBtn2+MsgOK;
If you pass it 0 for opt, the default MsgOK is assumed. The value returned by MsgBox is the numerical value for whichever button was selected, such as 1 for MsgOK, 2 for MsgCancel, etc.

void StatusMsg(String txt);
void HideStatus();
boolean StatShownQ();

If you just need to report the status of a computation without requiring user response, StatusMsg can show a small text message. When it finishes, you can HideStatus to hide the window. StatShownQ returns true if the status window is showing.

Window NewProgress(int vx, String txt);
Window ProgressBar(Window wx, int vx);
Window ProgressMsg(Window wx, int vx, String txt);
void EndProgress(Window wx);

For a fancier progress bar with a built-in cancel button, use NewProgress to initialize the window with the total number of units of estimated elapsed time (or items to process) and a brief text message explaining what is being done. Each subsequent call to ProgressBar or ProgressMsg should give a number less than this final value, to show a thermometer-like progress bar; ProgressMsg also replaces the text message. It returns null if the user hit the Cancel button, or you can use EndProgress to dismiss it at the end. The full implementation of these functions is in package ImDlogPkg; if you do not import it, you will get a reduced functionality which uses StatusMsg.

Miscellaneous Functions

boolean EqPt(Point a, Point b);
Point AddPt(Point a, Point b);
Point NegPt(Point pt);
int PointV(Point pt);
int PointH(Point pt);
Point MakePoint(int V, int H);

These are some operations on the Point data type. It's an opaque type, so the compiler does not allow you to compare it to anything, but EqPt compares two Points for equality. AddPt adds two Points as a vector, that is, separate sums of the vertical and horizontal components. Similarly, NegPt takes the negative of the two components. You can subtract two Points by taking the NegPt of one then AddPting them. Extract the signed components using PointV and PointH, or assemble two integers into a Point using MakePoint.

int LoHalf(int pt);
int HiWord(int pt);
int LoWord(int pt);
int Pack2ints(int V, int H);
int MaskInts(int a, int b, int m);

These are the same operations, but applied to plain integers. LoHalf returns a positive (unsigned) number between 0 and 65535; it's equivalent to (pt&0xFFFF). HiWord and LoWord return signed values. The mask in MaskInts picks out the bits of the second parameter that replace the corresponding bits of the first parameter in the returned value; thus Pack2ints(a,b) is exactly equivalent to MaskInts(a<<16,b,0xFFFF).

int Next2pow(int valu);
int Log2(int valu);

Next2pow returns the smallest power of two not less than its parameter. If it's not an exact power of two, the next bit up is returned. Log2 returns the bit number of the highest non-zero bit. To get the smallest non-zero bit in an integer, simply AND it with its negative: (valu&-valu), then (if desired) apply Log2 to get its bit number, which is the number of (insiginficant) zero bits below it.

char Capitize(char ach);
int YearMonthDay(int theTime);

Capitize capitalizes a letter, and returns other characters unchanged. For a time given in seconds since the turn of the millennium (as returned by GetTime), YearMonthDay returns in a single integer the year in the high 16 bits, the day of the month in the low six bits, and the month in the four bits above that.


These utilities are sometimes useful. First some obvious ones:

boolean IsOnlyLetter(char xCh); // true only if xCh is one of the 26 letters
boolean IsLetter(char xCh); // also true of digits and underscore '_'
int RomanDeco(String theStr); // returns the integer value of a roman numeral
String LastWordOf(String theStr); // like LastItem but ignores trailing whitespace

int TheItemIn(String seek, String inHere, char sep);

If seek is a whole item in the list inHere, this returns its item number, otherwise zero.

String SingleSpace(String theStr);

Each run of whitespace is replaced by a single space, except that leading and trailing whitespace is removed. Line breaks are preserved, but blank lines are removed (including at the front and end, as are also spaces at the front or end of each line.

String BlankLns(int here, String theData);

Unlike HyperCard, PutLineOf(something,here,theData) does not insert extra lines at the end of theData if here is greater than the number of lines in theData; BlankLns inserts enough blank lines so that PutLineOf works as desired.

String Trim2Lines(String theText, int nlns);

This returns only the first nlns of theText. If there are fewer, nothing is added (use BlankLns for that).

String FormMyChar(String prefx, char whom);

If whom is a valid (printable, 7-bit) ASCII character, this returns prefx followed by whom in apostrophes; otherwise it returns FormMyInt(prefx,ORD(whom)).

Char4 Char4int(int theNum);

This converts a decimal number less than 10000 (and greater than -1000) into a 4-digit, zero-fill Char4 representation. For example, -12 is returned as "-012".

String ReadWholeFile(File theFi);
void WriteWholeFile(File theFi, String fiData);

Given a file reference theFi to a text file (such as returned by the file dialogs FileDialog and NewFileDlog), this reads or writes the whole file. theFi is left in the reference state. These should be limited to text files of a couple megabytes or less.

String FileListInfo = "", FileListUsed = "";
File GetFileListRef(String refName, boolean newly);
String ReadListFile(String refName);
void WriteListFile(String refName, String fiData);

FileListInfo is a list of reference names and file name paths which the application program is responsible for maintaining. The first line is not part of the list, but thereafter, each line consists of some reference name followed by a '/' character, followed by the full-path file name. GetFileListRef looks in this list for the reference name, and tries to open that file (creating it if newly is true), then closes it and returns the reference, or null if it fails. FileListUsed (if not empty) is a list of the successful references sought: if it starts with '/' it records just the reference names, one per line, otherwise the whole line from FileListInfo; if it starts with '+' it adds only lines not already there. The other two functions combine the services of GetFileListRef and ReadWholeFile or WriteWholeFile.

IntArray MakeTxtAry(String theStr, String whom);
String GetTxAryLn(IntArray theData);

A common software requirement involves the processing of sequential lines from a block of text. Fetching text lines from a larger string is convenient in MOS, but not very fast, because each access must count the lines up to the next line desired. MakeTxtAry stores the text into an (integer) array with the line breaks already marked out (see also the Array Text Functions). GetTxAryLn then sequentially fetches those lines without searching the entire body of text each time. The array has integer indices at the front pointing to the line breaks; position [0] always is the line number of the next line to be accessed, or zero when finished.

char GetArrayByte(IntArray ary, int offs);
void PutArrayByte(IntArray ary, int offs, char whom);

This is a fail-safe way to fetch characters out of a block of text in an integer array (see also the Array Text Functions), or put them back. GetArrayByte(ary,offs) is equivalent to TextChar(ary[offs>>2],offs&3) but defaults to '\0' instead of crashing (MOS dies politely, but terminally) if ary is null or offs is out of bounds. Similarly, PutArrayByte simply does nothing in such a case. Array reference-counting in the function call is also slightly faster than string reference-counting and bounds-checking.

void Install_Font(Char4 whom, IntArray fnt, int inx, IntArray krn);

Besides the eight built-in fonts, you can install two more of your own devising (index inx=9 and inx=10) with their own Char4 names. The font table format is described elsewhere, and the kerning table krn is not used at this time (just pass it null).

IntArray PartOfArray(IntArray theData, int bgn, int nwds);

Returns a smaller array containing only the specified part.


T2 makes the use of text strings relatively convenient, but as a result they are not very fast. Object-based data structures and especially integer arrays are pretty fast, but hard to program and get right. This package offers a middle ground, some of the convenience of text, but semi-static allocation in integer arrays. Also included here is the API for integer-array-based strings which are actually in System and Utilities.

String EncodeIntArray(IntArray ary);
IntArray DecodeIntArray(String theStr);

These two functions convert an integer array to text and back, as a convenient way to send it to another process or save it in an Undo package.

Unrelated to them are the functions for embedding text into integer arrays for faster access, which are actually in System and Utilities:

void Str2TxtBlok(String st, int bg, int ln, IntArray ary);
String TxtBlok2Str(int bg, int ln, IntArray ary);
String Array2Str(IntArray ary);

int FindStTxBlk(String st, int bg, int ln, IntArray ary);
void InitPstr(IntArray ary);
void AppChar(IntArray ary, char theCh);
void AppCh4(IntArray ary, Char4 theCh);
void AppHex(IntArray ary, int nm, int nd);
void AppNum(IntArray ary, int nm);
void AppPstr(IntArray ary, String st);
boolean IsntPstr(String st);
int GetPstrLn(IntArray ary);
void TrimPstr(IntArray ary, int tab2);
String GetPstr(IntArray ary, boolean same);

IntArray MakeTxtAry(String theStr, String whom);
String GetTxAryLn(IntArray theData);


This package sorts arbitrary text as lines or as separated by other delimiters. It's reasonably fast (NlogN).

String SortLns(String str);
String SortWords(String str);
String SortItems(String str, char sep);
String SortGen(String str, char sep, boolean desc, boolean irev, boolean nemt);

The first three are more or less obvious. They call SortGen with specified default settings for the additional parameters. desc sorts in reverse (descending) order; irev informs the sort that the incoming data is mostly already sorted in reverse order, which might save some time if that is the case; nemt removes blank lines or multiple delimiters from the front (or end) of the final sorted text. All three default to false, except that nemt is true for SortWords (excess spaces removed).


MOS was originally defined to support multiple processes both as separate threads within a single program, and also as inter-process communication between independently running concurrent programs. Neither turned out to be easy to implement in the MacOS, so I now use a sort of middle ground, where separately compiled programs load into the same application space and share a single menubar and a single data heap, but communicate between each other only as if they were completely separate, by sending each other events.

Process NewProgram(String fnm, File dir, Char4 pnam, IntArray stkF);

New processes are created by starting up a separately compiled program. The containing directory (not yet used) and file name are the same as opening a file, the name of an existing program (file) compiled by the Turk/2 compiler. T2C ignores the file, and each sublaunched program must be linked into the main program at compile time, which should be automatically set up in the T2C compile (of course you still need to do the right thing in your make file). You can give NewProgram a suggested process ID in pnam, or Null4 to have it use a default based on the file name fnm. In any case, the process ID will be unique; multiple instances of the same program will be given different IDs. The compiled package code must either have different package numbers, or else have been compiled at the same time (and is re-used in memory). You can give it an array stkF to be used for the program stack, or null and it will allocate its own (T2C ignores your stkF). Unless you keep a pointer to the array, the memory will be deallocated when the subprogram quits. Use StakDepth to help calculate a reasonable size for stkF. If you run this program stand-alone, and call StakDepth just before quitting, the returned number, plus the size of its code, is the minimal size required for stkF. If you run it as a NewProgram, StakDepth returns the actual size used (but it's a good idea to allow another +1000 or so for margin).

You can use the returned Process reference to send messages to the new program. It can ask for its ParentProcess, so to send messages back.

Processes are managed asynchronously at (at least) two priority levels (on the Mac). A process that quickly blocks on GetEvent or SendEvent or some other blocking operation generally runs at the higher priority. Heavily compute-bound processes run round-robin at the lower priority. A new process is started in the lower priority (and its parent advanced the higher) so that the parent process has opportunity to make whatever adjustments it may need to do to shared data before the new process actually starts up.

A single line of code will generally execute on the MacOS without a context switch (to another process) unless it contains a loop or a function call. Note that some framework calls are invisible, such as array bounds checking and integer division (in the Mac) and all string operations. Win32 threads can interrupt at any time.

void Quit(); // (in System)
void KillProc(Process whom);

Quit unconditionally terminates the running process. You are responsible for making sure user data is saved before asking to Quit. When a sublaunched process Quits, a "Term" event is sent to the parent process. When the root process Quits, everything is unconditionally terminated. You can also KillProc another process, which is the same as if it asked to Quit; use it with care.

Process MySelf(); // (in System)
Process ParentProcess();
Process Proc4ch(Char4 pnam);
Process ProcByName(String st);
Char4 ProcID(Process pnam);
Char4 WhoAmI();
String GetProgName(Process prc);

These are some useful functions for accessing process information.

int MaxProcIx();
Char4 GetIxProc(int ix);

Sometimes it is useful to access every other running process without knowing in advance their names or IDs. You can index through all running processes using GetIxProc to get their IDs. The root process is always index 0, but others may shift around if additional processes start up or quit between looks. At most 15 processes may be running at one time (in the Mac).

int SendMsg(Char4 EvNo, int eInfo, int EvLoc, int EvDat, Process toProc);

This is like AsyncMsg, but it blocks waiting for the receiving process to reply. Obviously you must use this with care, because two processes could send each other messages at the same time and thus "deadlock", because neither can reply until the other does.

When you have multiple processes with their own event handlers, sometimes these events get sent to the top window, which may be owned by a different process than the event should be handled by. The ProcEvent subclass of EventBroadcast maintains lists of which events should go to which processes.

class ProcEvent extends EventBroadcast { // subclass in each process
  Process myProc; // set by constructor
  IntArray myEvents; // set by KnowTheseEvts
  boolean DispatchEvt(int whom, Event theEv) {return false;} // override to do it
  int IsMyEvent(Char4 theMsg); // rtns index in myEvents
  void KnowTheseEvts(String theList); // installs in myEvents
  ProcEvent() {myProc = MySelf();}~ProcEvent}~ProcEvent
Each process in your program would subclass ProcEvent and instantiate an object of it. Your DispatchEvt method is given the index in myEvents corresponding to this event (from IsMyEvent), which you can use in a switch statement to quickly get to the handler for that event. Like HandleEvent, it should return true if the event was handled. If theList installed by KnowTheseEvts is accurate, you should never need to return false, but if you do, the next handler in the list will get a chance at it.

Immediately after instantiating your subclass object, you should call its KnowTheseEvts method to set up the myEvents array. Give it a string list of the event names your DispatchEvt will handle, four characters each. You may separate them by spaces or commas for readability if you include the same delimiter at the front of theList (but theList must begin with an event name which starts with a letter or digit); in any case, each event name must still be exactly four characters long after the delimiters are removed. You can call the IsMyEvent method at any time to ask if a particular event name is in the list; it will return its index if it is, or -1 if not.

boolean SeeProcEvtLists();

This function logs the event names of all installed ProcEvent objects, and returns true if there are any duplicates. This is a diagnostic tool, because the first installed process in the list that accepts it will be sent the event, and it cannot be forwarded to another process that also accepts it. Generally, the first object/process instantiated will be the first in the list, and others are added behind it in reverse order, unless you ReInstall one or more of them.


This packages collects together the support functions for the system clipboard, Undo, DragonDrop, and (eventually, but not yet supported) scripting. Most of these calls create or need a Drag-Clip-Package datum of opaque type DraCliPkg, but for Undo operations it is implicit, because only one operation at a time can be undoable.

Undo Operations

Undo operations control the Undo menu. Your program supplies the menu item label, then adds one or more messages to be sent if the user chooses to undo the most recent operation. Generally, each undo-able operation can be broken into a sequence of steps, each individually undoable. Each step is stored into the package in the order done, possibly with a complementary redo step. If the user chooses to undo this composite operation, the steps are played back in reverse order, as event messages sent to your application. If Redo steps were included, a redo package is automatically constructed with Undo and Redo steps swapped; otherwise your program is responsible for constructing the Redo package as the Undo steps play out.

void CantUndo();
boolean BeganUndoPkg(Char4 sgn, int inf, String lbl, boolean fize);

Each time a user initiates some operation, your program should call BeganUndoPkg to start up a (possibly new) Undo package. Some operations should be incrementally accumulated, such as moving an object on the screen, where each separate "mDrg" event might call BeganUndoPkg again, but you want them accumulated into a single Undo that moves the object back to its starting point. Other operations are inherently atomic, and should be individually Undone. The parameter fize should be false for atomic operations, and true if like operations should be accumulated into one package. BeganUndoPkg returns true if it began a new package (always if fize is false), and false if it is continuing to build on an existing package with the same signature sgn. When fize=true, the signature should be unique for different operations, and the same when you want to collect multiple operations into a single Undo (when fize=false it always begins a new package, even if the signature is the same). Your program will be sent an event named by the signature sgn with EvInfo=inf if the user chooses Undo from the menu while an accumulated Undo is being built, that is, the "finalize" parameter fize was true when BeganUndoPkg was called; you use this event to do whatever needs to be finished up before the Undo package can be processed. Use CantUndo for the (hopefully rare) operation that cannot be undone.

void DoUndo(Window theWn);

Normally an Undo package is processed by the menu manager when the user chooses Undo from the menu. You can, however, start the process up yourself by calling DoUndo. However you are responsible for invoking your own finalize in this case. The Undo events will be queued immediately. Note that if there are other unprocessed events pending, they will come before the Undo package events, but the Undo events will all be posted before any unrelated events are added to the event queue.

void NewUndo(String lbl);
void RedoMenu(String txt);

If you find reason to change the name of the Undo menu from what was given in BeganUndoPkg, use NewUndo; a user might begin typing deletes, which your program would report as "Delete" in the Undo menu, then switch over to typing new data, necessitating the change to "Typing" while continuing the same package. RedoMenu stores in the package a different label for the automatically generated Redo menu, if you so wish. Note that MOS automatically adds the words "Undo" and "Redo" to the label you give it. The same label will be used for both Undo and Redo unless you specify differently with RedoMenu.

void UndoAdd(Char4 ms, int inf, int loc, int dat);
void RedoAdd(Char4 ms, int inf, int loc, int dat);
void UndoText(String txt);
void RedoText(String txt);
void UndoTxAdd(Char4 ms, String txt, int loc, int dat);
void RedoTxAdd(Char4 ms, String txt, int loc, int dat);
void UndoFile(File fi);
void RedoFile(File fi);

You can set up most any arbitrary event messages as records of the operation to be undone, but often you might just want to specify some text to be replaced. The UndoText and RedoText messages come as "TEXT" events, or you can specify your own message codes using UndoTxAdd and RedoTxAdd. Use UndoFile/RedoFile to package up file references. Other types of data you should cache yourself, and include in the Undo pack only some kind of reference to it.

Drag & Clip Operations

Clipboard and DragonDrop operations exchange data not only with your own program, but also with other programs running on the host system. They are implemented in MOS using the same data type, DraCliPkg, which packages up a variety of data types and contents. Normally these activities are initiated by the user through some kind of event; your program then puts together the data package and sends it off to the recipient, whoever that may be. When your program is the recipient, you can extract the data and apply it to the current context. Each item of data has a Char4 type to distinguish it from other data types; a few of these (right now only "File" and "TEXT") are predefined, but you can use any type code you wish for data received only by your own program.

DraCliPkg DroPastEvent(Event theEv);

This is the call you use when data is dropped on your window (you received the "Drop" or "OpFi" event), or else the user calls up "Paste" from the menu. For simple text copy/paste you can use the System functions SetClip and GetClip.

DraCliPkg NewClip(Event theEv, int sz);
DraCliPkg NewDragSelec(Event theEv, int sz);
DraCliPkg NewDragSelStr(Event theEv, int sz, String im);

Use one of these calls when the user has selected "Cut" or "Copy" from the menu, or has started a drag and you got a "Drag" event. You may offer an estimated size sz, or pass it zero and MOS will make an estimate; if the estimate proves too small, the package is enlarged. If the window management code supports it, you can have it define a drag outline matching the selection rectangle, possibly containing some short text. Otherwise it will use a default outline.

void DragFrom(DraCliPkg id, Point loc);
void PkgText(DraCliPkg id, String txt);
void PkgFile(DraCliPkg id, File fi);
void PkgItem(DraCliPkg id, Char4 ty, int val);
void PkgAdd(DraCliPkg id, Char4 ty, IntArray ary);
void PkgDone(DraCliPkg id);

Once you have defined a new drag package, you can add items to it. DragFrom gives a screen location where the drag will appear to  begin. Most often shared data will be text or file references. Individual (integer) items can be added with arbitrary specified type, or you can add a whole array of integers. When you are finished adding data to the package, call PkgDone to close it and send it on its way. If it's a drag and it successfully lands somewhere, you will get an event ("DrgX" or "DrTr"if it is to be deleted from its source document, or "Drug" if not) signifying that.

int FromPkg(DraCliPkg id, Char4 ty, IntArray ary);
int GetPkgInt(DraCliPkg id, Char4 ty);
String GetPkgTxt(DraCliPkg id);
File GetPkgFile(DraCliPkg id);
Point PkgLoc(DraCliPkg id, boolean drp);
int PkgInfo(DraCliPkg id);

When your program is the recipient of a drop or paste, you can extract the data by the obvious reverse functions. PkgLoc gets either the drop or origin screen location; PkgInfo returns the drag modifier bits (which match the shift and control bits from GetButtons).

System Events

These events are sent by the runtime to the main program, or to whatever process owns the relevant window. In these descriptions, additional parameters are mentioned by a brief name if relevant.
btns -- the value returned by GetButtons() at the time the event is posted.
time -- the time the event is posted, for event ordering, but not particularly related to real time.
cloc -- the location of the click at the time the event is posted, relative to the window.
winID -- the window in which this event occurred. Use EventWindow to convert this to a Window.

This is sent to the owner of the window whenever the window is activated (receives focus, activ=1) or deactivated (gives up focus, activ=0). You might want to redraw the window to reflect its new state.

This is sent to the owner of the window whenever the window is resized by the user; size is the new window size.

This is sent to the owner of the window whenever the window is resized for any reason, but contained widgits are not automatically resized and/or repositioned to match.

This is sent to the owner of the window whenever the user clicks the window close box.

This is sent to the owner of the window whenever the window is active and the user types a (non-modifier) key. The low 8 bits of keyc is the (ASCII) key code; the upper half of keyc contains the modifiers that were pressed at the time the event happened.

This is sent to the main program when the last window has closed. In Windows it is customary to quit.

This is sent to the parent program when a sublaunched process (procID) quits.

This is sent to the owner of the window whenever the mouse does something in that window and it does not trigger some other (programmed) event. If the user clicks on a defined hotspot, that hotspot's message is sent instead. If there is no hotspot defined where the mouse is clicked, "Clik" is sent the first time, and "2clk" is sent if another click happens very soon after in the same place. If there are any modifier keys down at the time, or an alternate mouse button is used (but not as defined for context menu), "ModK" is sent first. "msUp" is sent when the mouse is released.

These are sent to the owner of the window whenever the mouse is dragged in that window. If the mouse was clicked in a hotspot defined to be draggable, then the "Drag" event is sent after the mouse begins to move; if you build and send off a drag package, then you will receive "Drug" after the data is successfully dropped somewhere, unless it is into the trash ("DrTr") or the user has indicated that it should be moved not copied ("DraX"). If the user does not move the mouse after clicking in a draggable hotspot, "|Clk" is sent instead. If the user begins dragging somewhere other than a defined draggable hotspot, "mDrg" is sent each time its position changes after the initial "Clik" event, until the mouse is released.

"CxMn" is sent to the owner of the window whenever the user activates a context menu gesture (right-click, or control-click on a Mac) in that window. If the user holds the mouse down but does not move it for a few seconds, "CxMx" is sent instead of the usual "msUp".

One of these is sent to the owner of the window whenever the user drops something on it, and the specified window management code accepts it. If it does not accept a text file drop, then it is converted to "OpFi", and sent anyway; use EventFile to get a file reference.

These are sent to the owner of a managed window, in addition to programmed events, as defined in their respective packages. "Imag" is sent when the image window needs to be drawn. "WnUp" is sent with a ImDlogPkg window is opened or closed. "ReLo" is sent when a BigTxtPkg window needs a segment loaded.