SBTool.mesa
Copyright Ó 1992 by Xerox Corporation. All rights reserved.
removebutton CD; createbutton SB compile SBTool; run SBImpl SBTool;
Last edited by Curry on April 23, 1986 8:45:39 pm PST
Don Curry May 1, 1987 5:48:43 pm PDT
Last Edited by: Don Curry May 28, 1987 11:36:14 am PDT
Foote, April 28, 1992 11:18 am PDT
DIRECTORY SB, Basics, BasicTime, Convert, Buttons, Commander, CommanderOps, Containers, IO, FS, Labels, Menus, MessageWindow, Rope, VFonts, ViewerClasses, ViewerOps, ViewerTools;
SBTool:
CEDAR
PROGRAM
IMPORTS BasicTime, Buttons, Commander, CommanderOps, Convert, Containers, IO, FS, Labels, Menus, MessageWindow, Rope, SB, VFonts, ViewerOps, ViewerTools =
BEGIN
SBToolHandle: TYPE = REF SBToolRec; -- For data of a particular instance
SBToolRec:
TYPE =
RECORD [
outer: Containers.Container ← NIL,
height: CARDINAL ← 0, -- measured from top
league: SB.League ← NIL,
file: Rope.ROPE ← NIL,
lVwr: LeagueViewers,
team: SB.Team ← 0,
tVwr: TeamViewersSeq ← NIL,
member: SB.Member ← NIL,
iVwr: IndividualViewers ];
LeagueViewers:
TYPE =
RECORD [
read: Labels.Label ← NIL,
write: Labels.Label ← NIL,
file: ViewerClasses.Viewer ← NIL,
year: ViewerClasses.Viewer ← NIL,
time: ViewerClasses.Viewer ← NIL,
players: ViewerClasses.Viewer ← NIL,
keeps: ViewerClasses.Viewer ← NIL,
males: ViewerClasses.Viewer ← NIL,
females: ViewerClasses.Viewer ← NIL,
paid: ViewerClasses.Viewer ← NIL,
unpaid: ViewerClasses.Viewer ← NIL,
dolIn: ViewerClasses.Viewer ← NIL,
dolOut: ViewerClasses.Viewer ← NIL ];
TeamViewersSeq: TYPE = REF TeamViewersRec;
TeamViewersRec: TYPE = RECORD[SEQUENCE size: CARDINAL OF TeamViewers];
TeamViewers:
TYPE =
RECORD [
set: Labels.Label ← NIL,
team: ViewerClasses.Viewer ← NIL,
dIndex: ViewerClasses.Viewer ← NIL,
players: ViewerClasses.Viewer ← NIL,
keeps: ViewerClasses.Viewer ← NIL,
males: ViewerClasses.Viewer ← NIL,
females: ViewerClasses.Viewer ← NIL,
points: ViewerClasses.Viewer ← NIL,
prtctd: ViewerClasses.Viewer ← NIL ];
IndividualViewers:
TYPE =
RECORD [
edited: Labels.Label ← NIL,
name: ViewerClasses.Viewer ← NIL,
sse: ViewerClasses.Viewer ← NIL,
type: ViewerClasses.Viewer ← NIL,
rating: ViewerClasses.Viewer ← NIL,
team: ViewerClasses.Viewer ← NIL,
paid: ViewerClasses.Viewer ← NIL,
group: ViewerClasses.Viewer ← NIL,
shirt: ViewerClasses.Viewer ← NIL,
sex: ViewerClasses.Viewer ← NIL,
addr: ViewerClasses.Viewer ← NIL,
tnum: ViewerClasses.Viewer ← NIL,
pos: ViewerClasses.Viewer ← NIL,
atten: ViewerClasses.Viewer ← NIL,
comnt: ViewerClasses.Viewer ← NIL,
dIndex: ViewerClasses.Viewer ← NIL ];
entryHeight: CARDINAL = 15;
entryVSpace: CARDINAL = 2;
entryLeftBias: CARDINAL ← 5;
dataFont: VFonts.Font ← VFonts.EstablishFont["TimesRoman", 10, FALSE];
labelFont: VFonts.Font ← VFonts.EstablishFont["TimesRoman", 10, TRUE];
A few small caches
lastPreSlash1: Rope.ROPE ← NIL;
lastPostSlash1: Rope.ROPE ← NIL;
lastPreSlash2: Rope.ROPE ← NIL;
lastPostSlash2: Rope.ROPE ← NIL;
lastUserName: Rope.ROPE ← NIL;
lastHomeDirectory: Rope.ROPE ← NIL;
ConvertToSlashFormat:
PROC [path:
Rope.ROPE]
RETURNS [
Rope.ROPE] = {
ENABLE UNWIND => NULL;
SELECT
TRUE
FROM
Rope.Equal[path, lastPreSlash1] => RETURN [lastPostSlash1];
Rope.Equal[path, lastPreSlash2] => RETURN [lastPostSlash2];
ENDCASE => {
ToSlash: Rope.TranslatorType = {
TYPE = PROC [old: CHAR] RETURNS [CHAR]
gnu:
CHAR ←
SELECT old
FROM
'[, '], '<, '> => '/
ENDCASE => old;
RETURN[gnu];
};
Convert to slashes
pos: INT ← path.Find["]<"];
lastPreSlash1 ← lastPreSlash2;
lastPostSlash1 ← lastPostSlash2;
lastPreSlash2 ← path;
IF pos # -1 THEN path ← Rope.Replace[base: path, start: pos, len: 2, with: "/"];
IF Rope.SkipTo[s: path, pos: 0, skip: "[]<>"] # path.Length[]
THEN
path ← Rope.Translate[base: path, translator: ToSlash];
lastPostSlash2 ← path;
RETURN [path];
};
};
MakeSBTool: Commander.CommandProc = {
tool: SBToolHandle ← NEW[SBToolRec];
comlist: LIST OF Rope.ROPE ← CommanderOps.ParseToList[cmd].list;
tool.file ← IF comlist#NIL THEN comlist.first ELSE NIL;
tool.outer ← Containers.Create[[
name: "Softball Tool",
iconic: FALSE,
column: left,
menu: Menus.CreateMenu[],
scrollable: TRUE ]];
Menus.AppendMenuEntry[
menu: tool.outer.menu,
entry: Menus.CreateEntry[
name: "Update",
proc: RePaint,
clientData: tool ] ];
tool.league ← NIL;
tool.height ← entryHeight + entryVSpace;
BVwr ["ReadFile", 0, 0, LeagueRead, tool];
tool.lVwr.write ← LVwr ["WriteFile", 0, 40, 60, 20, LeagueWrite, tool];
tool.lVwr.file ← TVwr ["File:", 1, 0, 10, 70, LeagueFile, tool];
tool.lVwr.year ← TVwr ["Year:", 2, 0, 10, 10, LeagueYear, tool];
tool.lVwr.time ← DVwr ["Time:", 2, 20, 30, 40, RePaint, tool];
tool.lVwr.players ← DVwr ["Players:", 3, 0, 10, 10, RePaint, tool];
tool.lVwr.keeps ← DVwr ["Keeps:", 4, 0, 10, 10, RePaint, tool];
tool.lVwr.males ← DVwr ["Males:", 3, 20, 30, 10, RePaint, tool];
tool.lVwr.females ← DVwr ["Females:", 4, 20, 30, 10, RePaint, tool];
tool.lVwr.paid ← DVwr ["Paid:", 3, 40, 50, 10, RePaint, tool];
tool.lVwr.unpaid ← DVwr ["Unpaid:", 4, 40, 50, 10, RePaint, tool];
tool.lVwr.dolIn ← DVwr ["$ paid:", 3, 60, 70, 10, RePaint, tool];
tool.lVwr.dolOut ← DVwr ["$ owed:", 4, 60, 70, 10, RePaint, tool];
tool.height ← tool.height + (5+2)*(entryHeight + entryVSpace);
BVwr ["Find", 0, 0, PlayerFindM, tool];
BVwr ["Team", 0, 8, PlayerNextT, tool];
BVwr ["Mate", 0, 16, PlayerNextMate, tool];
BVwr ["Marry", 0, 0, PlayerMarry, tool];
BVwr ["Divorce", 0, 10, PlayerDivorce, tool];
BVwr ["Add", 0, 20, PlayerAddNew, tool];
BVwr ["Del", 0, 30, PlayerDelOld, tool];
IBVwr ["Draft", 0, 40, Draft, tool, 20];
BVwr ["Undo", 0, 50, PlayerUnDo, tool];
tool.iVwr.edited ← LVwr ["Mod", 0, 60, 65, 10, PlayerCngOld, tool];
tool.iVwr.name ← TVwr ["Name:", 1, 0, 10, 30, PlayerName, tool];
tool.iVwr.sse ← TVwr ["Mate:", 1, 40, 50, 30, PlayerSSE, tool];
tool.iVwr.team ← DVwr ["Team:", 2, 0, 10, 30, PlayerTeam, tool];
tool.iVwr.sex ← DVwr ["Sex:", 2, 40, 50, 10, PlayerSex, tool];
tool.iVwr.type ← DVwr ["Type:", 2, 60, 70, 20, PlayerType, tool];
tool.iVwr.rating ← DVwr ["Rating:", 3, 0, 10, 10, PlayerRating, tool];
tool.iVwr.shirt ← DVwr ["Shirt:", 3, 20, 30, 10, PlayerShirt, tool];
tool.iVwr.paid ← DVwr ["Paid:", 3, 40, 50, 10, PlayerPaid, tool];
tool.iVwr.group ← DVwr ["Group:", 3, 60, 70, 20, PlayerGroup, tool];
tool.iVwr.dIndex ← TVwr ["DIndex:", 4, 0, 10, 30, PlayerDIndex, tool];
tool.iVwr.pos ← TVwr ["Pos:", 4, 40, 50, 10, PlayerPos, tool];
tool.iVwr.atten ← TVwr ["Atten:", 4, 60, 70, 20, PlayerAtten, tool];
tool.iVwr.addr ← TVwr ["Addr:", 5, 0, 10, 30, PlayerAddr, tool];
tool.iVwr.tnum ← TVwr ["Tel:", 5, 40, 50, 30, PlayerTNum, tool];
tool.iVwr.comnt ← TVwr ["Cmnt:", 6, 0, 10, 60, PlayerComnt, tool];
tool.height ← tool.height + (7+1)*(entryHeight + entryVSpace);
ViewerOps.SetOpenHeight[tool.outer, tool.height]; -- hint our desired height
ViewerOps.PaintViewer[tool.outer, all]; -- reflect above change;
IF tool.file=
NIL
THEN {
year: [86..150] ← BasicTime.Unpack[BasicTime.Now[]].year-1900;
tool.file ← IO.PutFR1["SB%g.sum", IO.int[year]]};
tool.file ← ConvertToSlashFormat[FS.ExpandName[tool.file].fullFName];
SetTVwr[tool.lVwr.file, tool.file] };
AVwr:
PROC[row, tCol, tWid:
CARDINAL, handle: SBToolHandle]
RETURNS[textViewer: ViewerClasses.Viewer] = {
textViewer ← ViewerTools.MakeNewTextViewer[ [
parent: handle.outer,
wx: tCol*VFonts.CharWidth['0] + entryLeftBias,
ww: tWid*VFonts.CharWidth['0],
wy: row* (entryHeight + entryVSpace)+1 + handle.height,
wh: entryHeight,
scrollable: FALSE,
border: FALSE]];
ViewerTools.InhibitUserEdits[textViewer]};
IHdlRec:
TYPE =
RECORD[team: SB.Team, handle: SBToolHandle];
IBVwr: PROC[name: Rope.ROPE, row, lCol: CARDINAL, proc: Buttons.ButtonProc, handle: SBToolHandle, team: SB.Team] = {
button: Buttons.Button ← Buttons.Create[
info: [
name: name,
parent: handle.outer,
wx: lCol*VFonts.CharWidth['0] + entryLeftBias,
wy: row* (entryHeight + entryVSpace) + handle.height,
wh: entryHeight,
border: FALSE ],
proc: proc,
guarded: FALSE,
font: labelFont,
clientData: NEW[IHdlRec←[team, handle]] ]; };
BVwr:
PROC[name: Rope.
ROPE, row, lCol:
CARDINAL, proc: Buttons.ButtonProc, handle: SBToolHandle] = {
button: Buttons.Button ← Buttons.Create[
info: [
name: name,
parent: handle.outer,
wx: lCol*VFonts.CharWidth['0] + entryLeftBias,
wy: row* (entryHeight + entryVSpace) + handle.height,
wh: entryHeight,
border: FALSE ],
proc: proc,
guarded: FALSE,
font: labelFont,
clientData: handle]; };
DVwr:
PROC
[name: Rope.
ROPE, row, bCol, tCol, tWid:
CARDINAL, proc: Buttons.ButtonProc, handle: SBToolHandle]
RETURNS[textViewer: ViewerClasses.Viewer] =
{RETURN[TVwr[name, row, bCol, tCol, tWid, proc, handle, TRUE]]};
TVwr:
PROC
[name: Rope.ROPE, row, bCol, tCol, tWid: CARDINAL, proc: Buttons.ButtonProc, handle: SBToolHandle, noEdit: BOOL ← FALSE] RETURNS[textViewer: ViewerClasses.Viewer] = {
button: Buttons.Button ← Buttons.Create[
info: [
name: name,
parent: handle.outer,
wx: bCol*VFonts.CharWidth['0] + entryLeftBias,
wy: row* (entryHeight + entryVSpace) + handle.height,
wh: entryHeight,
border: FALSE ],
guarded: FALSE,
font: labelFont,
proc: proc,
clientData: handle];
textViewer ← ViewerTools.MakeNewTextViewer[ [
parent: handle.outer,
wx: tCol*VFonts.CharWidth['0] + entryLeftBias,
ww: tWid*VFonts.CharWidth['0],
wy: row* (entryHeight + entryVSpace)+1 + handle.height,
wh: entryHeight,
scrollable: FALSE,
border: FALSE]];
IF noEdit THEN ViewerTools.InhibitUserEdits[textViewer]};
LVwr:
PROC
[name: Rope.ROPE, row, bCol, tCol, tWid: CARDINAL, proc: Buttons.ButtonProc, handle: SBToolHandle] RETURNS[textViewer: ViewerClasses.Viewer] = {
button: Buttons.Button ← Buttons.Create[
info: [
name: name,
parent: handle.outer,
wx: bCol*VFonts.CharWidth['0] + entryLeftBias,
wy: row* (entryHeight + entryVSpace) + handle.height,
wh: entryHeight,
border: TRUE ],
guarded: FALSE,
font: labelFont,
proc: proc,
clientData: handle];
textViewer ← Labels.Create[ [
parent: handle.outer,
wx: tCol*VFonts.CharWidth['0] + entryLeftBias,
ww: tWid*VFonts.CharWidth['0],
wy: row* (entryHeight + entryVSpace)+1 + handle.height,
wh: entryHeight,
border: FALSE]] };
Buttons.ButtonProc = Menus.MenuProc = PROC
[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] ;
For menus, the parent field will contain the parent Viewer; for buttons, parent will be the button Viewer itself. Use NARROW to convert a REF ANY to a Viewer.
SetTVwr:
PROC[viewer: ViewerClasses.Viewer, contents: Rope.
ROPE] =
{ViewerTools.SetContents[viewer, contents]};
SetLVwr:
PROC[label: Labels.Label, contents: Rope.
ROPE] ={Labels.Set[label, contents]};
RePaint: Menus.MenuProc = {
RePaintLeague [parent, clientData];
RePaintTeams [parent, clientData];
RePaintMember [parent, clientData]};
RePaintLeague: Menus.MenuProc = {
sbTool: SBToolHandle ← NARROW[clientData];
league: SB.League ← sbTool.league;
IF league=NIL THEN RETURN;
MessageWindow.Append[message: "RePaint League", clearFirst: TRUE];
SetTVwr[sbTool.lVwr.file, league.file];
SetTVwr[sbTool.lVwr.year, IO.PutFR1["%3g",IO.card[league.year]]];
SetLVwr[sbTool.lVwr.time, IO.PutR1[IO.time[league.time]]];
SetLVwr[sbTool.lVwr.players, IO.PutFR1["%3g",IO.card[league.players]]];
SetLVwr[sbTool.lVwr.keeps, IO.PutFR1["%3g",IO.card[league.keeps]]];
SetLVwr[sbTool.lVwr.males, IO.PutFR1["%3g",IO.card[league.males]]];
SetLVwr[sbTool.lVwr.females, IO.PutFR1["%3g",IO.card[league.females]]];
SetLVwr[sbTool.lVwr.paid, IO.PutFR1["%3g",IO.card[league.paid]]];
SetLVwr[sbTool.lVwr.unpaid, IO.PutFR1["%3g",IO.card[league.unpaid]]];
SetLVwr[sbTool.lVwr.dolIn, IO.PutFR1["%4g",IO.card[league.dollarsIn]]];
SetLVwr[sbTool.lVwr.dolOut, IO.PutFR1["%4g",IO.card[league.dollarsOut]]] };
RePaintTeams: Menus.MenuProc = {
sbTool: SBToolHandle ← NARROW[clientData];
league: SB.League ← sbTool.league;
team: SB.Team ← sbTool.team;
IF league=NIL THEN RETURN;
MessageWindow.Append[message: "RePaint Teams", clearFirst: TRUE];
FOR t:
CARDINAL
IN [0..sbTool.tVwr.size)
DO
SetLVwr[sbTool.tVwr[t].team, league.teamNms[t]];
SetLVwr[sbTool.tVwr[t].players, IO.PutFR1["%3g",IO.card[league.teamStats[t].players]]];
SetLVwr[sbTool.tVwr[t].keeps, IO.PutFR1["%3g",IO.card[league.teamStats[t].keeps]]];
SetLVwr[sbTool.tVwr[t].males, IO.PutFR1["%3g",IO.card[league.teamStats[t].males]]];
SetLVwr[sbTool.tVwr[t].females, IO.PutFR1["%3g",IO.card[league.teamStats[t].females]]];
SetLVwr[sbTool.tVwr[t].points, IO.PutFR1["%3g",IO.card[league.teamStats[t].points]]];
SetLVwr[sbTool.tVwr[t].prtctd, IO.PutFR1["%3g",IO.card[league.teamStats[t].prtctd]]];
SetTVwr[sbTool.tVwr[t].dIndex,
IO.PutFR["%3g%g",
IO.card[league.teamStats[t].dIndex],
IO.rope[IF t = league.nextDTeam THEN " <" ELSE ""] ]];
ENDLOOP};
NULLMember: SB.Member ← NEW[SB.MemberRec ← [ ] ];
RePaintMember: Menus.MenuProc = {
sbTool: SBToolHandle ← NARROW[clientData];
league: SB.League ← sbTool.league;
mem: SB.Member ← sbTool.member;
IF league=NIL THEN RETURN;
IF mem=NIL THEN mem ← NULLMember;
MessageWindow.Append[message: "RePaint Member", clearFirst: TRUE];
SetTVwr[ sbTool.iVwr.name, mem.fname.Cat [" ", mem.lname]];
SetTVwr[ sbTool.iVwr.sse,
(IF mem.sse#NIL THEN mem.sse.fname.Cat[" ", mem.sse.lname] ELSE NIL)];
SetTVwr[sbTool.iVwr.team, league.teamNms [LOOPHOLE[mem.team]]];
SetTVwr[sbTool.iVwr.sex, league.sexNms [LOOPHOLE[mem.sex]]];
SetTVwr[sbTool.iVwr.type, league.typeNms [LOOPHOLE[mem.type]]];
SetTVwr[sbTool.iVwr.rating, league.ratingNms [LOOPHOLE[mem.rating]]];
SetTVwr[sbTool.iVwr.shirt, league.shirtNms [LOOPHOLE[mem.shirt]]];
SetTVwr[sbTool.iVwr.group, league.groupNms [LOOPHOLE[mem.group]]];
SetTVwr[sbTool.iVwr.paid, league.paidNms [LOOPHOLE[mem.paid]]];
SetTVwr[sbTool.iVwr.dIndex, IO.PutFR1["%3g", IO.card[mem.dIndex]]];
SetTVwr[sbTool.iVwr.pos, mem.pos];
SetTVwr[sbTool.iVwr.atten, mem.atten];
SetTVwr[sbTool.iVwr.addr, mem.addr];
SetTVwr[sbTool.iVwr.tnum, mem.tnum];
SetTVwr[sbTool.iVwr.comnt, mem.comnt] };
LeagueRead: Buttons.ButtonProc = {
league: SB.League;
sbTool: SBToolHandle ← NARROW[clientData];
sbTool.file ← ViewerTools.GetContents[sbTool.lVwr.file];
league ← SB.ReadLeagueFile[sbTool.file];
IF league=
NIL
THEN {
MessageWindow.Append[message: "File not found.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
sbTool.league ← league;
sbTool.team ← 0;
sbTool.member ← sbTool.league.members.first;
IF sbTool.tVwr=
NIL
OR (sbTool.tVwr#
NIL
AND sbTool.tVwr.size#league.teamNms.size)
THEN {
IF sbTool.tVwr=
NIL
THEN {
BVwr ["Team", 0, 0, Dummy, sbTool];
BVwr ["Players", 0, 17, Dummy, sbTool];
BVwr ["Keeps", 0, 26, Dummy, sbTool];
BVwr ["Males", 0, 33, Dummy, sbTool];
BVwr ["Females", 0, 41, Dummy, sbTool];
BVwr ["Points", 0, 50, Dummy, sbTool];
BVwr ["Prtctd", 0, 57, Dummy, sbTool];
BVwr ["DIndex", 0, 65, Dummy, sbTool];
sbTool.height ← sbTool.height + 2*(entryHeight + entryVSpace) }
ELSE {
FOR t:
CARDINAL
IN [0..sbTool.tVwr.size)
DO
ViewerOps.DestroyViewer[sbTool.tVwr[t].team ];
ViewerOps.DestroyViewer[sbTool.tVwr[t].players ];
ViewerOps.DestroyViewer[sbTool.tVwr[t].keeps ];
ViewerOps.DestroyViewer[sbTool.tVwr[t].males ];
ViewerOps.DestroyViewer[sbTool.tVwr[t].females ];
ViewerOps.DestroyViewer[sbTool.tVwr[t].points ];
ViewerOps.DestroyViewer[sbTool.tVwr[t].prtctd ];
ViewerOps.DestroyViewer[sbTool.tVwr[t].dIndex ];
ENDLOOP;
sbTool.height ← sbTool.height - (sbTool.tVwr.size+2)*(entryHeight + entryVSpace) };
sbTool.tVwr ← NEW[TeamViewersRec[league.teamNms.size]];
FOR t:
CARDINAL
IN [0..sbTool.tVwr.size)
DO
IBVwr [league.teamNms[t], 0, 0, Draft, sbTool, t];
sbTool.tVwr[t].team ← AVwr [0, 0, 20, sbTool];
sbTool.tVwr[t].players ← AVwr [0, 20, 8, sbTool];
sbTool.tVwr[t].keeps ← AVwr [0, 28, 8, sbTool];
sbTool.tVwr[t].males ← AVwr [0, 36, 8, sbTool];
sbTool.tVwr[t].females ← AVwr [0, 44, 8, sbTool];
sbTool.tVwr[t].points ← AVwr [0, 52, 8, sbTool];
sbTool.tVwr[t].prtctd ← AVwr [0, 60, 8, sbTool];
sbTool.tVwr[t].dIndex ← AVwr [0, 68, 8, sbTool];
sbTool.height ← sbTool.height + (entryHeight + entryVSpace);
ENDLOOP;
sbTool.height ← sbTool.height + 2*(entryHeight + entryVSpace);
ViewerOps.SetOpenHeight[sbTool.outer, sbTool.height] };
RePaint[parent, clientData];
SetLVwr[sbTool.lVwr.write, ""] };
LeagueWrite: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
sbTool.league.file ← ViewerTools.GetContents[sbTool.lVwr.file];
sbTool.league.year ← Convert.CardFromRope[ViewerTools.GetContents[sbTool.lVwr.year]];
SB.Update[sbTool.league];
SB.WriteLeagueFiles[sbTool.league];
SetLVwr[sbTool.lVwr.write, ""] };
LeagueFile: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
ViewerTools.SetSelection[sbTool.lVwr.file] };
LeagueYear: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
ViewerTools.SetSelection[sbTool.lVwr.year] };
Dummy: Buttons.ButtonProc = { };
TeamNext: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
sbTool.team ← CardModMouse[mouseButton, sbTool.team, sbTool.league.teamNms.size];
RePaintTeams[parent, clientData] };
TeamSet: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
dIndexStr: Rope.ROPE ← ViewerTools.GetContents[sbTool.tVwr.dIndex];
SetLVwr[sbTool.tVwr.set, "done"] };
TeamDIndex: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
current: CARDINAL ← sbTool.league.teamStats[sbTool.team].dIndex;
current ← current + (SELECT mouseButton FROM
red => 1,
yellow => 0,
ENDCASE => (IF current=0 THEN 0 ELSE -1));
sbTool.league.teamStats[sbTool.team].dIndex ← current;
RePaintTeams[parent, clientData] };
PlayerFindM: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
league: SB.League ← sbTool.league;
member: SB.Member ← NIL;
IF league=NIL THEN RETURN;
SELECT mouseButton
FROM
red => {
m: LIST OF SB.Member;
FOR m ← league.members, m.rest
WHILE m.rest#
NIL
DO
IF m.first#sbTool.member THEN LOOP;
sbTool.member ← m.rest.first;
EXIT REPEAT FINISHED => sbTool.member ← league.members.first ENDLOOP};
yellow => {
rec: SB.MemberRec;
[member, rec] ← ExtractIndividualViewerFields[sbTool];
IF member = NIL THEN member ← SB.FindMemberName[league, rec.lname];
IF member = NIL THEN member ← SB.FindMemberName[league, rec.fname];
IF member =
NIL
THEN {
FOR lst:
LIST
OF
SB.Member ← league.members, lst.rest
WHILE lst#
NIL
DO
subStr: Rope.ROPE ← Rope.Substr[lst.first.lname, 0, Rope.Length[rec.lname]];
IF ~Rope.Equal[subStr, rec.lname, FALSE] THEN LOOP;
member ← lst.first; EXIT ENDLOOP};
IF member =
NIL
THEN {
FOR lst:
LIST
OF
SB.Member ← league.members, lst.rest
WHILE lst#
NIL
DO
aChar: CHAR ← lst.first.lname.Fetch[0];
bChar: CHAR ← rec.lname.Fetch[0];
IF aChar <= 'Z AND aChar >= 'A THEN aChar ← aChar + ('a-'A);
IF bChar <= 'Z AND bChar >= 'A THEN bChar ← bChar + ('a-'A);
IF aChar < bChar THEN LOOP;
member ← lst.first; EXIT ENDLOOP;
IF member = NIL THEN member ← league.members.first;
MessageWindow.Append[message: "Not found.", clearFirst: TRUE];
MessageWindow.Blink[]};
IF member # NIL THEN sbTool.member ← member};
ENDCASE => {
m: LIST OF SB.Member;
FOR m ← league.members, m.rest
WHILE m.rest#
NIL
DO
IF m.rest.first#sbTool.member THEN LOOP;
sbTool.member ← m.first;
EXIT REPEAT FINISHED => sbTool.member ← m.first ENDLOOP};
RePaintMember[parent, clientData];
SetLVwr[sbTool.iVwr.edited, ""] };
PlayerNextMate: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
league: SB.League ← sbTool.league;
mem: SB.Member ← sbTool.member;
IF league=NIL THEN RETURN;
IF mem=NIL THEN RETURN;
IF mem.sse=NIL THEN RETURN;
sbTool.member ← mem.sse;
RePaintMember[parent, clientData];
SetLVwr[sbTool.iVwr.edited, ""] };
ScanType: TYPE = {name, sse, team, sex, type, rating, shirt, paid, group, dIndex};
Scan:
PROC[parent, clientData:
REF, mouseButton: Menus.MouseButton, stype: ScanType] = {
match:
PROC[m:
SB.Member]
RETURNS[
BOOL] = {
RETURN[
SELECT stype
FROM
name => rec.name = m.team,
sse => rec.sse = m.team,
team => rec.team = m.team,
sex => rec.sex = m.sex,
type => rec.type = m.type,
rating => rec.rating = m.rating,
shirt => rec.shirt = m.shirt,
paid => rec.paid = m.paid,
group => rec.group = m.group,
dIndex => rec.dIndex = m.dIndex,
ENDCASE => ERROR ]};
sbTool: SBToolHandle ← NARROW[clientData];
league: SB.League ← sbTool.league;
m: LIST OF SB.Member;
mem: SB.Member;
first: SB.Member ← NIL;
prev: SB.Member ← NIL;
next: SB.Member ← NIL;
last: SB.Member ← NIL;
rec: SB.MemberRec;
IF league=NIL THEN RETURN;
IF stype = dIndex
THEN
{ScanDIndex [NARROW[parent], clientData, mouseButton]; RETURN};
IF stype = name
THEN
{PlayerFindM [NARROW[parent], clientData, mouseButton]; RETURN};
IF stype = sse
THEN
{PlayerNextMate [NARROW[parent], clientData, mouseButton]; RETURN};
[mem, rec] ← ExtractIndividualViewerFields[sbTool];
IF mem=NIL THEN mem ← league.members.first;
FOR m ← league.members, m.rest
WHILE m.first#mem
DO
IF ~match[m.first] THEN LOOP;
IF first = NIL THEN first ← m.first;
prev ← m.first ENDLOOP;
FOR m ← m.rest, m.rest
WHILE m#
NIL
DO
IF ~match[m.first] THEN LOOP;
IF next = NIL THEN next ← m.first;
last ← m.first ENDLOOP;
SELECT mouseButton
FROM
red => sbTool.member ← IF next#NIL THEN next ELSE first;
yellow => { };
ENDCASE => sbTool.member ← IF prev#NIL THEN prev ELSE last;
RePaintMember[NARROW[parent], clientData];
SetLVwr[sbTool.iVwr.edited, ""] };
ScanDIndex:
PROC[parent, clientData:
REF, mouseButton: Menus.MouseButton] = {
match:
PROC[m:
SB.Member]
RETURNS[
BOOL] = {
RETURN[
SELECT mouseButton
FROM
red => rec.dIndex+1 = m.dIndex,
yellow => rec.dIndex = m.dIndex,
ENDCASE => rec.dIndex = m.dIndex+1]};
sbTool: SBToolHandle ← NARROW[clientData];
league: SB.League ← sbTool.league;
mem: SB.Member;
rec: SB.MemberRec;
IF league=NIL THEN RETURN;
rec ← ExtractIndividualViewerFields[sbTool].rec;
FOR m:
LIST
OF
SB.Member ← league.members, m.rest
WHILE m#NIL
DO
IF ~match[m.first] THEN LOOP;
mem ← m.first; EXIT ENDLOOP;
IF mem=
NIL
THEN {
MessageWindow.Append[message: "Not found.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
sbTool.member ← mem;
RePaintMember[NARROW[parent], clientData];
SetLVwr[sbTool.iVwr.edited, ""] };
ActionRec: TYPE = RECORD[action: Action, ref: REF];
Action: TYPE = {add, del, divorce, marry, draft};
Actions: TYPE = LIST OF ActionRec;
actions: Actions ← NIL;
PlayerMarry: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
league: SB.League ← sbTool.league;
member: SB.Member ← NIL;
rec: SB.MemberRec;
[member, rec] ← ExtractIndividualViewerFields[sbTool];
IF member=
NIL
THEN {
MessageWindow.Append[message: "Member not found.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
IF rec.sse=
NIL
THEN {
MessageWindow.Append[message: "Mate not found.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
IF member.sse#
NIL
AND member.sse#rec.sse
THEN {
MessageWindow.Append[message: "Member is already spoken for.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
IF rec.sse.sse#
NIL
AND rec.sse.sse#member
THEN {
MessageWindow.Append[message: "Mate is already spoken for.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
rec.sse.sse ← member;
member.sse ← rec.sse;
member.pair ← rec.sse.pair ← paired;
MessageWindow.Append[message: "Sniff sniff.", clearFirst: TRUE];
sbTool.member ← member;
SB.Update[league];
RePaint[parent, clientData];
SetLVwr[sbTool.lVwr.write, "edited"] };
PlayerDivorce: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
league: SB.League ← sbTool.league;
member: SB.Member ← NIL;
rec: SB.MemberRec;
[member, rec] ← ExtractIndividualViewerFields[sbTool];
IF member=
NIL
THEN {
MessageWindow.Append[message: "Member not found.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
IF rec.sse=
NIL
THEN {
MessageWindow.Append[message: "Mate not found.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
IF member.sse#rec.sse
OR rec.sse.sse#member
THEN {
MessageWindow.Append[message: "They are not married.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
rec.sse.sse ← NIL;
member.sse ← NIL;
member.pair ← rec.sse.pair ← sgl;
MessageWindow.Append[message: "Oh Lord! Free at last!", clearFirst: TRUE];
sbTool.member ← member;
SB.Update[league];
RePaint[parent, clientData];
SetLVwr[sbTool.lVwr.write, "edited"] };
PlayerAddNew: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
league: SB.League ← sbTool.league;
member: SB.Member ← NIL;
rec: SB.MemberRec;
[member, rec] ← ExtractIndividualViewerFields[sbTool];
IF member#
NIL
THEN {
MessageWindow.Append[message: "Old player. Not changed.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
IF rec.sse#
NIL
AND rec.sse.sse#
NIL
THEN {
MessageWindow.Append[message: "Mate is already engaged. Not added.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
member ← NEW[SB.MemberRec ← rec];
SB.AddMember[league, member];
IF rec.sse#NIL THEN rec.sse.sse ← member;
IF rec.sse#
NIL
THEN MessageWindow.Append[message: "Added 2 members", clearFirst: TRUE]
ELSE MessageWindow.Append[message: "Added 1 member", clearFirst: TRUE];
sbTool.member ← member;
SB.Update[league];
RePaint[parent, clientData];
SetLVwr[sbTool.lVwr.write, "edited"] };
PlayerDelOld: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
league: SB.League ← sbTool.league;
member: SB.Member ← NIL;
first: SB.Member ← NIL;
prev: SB.Member ← NIL;
next: SB.Member ← NIL;
last: SB.Member ← NIL;
rec: SB.MemberRec;
m: LIST OF SB.Member;
[member, rec] ← ExtractIndividualViewerFields[sbTool];
IF member=
NIL
THEN {
MessageWindow.Append[message: "Player not found.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
IF member.sse#NIL THEN {member.sse.pair ← sgl; member.sse.sse ← NIL};
FOR m ← league.members, m.rest
WHILE m.first#member
DO
IF first = NIL THEN first ← m.first;
prev ← m.first ENDLOOP;
FOR m ← m.rest, m.rest
WHILE m#
NIL
DO
IF next = NIL THEN next ← m.first;
last ← m.first ENDLOOP;
SELECT mouseButton
FROM
red => sbTool.member ← IF next#NIL THEN next ELSE first;
yellow => { };
ENDCASE => sbTool.member ← IF prev#NIL THEN prev ELSE last;
actions ← CONS[[del, member], actions];
SB.DeleteMember[league, member];
MessageWindow.Append[message: "Deleted member", clearFirst: TRUE];
SB.Update[league];
RePaint[parent, clientData];
SetLVwr[sbTool.lVwr.write, "edited"] };
Draft: Buttons.ButtonProc = {
data: REF IHdlRec ← NARROW[clientData];
sbTool: SBToolHandle ← data.handle;
team: SB.Team ← data.team;
league: SB.League ← sbTool.league;
member: SB.Member ← NIL;
rec: SB.MemberRec;
[member, rec] ← ExtractIndividualViewerFields[sbTool];
IF team=20 THEN team ← league.nextDTeam;
SELECT mouseButton
FROM
red, blue => {
SetTVwr[sbTool.iVwr.team, league.teamNms [LOOPHOLE[team]]];
Scan[ parent, sbTool, mouseButton, team];
RETURN } ENDCASE;
IF member=
NIL
THEN {
MessageWindow.Append[message: "Member not found.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
IF team#0
THEN{
IF member.team#0
THEN {
MessageWindow.Append[message: "Already drafted.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
IF member.sse#
NIL
AND member.sse.team#0
THEN {
MessageWindow.Append[message: "Spouse already drafted.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
member.team ← team;
member.dIndex ← league.nextDIndex;
IF member.sse=
NIL
THEN MessageWindow.Append[message: "Drafted", clearFirst: TRUE]
ELSE {
member.sse.team ← team;
member.sse.dIndex ← IF team#0 THEN league.nextDIndex+1 ELSE 0;
MessageWindow.Append[message: "Pair Drafted", clearFirst: TRUE]} }
ELSE {
lastIndex: CARDINAL ← member.dIndex;
IF member.team=0
THEN {
MessageWindow.Append[message: "Already unassigned.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
IF member.sse#
NIL
AND member.sse.team=0
THEN {
MessageWindow.Append[message: "Spouse already unassigned.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
member.team ← team;
member.dIndex ← 0;
IF member.sse=
NIL
THEN MessageWindow.Append[message: "Unassigned", clearFirst: TRUE]
ELSE {
lastIndex ← MIN[ member.dIndex, lastIndex];
lastIndex ← MAX[ 1, lastIndex]-1;
member.sse.team ← team;
member.sse.dIndex ← IF team#0 THEN league.nextDIndex+1 ELSE 0;
MessageWindow.Append[message: "Pair Unassigned", clearFirst: TRUE]}};
ViewerTools.SetContents[sbTool.iVwr.dIndex, Convert.RopeFromCard[lastIndex]];
Scan[ parent, sbTool, mouseButton, dIndex] } }; -- too confusing
sbTool.member ← member;
SB.Update[league];
RePaint[parent, sbTool];
SetLVwr[sbTool.lVwr.write, "edited"] };
PlayerUnDo: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
league: SB.League ← sbTool.league;
IF league=NIL THEN RETURN;
IF actions=
NIL
THEN {
MessageWindow.Append[message: "No actions.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
SELECT actions.first.action
FROM
add => {
sbTool.member ← NARROW[actions.first.ref];
actions ← actions.rest;
RePaint[parent, clientData];
PlayerDelOld[parent, clientData, red, control]};
del => {
sbTool.member ← NARROW[actions.first.ref];
actions ← actions.rest;
MessageWindow.Append[message: "Un delete", clearFirst: TRUE];
IF sbTool.member.sse#
NIL
THEN
IF sbTool.member.sse.sse#
NIL
AND sbTool.member.sse.sse#sbTool.member
THEN {sbTool.member.pair ← sgl; sbTool.member.sse ← NIL}
ELSE {
sbTool.member.sse.pair ← paired;
sbTool.member.sse.sse ← sbTool.member;
MessageWindow.Append[message: " and remarry", clearFirst: FALSE] };
SB.AddMember[league, sbTool.member]};
ENDCASE => {
MessageWindow.Append[message: "Action is not reversible.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
SB.Update[league];
RePaint[parent, clientData];
SetLVwr[sbTool.lVwr.write, "edited"] };
PlayerCngOld: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
league: SB.League ← sbTool.league;
member: SB.Member ← NIL;
rec: SB.MemberRec;
[member, rec] ← ExtractIndividualViewerFields[sbTool];
IF member=
NIL
THEN {
MessageWindow.Append[message: "Player not found.", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
IF member.sse#rec.sse
THEN {
MessageWindow.Append[message: "Mating operations must be explicit", clearFirst: TRUE];
MessageWindow.Blink[];
RETURN};
member^ ← rec;
MessageWindow.Append[message: "Done", clearFirst: TRUE];
sbTool.member ← member;
SB.Update[league];
RePaint[parent, clientData];
SetLVwr[sbTool.lVwr.write, "edited"] };
ExtractIndividualViewerFields:
PROC[sbTool: SBToolHandle]
RETURNS[mem: SB.Member, rec: SB.MemberRec] = {
league: SB.League ← sbTool.league;
sseFName: Rope.ROPE;
sseLName: Rope.ROPE;
[rec.fname, rec.lname] ← ParseName[ViewerTools.GetContents[sbTool.iVwr.name]];
mem ← SB.FindMember[league, rec.lname, rec.fname];
[sseFName, sseLName] ← ParseName[ViewerTools.GetContents[sbTool.iVwr.sse]];
rec.sse ← SB.FindMember[league, sseLName, sseFName];
IF rec.sse#NIL THEN rec.pair ← paired ELSE rec.pair ← sgl;
rec.type ←
LOOPHOLE[
SB.RopeSeqIndex[ViewerTools.GetContents[sbTool.iVwr.type], league.typeNms]];
rec.rating ←
LOOPHOLE[
SB.RopeSeqIndex[ViewerTools.GetContents[sbTool.iVwr.rating], league.ratingNms]];
rec.team ←
LOOPHOLE[
SB.RopeSeqIndex[ViewerTools.GetContents[sbTool.iVwr.team], league.teamNms]];
rec.paid ←
LOOPHOLE[
SB.RopeSeqIndex[ViewerTools.GetContents[sbTool.iVwr.paid], league.paidNms]];
rec.group ←
LOOPHOLE[
SB.RopeSeqIndex[ViewerTools.GetContents[sbTool.iVwr.group], league.groupNms]];
rec.shirt ←
LOOPHOLE[
SB.RopeSeqIndex[ViewerTools.GetContents[sbTool.iVwr.shirt], league.shirtNms]];
rec.sex ←
LOOPHOLE[
SB.RopeSeqIndex[ViewerTools.GetContents[sbTool.iVwr.sex], league.sexNms]];
rec.addr ← ViewerTools.GetContents[sbTool.iVwr.addr];
rec.tnum ← ViewerTools.GetContents[sbTool.iVwr.tnum];
rec.pos ← ViewerTools.GetContents[sbTool.iVwr.pos];
rec.atten ← ViewerTools.GetContents[sbTool.iVwr.atten];
rec.comnt ← ViewerTools.GetContents[sbTool.iVwr.comnt];
rec.dIndex ← Convert.CardFromRope[ViewerTools.GetContents[sbTool.iVwr.dIndex]] };
PlayerName: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
IF shift
THEN Scan[ parent, clientData, mouseButton, name]
ELSE ViewerTools.SetSelection[sbTool.iVwr.name] };
PlayerSSE: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
IF shift
THEN Scan[ parent, clientData, mouseButton, sse]
ELSE ViewerTools.SetSelection[sbTool.iVwr.sse] };
PlayerTeam: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
IF shift
THEN Scan[ parent, clientData, mouseButton, team]
ELSE IncVwrSeq[mouseButton, sbTool.iVwr.team, sbTool.league.teamNms] };
PlayerSex: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
IF shift
THEN Scan[ parent, clientData, mouseButton, sex]
ELSE IncVwrSeq[mouseButton, sbTool.iVwr.sex, sbTool.league.sexNms] };
PlayerType: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
IF shift
THEN Scan[ parent, clientData, mouseButton, type]
ELSE IncVwrSeq[mouseButton, sbTool.iVwr.type, sbTool.league.typeNms] };
PlayerRating: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
IF shift
THEN Scan[ parent, clientData, mouseButton, rating]
ELSE IncVwrSeq[mouseButton, sbTool.iVwr.rating, sbTool.league.ratingNms] };
PlayerShirt: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
IF shift
THEN Scan[ parent, clientData, mouseButton, shirt]
ELSE IncVwrSeq[mouseButton, sbTool.iVwr.shirt, sbTool.league.shirtNms] };
PlayerPaid: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
IF shift
THEN Scan[ parent, clientData, mouseButton, paid]
ELSE IncVwrSeq[mouseButton, sbTool.iVwr.paid, sbTool.league.paidNms] };
PlayerGroup: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
IF shift
THEN Scan[ parent, clientData, mouseButton, group]
ELSE IncVwrSeq[mouseButton, sbTool.iVwr.group, sbTool.league.groupNms] };
PlayerDIndex: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
IF shift
THEN Scan[ parent, clientData, mouseButton, dIndex]
ELSE ViewerTools.SetSelection[sbTool.iVwr.dIndex] };
PlayerPos: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
ViewerTools.SetSelection[sbTool.iVwr.pos] };
PlayerAtten: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
ViewerTools.SetSelection[sbTool.iVwr.atten] };
PlayerAddr: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
ViewerTools.SetSelection[sbTool.iVwr.addr] };
PlayerTNum: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
ViewerTools.SetSelection[sbTool.iVwr.tnum] };
PlayerComnt: Buttons.ButtonProc = {
sbTool: SBToolHandle ← NARROW[clientData];
ViewerTools.SetSelection[sbTool.iVwr.comnt] };
IncVwrSeq:
PROC
[mouse: Menus.MouseButton, viewer: ViewerClasses.Viewer, seq: SB.RopeSeq] = {
index: CARDINAL ← SB.RopeSeqIndex[ViewerTools.GetContents[viewer], seq];
SetTVwr[viewer, seq[CardModMouse[mouse, index, seq.size]]] };
CardModMouse:
PROC[mouseButton: Menus.MouseButton, val, mod:
CARDINAL]
RETURNS[result: CARDINAL] = {
SELECT mouseButton
FROM
red => {result ← (val +1) MOD mod};
yellow => {result ← val};
ENDCASE => {result ← (val+mod-1) MOD mod} };
ParseName:
PROC[rope: Rope.
ROPE]
RETURNS[first, last: Rope.
ROPE] = {
temp: Rope.ROPE;
in: IO.STREAM ← IO.RIS[rope];
DO
last ← first ← NIL;
last ← IO.GetTokenRope[in ! IO.EndOfStream => {last ← NIL; EXIT}].token;
first ← IO.GetTokenRope[in ! IO.EndOfStream => {first ← NIL; EXIT}].token;
temp ← first; first ← last; last ← temp; EXIT; ENDLOOP };
Commander.Register[key: "SB", proc: MakeSBTool, doc: "Create a Softball tool" ];
END.