-- file: ChollaStep.mesa
-- edited by Barth, August 17, 1981 1:04 AM
-- edited by Crowther, January 14, 1982 12:03 PM
-- edited by Brotz, March 3, 1983 5:33 PM

DIRECTORY
Ascii,
ccD: FROM "ChollaCmdDefs",
dsD: FROM "DisplayDefs",
DMSTimeDefs,
Editor,
exD: FROM "ExceptionDefs",
inD: FROM "InteractorDefs",
intCommon,
MessageParse,
opD: FROM "OperationsDefs",
prD: FROM "ProtectionDefs",
Storage,
String,
TimeDefs,
vmD: FROM "VirtualMgrDefs";

ChollaStep: PROGRAM
IMPORTS ccD, dsD, DMSTimeDefs, Editor, exD, inD, intC: intCommon, MessageParse,
opD, prD, Storage, String, TimeDefs, vmD
EXPORTS ccD =
BEGIN


currentStepSpecName: STRING ← [ccD.maxSpecNameLength]; -- from step list
currentStepDataName: STRING ← [ccD.maxSpecNameLength];
currentProcessName: STRING ← [ccD.maxProcessNameLength];
currentStepName: STRING ← [50];

currentStep: ccD.ParsedStep ←
[currentStepSpecName, 0, currentStepDataName, 0, currentStepName, notStarted, 0, 0];

currentStepNotStarted: BOOLEAN ← TRUE; -- valid only when ccD.currentStepNumber#0.
invalidDataFile: BOOLEAN ← TRUE;
mySpecVersion: CARDINAL;


StepOperation: PUBLIC PROCEDURE [stepListString: STRING] =
-- display the spec and data for the current step. "stepListString" contains a toc string
-- from which the spec name, version, data form name, version and parameters may be
-- extracted.
BEGIN
specName: STRING ← [ccD.tocCacheFileStringLength];
ccD.ParseStep[stepListString, @currentStep];
String.AppendString[specName, currentStep.specName];
String.AppendString[specName, "Spec"L];
SpecCmd[specName, currentStep.specVersion];
IF currentStep.dataName.length = 0
THEN String.AppendString[currentStep.dataName, currentStep.specName];
PreserveChangeInDoneFile[];
FillLowerWindowWithDataForm
[currentStep.dataName, currentStep.dataVersion, stepListString];
ccD.iOwnThisStep ← FALSE;
invalidDataFile ← FALSE;
END; -- of StepOperation --


SpecCommand: PUBLIC inD.CommandProcedure=
-- display the spec for the step typed into the brackets
BEGIN
cm: vmD.ComposedMessagePtr = vmD.ComposedMessage[intC.cmTextNbr.message];
name: STRING ← [ccD.tocCacheFileStringLength]; --my copy of the file name
version: INTEGER ← 0; -- each message in the file is a different version
IF ~ccD.IsAuthorized[onlooker, hp] THEN RETURN;
IF ~confirmed AND ~inD.ConfirmBrackets[hp: hp.nextHouse] THEN RETURN;
version ← ParseSpecBrackets[hp, name]; --fills in name too
IF version < 0 THEN RETURN; --something went wrong
ccD.DoLowerMenu[ccD.stepListLowerHp];
PreserveChangeInDoneFile[];
SpecCmd[name, version];
invalidDataFile ← TRUE;
END; -- of SpecCommand --


ParseSpecBrackets: PROCEDURE [hp: inD.HousePtr, name: STRING]
RETURNS [version: INTEGER] =
-- Breaks apart hp.nextHouse.text of form "name!version" (no blanks allowed) into name
-- and version, which are returned in the respectively named variables.
-- N.B. Blanks terminate brackets typein, so there won’t be any in hp.nextHouse.text.
BEGIN
s: STRING = hp.nextHouse.text; --the string as typed into the brackets
dotFound: BOOLEAN ← FALSE;
version ← 0;
name.length ← s.length;
FOR i: CARDINAL IN [0 .. s.length) DO
SELECT s[i] FROM
’. => dotFound ← TRUE;
’! => BEGIN
versionString: STRING ← [10];
name.length ← i;
versionString.length ← s.length - i - 1;
FOR j: CARDINAL IN [0 .. versionString.length) DO
versionString[j] ← s[i + 1 + j];
ENDLOOP;
version ← String.StringToDecimal[versionString ! String.InvalidNumber => GOTO bad];
EXIT;
EXITS bad=> {exD.DisplayExceptionString["Illegal number."L]; RETURN[-1]};
END;
ENDCASE;
name[i] ← s[i];
ENDLOOP;
IF ~dotFound THEN String.AppendString[name, "Spec.chml"L];
END; -- of ParseSpecBrackets --


SpecCmd: PROCEDURE [specName: STRING, messageId: CARDINAL] =
--given a file name and a message number in the file,
-- put that selected message in the display window. Presumably it contains insructions for how to do the step
--Should the messageId happen to be 0, the correct messageId will be found in the first message of the file under Current Version: <INTEGER>
BEGIN
dm: vmD.DisplayMessagePtr = vmD.DisplayMessage[intC.dmTextNbr.message];
key: CARDINAL;
toc: vmD.TOCHandle;
ccD.CleanupDisplayMessage[];
ccD.activeStepSpecName.length ← 0;
String.AppendString[ccD.activeStepSpecName, specName];
[toc, key] ← ccD.GetTOCForFile[specName, spec];
IF toc.indexFF = 1 THEN
{exD.DisplayExceptionString["Not a spec file!"L]; vmD.UnlockTOC[toc, key]; RETURN};
IF messageId = 0 THEN messageId ← CurrentVersionMessageID[toc, key];
IF messageId + 1 IN (1 .. vmD.FirstFreeTOCIndex[toc, key]) THEN
BEGIN
mySpecVersion ← messageId;
IF RetrieveFromArchiveIfNecessary[toc, key, specName, messageId] THEN
BEGIN
vmD.LoadDisplayMessage[toc, key, messageId + 1, dm];
WriteMessageIntoDisplayWindow[];
END;
END
ELSE exD.DisplayExceptionString["No such version."L];
vmD.UnlockTOC[toc, key];
END; -- of SpecCmd --


RetrieveFromArchiveIfNecessary: PROCEDURE
[toc: vmD.TOCHandle, key: CARDINAL, name: STRING, version: CARDINAL]
RETURNS [haveIt: BOOLEAN] =
BEGIN
fp: vmD.TOCFixedPart;
vmD.GetTOCFixedPart[toc, key, version + 1, @fp];
IF fp.mark = ’a THEN
BEGIN
cm: vmD.ComposedMessagePtr ← vmD.AllocateComposedMessageObject[];
haveIt ← ccD.RetrieveFromArchive[name, version, cm];
IF haveIt THEN
[] ← opD.ReplaceMailOperation
[delete: TRUE, index: version + 1, msg: cm, toc: toc, key: key];
vmD.FreeVirtualMessageObject[cm];
END
ELSE haveIt ← TRUE;
END; -- of RetrieveFromArchiveIfNecessary --


WriteMessageIntoDisplayWindow: PROCEDURE =
BEGIN
mnp: inD.MessageTextNbrPtr = intC.dmTextNbr;
mnp.message.formatStart ← mnp.message.formatEnd ← 0;
mnp.haveMessage ← TRUE;
dsD.ClearRectangle[inD.leftMargin, inD.rightMargin, mnp.topY, mnp.bottomY];
Editor.RefreshSoThatFirstCharStartsLine[firstChar: 0, firstLine: mnp.lines, mnp: mnp];
END;


FillLowerWindowWithDataForm: PROCEDURE
[dataName: STRING, messageId: CARDINAL, tocString: STRING] =
-- Given a file name and given a messageId, put that selected message in the lower
-- window. Presumably it contains a blank data form for the step
-- Should the messageId happen to be 0, the correct messageId will be found in the first
-- message of the file under Current Version: <INTEGER>
BEGIN
toc: vmD.TOCHandle;
key: CARDINAL;
dm: vmD.DisplayMessagePtr ← vmD.AllocateDisplayMessageObject[];
fileName: STRING ← [ccD.tocCacheFileStringLength];
index, firstLegalIndex: vmD.TOCIndex;
fieldList: MessageParse.FieldList ← NIL;
ccD.currentDoneFileName.length ← 0;
String.AppendString[ccD.currentDoneFileName, ccD.stepListRunName];
String.AppendString[ccD.currentDoneFileName, "Done.chml"L];
ccD.currentStepNumber ← ccD.CurrentStep[];
currentStepNotStarted ← currentStep.status = notStarted;
BEGIN -- for EXITS
IF currentStepNotStarted THEN
BEGIN
String.AppendString[fileName, dataName];
String.AppendString[fileName, "Data"L];
[toc, key] ← ccD.GetTOCForFile[fileName, data];
IF toc.indexFF = 1 THEN
{exD.DisplayExceptionString["Not a data form file!"L]; GO TO Return};
IF messageId = 0 THEN messageId ← CurrentVersionMessageID[toc, key];
index ← messageId + 1;
firstLegalIndex ← 2;
END
ELSE BEGIN
[toc, key] ← ccD.GetTOCForFile[ccD.currentDoneFileName, done];
index ← ccD.currentStepNumber;
firstLegalIndex ← 1;
END;
IF index IN [firstLegalIndex .. vmD.FirstFreeTOCIndex[toc, key]) THEN
BEGIN
IF currentStepNotStarted
AND ~RetrieveFromArchiveIfNecessary[toc, key, fileName, messageId]
THEN GO TO Return;
vmD.LoadDisplayMessage[toc, key, index, dm];
fieldList ← MessageParse.MakeFieldList[dm];
WriteMessageIntoComposeWindow[dm, @fieldList];
vmD.FlushDisplayMessage[dm, key];
IF currentStepNotStarted AND currentStep.paramIndex # 0 THEN
BEGIN -- fill in the parameters.
i: CARDINAL ← currentStep.paramIndex;
name: STRING ← Storage.String[50];
value: STRING ← Storage.String[100];
stringToFill: POINTER TO STRING;
char: CHARACTER;
cm: vmD.ComposedMessagePtr = vmD.ComposedMessage[intC.cmTextNbr.message];
IF i >= tocString.length OR tocString[i] # ’( THEN exD.SysBug[];
DO
initialWhite: BOOLEAN ← TRUE;
name.length ← value.length ← 0;
stringToFill ← @name;
FOR i ← i + 1, i + 1 UNTIL i >= tocString.length DO
SELECT char ← tocString[i] FROM
’), ’; => EXIT;
’: => {stringToFill ← @value; initialWhite ← TRUE};
’/ => FOR i ← i + 1, i + 1 UNTIL i >= tocString.length OR tocString[i] = ’/
DO ENDLOOP;
ENDCASE =>
IF ~initialWhite OR ~(initialWhite ← dsD.GetCharProperty[char, white]) THEN
BEGIN
IF stringToFill↑.length + 2 >= stringToFill↑.maxlength THEN
Storage.ExpandString[stringToFill, stringToFill↑.maxlength / 2];
String.AppendChar[stringToFill↑, char];
END;
ENDLOOP;
IF name.length > 0 THEN
BEGIN
IF ~String.EquivalentString[name, "Replace"L]
AND ~String.EquivalentString[name, "Append"L] THEN
FOR i: CARDINAL IN [0 .. value.length) DO
IF value[i] = Ascii.ControlA THEN EXIT;
REPEAT
FINISHED => String.AppendString[value, "̌"L];
ENDLOOP;
MessageParse.ReplaceOrAppendField[cm, @fieldList, name, value];
END;
IF i >= tocString.length OR char = ’) THEN EXIT;
ENDLOOP;
Storage.FreeString[name];
Storage.FreeString[value];
END;
ResetTheEditor[FALSE, fieldList];
END
ELSE exD.DisplayExceptionString["No such version."L];
EXITS
Return => NULL;
END; -- of block for EXITS
vmD.FreeVirtualMessageObject[dm];
vmD.UnlockTOC[toc, key];
MessageParse.FreeFieldList[fieldList];
END; -- of FillLowerWindowWithDataForm --


WriteMessageIntoComposeWindow: PUBLIC PROCEDURE
[newm: vmD.VirtualMessagePtr, fieldListPtr: MessageParse.FieldListPtr] =
BEGIN
oldm: vmD.ComposedMessagePtr = vmD.ComposedMessage[intC.cmTextNbr.message];
oldSize: vmD.CharIndex ← vmD.GetMessageSize[oldm];
newSize: vmD.CharIndex ← vmD.GetMessageSize[newm];
vmD.ReplaceRangeInMessage[[0, oldSize, oldm], [0, newSize, newm]];
MessageParse.DeleteField[oldm, fieldListPtr, "UniqueID"L];
MessageParse.DeleteField[oldm, fieldListPtr, "ThisID"L];
END; -- of WriteMessageIntoComposeWindow --


ResetTheEditor: PUBLIC PROCEDURE
[keepDeletionBuffer: BOOLEAN ← FALSE, fieldList: MessageParse.FieldList ← NIL] =
BEGIN
mnp: inD.MessageTextNbrPtr = intC.cmTextNbr;
composedMessage: vmD.ComposedMessagePtr = vmD.ComposedMessage[mnp.message];
targetRange: vmD.MessageRange ← [0, 0, composedMessage];
Editor.ResetInsertionBuffer[mnp];
IF ~keepDeletionBuffer THEN Editor.ResetDeletionBuffer[mnp];
intC.commandType ← IF keepDeletionBuffer THEN get ELSE noCommand;
IF intC.source.key # 0 THEN exD.SysBug[];
IF mnp.protectedFieldPtr # NIL THEN prD.UnprotectAllFields[@mnp.protectedFieldPtr];
UNTIL fieldList = NIL DO
prD.ProtectNewField
[pfpp: @mnp.protectedFieldPtr,
range: [MAX[fieldList.start, 1] - 1, fieldList.valueStart, composedMessage]];
fieldList ← fieldList.next;
ENDLOOP;
prD.FindUnprotectedSubrange[mnp.protectedFieldPtr, @targetRange, TRUE];
intC.source ← intC.target ←
[mnp: mnp, key: 0, start: targetRange.start, end: targetRange.start, point: targetRange.start,
mode: char, pendingDelete: FALSE];
intC.newTargetSelection ← TRUE;
intC.actionPoint ← targetRange.start;
intC.pendingDeleteSetByControl ← FALSE;
composedMessage.formatStart ← composedMessage.formatEnd ← 0;
Editor.RefreshSoThatFirstCharStartsLine[firstChar: 0, firstLine: mnp.lines, mnp: mnp];
mnp.haveMessage ← TRUE;
intC.runCommandMode ← FALSE;
intC.composedMessageEdited ← FALSE;
END; -- ResetTheEditor --


CurrentVersionMessageID: PROCEDURE [toc: vmD.TOCHandle, key: CARDINAL]
RETURNS [messageID: vmD.TOCIndex] =
-- Returns the messageID of the spec or data form that is the current version in toc. The
-- messageID is one less than the TOCIndex of that message.
BEGIN
-- First search the toc for a message marked with *. If none is found, then look in the
-- first message for the current version, and mark the corresponding message with a *
-- for future reference.
fp: vmD.TOCFixedPart;
dm: vmD.DisplayMessagePtr;
fieldList: MessageParse.FieldList;
FOR i: vmD.TOCIndex IN [1 .. vmD.FirstFreeTOCIndex[toc, key]) DO
vmD.GetTOCFixedPart[toc, key, i, @fp];
IF fp.mark = ’* AND i > 1 THEN RETURN[i - 1];
ENDLOOP;
dm ← vmD.AllocateDisplayMessageObject[];
vmD.LoadDisplayMessage[toc, key, 1, dm];
fieldList ← MessageParse.MakeFieldList[dm];
messageID ← MessageParse.GetNumberFromField[dm, fieldList, "CurrentVersion"L];
vmD.FlushDisplayMessage[dm, key];
vmD.FreeVirtualMessageObject[dm];
MessageParse.FreeFieldList[fieldList];
vmD.GetTOCFixedPart[toc, key, messageID + 1, @fp];
IF fp.mark # ’a THEN
{fp.mark ← ’*; vmD.PutTOCFixedPart[toc, key, messageID + 1, @fp]};
END; -- of CurrentVersionMessageID --


-- ///////// THE LOWER WINDOW COMMANDS

-- to understand these procedures you must understand the lower window utilities; briefly:
-- GetStatus plucks the Status from currentStep (may fail if no current step)
-- CheckStatus deals with inappropiate status for commands
-- FillAField finds (or makes) a field and replaces it with a string
-- MakeTimeField is FillAField with the current time
-- MakeitHappen refreshes the window and sends the message


StartStepCommand: PUBLIC inD.CommandProcedure =
BEGIN
-- send a message via a routine exported by chollacmd that updates the
-- bulletin board and the step list entry. The bulletin board change should
-- be of the form "add this active step to the bulletin board list" rather
-- than replacing the entire entry for the run in order to avoid update
-- conflicts. The step list entry must change to point to the bound
-- specification and the new data record entry created in the done file
-- for the run.
cm: vmD.ComposedMessagePtr = vmD.ComposedMessage[intC.cmTextNbr.message];
st: STRING ← [10];
cantStart: STRING = "You cannot start a step twice."L;
status: ccD.StepStatus;
fieldList: MessageParse.FieldList ← NIL;
IF ~ccD.IsAuthorized[technician, hp] THEN RETURN;
ccD.iOwnThisStep ← TRUE;
status ← GetStatus[];
IF status = none THEN {ccD.iOwnThisStep ← FALSE; RETURN};
IF ~CheckStatus
[status: status,
notStarted: NIL, -- Returns TRUE if status = notStarted --
started: cantStart,
inProcess: cantStart,
processDone: cantStart,
rejected: cantStart,
finished: cantStart]
THEN RETURN;
currentStepNotStarted ← FALSE;
currentStep.status ← started;
CheckTechnician[intC.user.name, currentStep.specName, mySpecVersion];
st.length ← 0;
String.AppendDecimal[st, ccD.currentStepNumber];
fieldList ← MessageParse.MakeFieldList[cm];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Run"L, ccD.stepListRunName];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Step"L, st];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Status"L, "Started"L];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Started-By"L, intC.user.name];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Spec"L, currentStep.specName];
st.length ← 0;
String.AppendDecimal[st, mySpecVersion];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Version"L, st];
MakeTimeField[cm, @fieldList, "StartTime"L];
ReplaceSpecialPlaceholders[cm, @fieldList];
FillInIncludedFields[cm, @fieldList];
SendDataMail[cm, @fieldList, new];
MessageParse.FreeFieldList[fieldList];
END; -- of StartStepCommand --


ProcessStepCommand: PUBLIC inD.CommandProcedure =
BEGIN
-- update the bulletin board and step list, check to see if on proper
-- workstation first. Startup the equipment.
cm: vmD.ComposedMessagePtr = vmD.ComposedMessage[intC.cmTextNbr.message];
error: STRING;
fieldList: MessageParse.FieldList ← NIL;
IF ~ccD.IsAuthorized[technician, hp] THEN RETURN;
IF ~CheckStatus
[status: GetStatus[],
notStarted: "Must start before process."L,
started: NIL,
inProcess: "Already in process."L,
processDone: "Can’t process twice."L,
rejected: "Can’t process a rejected step."L,
finished: "Can’t process a finished step."L]
THEN RETURN;
BEGIN -- for EXITS --
fieldList ← MessageParse.MakeFieldList[cm];
MessageParse.GetStringFromField[cm, fieldList, "Process"L, currentProcessName
! MessageParse.ParseFault => GOTO Bad];
IF currentProcessName.length = 0 THEN GO TO Bad;
error ← ccD.MachineDriver
[data: cm, spec: intC.dmTextNbr.message, name: currentProcessName];
IF error # NIL THEN {exD.DisplayExceptionString[error]; RETURN};
MakeTimeField[cm, @fieldList, "ProcessStartTime"L];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Processed-By"L, intC.user.name];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Status"L, "In Process"L];
currentStep.status ← inProcess;
SendDataMail[cm, @fieldList, old];
EXITS
Bad => exD.DisplayExceptionString["Not a process type step."L];
END; -- of EXITS block --
MessageParse.FreeFieldList[fieldList];
END; -- of ProcessStepCommand --


CancelStepCommand: PUBLIC inD.CommandProcedure =
BEGIN
-- check to see if step in automatic processing. If not then bitch.
-- Update bulletin board and step list.
cm: vmD.ComposedMessagePtr = vmD.ComposedMessage[intC.cmTextNbr.message];
status: ccD.StepStatus;
fieldList: MessageParse.FieldList ← NIL;
IF ~ccD.IsAuthorized[technician, hp] THEN RETURN;
status ← GetStatus[];
IF ~CheckStatus
[status: status,
notStarted: "Must start before cancelling."L,
started: "This step is not in automatic processing."L,
inProcess: NIL,
processDone: "Processing has already finished."L,
rejected: "This step has already been rejected."L,
finished: "This step has already been finished."L]
THEN RETURN;
SELECT status FROM
started, processDone => NULL;
inProcess => IF ~ccD.MachineCancel
[data: cm, spec: intC.dmTextNbr.message, name: currentProcessName] THEN
{exD.DisplayExceptionString["Cannot cancel!"L]; RETURN};
ENDCASE => exD.SysBug[];
fieldList ← MessageParse.MakeFieldList[cm];
MakeTimeField[cm, @fieldList, "CancelledTime"L];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Cancelled-By"L, intC.user.name];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Status"L, "Rejected"L];
currentStep.status ← rejected;
SendDataMail[cm, @fieldList, old];
MessageParse.FreeFieldList[fieldList];
END; -- of CancelStepCommand --


ProcessDoneStepCommand: PUBLIC inD.CommandProcedure =
BEGIN
-- check to see if step started, if not bitch. Update bulletin board
-- and step list with new state.
cm: vmD.ComposedMessagePtr = vmD.ComposedMessage[intC.cmTextNbr.message];
fieldList: MessageParse.FieldList ← NIL;
IF ~ccD.IsAuthorized[technician, hp] THEN RETURN;
IF ~CheckStatus
[status: GetStatus[],
notStarted: "No process running."L,
started: NIL,
inProcess: "Process is still under way."L,
processDone: "It’s already done."L,
rejected: "No process running."L,
finished: "No process running."L]
THEN RETURN;
fieldList ← MessageParse.MakeFieldList[cm];
MakeTimeField[cm, @fieldList, "ProcessFinishTime"L];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Processed-By"L, intC.user.name];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Status"L, "Processed"L];
currentStep.status ← processDone;
SendDataMail[cm, @fieldList, old];
MessageParse.FreeFieldList[fieldList];
END; -- of ProcessDoneStepCommand --


PreserveChangeInDoneFile: PUBLIC PROCEDURE =
BEGIN
cm: vmD.ComposedMessagePtr ← vmD.ComposedMessage[intC.cmTextNbr.message];
fieldList: MessageParse.FieldList;
IF ccD.currentStepNumber = 0 THEN RETURN;
IF intC.composedMessageEdited AND ~currentStepNotStarted THEN
BEGIN
exD.DisplayBothExceptionLines
[string1: "The current data form has been changed."L, exception1: exD.nil,
string2: "Type ESC to save it in the Done file, DEL to proceed without saving it."L,
exception2: exD.nil];
IF inD.Confirm[0] THEN
BEGIN
fieldList ← MessageParse.MakeFieldList[cm];
SetEditedBy[cm, @fieldList, 0];
SendDataMail[cm, @fieldList, old];
MessageParse.FreeFieldList[fieldList];
END;
END;
vmD.InitComposedMessage[cm, ""L];
ccD.currentStepNumber ← 0;
intC.composedMessageEdited ← ccD.iOwnThisStep ← FALSE;
ResetTheEditor[];
END; -- of PreserveChangeInDoneFile --


RejectStepCommand: PUBLIC inD.CommandProcedure =
BEGIN
cm: vmD.ComposedMessagePtr = vmD.ComposedMessage[intC.cmTextNbr.message];
fieldList: MessageParse.FieldList ← NIL;
IF ~ccD.IsAuthorized[technician, hp] THEN RETURN;
IF ~CheckStatus
[status: GetStatus[],
notStarted: "Must start before rejecting."L,
started: NIL,
inProcess:
"Can’t reject until process finishes. You may wish to invoke ""Cancel"" first."L,
processDone: NIL,
rejected: "Step has already been rejected."L,
finished: "Step has already been finished."L]
THEN RETURN;
fieldList ← MessageParse.MakeFieldList[cm];
MakeTimeField[cm, @fieldList, "RejectedTime"L];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Rejected-By"L, intC.user.name];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Status"L, "Rejected"L];
currentStep.status ← rejected;
SendDataMail[cm, @fieldList, old];
ShipToInterestedOnlookers[cm, @fieldList];
MessageParse.FreeFieldList[fieldList];
END; -- of RejectStepCommand --


FinishStepCommand: PUBLIC inD.CommandProcedure =
BEGIN
-- check to see if started, if not bitch. check to see if all fields of
-- data record are filled out. If not ask technician to confirm. If confirmed
-- then send exception message to line manager indicating the run, step,
-- date/time, and technician name. If not confirmed forget the whole thing.
-- send update message to bulletin board removing step from active list,
-- maybe if that reduces the active step list on the bulletin board to nil
-- then we should post the next unstarted step on the bulletin board.
-- update the step list to reflect new state of step.
cm: vmD.ComposedMessagePtr = vmD.ComposedMessage[intC.cmTextNbr.message];
fieldList: MessageParse.FieldList ← NIL;
IF ~ccD.IsAuthorized[technician, hp] THEN RETURN;
IF ~CheckStatus
[status: GetStatus[],
notStarted: "Must start before finishing."L,
started: NIL,
inProcess: "Can’t finish until process finishes."L,
processDone: NIL,
rejected: "Step has already been rejected."L,
finished: "Can’t finish twice."L]
THEN RETURN;
FOR i: vmD.CharIndex IN [0 .. vmD.GetMessageSize[cm]) DO
IF vmD.GetMessageChar[cm, i] = Ascii.ControlA THEN
BEGIN
exD.DisplayExceptionStringOnLine["A field of the data form is not filled in!"L, 1];
IF ~ccD.IsAuthorized[kahuna, hp, FALSE] THEN
BEGIN
exD.DisplayExceptionStringOnLine
["Please correct the form and try the Finish command again."L, 2];
exD.FlashExceptionsRegion[];
RETURN;
END;
IF inD.Confirm[2] THEN EXIT ELSE RETURN;
END;
ENDLOOP;
fieldList ← MessageParse.MakeFieldList[cm];
MakeTimeField[cm, @fieldList, "FinishTime"L];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Finished-By"L, intC.user.name];
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Status"L, "Finished"L];
currentStep.status ← finished;
SendDataMail[cm, @fieldList, old];
ShipToInterestedOnlookers[cm, @fieldList];
MessageParse.FreeFieldList[fieldList];
END; -- of FinishStepCommand --


-- LOWER WINDOW UTILITIES


GetStatus: PROCEDURE RETURNS [status: ccD.StepStatus] =
-- Parses "vm", looking for a "Status:" field. If not found, then an error message is
-- returned flashed, and "none" is returned.
BEGIN
noStatus: STRING = "No status field: malformed data form."L;
noStepSelected: STRING = "No step selected."L;
IF invalidDataFile THEN {exD.DisplayExceptionString[noStepSelected]; RETURN[none]};
IF ~ccD.iOwnThisStep AND ~(ccD.iOwnThisStep ← inD.Confirm[1]) THEN RETURN[none];
status ← currentStep.status;
IF status = none THEN exD.DisplayExceptionString[noStatus];
END; -- of GetStatus --


CheckStatus: PROCEDURE [status: ccD.StepStatus,
notStarted, started, inProcess, processDone, rejected, finished: STRING]
RETURNS [ok: BOOLEAN] =
BEGIN
ans: STRING;
IF status = none THEN RETURN[FALSE];
ans ← SELECT status FROM
started => started,
notStarted => notStarted,
inProcess => inProcess,
processDone => processDone,
rejected => rejected,
finished => finished,
ENDCASE => ERROR;
ok ← ans = NIL;
IF ~ok THEN exD.DisplayExceptionString[ans];
END; -- of CheckStatus --


FillInIncludedFields: PROCEDURE
[cm: vmD.ComposedMessagePtr, fieldListPtr: MessageParse.FieldListPtr] =
BEGIN
IncludeType: TYPE = {replace, append};
fields: ARRAY IncludeType OF MessageParse.FieldList;
dm: vmD.DisplayMessagePtr;
replaceCount, appendCount: CARDINAL;
stepListToc, doneToc: vmD.TOCHandle;
stepListKey, doneKey: CARDINAL;
[fields[replace], replaceCount] ← MessageParse.LocateField[fieldListPtr↑, "Replace"L];
[fields[append], appendCount] ← MessageParse.LocateField[fieldListPtr↑, "Append"L];
IF replaceCount = 0 AND appendCount = 0 THEN RETURN;
dm ← vmD.AllocateDisplayMessageObject[];
stepListToc ← intC.tocTextNbr.toc;
stepListKey ← vmD.WaitForLock[stepListToc];
[doneToc, doneKey] ← ccD.GetTOCForFile[ccD.currentDoneFileName, done];
FOR type: IncludeType IN IncludeType DO
fieldList: MessageParse.FieldList ← fields[type];
IF (type = replace AND replaceCount # 0) OR (type = append AND appendCount # 0)
THEN BEGIN
charIndex: vmD.CharIndex ← fieldList.valueStart;
spec: STRING ← [50];
field: STRING ← [50];
currentField: STRING ← [50];
specName: STRING ← [ccD.tocCacheFileStringLength - 9];
dataName: STRING ← [0];
stepName: STRING ← [0];
tocString: STRING ← [opD.maxTOCStringLength];
parsedStep: ccD.ParsedStep ← [specName, 0, dataName, 0, stepName, notStarted, 0, 0];
index: vmD.TOCIndex;

GetField: PROCEDURE [s: STRING] RETURNS [BOOLEAN] =
BEGIN
charIndex ← MessageParse.GetNextWord[cm, charIndex, fieldList.valueEnd, s];
IF s.length = 0 THEN
exD.DisplayExceptionString["Included fields must be specified properly."L];
RETURN[s.length # 0];
END; -- of GetField --

DO
charIndex ← MessageParse.GetNextWord
[cm, charIndex, fieldList.valueEnd, currentField];
IF currentField.length = 0 THEN EXIT;
IF ~GetField[spec] THEN EXIT;
IF ~GetField[field] THEN EXIT;
FOR index DECREASING IN [1 .. MIN[doneToc.indexFF, ccD.currentStepNumber]) DO
vmD.GetTOCString[stepListToc, stepListKey, index, tocString];
ccD.ParseStep[tocString, @parsedStep];
IF String.EquivalentString[parsedStep.specName, spec] THEN
BEGIN
fl: MessageParse.FieldList;
count: CARDINAL;
vmD.LoadDisplayMessage[doneToc, doneKey, index, dm];
fl ← MessageParse.MakeFieldList[dm];
[fl, count] ← MessageParse.LocateField[fl, field];
IF count > 0 THEN
BEGIN
cmSize: vmD.CharIndex;
fromRange: vmD.MessageRange ← [fl.valueStart, fl.valueEnd, dm];
cfl: MessageParse.FieldList;
delta: INTEGER;
[cfl, count] ← MessageParse.LocateField[fieldListPtr↑, currentField];
IF count = 0 THEN
BEGIN
MessageParse.ReplaceOrAppendField[cm, fieldListPtr, currentField, " "L];
[cfl, count] ← MessageParse.LocateField[fieldListPtr↑, currentField];
END;
cmSize ← vmD.GetMessageSize[cm];
IF type = replace THEN
BEGIN
vmD.ReplaceRangeInMessage
[to: [cfl.valueStart, cfl.valueEnd, cm], from: fromRange];
cfl.valueEnd ← cfl.valueStart + (fromRange.end - fromRange.start);
END
ELSE BEGIN -- type = append --
vmD.StartMessageInsertion[cm, cfl.valueEnd];
vmD.InsertStringInMessage[cm, "

"L];
vmD.StopMessageInsertion[cm];
vmD.InsertRangeInMessage[cfl.valueEnd + 2, cm, fromRange];
cfl.valueEnd ← cfl.valueEnd + (fromRange.end - fromRange.start) + 2;
END;
delta ← vmD.GetMessageSize[cm] - cmSize;
MessageParse.UpdateFieldList[cfl.next, delta];
vmD.FlushDisplayMessage[dm, doneKey];
MessageParse.FreeFieldList[fl];
IF cfl.start < charIndex THEN charIndex ← charIndex + delta;
EXIT;
END;
vmD.FlushDisplayMessage[dm, doneKey];
MessageParse.FreeFieldList[fl];
END;
REPEAT
FINISHED =>
exD.DisplayExceptionString["Included field not found."L];
ENDLOOP;
ENDLOOP;
END;
ENDLOOP;
vmD.UnlockTOC[doneToc, doneKey];
vmD.UnlockTOC[stepListToc, stepListKey];
vmD.FreeVirtualMessageObject[dm];
END; -- of FillInIncludedFields --


ReplaceSpecialPlaceholders: PROCEDURE
[cm: vmD.ComposedMessagePtr, fieldListPtr: MessageParse.FieldListPtr] =
BEGIN
start: vmD.CharIndex ← 0;
end: vmD.CharIndex;
found, anyFound: BOOLEAN ← FALSE;
DO
[found, start, end, ]
← Editor.FindOperation["$̌tep¿"L, start, vmD.GetMessageSize[cm], cm];
IF ~found THEN EXIT;
anyFound ← TRUE;
vmD.DeleteRangeInMessage[[start, end, cm]];
InsertString[cm, start, currentStep.stepName];
start ← start + currentStep.stepName.length;
ENDLOOP;
IF anyFound THEN
BEGIN
MessageParse.FreeFieldList[fieldListPtr↑];
fieldListPtr↑ ← MessageParse.MakeFieldList[cm];
END;
END; -- of ReplaceSpecialPlaceholders --


MakeTimeField: PROCEDURE
[cm: vmD.ComposedMessagePtr, fieldListPtr: MessageParse.FieldListPtr, field: STRING] =
BEGIN
st: STRING ← [DMSTimeDefs.timeStringLength];
DMSTimeDefs.MapPackedTimeToTimeZoneString
[LOOPHOLE[TimeDefs.CurrentDayTime[]], st, arpaMsg];
MessageParse.ReplaceOrAppendField[cm, fieldListPtr, field, st];
END; -- of MakeTimeField --


InsertString: PUBLIC PROCEDURE
[cm: vmD.ComposedMessagePtr, start: vmD.CharIndex, s: STRING] =
BEGIN
IF cm = NIL OR s = NIL THEN RETURN;
vmD.StartMessageInsertion[cm, start];
vmD.InsertStringInMessage[cm, s];
vmD.StopMessageInsertion[cm];
END; -- of InsertString --


SetEditedBy: PROCEDURE
[cm: vmD.ComposedMessagePtr, fieldListPtr: MessageParse.FieldListPtr, n: INTEGER] =
BEGIN
technician: STRING ← [50];
fieldName: STRING ← [15];
String.AppendString[fieldName, "Edited-By"L];
IF n # 0 THEN String.AppendDecimal[fieldName, n];
MessageParse.GetStringFromField[cm, fieldListPtr↑, fieldName, technician];
SELECT TRUE FROM
technician.length = 0 =>
MessageParse.ReplaceOrAppendField[cm, fieldListPtr, fieldName, intC.user.name];
String.EquivalentString[technician, intC.user.name] => NULL;
n >= 9 => NULL;
ENDCASE => SetEditedBy[cm, fieldListPtr, n + 1];
END; -- of SetEditedBy --


New: TYPE = {new, old};


SendDataMail: PROCEDURE
[cm: vmD.ComposedMessagePtr, fieldListPtr: MessageParse.FieldListPtr, n: New] =
BEGIN
index: vmD.TOCIndex = ccD.currentStepNumber;
key: CARDINAL;
toc: vmD.TOCHandle;
ccD.StartMultiTOCOperation[];
[toc, key] ← ccD.GetTOCForFile[ccD.currentDoneFileName, done];
IF n = old THEN ccD.CopyUniqueIDsIntoMessage[toc, key, index, cm, fieldListPtr]
ELSE BEGIN --may have to insert dummy messages
st: STRING ← [10];
spare: vmD.ComposedMessagePtr ← NIL;
spareFieldList: MessageParse.FieldList ← NIL;
UNTIL toc.indexFF >= index DO
IF spare = NIL THEN spare ← vmD.AllocateComposedMessageObject[];
vmD.InitComposedMessage[spare, "Subject: Dummy Message to keep file in order

Run: "L];
vmD.StartMessageInsertion[spare, vmD.GetMessageSize[spare]];
vmD.InsertStringInMessage[spare, ccD.stepListRunName];
vmD.InsertStringInMessage[spare, "
Step: "L];
st.length ← 0;
String.AppendDecimal[st, toc.indexFF];
vmD.InsertStringInMessage[spare, st];
vmD.InsertMessageChar[spare, Ascii.CR];
vmD.StopMessageInsertion[spare];
spareFieldList ← MessageParse.MakeFieldList[spare];
IF ~ccD.SendChollaMail
[insert, done, toc, key, toc.indexFF, spare, @spareFieldList, TRUE]
THEN exD.DisplayExceptionString["Problem sending placeholder message!"L];
MessageParse.FreeFieldList[spareFieldList];
ENDLOOP;
IF spare # NIL THEN vmD.FreeVirtualMessageObject[spare];
END;
IF ~ccD.SendChollaMail[insert, done, toc, key, index, cm, fieldListPtr] THEN
exD.DisplayExceptionString["Problem with step update!"L];
vmD.UnlockTOC[toc, key];
ccD.FinishMultiTOCOperation[];
END; -- of SendDataMail --


ShipToInterestedOnlookers: PROCEDURE
[cm: vmD.ComposedMessagePtr, fieldListPtr: MessageParse.FieldListPtr] =
BEGIN
sendToFL: MessageParse.FieldList;
cmSize: vmD.CharIndex;
value: STRING;
count: CARDINAL;
[sendToFL, count] ← MessageParse.LocateField[fieldListPtr↑, "SendTo"L];
IF count = 0 THEN RETURN;
value ← Storage.String[sendToFL.valueEnd - sendToFL.valueStart];
value.length ← sendToFL.valueEnd - sendToFL.valueStart;
cmSize ← vmD.GetMessageSize[cm];
FOR i: CARDINAL IN [0 .. value.length) DO
value[i] ← vmD.GetMessageChar[cm, sendToFL.valueStart + i];
ENDLOOP;
ccD.InsertString[cm, 0, "

"L];
ccD.InsertString[cm, 0, value];
ccD.InsertString[cm, 0, "

To: "L];
ccD.InsertString[cm, 0, intC.workstationName];
ccD.InsertString[cm, 0, "From: "L];
ccD.AppendToUnsentMailQueue[cm];
vmD.DeleteRangeInMessage[[0, vmD.GetMessageSize[cm] - cmSize, cm]];
Storage.FreeString[value];
END; -- of ShipToInterestedOnlookers --


--///////OPERATOR SECTION


CheckTechnician: PROC [technician: STRING, spec: STRING, version: CARDINAL] =
BEGIN
key, oldVersion: CARDINAL;
toc: vmD.TOCHandle;
index: vmD.TOCIndex;
dm: vmD.DisplayMessagePtr ← vmD.AllocateDisplayMessageObject[];
fieldList: MessageParse.FieldList ← NIL;
IF version = 0 THEN exD.SysBug[];
[toc, key, index] ← TechnicianNameToMessageDescriptor[technician];
vmD.LoadDisplayMessage[toc, key, index, dm];
fieldList ← MessageParse.MakeFieldList[dm];
oldVersion ← MessageParse.GetNumberFromField[dm, fieldList, spec];
IF oldVersion # version THEN
DO
exD.DisplayBothExceptionLines
["This specification is different from the last of its type that you have read!"L,
exD.nil, "Type ESC to continue"L, exD.nil];
IF inD.Confirm[0] THEN EXIT;
ENDLOOP;
vmD.FlushDisplayMessage[dm, key];
vmD.FreeVirtualMessageObject[dm];
vmD.UnlockTOC[toc, key];
MessageParse.FreeFieldList[fieldList];
END; -- of CheckTechnician --


UpdateTechnician: PUBLIC PROCEDURE
[technician: STRING, spec: STRING, version: CARDINAL] =
BEGIN
key, oldVersion: CARDINAL;
toc: vmD.TOCHandle;
index: vmD.TOCIndex;
cm: vmD.ComposedMessagePtr ← vmD.AllocateComposedMessageObject[];
fieldList: MessageParse.FieldList ← NIL;
IF version = 0 THEN exD.SysBug[];
[toc, key, index] ← TechnicianNameToMessageDescriptor[technician];
ccD.LoadComposedMessage[toc, key, index, cm];
fieldList ← MessageParse.MakeFieldList[cm];
oldVersion ← MessageParse.GetNumberFromField[cm, fieldList, spec];
IF oldVersion # version THEN BEGIN
s: STRING ← [10];
String.AppendDecimal[s, version];
MessageParse.ReplaceOrAppendField[cm, @fieldList, spec, s];
[] ← opD.ReplaceMailOperation
[delete: TRUE, index: index, msg: cm, toc: toc, key: key];
END;
vmD.FreeVirtualMessageObject[cm];
vmD.UnlockTOC[toc, key];
MessageParse.FreeFieldList[fieldList];
END; -- of UpdateTechnician --


TechnicianNameToMessageDescriptor: PROCEDURE [technician: STRING]
RETURNS [toc: vmD.TOCHandle, key: CARDINAL, index: vmD.TOCIndex] =
BEGIN
cm: vmD.ComposedMessagePtr ← vmD.AllocateComposedMessageObject[];
fieldList: MessageParse.FieldList ← NIL;
[toc, key] ← ccD.GetTOCForFile["Technician"L, technician];
IF toc.indexFF # 1 THEN ccD.LoadComposedMessage[toc, key, 1, cm] ELSE
BEGIN
vmD.InitComposedMessage[cm, "

"L];
[] ← opD.ReplaceMailOperation [delete: FALSE, index: 1, msg: cm, toc: toc, key: key];
END;
fieldList ← MessageParse.MakeFieldList[cm];
index ← MessageParse.GetNumberFromField[cm, fieldList, technician];
IF index >= toc.indexFF THEN exD.SysBug[];
IF index = 0 THEN BEGIN
s: STRING ← [10];
index ← toc.indexFF;
String.AppendDecimal[s, index];
MessageParse.ReplaceOrAppendField[cm, @fieldList, technician, s];
[] ← opD.ReplaceMailOperation [delete: TRUE, index: 1, msg: cm, toc: toc, key: key];
vmD.InitComposedMessage[cm, "

"L];
[] ← opD.ReplaceMailOperation
[delete: FALSE, index: index, msg: cm, toc: toc, key: key];
END;
vmD.FreeVirtualMessageObject[cm];
MessageParse.FreeFieldList[fieldList];
END; -- of TechnicianNameToMessageDescriptor --


END. -- of ChollaStep --