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.ROPENIL,
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.ROPENIL;
lastPostSlash1: Rope.ROPENIL;
lastPreSlash2: Rope.ROPENIL;
lastPostSlash2: Rope.ROPENIL;
lastUserName: Rope.ROPENIL;
lastHomeDirectory: Rope.ROPENIL;
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: CHARSELECT 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.fileIF 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: BOOLFALSE] 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=NILTHEN RETURN;
IF mem=NIL  THEN RETURN;
IF mem.sse=NILTHEN 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=NILTHEN 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: CARDINALSB.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.STREAMIO.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.