-- GCellImplB.mesa
-- Last Edited by: Sturgis, August 15, 1985 11:33:42 am PDT

DIRECTORY
Ascii USING[Digit],
Expressions USING[AnalyzedExpression, AnalyzedIdentifier, AnalyzeExpression, AnalyzeIdentifier, EvaluateExpression, Expression, Identifier, IdTableSet, ParseExpression, ParseIdentifier, SetAnalyzedIdentifier, SyntaxError, Token],
Buttons USING[ButtonProc, Create, ReLabel],
Dependencies USING[Action, DataItem, DeactivateAction, DeactivateDataItem, DefineDataItem, DependencySet, DefineAction, MarkActionDirty, MarkDataItemDirty, NoteThatActionModifiesDataItem, NoteThatDataItemIsReadByAction],
GCell USING[GCellItemDescriptor, GCellItemJustification, GCellItemMode, GCellItemRowColSumMode, GCellItemValueMode, GetIdTableSet, NoteItemSelection],
GCellPrivateDefs USING[GCell, Item, ItemBody],
NewCalcGlobal USING[Displayer, Document],
Real USING[RoundLI],
Rope USING[Cat, Equal, Fetch, Find, Length, Replace, ROPE, Substr],
IO USING[atom, BreakProc, char, GetInt, GetRefAny, GetTokenRope, int, Put, PutFR, rope, STREAM],
StructureNodes USING[DisplayArray, GetElementAt, MarkElementDirty, StructureNode],
VFonts USING[FontHeight, StringWidth],
ViewerClasses USING[Viewer],
ViewerOps USING[AddProp, DestroyViewer, FetchProp, PaintViewer],
ViewerTools USING[GetContents, GetSelectedViewer, InhibitUserEdits, MakeNewTextViewer, SetContents, SetSelection];

GCellImplB: PROGRAM IMPORTS Ascii, Buttons, Dependencies, Expressions, GCell, IO, Real, Rope, StructureNodes, VFonts, ViewerOps, ViewerTools EXPORTS GCell, GCellPrivateDefs =

BEGIN OPEN Dependencies, Expressions, GCell, GCellPrivateDefs, Rope, IO, ViewerClasses;

-- for debugging

nGCellItems: LONG CARDINAL ← 0;


-- main stuff


ItemRefDesc: TYPE = REF ItemRefDescBody;
ItemRefDescBody: TYPE = RECORD[document: NewCalcGlobal.Document, da: StructureNodes.DisplayArray, I, J: CARDINAL, itemX: CARDINAL];





-- local procedures

GCellItemVersion: INTEGER = 3;

WriteOneItem: PUBLIC PROCEDURE[desc: GCellItemDescriptor, to: STREAM] =
 BEGIN
 modeAtom: ATOM ← SELECT desc.mode FROM
   showId => $a,
   showText => $b,
   showExp => $c,
   showValue => $d,
   noShow => $e,
   ENDCASE => ERROR;
 justAtom: ATOM ← SELECT desc.justification FROM
   left => $a,
   centered => $b,
   right => $c,
   leftDecimal => $d,
   rightDecimal => $e,
   ENDCASE => ERROR;
 rowAtom: ATOM ← SELECT desc.rowSumMode FROM
   addVal => $a,
   subVal => $b,
   noEffect => $c,
   setZero => $d,
   ENDCASE => ERROR;
 colAtom: ATOM ← SELECT desc.colSumMode FROM
   addVal => $a,
   subVal => $b,
   noEffect => $c,
   setZero => $d,
   ENDCASE => ERROR;
 valAtom: ATOM ← SELECT desc.valueMode FROM
   rowSum => $a,
   colSum => $b,
   expression => $c,
   ENDCASE => ERROR;
Put[to, int[GCellItemVersion]];
 Put[to, char[' ], atom[modeAtom]];
 Put[to, char[' ], atom[justAtom]];
 Put[to, char[' ], atom[rowAtom]];
 Put[to, char[' ], atom[colAtom]];
 Put[to, char[' ], atom[valAtom]];
 Put[to, char[' ], int[Rope.Length[desc.id]], char[' ]];
 Put[to, char['{], rope[desc.id], char['}]];
 Put[to, char[' ], int[Rope.Length[desc.text]], char[' ]];
 Put[to, char['{], rope[desc.text], char['}]];
 Put[to, char[' ], int[Rope.Length[desc.zeroValText]], char[' ]];
 Put[to, char['{], rope[desc.zeroValText], char['}]];
 END;

ReadOneItem: PUBLIC PROCEDURE[from: STREAM, version: CARDINAL] RETURNS[GCellItemDescriptor] =
 BEGIN
 fileVersion: INTEGER ← GetInt[from];
 IF fileVersion = 2 THEN RETURN[OldReadOneItem[from]] ELSE
  BEGIN
  modeAtom: ATOM ← NARROW[GetRefAny[from]];
  mode: GCellItemMode ← SELECT modeAtom FROM
   $a => showId,
   $b => showText,
   $c => showExp,
   $d => showValue,
   $e => noShow,
   ENDCASE => ERROR;
  justAtom: ATOM ← NARROW[GetRefAny[from]];
  just: GCellItemJustification ← SELECT justAtom FROM
   $a => left,
   $b => centered,
   $c => right,
   $d => leftDecimal,
   $e => rightDecimal,
   ENDCASE => ERROR;
  rowAtom: ATOM ← NARROW[GetRefAny[from]];
  rowMode: GCellItemRowColSumMode ← SELECT rowAtom FROM
   $a => addVal,
   $b => subVal,
   $c => noEffect,
   $d => setZero,
   ENDCASE => ERROR;
  colAtom: ATOM ← NARROW[GetRefAny[from]];
  colMode: GCellItemRowColSumMode ← SELECT colAtom FROM
   $a => addVal,
   $b => subVal,
   $c => noEffect,
   $d => setZero,
   ENDCASE => ERROR;
  valAtom: ATOM ← NARROW[GetRefAny[from]];
  valMode: GCellItemValueMode ← SELECT valAtom FROM
   $a => rowSum,
   $b => colSum,
   $c => expression,
   ENDCASE => ERROR;
  
     
  id: Rope.ROPE;
  text: Rope.ROPE;
  zeroValText: Rope.ROPE;
  
  ReadText: PROCEDURE RETURNS[Rope.ROPE] =
   BEGIN
   -- assumes that the form of text is
    -- a char count
    -- one or more blanks
    -- a '{
    -- the characters of the text
    -- a '}
   
   n: INTEGER; -- number of characters in rope (new form)
   rSoFar: INTEGER; -- number of chars collected
   b1: Rope.ROPE; -- the first break char
   r: Rope.ROPE; -- either the text, or the second break char
   b3: Rope.ROPE; -- second break char, if not in r
   
   B1Proc: IO.BreakProc = TRUSTED
    {IF char # '{ THEN RETURN[sepr] ELSE RETURN[break]};
   
   B2Proc: IO.BreakProc = TRUSTED
    {rSoFar ← rSoFar+1; IF rSoFar <= n THEN RETURN[other] ELSE RETURN[break]};
   
   B3Proc: IO.BreakProc = TRUSTED
    {IF char # '} THEN ERROR; RETURN[break]};
   
   n ← IO.GetInt[from];
   rSoFar ← 0;
   IF NOT Rope.Equal[b1 ← IO.GetTokenRope[from, B1Proc].token, "{"] THEN ERROR;
   r ← IO.GetTokenRope[from, B2Proc].token;
   IF Rope.Length[r] > n THEN
    {IF NOT Rope.Equal[r, "}"] THEN ERROR; RETURN[""]};
   IF NOT Rope.Equal[b3 ← IO.GetTokenRope[from, B3Proc].token, "}"] THEN ERROR;
   RETURN[r];
   END;
  
  
  id ← ReadText[];
  text ← ReadText[];
  zeroValText ← ReadText[];
  RETURN[[mode, just, rowMode, colMode, valMode, id, text, zeroValText]];
  END;
 END;

OldReadOneItem: PROCEDURE[from: STREAM] RETURNS[GCellItemDescriptor] =
 BEGIN
 modeAtom: ATOM ← NARROW[GetRefAny[from]];
 mode: GCellItemMode ← SELECT modeAtom FROM
  $showId => showId,
  $showText => showText,
  $showExp => showExp,
  $showValue => showValue,
  $noShow => noShow,
  ENDCASE => ERROR;
 justAtom: ATOM ← NARROW[GetRefAny[from]];
 just: GCellItemJustification ← SELECT justAtom FROM
   $left => left,
   $centered => centered,
   $right => right,
   $leftDecimal => leftDecimal,
   $rightDecimal => rightDecimal,
  ENDCASE => ERROR;
  
 -- assumes a leading blank, and surrounded by matched { }

 d: INTEGER ← 0; -- commentDepth

 -- this code modified June 22, 1984 4:10:07 pm PDT so as to compile, by replacing with error, no attempt to make it work.
 CProc: IO.BreakProc = TRUSTED {ERROR};
  -- BEGIN
  -- IF char = '{ THEN
  --  BEGIN
  --  d ← d + 1;
  --  RETURN[include: d>1, quit: FALSE]
  --  END;
  -- IF char = '} THEN
  --  BEGIN
  --  d ← d-1;
  --  RETURN[include: d>0, quit: d=0];
  --  END;
  -- RETURN[include: d>0, quit: FALSE];
  -- END;

 id: Rope.ROPE ← IO.GetTokenRope[from, CProc].token;
 text: Rope.ROPE ← IO.GetTokenRope[from, CProc].token;
 zeroValText: Rope.ROPE ← IO.GetTokenRope[from, CProc].token;


RETURN[[mode, just, noEffect, noEffect, expression, id, text, zeroValText]];
 END;

ClearGCell: PUBLIC PROCEDURE[gc: GCell] =
 BEGIN
 nextItem: Item ← gc.items;
 WHILE nextItem # NIL DO
  item: Item ← nextItem;
  nextItem ← item.next;
  AbandonItem[item];
  ENDLOOP;
 gc.items ← NIL;
 gc.nItems ← 0;
 gc.unPainted ← TRUE;
 END;

-- local procedures, item specific

CreateItem: PUBLIC PROCEDURE[idTables: IdTableSet, gc: GCell, da: StructureNodes.DisplayArray, selfI, selfJ, selfItemX: CARDINAL, desc: GCellItemDescriptor, show: BOOLEAN] RETURNS[item: Item] =
 BEGIN
 item ← NEW[ItemBody ← [da: da, selfI: selfI, selfJ: selfJ, selfItemX: selfItemX, mode: desc.mode, justification: desc.justification, rowSumMode: desc.rowSumMode, colSumMode: desc.colSumMode, valueMode: desc.valueMode, idName: desc.id, expressionText: desc.text, zeroValText: desc.zeroValText]];

 nGCellItems ← nGCellItems + 1;

 --item.valueChanges ← Dependencies.DefineDataItem[item.gc.ncGlobal.dSet];

 SELECT desc.mode FROM
  
  showText =>
   BEGIN
   item.buttonText ← desc.text;
   item.buttonTextDirty ← TRUE;
   item.buttonTextAnalyzed ← FALSE;
   item.editable ← TRUE;
   HandleItemValueChanges[gc, item];
   END;
   
  showId =>
   BEGIN
   item.buttonText ← desc.id;
   item.buttonTextDirty ← TRUE;
   item.buttonTextAnalyzed ← FALSE;
   item.editable ← TRUE;
   AnalyzeItemExpression[idTables, gc, item];
   AnalyzeItemId[idTables, gc, item];
   HandleItemValueChanges[gc, item];
   END;
   
  showExp =>
   BEGIN
   item.buttonText ← desc.text;
   item.buttonTextDirty ← TRUE;
   item.buttonTextAnalyzed ← FALSE;
   item.editable ← TRUE;
   AnalyzeItemExpression[idTables, gc, item];
   AnalyzeItemId[idTables, gc, item];
   HandleItemValueChanges[gc, item];
   END;
   
  showValue, noShow =>
   BEGIN
   AnalyzeItemExpression[idTables, gc, item];
   AnalyzeItemId[idTables, gc, item];
   HandleItemValueChanges[gc, item];
   IF show THEN SetItemValue[gc, item, item.value];
   END;
   
  ENDCASE => ERROR;
 IF gc.da # NIL THEN StructureNodes.MarkElementDirty[gc.da, 0, 0];
 END;

GetItemDescriptor: PUBLIC PROCEDURE[item: Item] RETURNS[GCellItemDescriptor] =
 {RETURN[[item.mode, item.justification, item.rowSumMode, item.colSumMode, item.valueMode, item.idName, item.expressionText, item.zeroValText]]};

SubstituteInItem: PUBLIC PROCEDURE[idTables: IdTableSet, gc: GCell, item: Item, newText: Rope.ROPE, oldText: Rope.ROPE] =
 BEGIN
 text: Rope.ROPE ← NIL;
 loc: INT;
 changes: BOOLEAN ← FALSE; -- tentative
 CheckItemForDirtyData[gc, item];
 IF item.expressionText # NIL THEN
  BEGIN
  loc ← Rope.Find[item.expressionText, oldText];
  IF loc # -1 THEN
   BEGIN
   item.expressionText ← text ← Rope.Replace[item.expressionText, loc, Rope.Length[oldText], newText];
   SELECT item.mode FROM
    showText, showExp => {item.buttonText ← text; item.buttonTextDirty ← TRUE};
    showId, noShow, showValue => NULL;
    ENDCASE => ERROR;
   IF item.valueMode = expression THEN AnalyzeItemExpression[idTables, gc, item];
   changes ← TRUE;
   END;
  END;
 IF item.idName # NIL THEN
  BEGIN
  loc ← Rope.Find[item.idName, oldText];
  IF loc # -1 THEN
   BEGIN
   item.idName ← text ← Rope.Replace[item.idName, loc, Rope.Length[oldText], newText];
   SELECT item.mode FROM
    showId => {item.buttonText ← text; item.buttonTextDirty ← TRUE};
    showText, showExp, noShow, showValue => NULL;
    ENDCASE => ERROR;
   AnalyzeItemId[idTables, gc, item];
   changes ← TRUE;
   END;
  END;
 IF changes THEN
  {HandleItemValueChanges[gc, item]; DisplayNewButtonText[gc, item]};
 StructureNodes.MarkElementDirty[gc.da, 0, 0];
 END;

CheckItemForDirtyData: PUBLIC PROCEDURE[gc: GCell, item: Item] =
 BEGIN -- use different procedure for the just created case
 IF item.viewerMode = textBox THEN
  BEGIN
  text: Rope.ROPE ← ViewerTools.GetContents[item.viewer];
  
  SELECT item.mode FROM
   showText, showExp => item.expressionText ← item.buttonText ← text;
   showId => item.idName ← item.buttonText ← text;
   noShow, showValue => NULL;
   ENDCASE => ERROR;
   
  ItemHasDirtyData[gc, item];
  END;
 END;

ItemHasDirtyData: PROCEDURE[gc: GCell, item: Item] =
 BEGIN
 idTablesPresent: BOOLEAN ← FALSE; 
 idTables: IdTableSet;
 ReceiveIdTables: PROCEDURE[ids: IdTableSet] = {idTables ← ids};  
 SELECT item.mode FROM
   showText, showExp, showId =>
     BEGIN
     item.buttonTextDirty ← TRUE;
     item.buttonTextAnalyzed ← FALSE;
     END;
   noShow, showValue => NULL;
   ENDCASE => ERROR;
  
 IF NOT item.buttonTextAnalyzed THEN SELECT item.mode FROM
  
  showExp =>
   BEGIN
   IF NOT idTablesPresent THEN GetIdTableSet[gc.gcaSn, item.selfI, item.selfJ, ReceiveIdTables];
   AnalyzeItemExpression[idTables, gc, item];
   HandleItemValueChanges[gc, item];
   END;
    
  showId =>
   BEGIN
   IF NOT idTablesPresent THEN GetIdTableSet[gc.gcaSn, item.selfI, item.selfJ, ReceiveIdTables];
   AnalyzeItemId[idTables, gc, item];
   HandleItemValueChanges[gc, item];
   END;
    
  showText, noShow, showValue => NULL;
    
  ENDCASE => ERROR;
  
 item.buttonTextAnalyzed ← TRUE;
 END;

AbandonItem: PROCEDURE[item: Item] =
 BEGIN
 DeactivateAction[item.evalAction];
 DeactivateDataItem[item.valueChanges];
 DeactivateAction[item.assignAction];
 ClearItemViewer[item];
 nGCellItems ← nGCellItems - 1;
 END;

ItemAcceptsSelection: PUBLIC PROCEDURE[item: Item] RETURNS[BOOLEAN] =
 BEGIN
 RETURN[ SELECT item.mode FROM
  showText, showId, showExp, showValue => TRUE,
  ENDCASE => FALSE];
 END;

-- item specific computation procedures

AnalyzeItemExpression: PUBLIC PROCEDURE[idTables: IdTableSet, gc: GCell, item: Item] =
 BEGIN
 BEGIN
 expression: Expression;
 IF item.valueMode # expression THEN RETURN;
 IF idTables[self] = NIL THEN RETURN;
 IF item.mode = showText THEN RETURN;
 expression ← Expressions.ParseExpression[item.expressionText
   ! Expressions.SyntaxError =>
    BEGIN
    IF item.mode # showExp THEN ERROR;
    item.buttonText ← Rope.Cat["&&&", item.expressionText, "&&&"];
    item.buttonTextDirty ← TRUE;
    IF item.viewer.destroyed THEN ERROR;
    ViewerTools.SetContents[item.viewer, item.buttonText];
    item.expressionBad ← TRUE;
    GOTO syntaxError;
    END];
 item.expressionBad ← FALSE;
 IF item.evalAction # NIL THEN
  {Dependencies.DeactivateAction[item.evalAction]; item.evalAction ← NIL};
 IF ExpressionNonConstant[expression] THEN
  BEGIN
  item.evalAction ← Dependencies.DefineAction[gc.document.dSet, EvalItemExpression, item];
  Dependencies.MarkActionDirty[item.evalAction];
  END;
 item.expression ← AnalyzeExpression[expression, idTables, gc.document.dSet, item.evalAction];
 IF item.valueMode = expression AND item.evalAction = NIL THEN
   SetItemValue[gc, item, Expressions.EvaluateExpression[item.expression]];
 EXITS
  syntaxError => NULL;
 END;
 END;

AnalyzeItemId: PUBLIC PROCEDURE[idTables: IdTableSet, gc: GCell, item: Item] =
 BEGIN
 BEGIN
 id: Identifier;
 IF idTables[self] = NIL THEN RETURN;
 IF item.mode = showText THEN RETURN;
 IF item.idName= NIL OR Rope.Equal[item.idName, ""] OR Rope.Equal[item.idName, " "] THEN GOTO idBad
  ELSE
   BEGIN
  id ← Expressions.ParseIdentifier[item.idName
   ! Expressions.SyntaxError =>
    BEGIN
    IF item.mode # showId THEN ERROR;
    item.buttonText ← Rope.Cat["&&&", item.idName, "&&&"];
    item.buttonTextDirty ← TRUE;
    IF item.viewer.destroyed THEN ERROR;
    ViewerTools.SetContents[item.viewer, item.buttonText];
    GOTO idBad;
    END];
  item.idBad ← FALSE;
  Dependencies.DeactivateAction[item.assignAction];
  item.assignAction ← Dependencies.DefineAction[gc.document.dSet, AssignItemValue, item];
  item.id ← AnalyzeIdentifier[id, idTables, gc.document.dSet, item.assignAction];
  Dependencies.MarkActionDirty[item.assignAction];
  END;
 EXITS
  idBad =>
   BEGIN
   item.idBad ← TRUE;
   Dependencies.DeactivateAction[item.assignAction]; item.assignAction ← NIL;
   item.id ← [NIL];
   END;
 END;
 END;

HandleItemValueChanges: PUBLIC PROCEDURE[gc: GCell, item: Item] =
 BEGIN
 valueChangesNeeded: BOOLEAN ← -- info passes through the item
   ((item.valueMode = rowSum AND gc.rowSummer # NIL) OR
   (item.valueMode = colSum AND gc.colSummer # NIL) OR
   (item.valueMode = expression AND item.evalAction # NIL))
  AND
   (((item.rowSumMode = addVal OR item.rowSumMode = subVal) AND gc.rowSummer # NIL) OR
   ((item.colSumMode = addVal OR item.colSumMode = subVal) AND gc.colSummer # NIL) OR
   item.assignAction # NIL);
 IF valueChangesNeeded THEN
  BEGIN
  IF item.valueChanges # NIL THEN Dependencies.DeactivateDataItem[item.valueChanges];
  item.valueChanges ← Dependencies.DefineDataItem[ gc.document.dSet];
  Dependencies.MarkDataItemDirty[item.valueChanges];
  IF item.valueMode = expression AND item.evalAction # NIL THEN
    Dependencies.NoteThatActionModifiesDataItem[item.evalAction, item.valueChanges];
  IF item.valueMode = rowSum AND gc.rowSummer # NIL THEN
   Dependencies.NoteThatActionModifiesDataItem[gc.rowSummer, item.valueChanges];
  IF item.valueMode = colSum AND gc.colSummer # NIL THEN
   Dependencies.NoteThatActionModifiesDataItem[gc.colSummer, item.valueChanges];
  IF item.rowSumMode # noEffect AND gc.rowSummer # NIL THEN
   Dependencies.NoteThatDataItemIsReadByAction[item.valueChanges, gc.rowSummer];
  IF item.colSumMode # noEffect AND gc.colSummer # NIL THEN
   Dependencies.NoteThatDataItemIsReadByAction[item.valueChanges, gc.colSummer];
  IF item.assignAction # NIL THEN
   Dependencies.NoteThatDataItemIsReadByAction[item.valueChanges, item.assignAction];
  END
  ELSE
   BEGIN
   IF item.valueChanges # NIL THEN Dependencies.DeactivateDataItem[item.valueChanges];
   item.valueChanges ← NIL;
   END;
   
 IF item.evalAction # NIL THEN
    Dependencies.MarkActionDirty[item.evalAction]; 
 IF gc.rowSummer # NIL THEN
   Dependencies.MarkActionDirty[gc.rowSummer];
 IF gc.colSummer # NIL THEN
   Dependencies.MarkActionDirty[gc.colSummer];
 IF item.assignAction # NIL THEN
   Dependencies.MarkActionDirty[item.assignAction];
 END;

EvalItemExpression: PROCEDURE[action: Action, info: REF ANY] =
 BEGIN
 item: Item ← NARROW[info];
 gcSn: StructureNodes.StructureNode ← StructureNodes.GetElementAt[item.da, item.selfI, item.selfJ].sn;
 gc: GCell ← NARROW[gcSn.ref];
 IF item.expressionBad THEN RETURN;
 SetItemValue[gc, item, Expressions.EvaluateExpression[item.expression]];
 END;

DoItemRowSum: PUBLIC PROCEDURE[gc: GCell, item: Item, sum: REAL] RETURNS[REAL] =
 BEGIN
 IF item.valueMode = rowSum THEN SetItemValue[gc, item, sum];
 IF item.rowSumMode = addVal THEN sum ← sum + item.value;
 IF item.rowSumMode = subVal THEN sum ← sum - item.value;
 IF item.rowSumMode = setZero THEN sum ← 0;
 RETURN[sum];
 END;

DoItemColSum: PUBLIC PROCEDURE[gc: GCell, item: Item, sum: REAL] RETURNS[REAL] =
 BEGIN
 IF item.valueMode = colSum THEN SetItemValue[gc, item, sum];
 IF item.colSumMode = addVal THEN sum ← sum + item.value;
 IF item.colSumMode = subVal THEN sum ← sum - item.value;
 IF item.colSumMode = setZero THEN sum ← 0;
 RETURN[sum];
 END;

SetItemValue: PROCEDURE[gc: GCell, item: Item, newVal: REAL] =
 BEGIN
 IF item.value # newVal OR item.firstTime OR item.viewerMode = textBox THEN
   BEGIN
   item.value ← newVal;
   item.firstTime ← FALSE;
   IF item.mode = showValue THEN
    BEGIN
    item.buttonText ← IF item.value = 0 AND item.zeroValText # NIL AND Rope.Length[item.zeroValText] # 0
      THEN item.zeroValText
      ELSE ComputeItemButtonText[item.value];
    item.buttonTextDirty ← TRUE;
    DisplayNewButtonText[gc, item];
    END;
   IF item.valueChanges = NIL THEN -- must hand set a few things dirty, this call is probably occuring during check for dirty data.
    BEGIN
    IF item.rowSumMode # noEffect AND gc.rowSummer # NIL THEN
     Dependencies.MarkActionDirty[gc.rowSummer];
    IF item.colSumMode # noEffect AND gc.colSummer # NIL THEN
     Dependencies.MarkActionDirty[gc.colSummer];
    IF item.assignAction # NIL THEN
     Dependencies.MarkActionDirty[item.assignAction];
    END;
   END;
 END;

ComputeItemButtonText: PROCEDURE[value: REAL] RETURNS[Rope.ROPE] =
 BEGIN
 modifiedVal: REAL ← value*100;
 valAsLongInteger: LONG INTEGER ← Real.RoundLI[modifiedVal];
 negSign: BOOLEAN ← valAsLongInteger < 0;
 highPart: LONG INTEGER ← valAsLongInteger/100;
 lowPart: LONG INTEGER ← IF negSign THEN ((-valAsLongInteger) MOD 100) ELSE ((valAsLongInteger) MOD 100);
zeros: Rope.ROPE ← IF lowPart < 10 THEN "0" ELSE "";
 RETURN[IO.PutFR["%g.%g%g", IF highPart = 0 AND negSign THEN IO.rope["-0"] ELSE IO.int[highPart], IO.rope[zeros], IO.int[lowPart]]];
 END;

AssignItemValue: PROCEDURE[action: Action, info: REF ANY] =
 BEGIN
 item: Item ← NARROW[info];
 IF NOT item.idBad THEN
  SetAnalyzedIdentifier[item.id, item.value];
 END;


-- item specific display procedures

ShowItem: PUBLIC PROCEDURE[item: Item, L, R, W: INTEGER, to: STREAM] =
 BEGIN
 length: INTEGER ← Rope.Length[item.buttonText];
 fullSkip: INTEGER ← IF length < W THEN (W-length) ELSE 0;
 leftSkip, rightSkip: INTEGER;
 SELECT item.justification FROM
  left =>
   BEGIN
   rightSkip ← fullSkip;
   leftSkip ← 0;
   END;
   
  centered =>
   BEGIN
   leftSkip ← fullSkip/2;
   rightSkip ← fullSkip - leftSkip;
   END;
   
  right =>
   BEGIN
   leftSkip ← fullSkip;
   rightSkip ← 0;
   END;
   
  leftDecimal =>
   BEGIN
   leftSkip ← IF length < W AND item.textL < L THEN L - item.textL ELSE 0;
   rightSkip ← fullSkip - leftSkip;
   END;
   
  rightDecimal =>
   BEGIN
   rightSkip ← IF length < W AND item.textR < R THEN R - item.textR ELSE 0;
   leftSkip ← fullSkip - rightSkip;
   END;
   
  ENDCASE => ERROR;
  
 FOR I: INTEGER IN [1..leftSkip] DO
  Put[to, char[' ]];
  ENDLOOP;
 Put[to, rope[item.buttonText]];
 FOR I: INTEGER IN [1..rightSkip] DO
  Put[to, char[' ]];
  ENDLOOP;
 END;

PrePaintItem: PUBLIC PROCEDURE[gc: GCell, item: Item, y, x, L, R, W: INTEGER] =
 BEGIN
 leftGap: INTEGER;

 item.viewerY ← y;
 item.viewerW ← IF item.buttonW < W THEN item.buttonW ELSE W;

 SELECT item.justification FROM
  left => leftGap ← 0;
  centered => leftGap ← (W - item.viewerW)/2;
  right => leftGap ← (W-item.viewerW);
  leftDecimal => leftGap ← IF item.buttonW < W AND item.buttonL < L
       THEN L - item.buttonL
       ELSE 0;
  rightDecimal =>
   BEGIN
   fullGap: INTEGER ← IF item.buttonW < W THEN W-item.buttonW ELSE 0;
   rightGap: INTEGER ← IF item.buttonW < W AND item.buttonR < R
       THEN R - item.buttonR
       ELSE 0;
   leftGap ← fullGap - rightGap;
   END;   
  ENDCASE => ERROR;
  
 item.viewerX ← x + leftGap;
 DisplayButtonTextAsButton[gc, item];
 END;

PaintItem: PUBLIC PROCEDURE[gc: GCell, item: Item, dirtyOnly: BOOLEAN] =
 BEGIN
 IF item.dirty OR NOT dirtyOnly THEN
  {ViewerOps.PaintViewer[item.viewer, all, FALSE]; item.dirty ← FALSE}
 END;

NoteCoordinatesItem: PUBLIC PROCEDURE[item: Item, document: NewCalcGlobal.Document, da: StructureNodes.DisplayArray, I, J, x: CARDINAL] =
 BEGIN
 IF da = NIL THEN ERROR;
 item.da ← da;
 item.selfI ← I;
 item.selfJ ← J;
 item.selfItemX ← x;

 SELECT item.viewerMode FROM
  nil => NULL;
  button, textBox =>
   BEGIN
   descRef: REF ANY ← ViewerOps.FetchProp[item.viewer, $GCellItem];
   itemRefDesc: ItemRefDesc ← NARROW[descRef];
   itemRefDesc.da ← da;
   itemRefDesc.I ← I;
   itemRefDesc.J ← J;
   itemRefDesc.itemX ← x;
   END;
  ENDCASE;
 END;


ComputeTextShape: PUBLIC PROCEDURE[item: Item] =
 BEGIN
 IF item.buttonTextDirty THEN
  BEGIN
  item.buttonTextDirty ← FALSE;
  item.buttonShapeDirty ← TRUE;
  IF item.buttonText = NIL THEN
   BEGIN
   item.buttonL ← item.buttonA ← item.buttonB ← item.buttonR ← item.buttonW ← 0;
   item.buttonHeight ← 0;
   item.textL ← item.textA ← item.textB ← item.textR ← item.textW ← 0;
   RETURN;
   END;
  SELECT item.justification FROM
   left, centered, right =>
    BEGIN
    item.buttonL ← item.buttonA ← item.buttonB ← item.buttonR ← 0;
    item.textL ← item.textA ← item.textB ← item.textR ← 0;
    item.buttonW ← VFonts.StringWidth[item.buttonText] + 20;
    item.buttonHeight ← VFonts.FontHeight[] + 5;
    item.textW ← Rope.Length[item.buttonText];
    END;
    
   leftDecimal =>
    BEGIN
    decimals: CARDINAL ← 0;
    length: CARDINAL ← Rope.Length[item.buttonText];
    FOR I: CARDINAL DECREASING IN [0..length) DO
     IF Rope.Fetch[item.buttonText, I] = '. THEN
      {decimals ← length - I; EXIT};
     IF NOT Ascii.Digit[Rope.Fetch[item.buttonText, I]] THEN EXIT;
     ENDLOOP;
    item.buttonL ← VFonts.StringWidth[Rope.Substr[item.buttonText, 0, length-decimals]]+5;
    item.buttonA ← VFonts.StringWidth[Rope.Substr[item.buttonText, length-decimals, length]];
    item.buttonB ← item.buttonR ← 0;
    item.buttonW ← VFonts.StringWidth[item.buttonText] + 20;
    item.textL ← length-decimals; item.textA ← decimals;
    item.buttonHeight ← VFonts.FontHeight[] + 5;
    item.textB ← item.textR ← 0;
    item.textW ← length;
    END;

   rightDecimal =>
    BEGIN
    decimals: CARDINAL ← 0;
    length: CARDINAL ← Rope.Length[item.buttonText];
    FOR I: CARDINAL DECREASING IN [0..length) DO
     IF Rope.Fetch[item.buttonText, I] = '. THEN
      {decimals ← length - I; EXIT};
     IF NOT Ascii.Digit[Rope.Fetch[item.buttonText, I]] THEN EXIT;
     ENDLOOP;
    item.buttonL ← item.buttonA ← 0;
    item.buttonB ← VFonts.StringWidth[Rope.Substr[item.buttonText, 0, length-decimals]];
    item.buttonR ← VFonts.StringWidth[Rope.Substr[item.buttonText, length-decimals, length]]+5;
    item.buttonW ← VFonts.StringWidth[item.buttonText] + 20;
    item.buttonHeight ← VFonts.FontHeight[] + 5;
    item.textL ← item.textR ← 0;
    item.textB ← length-decimals; item.textR ← decimals;
    item.textW ← length;
    END;

   ENDCASE => ERROR;
  END;
 END;

DisplayNewButtonText: PROCEDURE[gc: GCell, item: Item] =
 BEGIN
 SELECT item.viewerMode FROM
  textBox => DisplayButtonTextAsButton[gc, item];
  button => Buttons.ReLabel[item.viewer, item.buttonText, TRUE];
  nil => NULL;
  ENDCASE => ERROR;
 END;

DisplayButtonTextAsButton: PROCEDURE[gc: GCell, item: Item] =
 BEGIN
 button: Viewer;
 IF ViewerTools.GetSelectedViewer[] = item.viewer THEN
  BEGIN
  gcSn: StructureNodes.StructureNode ← StructureNodes.GetElementAt[item.da, item.selfI, item.selfJ].sn;
  ForceItemSelection[gcSn, gc, item] -- display it as a text viewer anyway, possibly with new text, and certainly it will continue to be selected.
  END
 ELSE
  BEGIN
  ClearItemViewer[item];
  button ← Buttons.Create[
   info: [
    name: item.buttonText,
    wx: item.viewerX,
    ww: item.viewerW,
    wy: item.viewerY,
    wh: VFonts.FontHeight[]+5,
    parent: gc.vParent,
    border: FALSE,
    scrollable: FALSE],
   proc: DisplayButtonTextAsTextViewer,
   clientData: NIL,
   paint: FALSE,
   fork: TRUE];
  ViewerOps.AddProp[button, $GCellItem, NEW[ItemRefDescBody ← [gc.document, item.da, item.selfI, item.selfJ, item.selfItemX]]];
  item.viewerMode ← button;
  item.viewer ← button;
  item.dirty ← TRUE;
  END;
 END;




DisplayButtonTextAsTextViewer: Buttons.ButtonProc = TRUSTED
 BEGIN
 viewer: Viewer ← NARROW[parent]; -- the button itself
 itemRefDesc: ItemRefDesc ← NARROW[ViewerOps.FetchProp[viewer, $GCellItem]];
 DBTATVInternal: PROCEDURE =
  BEGIN
  gcSn: StructureNodes.StructureNode; gc: GCell; item: Item;
  [gcSn, gc, item] ← DecodeItemRefDesc[itemRefDesc];
  IF item.viewer # NARROW[parent, ViewerClasses.Viewer] THEN
   RETURN; -- some concurrent activity must have removed this button from the item, presumably it is being destroyed.
  ForceItemSelection[gcSn, gc, item];
  StructureNodes.MarkElementDirty[gc.da, 0, 0]
  END;
 itemRefDesc.document.procs.executeInMonitor[itemRefDesc.document, DBTATVInternal];
 END;

ForceItemSelection: PUBLIC PROCEDURE[gcSn: StructureNodes.StructureNode, gc: GCell, item: Item] =
 BEGIN
 textBox: ViewerClasses.Viewer;
 NoteItemSelection[gc.displayer, gcSn, item.selfJ];
 ClearItemViewer[item];
 textBox ← ViewerTools.MakeNewTextViewer[
  info: [
   parent: gc.vParent,
   wx: item.viewerX,
   wy: item.viewerY,
   ww: item.viewerW,
   wh: VFonts.FontHeight[]+5,
   border: FALSE,
   scrollable: FALSE],
  paint: FALSE];
 IF NOT item.editable THEN ViewerTools.InhibitUserEdits[textBox];
 IF textBox.destroyed THEN ERROR;
 ViewerTools.SetContents[textBox, item.buttonText];
 ViewerOps.AddProp[textBox, $GCellItem, NEW[ItemRefDescBody ← [gc.document, item.da, item.selfI, item.selfJ, item.selfItemX]]];
 ViewerTools.SetSelection[textBox, NIL];
 item.viewer ← textBox;
 item.viewerMode ← textBox;
 StructureNodes.MarkElementDirty[gc.da, 0, 0];
 END;

ClearItemViewer: PROCEDURE[item: Item] =
 BEGIN
 -- note: August 15, 1985 11:31:30 am PDT: When Cedar changed from 5.2 to 6.0, buttons and text boxes are painted asynchronously. The fix is to set the contents with paint=FALSE, then directly call ViewerOps.PaintViewer. See similar commend in HCellImpl.
 IF item.viewerMode = button THEN
  Buttons.ReLabel[item.viewer, "", FALSE];

 IF item.viewerMode = textBox THEN
  BEGIN
  IF item.viewer.destroyed THEN ERROR;
  ViewerTools.SetContents[item.viewer, "", FALSE];
  END;

 ViewerOps.PaintViewer[item.viewer, all];

 IF item.viewerMode # nil THEN
  BEGIN
  ViewerOps.DestroyViewer[item.viewer, FALSE];
  item.viewerMode ← nil;
  item.viewer ← NIL;
  END;
 END;

ForceNoteItemSelection: PUBLIC PROCEDURE[itemRef: REF ANY] =
 BEGIN
 itemRefDesc: ItemRefDesc ← NARROW[itemRef];
 gcSn: StructureNodes.StructureNode;
 gc: GCell;
 item: Item;
 [gcSn, gc, item] ← DecodeItemRefDesc[itemRefDesc];
 NoteItemSelection[gc.displayer, gcSn, item.selfJ];
 END;

-- item ref descriptors

DecodeItemRefDesc: PROCEDURE[itemRefDesc: ItemRefDesc] RETURNS[gcSn: StructureNodes.StructureNode, gc: GCell, item: Item] =
 BEGIN
 IF itemRefDesc.da = NIL THEN ERROR;
 IF itemRefDesc.I = 0 THEN ERROR;
 IF itemRefDesc.J = 0 THEN ERROR;
 gcSn ← StructureNodes.GetElementAt[itemRefDesc.da, itemRefDesc.I, itemRefDesc.J].sn;
 gc ← NARROW[gcSn.ref];
 item ← gc.items; -- tentative
 FOR x: CARDINAL IN [1..itemRefDesc.itemX) DO item ← item.next ENDLOOP;
 END;

-- BELONGS IN EXPRESSION CODE

ExpressionNonConstant: PROCEDURE[expression: Expression] RETURNS[BOOLEAN] =
 BEGIN -- this belongs in expresion code
 E1: Expression;
 T: Token;
 IF expression = NIL THEN RETURN[FALSE];
 IF expression.operator # topExp THEN RETURN[TRUE];
 IF expression.data2 = NIL THEN RETURN[TRUE];
 E1 ← NARROW[expression.data2];
 IF E1.operator # token THEN RETURN[TRUE];
 IF E1.data1 = NIL THEN RETURN[TRUE];
 T ← NARROW[E1.data1];
 IF T.operator = number THEN RETURN[FALSE];
 RETURN[TRUE];
 END;


END..

-- February 28, 1983 5:01 pm: Sturgis, started GCellImplB.mesa, will hold the item procedures of old GCellImpl.

-- RTE: March 1, 1983 9:05 am: de-referencing item.viewer fails because item is not yet known. Have to wrok from parent = the button.
-- RTE: March 1, 1983 9:14 am: ForceNoteItemRefDesc forgot to decode the item ref.
-- RTE: March 1, 1983 1:38 pm: monitor problems. Must enter monitor before decoding ItemRefDesc, so have to add ngcGlobal to the descriptor. Also, must not change the descriptor associated with a property, only its contents (since the descriptor is fetched outside the monitor, so that ngcglobal can be sued to enter the monitor.)
-- RTE: March 1, 1983 1:56 pm: last fix dropped recrding of da in item at ItemNoteCoordinates.
-- RTE: March 1, 1983 2:00 pm: item scan loop in DecodeItemRefDescriptor used wrong limit. (J instead of x).
-- RTE: March 1, 1983 2:55 pm: destroyed viewer tries to scroll. Add a test for viewer.destroyed before each set contents to try to catch it.
-- March 8, 1983 12:15 pm: add code to call display array to mark item dirty.
-- Change: August 14, 1983: modify computation of button text (when computed from value) to always output exactly two digits after decimal point (and round to that representation).
-- Change: June 22, 1984 4:17:57 pm PDT: convert to Cedar 5.2 (old style formats are not converted)
-- CTE: June 22, 1984 5:38:47 pm PDT: bugs in scanning, added a break char return;
-- CTE: June 23, 1984 12:08:33 pm PDT: more bugs in scanning, had to redisign the scheme for reading in the text, is now more of a kludge then ever, but it works.
-- RTE: August 15, 1985 11:32:59 am PDT: The change from Cedar 5.2 to Cedar 6.0 caused some painting bugs. The fix is in ClearItemViewer.