<> <> <> DIRECTORY Atom USING [GetPName], BasicTime USING [GetClockPulses, GMT, Now, Period, Pulses, PulsesToMicroseconds], Buttons USING [Button, ButtonProc, Create, Destroy, SetDisplayStyle], Commander USING [CommandProc, Register], Containers USING [ChildXBound, ChildYBound, Create], IO USING [Flush, PutF, PutRope, STREAM, Value], Labels USING [Create], Loader USING [BCDBuildTime], Rope USING [ROPE], RPCEcho USING [DoAtom, DoRope, Simple, SloshTenPages, SloshTenWords, PullPage, PushPage, SloshPage, Page, TenPages, TenWords], RPCEchoRpcControl USING [ImportInterface, UnimportInterface], Rules USING [Create], TypeScript USING [ChangeLooks, Create], ViewerClasses USING [Viewer], ViewerIO USING [CreateViewerStreams], ViewerOps USING [AddProp, ComputeColumn, CreateViewer, MoveViewer, OpenIcon, SetOpenHeight], ViewerTools USING [GetContents, MakeNewTextViewer, SetContents, SetSelection]; RPCEchoToolImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, Buttons, Commander, Containers, IO, Labels, Loader, RPCEcho, RPCEchoRpcControl, Rules, TypeScript, ViewerIO, ViewerOps, ViewerTools = { ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; Viewer: TYPE = ViewerClasses.Viewer; <<>> <> buttonHeight: INTEGER _ 0; buttonWidth: INTEGER _ 0; ClientData: TYPE = REF ClientDataRep; ClientDataRep: TYPE = RECORD [ log: STREAM _ NIL, in: STREAM _ NIL, pleaseStop: BOOLEAN _ FALSE, target: Viewer _ NIL, minDelay, maxDelay: INT _ 0, delayHist: ARRAY DelayRange OF INT _ ALL[0] ]; DelayRange: TYPE = { d1, d1a, d2, d2a, d3, d3a, d4, d4a, d5, d5a, d6, d6a, d7, d7a, d8, d8a, d9, d9a, d10, d14, d20, d28, d50, d70, d100, d140, d200, d280, d500, d700, d1000, d1400, d2000, d2800, d5000, d7000, d10000, d14000, d20000, d28000, more}; delayTime: ARRAY DelayRange OF INT = [ d1: 1000, d1a: 1500, d2: 2000, d2a: 2500, d3: 3000, d3a: 3500, d4: 4000, d4a: 4500, d5: 5000, d5a: 5500, d6: 6000, d6a: 6500, d7: 7000, d7a: 7500, d8: 8000, d8a: 8500, d9: 9000, d9a: 9500, d10: 10000, d14: 14000, d20: 20000, d28: 28000, d50: 50000, d70: 70000, d100: 100000, d140: 140000, d200: 200000, d280: 280000, d500: 500000, d700: 700000, d1000: 1000000, d1400: 1400000, d2000: 2000000, d2800: 2800000, d5000: 5000000, d7000: 7000000, d10000: 10000000, d14000: 14000000, d20000: 20000000, d28000: 28000000, more: LAST[INT]]; global: ClientData _ NIL; -- debugging Create: Commander.CommandProc = TRUSTED { viewer, buttons, log: Viewer _ NIL; data: ClientData _ NEW[ClientDataRep _ []]; global _ data; viewer _ ViewerOps.CreateViewer [ flavor: $Container, info: [name: "RPC EchoTool", column: right, iconic: TRUE, scrollable: FALSE]]; ViewerOps.AddProp[viewer, $RPCEcho, data]; { -- Kludge to find Button size temp: Buttons.Button = Buttons.Create[ info: [name: "Length:", parent: viewer, border: FALSE, wx: 0, wy: 0], proc: NIL, clientData: NIL, fork: FALSE, paint: FALSE]; buttonWidth _ temp.ww; buttonHeight _ temp.wh; Buttons.Destroy[temp]; }; log _ TypeScript.Create[ [name: "RPCEchoTool.log", wy: 27+4, parent: viewer, border: FALSE], FALSE]; [data.in, data.log] _ ViewerIO.CreateViewerStreams [ name: "RPCEchoTool.log", backingFile: "RPCEchoTool.log", viewer: log, editedStream: FALSE]; Containers.ChildXBound[viewer, log]; Containers.ChildYBound[viewer, log]; CreateButtons[data, viewer, log]; TypeScript.ChangeLooks[log, 'f]; IO.PutF[data.log, "RPC EchoTool of %G.\n", [time[Loader.BCDBuildTime[Create]]]]; ViewerOps.OpenIcon[viewer]; }; CreateButtons: ENTRY PROC[data: ClientData, parent, log: Viewer] = { child: Viewer _ NIL; kids: Viewer = Containers.Create[ info: [parent: parent, border: FALSE, scrollable: FALSE, wx: 0, wy: -9999, ww: 9999, wh: 0] ]; Containers.ChildXBound[parent, kids]; child _ MakeRule[kids, child]; child _ data.target _ MakeLabeledText[ parent: kids, sibling: child, name: "Target:", data: "ME", prev: data.target ]; child _ MakeRule[kids, child]; child _ MakeLabel[kids, child, "What: "]; child _ MakeButton[kids, child, data, "Stop", StopProc]; child _ MakeButton[kids, child, data, "Simple", DoSimple]; child _ MakeButton[kids, child, data, "10 Wds", DoTenWords]; child _ MakeButton[kids, child, data, "Pull", DoPull]; child _ MakeButton[kids, child, data, "Push", DoPush]; child _ MakeButton[kids, child, data, "Page", DoPage]; child _ MakeButton[kids, child, data, "10 Pgs", DoTenPages]; child _ MakeButton[kids, child, data, "Atom", DoAtom]; child _ MakeButton[kids, child, data, "Rope", DoRope]; child _ MakeRule[kids, child]; { kidsY: INTEGER = 2; kidsH: INTEGER = child.wy + child.wh + 2; ViewerOps.MoveViewer[viewer: log, x: 0, y: kidsY + kidsH, w: log.ww, h: parent.ch - (kids.wy + kidsH), paint: FALSE]; ViewerOps.SetOpenHeight[parent, kidsY + kidsH + 12 * buttonHeight]; IF ~parent.iconic THEN ViewerOps.ComputeColumn[parent.column]; ViewerOps.MoveViewer[viewer: kids, x: kids.wx, y: kidsY, w: kids.ww, h: kidsH]; }; }; StopProc: Buttons.ButtonProc = { <> data: ClientData _ NARROW[clientData]; data.pleaseStop _ TRUE; }; DoSimple: Buttons.ButtonProc = { <<[parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]>> ENABLE UNWIND => NULL; data: ClientData _ NARROW[clientData]; start, finish: BasicTime.GMT; calls: INT _ 0; InitDelayHist[data]; IO.PutRope[data.log, "\n\nStarting Simple test...\n"]; data.pleaseStop _ FALSE; start _ BasicTime.Now[]; RPCEchoRpcControl.ImportInterface[ interfaceName: [instance: ViewerTools.GetContents[data.target]] ]; UNTIL data.pleaseStop DO callStart: BasicTime.Pulses _ BasicTime.GetClockPulses[]; callStop: BasicTime.Pulses; RPCEcho.Simple[]; callStop _ BasicTime.GetClockPulses[]; AddToDelayHist[data, BasicTime.PulsesToMicroseconds[callStop-callStart]]; calls _ calls.SUCC; ENDLOOP; TRUSTED { RPCEchoRpcControl.UnimportInterface[]; }; finish _ BasicTime.Now[]; BEGIN callsPerSecond, msPerCall: REAL; seconds: LONG CARDINAL _ BasicTime.Period[from: start, to: finish]; IF seconds = 0 THEN seconds _ 1; callsPerSecond _ REAL[calls]/REAL[seconds]; IF calls = 0 THEN msPerCall _ 0 ELSE msPerCall _ 1000.0*seconds/calls; IO.PutF[data.log, "%5G calls in %G seconds =>\n", [integer[calls]], [cardinal[seconds]]]; IO.PutF[data.log, "%8.2F calls/second.\n", [real[callsPerSecond]] ]; IO.PutF[data.log, "%8.2F ms/call.\n", [real[msPerCall]] ]; END; PrintDelayHist[data, calls]; IO.Flush[data.log]; }; DoTenWords: Buttons.ButtonProc = { <<[parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]>> ENABLE UNWIND => NULL; data: ClientData _ NARROW[clientData]; start, finish: BasicTime.GMT; calls: INT _ 0; tenWords: RPCEcho.TenWords; InitDelayHist[data]; IO.PutRope[data.log, "\n\nStarting 10 word test...\n"]; data.pleaseStop _ FALSE; start _ BasicTime.Now[]; RPCEchoRpcControl.ImportInterface[ interfaceName: [instance: ViewerTools.GetContents[data.target]] ]; UNTIL data.pleaseStop DO callStart: BasicTime.Pulses _ BasicTime.GetClockPulses[]; callStop: BasicTime.Pulses; tenWords _ RPCEcho.SloshTenWords[tenWords]; callStop _ BasicTime.GetClockPulses[]; AddToDelayHist[data, BasicTime.PulsesToMicroseconds[callStop-callStart]]; calls _ calls.SUCC; ENDLOOP; TRUSTED { RPCEchoRpcControl.UnimportInterface[]; }; finish _ BasicTime.Now[]; BEGIN callsPerSecond, msPerCall: REAL; seconds: LONG CARDINAL _ BasicTime.Period[from: start, to: finish]; IF seconds = 0 THEN seconds _ 1; callsPerSecond _ REAL[calls]/REAL[seconds]; IF calls = 0 THEN msPerCall _ 0 ELSE msPerCall _ 1000.0*seconds/calls; IO.PutF[data.log, "%5G calls in %G seconds =>\n", [integer[calls]], [cardinal[seconds]]]; IO.PutF[data.log, "%8.2F calls/second.\n", [real[callsPerSecond]] ]; IO.PutF[data.log, "%8.2F ms/call.\n", [real[msPerCall]] ]; END; PrintDelayHist[data, calls]; IO.Flush[data.log]; }; DoPull: Buttons.ButtonProc = { <<[parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]>> ENABLE UNWIND => NULL; data: ClientData _ NARROW[clientData]; start, finish: BasicTime.GMT; calls: INT _ 0; page: RPCEcho.Page; InitDelayHist[data]; IO.PutRope[data.log, "\n\nStarting Pull Page test...\n"]; data.pleaseStop _ FALSE; start _ BasicTime.Now[]; RPCEchoRpcControl.ImportInterface[ interfaceName: [instance: ViewerTools.GetContents[data.target]] ]; UNTIL data.pleaseStop DO callStart: BasicTime.Pulses _ BasicTime.GetClockPulses[]; callStop: BasicTime.Pulses; page _ RPCEcho.PullPage[]; callStop _ BasicTime.GetClockPulses[]; AddToDelayHist[data, BasicTime.PulsesToMicroseconds[callStop-callStart]]; calls _ calls.SUCC; ENDLOOP; TRUSTED { RPCEchoRpcControl.UnimportInterface[]; }; finish _ BasicTime.Now[]; BEGIN callsPerSecond, msPerCall: REAL; seconds: LONG CARDINAL _ BasicTime.Period[from: start, to: finish]; IF seconds = 0 THEN seconds _ 1; callsPerSecond _ REAL[calls]/REAL[seconds]; IF calls = 0 THEN msPerCall _ 0 ELSE msPerCall _ 1000.0*seconds/calls; IO.PutF[data.log, "%5G calls in %G seconds =>\n", [integer[calls]], [cardinal[seconds]]]; IO.PutF[data.log, "%8.2F calls/second.\n", [real[callsPerSecond]] ]; IO.PutF[data.log, "%8.2F ms/call.\n", [real[msPerCall]] ]; END; PrintDelayHist[data, calls]; IO.Flush[data.log]; }; DoPush: Buttons.ButtonProc = { <<[parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]>> ENABLE UNWIND => NULL; data: ClientData _ NARROW[clientData]; start, finish: BasicTime.GMT; calls: INT _ 0; page: RPCEcho.Page; InitDelayHist[data]; IO.PutRope[data.log, "\n\nStarting Push Page test...\n"]; data.pleaseStop _ FALSE; start _ BasicTime.Now[]; RPCEchoRpcControl.ImportInterface[ interfaceName: [instance: ViewerTools.GetContents[data.target]] ]; UNTIL data.pleaseStop DO callStart: BasicTime.Pulses _ BasicTime.GetClockPulses[]; callStop: BasicTime.Pulses; RPCEcho.PushPage[page]; callStop _ BasicTime.GetClockPulses[]; AddToDelayHist[data, BasicTime.PulsesToMicroseconds[callStop-callStart]]; calls _ calls.SUCC; ENDLOOP; TRUSTED { RPCEchoRpcControl.UnimportInterface[]; }; finish _ BasicTime.Now[]; BEGIN callsPerSecond, msPerCall: REAL; seconds: LONG CARDINAL _ BasicTime.Period[from: start, to: finish]; IF seconds = 0 THEN seconds _ 1; callsPerSecond _ REAL[calls]/REAL[seconds]; IF calls = 0 THEN msPerCall _ 0 ELSE msPerCall _ 1000.0*seconds/calls; IO.PutF[data.log, "%5G calls in %G seconds =>\n", [integer[calls]], [cardinal[seconds]]]; IO.PutF[data.log, "%8.2F calls/second.\n", [real[callsPerSecond]] ]; IO.PutF[data.log, "%8.2F ms/call.\n", [real[msPerCall]] ]; END; PrintDelayHist[data, calls]; IO.Flush[data.log]; }; DoPage: Buttons.ButtonProc = { <<[parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]>> ENABLE UNWIND => NULL; data: ClientData _ NARROW[clientData]; start, finish: BasicTime.GMT; calls: INT _ 0; page: RPCEcho.Page; InitDelayHist[data]; IO.PutRope[data.log, "\n\nStarting Page test...\n"]; data.pleaseStop _ FALSE; start _ BasicTime.Now[]; RPCEchoRpcControl.ImportInterface[ interfaceName: [instance: ViewerTools.GetContents[data.target]] ]; UNTIL data.pleaseStop DO callStart: BasicTime.Pulses _ BasicTime.GetClockPulses[]; callStop: BasicTime.Pulses; page _ RPCEcho.SloshPage[page]; callStop _ BasicTime.GetClockPulses[]; AddToDelayHist[data, BasicTime.PulsesToMicroseconds[callStop-callStart]]; calls _ calls.SUCC; ENDLOOP; TRUSTED { RPCEchoRpcControl.UnimportInterface[]; }; finish _ BasicTime.Now[]; BEGIN callsPerSecond, msPerCall: REAL; seconds: LONG CARDINAL _ BasicTime.Period[from: start, to: finish]; IF seconds = 0 THEN seconds _ 1; callsPerSecond _ REAL[calls]/REAL[seconds]; IF calls = 0 THEN msPerCall _ 0 ELSE msPerCall _ 1000.0*seconds/calls; IO.PutF[data.log, "%5G calls in %G seconds =>\n", [integer[calls]], [cardinal[seconds]]]; IO.PutF[data.log, "%8.2F calls/second.\n", [real[callsPerSecond]] ]; IO.PutF[data.log, "%8.2F ms/call.\n", [real[msPerCall]] ]; END; PrintDelayHist[data, calls]; IO.Flush[data.log]; }; DoTenPages: Buttons.ButtonProc = { <<[parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]>> ENABLE UNWIND => NULL; data: ClientData _ NARROW[clientData]; start, finish: BasicTime.GMT; calls: INT _ 0; pages: RPCEcho.TenPages; InitDelayHist[data]; IO.PutRope[data.log, "\n\nStarting 10 Page test...\n"]; data.pleaseStop _ FALSE; start _ BasicTime.Now[]; RPCEchoRpcControl.ImportInterface[ interfaceName: [instance: ViewerTools.GetContents[data.target]] ]; UNTIL data.pleaseStop DO callStart: BasicTime.Pulses _ BasicTime.GetClockPulses[]; callStop: BasicTime.Pulses; pages _ RPCEcho.SloshTenPages[pages]; callStop _ BasicTime.GetClockPulses[]; AddToDelayHist[data, BasicTime.PulsesToMicroseconds[callStop-callStart]]; calls _ calls.SUCC; ENDLOOP; TRUSTED { RPCEchoRpcControl.UnimportInterface[]; }; finish _ BasicTime.Now[]; BEGIN callsPerSecond, msPerCall: REAL; seconds: LONG CARDINAL _ BasicTime.Period[from: start, to: finish]; IF seconds = 0 THEN seconds _ 1; callsPerSecond _ REAL[calls]/REAL[seconds]; IF calls = 0 THEN msPerCall _ 0 ELSE msPerCall _ 1000.0*seconds/calls; IO.PutF[data.log, "%5G calls in %G seconds =>\n", [integer[calls]], [cardinal[seconds]]]; IO.PutF[data.log, "%8.2F calls/second.\n", [real[callsPerSecond]] ]; IO.PutF[data.log, "%8.2F ms/call.\n", [real[msPerCall]] ]; END; PrintDelayHist[data, calls]; IO.Flush[data.log]; }; DoAtom: Buttons.ButtonProc = { <<[parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]>> ENABLE UNWIND => NULL; data: ClientData _ NARROW[clientData]; start, finish: BasicTime.GMT; calls: INT _ 0; InitDelayHist[data]; IO.PutRope[data.log, "\n\nStarting ATOM test...\n"]; data.pleaseStop _ FALSE; start _ BasicTime.Now[]; RPCEchoRpcControl.ImportInterface[ interfaceName: [instance: ViewerTools.GetContents[data.target]] ]; UNTIL data.pleaseStop DO callStart: BasicTime.Pulses _ BasicTime.GetClockPulses[]; callStop: BasicTime.Pulses; RPCEcho.DoAtom[$Atom]; callStop _ BasicTime.GetClockPulses[]; AddToDelayHist[data, BasicTime.PulsesToMicroseconds[callStop-callStart]]; calls _ calls.SUCC; ENDLOOP; TRUSTED { RPCEchoRpcControl.UnimportInterface[]; }; finish _ BasicTime.Now[]; BEGIN callsPerSecond, msPerCall: REAL; seconds: LONG CARDINAL _ BasicTime.Period[from: start, to: finish]; IF seconds = 0 THEN seconds _ 1; callsPerSecond _ REAL[calls]/REAL[seconds]; IF calls = 0 THEN msPerCall _ 0 ELSE msPerCall _ 1000.0*seconds/calls; IO.PutF[data.log, "%5G calls in %G seconds =>\n", [integer[calls]], [cardinal[seconds]]]; IO.PutF[data.log, "%8.2F calls/second.\n", [real[callsPerSecond]] ]; IO.PutF[data.log, "%8.2F ms/call.\n", [real[msPerCall]] ]; END; PrintDelayHist[data, calls]; IO.Flush[data.log]; }; DoRope: Buttons.ButtonProc = { <<[parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]>> ENABLE UNWIND => NULL; data: ClientData _ NARROW[clientData]; start, finish: BasicTime.GMT; calls: INT _ 0; InitDelayHist[data]; IO.PutRope[data.log, "\n\nStarting ROPE test...\n"]; data.pleaseStop _ FALSE; start _ BasicTime.Now[]; RPCEchoRpcControl.ImportInterface[ interfaceName: [instance: ViewerTools.GetContents[data.target]] ]; UNTIL data.pleaseStop DO callStart: BasicTime.Pulses _ BasicTime.GetClockPulses[]; callStop: BasicTime.Pulses; RPCEcho.DoRope["Mumble foo, asdfasdfasdfasdfasd"]; callStop _ BasicTime.GetClockPulses[]; AddToDelayHist[data, BasicTime.PulsesToMicroseconds[callStop-callStart]]; calls _ calls.SUCC; ENDLOOP; TRUSTED { RPCEchoRpcControl.UnimportInterface[]; }; finish _ BasicTime.Now[]; BEGIN callsPerSecond, msPerCall: REAL; seconds: LONG CARDINAL _ BasicTime.Period[from: start, to: finish]; IF seconds = 0 THEN seconds _ 1; callsPerSecond _ REAL[calls]/REAL[seconds]; IF calls = 0 THEN msPerCall _ 0 ELSE msPerCall _ 1000.0*seconds/calls; IO.PutF[data.log, "%5G calls in %G seconds =>\n", [integer[calls]], [cardinal[seconds]]]; IO.PutF[data.log, "%8.2F calls/second.\n", [real[callsPerSecond]] ]; IO.PutF[data.log, "%8.2F ms/call.\n", [real[msPerCall]] ]; END; PrintDelayHist[data, calls]; IO.Flush[data.log]; }; InitDelayHist: PROC [data: ClientData] = { data.minDelay _ INT.LAST; data.maxDelay _ INT.FIRST; data.delayHist _ ALL[0]; }; AddToDelayHist: PROC [data: ClientData, micro: INT] = { FOR d: DelayRange IN DelayRange DO -- Slow but clean IF delayTime[d] < micro THEN LOOP; data.delayHist[d] _ data.delayHist[d] + 1; EXIT; REPEAT FINISHED => ERROR; ENDLOOP; data.minDelay _ MIN[data.minDelay, micro]; data.maxDelay _ MAX[data.maxDelay, micro]; }; PrintDelayHist: PROC [data: ClientData, calls: INT] = { total: INT _ 0; IF calls = 0 THEN RETURN; IO.PutF[data.log, " Incremental Cumulative Microseconds\n"]; FOR d: DelayRange IN DelayRange DO IF data.delayHist[d] = 0 THEN LOOP; total _ total + data.delayHist[d]; IO.PutF[data.log, "%8G", [integer[data.delayHist[d]]]]; IO.PutF[data.log, "%7.2F%%", [real[100.0*data.delayHist[d]/calls]]]; IO.PutF[data.log, "%8G", [integer[total]]]; IO.PutF[data.log, "%7.2F%%", [real[100.0*total/calls]]]; IO.PutF[data.log, "%9G\n", [integer[delayTime[d]]]]; ENDLOOP; IO.PutF[data.log, "The min delay was %G microseconds.\n", [integer[data.minDelay]]]; IO.PutF[data.log, "The max delay was %G microseconds.\n", [integer[data.maxDelay]]]; }; MakeRule: PROC [parent, sibling: Viewer] RETURNS [child: Viewer] = { child _ Rules.Create[ info: [parent: parent, border: FALSE, wy: IF sibling = NIL THEN 0 ELSE sibling.wy + sibling.wh + 2, wx: 0, ww: parent.ww, wh: 1], paint: FALSE ]; Containers.ChildXBound[parent, child]; }; MakeButton: PROC [parent, sibling: Viewer, data: REF ANY, name: ROPE, proc: Buttons.ButtonProc] RETURNS[child: Viewer] = { child _ Buttons.Create[ info: [name: name, parent: parent, border: TRUE, wy: sibling.wy, wx: sibling.wx + buttonWidth - 1, ww: buttonWidth], proc: proc, clientData: data, fork: TRUE, paint: FALSE]; }; SelectorProc: TYPE = PROC [parent: Viewer, clientData: REF, value: ATOM]; Selector: TYPE = REF SelectorRec; SelectorRec: TYPE = RECORD [ value: REF ATOM, change: PROC [parent: Viewer, clientData: REF, value: ATOM], clientData: REF, buttons: LIST OF Buttons.Button, values: LIST OF ATOM ]; MakeSelector: PROC [name: ROPE, values: LIST OF ATOM, init: REF ATOM _ NIL, change: SelectorProc _ NIL, clientData: REF _ NIL, parent: Viewer, x, y: INTEGER] RETURNS [child: Viewer] = { selector: Selector _ NEW [SelectorRec _ [ value: IF init # NIL THEN init ELSE NEW [ATOM _ values.first], change: change, clientData: clientData, buttons: NIL, values: values ] ]; last: LIST OF Buttons.Button _ NIL; child _ Labels.Create[info: [name: name, parent: parent, border: FALSE, wx: x, wy: y] ]; FOR a: LIST OF ATOM _ values, a.rest UNTIL a = NIL DO child _ Buttons.Create[ info: [name: Atom.GetPName[a.first], parent: parent, border: TRUE, wx: child.wx + child.ww + 2, wy: child.wy], proc: SelectorHelper, clientData: selector, fork: TRUE, paint: TRUE]; IF last = NIL THEN last _ selector.buttons _ CONS[first: child, rest: NIL] ELSE { last.rest _ CONS[first: child, rest: NIL]; last _ last.rest }; IF a.first = selector.value^ THEN Buttons.SetDisplayStyle[child, $WhiteOnBlack]; ENDLOOP; }; SelectorHelper: Buttons.ButtonProc = { <> self: Buttons.Button = NARROW[parent]; selector: Selector = NARROW[clientData]; buttons: LIST OF Buttons.Button _ selector.buttons; FOR a: LIST OF ATOM _ selector.values, a.rest UNTIL a = NIL DO IF self = buttons.first THEN { selector.value^ _ a.first; IF selector.change # NIL THEN selector.change[self.parent, selector.clientData, a.first]; Buttons.SetDisplayStyle[buttons.first, $WhiteOnBlack]; } ELSE Buttons.SetDisplayStyle[buttons.first, $BlackOnWhite]; buttons _ buttons.rest; ENDLOOP; }; BoolProc: TYPE = PROC [parent: Viewer, clientData: REF, value: BOOL]; Bool: TYPE = REF BoolRec; BoolRec: TYPE = RECORD [ value: REF BOOL, change: BoolProc, clientData: REF, button: Viewer ]; MakeBool: PROC [name: ROPE, init: REF BOOL, change: BoolProc _ NIL, clientData: REF _ NIL, parent: Viewer, x, y: INTEGER] RETURNS [child: Viewer] = { bool: Bool _ NEW [BoolRec _ [ value: IF init # NIL THEN init ELSE NEW [BOOL _ TRUE], change: change, clientData: clientData, button: NIL ] ]; child _ Buttons.Create[ info: [name: name, parent: parent, border: TRUE, wx: x, wy: y], proc: BoolHelper, clientData: bool, fork: TRUE, paint: TRUE]; bool.button _ child; IF bool.value^ THEN Buttons.SetDisplayStyle[child, $WhiteOnBlack]; }; BoolHelper: Buttons.ButtonProc = { <> self: Buttons.Button = NARROW[parent]; bool: Bool = NARROW[clientData]; bool.value^ _ ~bool.value^; IF bool.value^ THEN Buttons.SetDisplayStyle[bool.button, $WhiteOnBlack] ELSE Buttons.SetDisplayStyle[bool.button, $BlackOnWhite]; IF bool.change # NIL THEN bool.change[self.parent, bool.clientData, bool.value^]; }; MakeLabel: PROC [parent, sibling: Viewer, name: ROPE] RETURNS [child: Viewer] = { child _ Labels.Create[ info: [name: name, parent: parent, border: FALSE, wy: sibling.wy + sibling.wh + (IF sibling.class.flavor = $Button THEN -1 ELSE 2), wx: 2], paint: FALSE ]; }; MakeLabeledText: PROC [ parent, sibling: Viewer, name, data: ROPE, prev: Viewer, newline: BOOL _ TRUE] RETURNS [child: Viewer] = { x: INTEGER = IF newline THEN 2 ELSE sibling.wx + sibling.ww + 10; y: INTEGER = IF newline THEN sibling.wy + sibling.wh + 1 ELSE sibling.wy; child _ ViewerTools.MakeNewTextViewer[ info: [parent: parent, wh: buttonHeight, ww: 999, scrollable: TRUE, data: IF prev = NIL THEN data ELSE ViewerTools.GetContents[prev], border: FALSE, wx: x + buttonWidth + 2, wy: y], paint: FALSE ]; Containers.ChildXBound[parent, child]; [] _ Buttons.Create[ info: [name: name, parent: parent, wh: buttonHeight, border: FALSE, wx: x, wy: y], proc: LabeledTextProc, clientData: child, fork: FALSE, paint: FALSE]; RETURN[child]; }; LabeledTextProc: Buttons.ButtonProc = { <> text: Viewer = NARROW[clientData]; SELECT mouseButton FROM red => ViewerTools.SetSelection[text, NIL]; yellow => NULL; blue => { ViewerTools.SetContents[text, NIL]; ViewerTools.SetSelection[text, NIL] }; ENDCASE => ERROR; }; Commander.Register["RPCEchoTool", Create, "Simple RPC Tests."]; Commander.Register["RPCEchoToolImpl", Create, "Simple RPC Tests."]; -- Debugging hack }.