-- GCellImpl.mesa
-- Last Edited by: Sturgis, February 28, 1983 4:47 pm

DIRECTORY
   Expressions USING[AnalyzedExpression, AnalyzedIdentifier, AnalyzeExpression, AnalyzeIdentifier, EvaluateExpression, Expression, Identifier, IdTableSet, ParseExpression, ParseIdentifier, SetAnalyzedIdentifier, SyntaxError, Token],
   Buttons USING[ButtonProc, Create, ReLabel],
   Convert USING[Value, ValueToRope],
   Dependencies USING[Action, DataItem, DeactivateAction,  DeactivateDataItem, DefineDataItem, DependencySet, DefineAction, MarkActionDirty, MarkDataItemDirty, NoteThatActionModifiesDataItem, NoteThatDataItemIsReadByAction],
   Displayer USING[Displayer],
   GCell USING[GCellItemDescriptor, GCellItemJustification, GCellItemMode, GCellItemRowColSumMode, GCellItemValueMode, NoteItemSelection, SelectionDirection, SetAGCellProc, StandardGCells],
   NewCalcGlobal USING[NCGlobal],
   Rope USING[Cat, Digit, Equal, Fetch, Find, Length, Replace, ROPE, Substr],
   IO USING[atom, char, CharProc, GetInt, GetRefAny, GetSequence, int, Put, PutChar, rope, STREAM],
   StructureNodes USING[Action, DACell, DisplayArray, GetContextDA, GetElementAt, StructureNode, StructureNodeBody, StructureNodeProcs],
   VFonts USING[FontHeight, StringWidth],
   ViewerClasses USING[Viewer],
   ViewerOps USING[AddProp, DestroyViewer, FetchProp], 
   ViewerTools USING[GetContents, GetSelectedViewer, InhibitUserEdits, MakeNewTextViewer, SetContents, SetSelection];
   
GCellImpl: PROGRAM IMPORTS Buttons, Convert, Dependencies, Expressions, GCell, IO, Rope, StructureNodes, VFonts, ViewerOps, ViewerTools EXPORTS GCell =

BEGIN OPEN Dependencies, Displayer, Expressions, GCell, NewCalcGlobal, Rope, IO, ViewerClasses;

-- synthetic, to be removed when expressions interface fixed

--RecognizeIdentifiers: PROCEDURE[exp: Expression, dSet: DependencySet, eval: Action, n, e, s, w, self, global: IdTable] RETURNS[Expression];

--RecognizeTargetIdentifier: PROCEDURE[id: Expression, dSet: DependencySet, store: Action, n, e, s, w, self, global: IdTable] RETURNS[Expression];

-- issues to deal with later
	-- September 25, 1982 10:25 am: include leading space to left of a column in these cells as part of their viewers, to allow insertion of more new text during edit.
	-- September 25, 1982 11:22 am: in the save file must quote {, }, and '.  Also, must undo the quotes on input.
	-- September 25, 1982 11:25 am: I am sure I paint the viewers far too often.
	-- September 25, 1982 12:04 pm: abandonItem also has to de activate any dataitems and actions the gcellitem has created.
	
	

-- for debugging
	
nGCells: LONG CARDINAL ← 0;
nGCellItems: LONG CARDINAL ← 0;


-- main stuff
	

	

GCell: TYPE = REF GCellBody;
GCellBody: PUBLIC TYPE = RECORD[
	da: StructureNodes.DisplayArray,
	selfI, selfJ: CARDINAL ← 0,
	
	ncGlobal: NCGlobal,
	displayer: Displayer,
	gcaSn: StructureNodes.StructureNode,
	vParent: Viewer,
	
	idTables: IdTableSet,
	rowSummer, colSummer: Action ← NIL,
	
	screenLead: INTEGER ← 0,
	screenSpace: INTEGER ← 0,
	
	textLead: INTEGER ← 0,
	textSpace: INTEGER ← 2, -- eventually this will become changeable by GCellTool
	textNLines: INTEGER ← 0, -- set by preshow
	
	lastPrePaintY, lastPrePaintX, lastPrePaintL, lastPrePaintR, lastPrePaintW: INTEGER ← 0, 
	unPainted: BOOLEAN ← TRUE,
	
	
	nItems: CARDINAL ← 0,
	items: Item ← NIL
	];
	

Item: TYPE = REF ItemBody;
ItemBody: TYPE = RECORD[
	da: StructureNodes.DisplayArray,
	selfI, selfJ: CARDINAL ← 0,
	selfItemX: CARDINAL ← 0,
	
	mode: GCellItemMode,
	justification: GCellItemJustification,
	rowSumMode: GCellItemRowColSumMode,
   colSumMode: GCellItemRowColSumMode,
   valueMode: GCellItemValueMode,
	idName: ROPE,
	expressionText: ROPE,
	zeroValText: ROPE,
	
	created: BOOLEAN ← TRUE,

	expressionBad: BOOLEAN ← TRUE,
	expression: AnalyzedExpression ← [NIL],
	
	evalAction:  Action ← NIL, -- created only if expression is not simple constant
	
	firstTime: BOOLEAN ← TRUE, -- set false after value is first displayed
	value: REAL ← 0,
	valueChanges: DataItem ← NIL, -- created only if information passes through this item
	
	
	idBad: BOOLEAN ← TRUE,
	id: AnalyzedIdentifier ← [NIL],
	
	assignAction: Action ← NIL, -- created only if there is an identifier
	
	buttonText: ROPE ← "",
	buttonTextDirty: BOOLEAN ← FALSE, -- set true when buttonText changed
	buttonTextAnalyzed: BOOLEAN← FALSE, -- set false when text becomes dirty, is set true when analyzed
	buttonShapeDirty: BOOLEAN ← FALSE, -- set true when shape recomputed, textDirty is set false at that time.
	
	buttonL, buttonA, buttonB, buttonR, buttonW: INTEGER ← 0,
	buttonHeight: INTEGER ← 0,
	textL, textA, textB, textR, textW: INTEGER ← 0,
	
	viewerX, viewerY, viewerW: INTEGER ← 0,
	viewer: Viewer ← NIL,
	viewerMode: ItemViewerMode ← nil,
	editable: BOOLEAN ← FALSE,
	
	next: Item ← NIL];

ItemViewerMode: TYPE = {nil, button, textBox};

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



GCellSNProcs: REF StructureNodes.StructureNodeProcs ← NEW[StructureNodes.StructureNodeProcs ← [
	CheckGCellForDirtyData,
	PreShowGCell,
	ShowGCellLine,
	PrePrePaintGCell,
	PrePaintGCell,
	NoteEnclosingCellGCell,
	NoteCoordinatesGCell,
	
	SaveGCell,
	ActGCell,
	SubstituteInGCell,
	AbandonGCell,
	VerifyGCell
	]];

-- GCell specific interface procedures

LoadGCell: PUBLIC PROCEDURE[global: NCGlobal, displayer: Displayer, gcaSn: StructureNodes.StructureNode, vParent: Viewer, from: STREAM, version: CARDINAL] RETURNS[StructureNodes.StructureNode] =
	BEGIN
	nItems: CARDINAL;
	gc: GCell ← NEW[GCellBody ← [displayer: displayer, gcaSn: gcaSn, vParent: vParent, ncGlobal: global]];
	previousItem: Item ← NIL;
	
	nGCells ← nGCells + 1;
	
	nItems ← GetInt[from];
	
	
	FOR J: CARDINAL IN [1..nItems] DO
		desc: GCellItemDescriptor;
		item: Item;
		desc ← ReadOneItem[from, version];
		item ← CreateItem[gc, NIL, 0, 0, J, desc, FALSE];
		IF previousItem # NIL THEN previousItem.next ← item ELSE gc.items ← item;
		previousItem ← item;
		gc.nItems ← gc.nItems + 1;
		ENDLOOP;
		
	
	RETURN[NEW[StructureNodes.StructureNodeBody ← [gc, GCellSNProcs]]];
	END;
	 
CreateGCell: PUBLIC PROCEDURE[global: NCGlobal, displayer: Displayer, gcaSn: StructureNodes.StructureNode, vParent: Viewer, type: StandardGCells] RETURNS[StructureNodes.StructureNode] =
	BEGIN
	gc: GCell ← NEW[GCellBody ← [vParent: vParent, ncGlobal: global, displayer: displayer, gcaSn: gcaSn]];
	LoadGCellWithType[gc, type];
	nGCells ← nGCells + 1;
	RETURN[NEW[StructureNodes.StructureNodeBody ← [gc, GCellSNProcs]]];
	END;
	
SetGCellIdInfo: PUBLIC PROCEDURE[gcn: StructureNodes.StructureNode, idTables: IdTableSet] =
	BEGIN
	gc: GCell ← NARROW[gcn.ref];
	gc.idTables ← idTables;
	FOR item: Item ← gc.items, item.next WHILE item # NIL DO
		AnalyzeItemExpression[gc, item];
		AnalyzeItemId[gc, item];
		HandleItemValueChanges[gc, item];
		ENDLOOP;
	END;

NoteSummers: PUBLIC PROCEDURE[gcn: StructureNodes.StructureNode, rowSummer, colSummer: Action] =
	BEGIN
	gc: GCell ← NARROW[gcn.ref];
	IF gc.rowSummer # NIL OR gc.colSummer # NIL THEN ERROR;  -- if non nil, then actions have already been tied to valueChanges, would have to re-think this code.
	gc.rowSummer ← rowSummer;
	gc.colSummer ← colSummer;
	FOR item: Item ← gc.items, item.next WHILE item # NIL DO
		HandleItemValueChanges[gc, item];
		ENDLOOP;
	
	-- now, since it is too hard to figure ouut exactly when this should be done, we set the summers dirty
	Dependencies.MarkActionDirty[rowSummer];
	Dependencies.MarkActionDirty[colSummer];
	END;
	
-- REMARK: WARNING: this algorithm implies that some items may depend on earlier items through a row or column sum.  THIS IS NOT RECORDED IN THE DEPENDENCY STRUCTURE!!!

DoRowSum: PUBLIC PROCEDURE[gcn: StructureNodes.StructureNode, sumToLeft: REAL] RETURNS[REAL] =
	BEGIN
	gc: GCell ← NARROW[gcn.ref];
	sum: REAL ← sumToLeft;
	FOR item: Item ← gc.items, item.next WHILE item # NIL DO
		sum ← DoItemRowSum[gc, item, sum];
		ENDLOOP;
	RETURN[sum];
	END;

DoColSum: PUBLIC PROCEDURE[gcn: StructureNodes.StructureNode, sumAbove: REAL] RETURNS[REAL] =
	BEGIN
	gc: GCell ← NARROW[gcn.ref];
	sum: REAL ← sumAbove;
	FOR item: Item ← gc.items, item.next WHILE item # NIL DO
		sum ← DoItemColSum[gc, item, sum];
		ENDLOOP;
	RETURN[sum];
	END;
	
SetGCellContents: PUBLIC PROCEDURE[gcn: StructureNodes.StructureNode, nItems: CARDINAL, getOneItem: PROCEDURE RETURNS[GCellItemDescriptor], entry: BOOLEAN] =
	BEGIN
	gc: GCell ← NARROW[gcn.ref];
	SetGCellContentsAsGCell[gc, nItems, getOneItem, entry];
	END;
	
SetGCellContentsAsGCell: PROCEDURE[gc: GCell, nItems: CARDINAL, getOneItem: PROCEDURE RETURNS[GCellItemDescriptor], entry: BOOLEAN] =
	BEGIN
	SGCC: PROCEDURE =
	BEGIN
	previousItem: Item ← NIL;
	ClearGCell[gc];
	FOR J: CARDINAL IN [1..nItems] DO
		desc: GCellItemDescriptor ← getOneItem[];
		item: Item ← CreateItem[gc, gc.da, gc.selfI, gc.selfJ, J, desc, TRUE];
		IF previousItem # NIL THEN previousItem.next ← item ELSE gc.items ← item;
		previousItem ← item;
		gc.nItems ← gc.nItems + 1;
		ENDLOOP;
	END;
	IF entry THEN gc.ncGlobal.executeInMonitor[gc.ncGlobal, SGCC] ELSE SGCC[];
	END;
	
GetGCellContents: PUBLIC PROCEDURE[gcn: StructureNodes.StructureNode, send: SetAGCellProc, entry: BOOLEAN] =
	BEGIN
	gc: GCell ← NARROW[gcn.ref];
	GGCC: PROCEDURE =
	BEGIN
	previousItem: Item ← NIL;
	getOneItem: PROCEDURE RETURNS[GCellItemDescriptor] =
		BEGIN
		previousItem ← IF previousItem = NIL THEN gc.items ELSE previousItem.next;
		RETURN[GetItemDescriptor[previousItem]];
		END;
	send[gc.nItems, getOneItem];
	END;
	IF entry THEN gc.ncGlobal.executeInMonitor[gc.ncGlobal, GGCC] ELSE GGCC[];
	END;
	   
NarrowGCell: PUBLIC PROCEDURE[ref: REF ANY] RETURNS[StructureNodes.StructureNode] =
	BEGIN
	sn: StructureNodes.StructureNode ← NARROW[ref];
	gCell: GCell ← NARROW[sn.ref]; -- checks to be sure is a structure node for a gcell
	RETURN[sn]
	END;
	
	
StepSelection: PUBLIC PROCEDURE[gcn: StructureNodes.StructureNode, itemX: CARDINAL, direction: SelectionDirection] RETURNS[ok: BOOLEAN] =
	BEGIN
	gc: GCell ← NARROW[gcn.ref];
	SELECT direction FROM
		first =>
			BEGIN
			FOR item: Item ← gc.items, item.next WHILE item # NIL DO
				IF ItemAcceptsSelection[item] THEN
					{ForceItemSelection[gcn, gc, item]; RETURN[TRUE]};
				ENDLOOP;
			RETURN[FALSE];
			END;
			
		next =>
			BEGIN
			itemStart: Item ← gc.items;
			FOR X: CARDINAL IN [1..itemX] DO itemStart ← itemStart.next ENDLOOP;
			FOR item: Item ← itemStart, item.next WHILE item # NIL DO
				IF ItemAcceptsSelection[item] THEN
					{ForceItemSelection[gcn, gc, item]; RETURN[TRUE]};
				ENDLOOP;
			RETURN[FALSE];
			END;
			
		previous =>
			BEGIN
			itemTry: Item ← gc.items;
			itemLast: Item ← NIL;
			FOR X: CARDINAL IN [1..itemX) DO
				IF ItemAcceptsSelection[itemTry] THEN itemLast ← itemTry;
				itemTry ← itemTry.next
				ENDLOOP;
			IF itemLast # NIL THEN
					{ForceItemSelection[gcn, gc, itemLast]; RETURN[TRUE]}
				ELSE RETURN[FALSE];
			END;
			
		last =>
			BEGIN
			itemTry: Item ← gc.items;
			itemLast: Item ← NIL;
			FOR itemTry: Item ← gc.items, itemTry.next WHILE itemTry # NIL DO
				IF ItemAcceptsSelection[itemTry] THEN itemLast ← itemTry;
				ENDLOOP;
			IF itemLast # NIL THEN
					{ForceItemSelection[gcn, gc, itemLast]; RETURN[TRUE]}
				ELSE RETURN[FALSE];
			END;
			
		ENDCASE => ERROR;
	END;
	
		
-- Logical node interface procedures

SaveGCell: PROCEDURE[node: StructureNodes.StructureNode, to: STREAM] =
	BEGIN
	gc: GCell ← NARROW[node.ref];
	Put[to, char[' ], int[gc.nItems], char[' ]];
	
	FOR item: Item ← gc.items, item.next WHILE item # NIL DO
		desc: GCellItemDescriptor ← GetItemDescriptor[item];
		Put[to, char[' ]];
		WriteOneItem[desc, to];
		ENDLOOP;
	END;

ActGCell: PROCEDURE[info: StructureNodes.StructureNode,  I, J: CARDINAL, action: StructureNodes.Action, p1, p2: REF ANY] RETURNS[StructureNodes.StructureNode, CARDINAL, CARDINAL] =
	BEGIN -- note: J is the index of the selected item
	gc: GCell ← NARROW[info.ref];
	sn: StructureNodes.StructureNode; cI, cJ: CARDINAL;
	
	
	SELECT action FROM
		MoveSelectionToNext => IF StepSelection[info, J, next] THEN RETURN[NIL, 0, 0];
		MoveSelectionToPrevious => IF StepSelection[info, J, previous] THEN RETURN[NIL, 0, 0];
		ENDCASE => NULL;
		
	[sn , , ] ← StructureNodes.GetContextDA[gc.da];
	RETURN[sn, gc.selfI, gc.selfJ];
	END;

SubstituteInGCell: PROCEDURE[info: StructureNodes.StructureNode, newText: Rope.ROPE, oldText: Rope.ROPE] =
	BEGIN
	gc: GCell ← NARROW[info.ref];
	FOR item: Item ← gc.items, item.next WHILE item # NIL DO
		SubstituteInItem[gc, item, newText, oldText];
		ENDLOOP;
	END;

AbandonGCell: PROCEDURE[node: StructureNodes.StructureNode] =
	BEGIN
	gc: GCell ← NARROW[node.ref];
	ClearGCell[gc];
	nGCells ← nGCells - 1;
	END;
	
VerifyGCell: PROCEDURE[node: StructureNodes.StructureNode] =
	BEGIN
	gc: GCell ← NARROW[node.ref];
	nItems: CARDINAL ← 0;
	FOR item: Item ← gc.items, item.next WHILE item # NIL DO nItems ← nItems + 1 ENDLOOP;
	IF nItems # gc.nItems THEN ERROR;
	END;
	

-- display node interface procedures

CheckGCellForDirtyData: PROCEDURE[node: StructureNodes.StructureNode] =
	BEGIN
	gc: GCell ← NARROW[node.ref];
	FOR item: Item ← gc.items, item.next WHILE item # NIL DO
		CheckItemForDirtyData[gc, item];
		ENDLOOP;
	END;

PreShowGCell: PROCEDURE[node: StructureNodes.StructureNode] RETURNS[lead, height: INTEGER, space, L, A, B, R, W: INTEGER] =
	BEGIN
	gc: GCell ← NARROW[node.ref];
	height ← 0;
	L ← A ← B ← R ← W ← 0;
	FOR item: Item ← gc.items, item.next WHILE item # NIL DO
	    IF item.buttonTextDirty THEN ComputeTextShape[item];
		IF item.buttonText # NIL THEN
			BEGIN
			height ← height + 1;
			L ← MAX[L, item.textL];
			A ← MAX[A, item.textA];
			B ← MAX[B, item.textB];
			R ← MAX[R, item.textR];
			W ← MAX[W, item.textW];
			END;
		ENDLOOP;
	gc.textNLines ← height;
	RETURN[gc.textLead, height, gc.textSpace, L, A, B, R, W];
	END;

ShowGCellLine: PROCEDURE[node: StructureNodes.StructureNode, L, R, W: INTEGER, lineX: INTEGER, to: STREAM] =
	BEGIN
	gc: GCell ← NARROW[node.ref];
	lx: INTEGER ← 1;
	IF lineX > gc.textNLines THEN
		BEGIN
		FOR I: INTEGER IN [1..W] DO IO.PutChar[to, ' ] ENDLOOP;
		RETURN;
		END;
	FOR item: Item ← gc.items, item.next WHILE item # NIL DO
		IF item.buttonText # NIL THEN
			IF lx = lineX THEN
					BEGIN
					ShowItem[item, L, R, W, to];
					RETURN;
					END
			   ELSE lx ← lx + 1;
	 	ENDLOOP;
	END;

PrePrePaintGCell: PROCEDURE[node: StructureNodes.StructureNode] RETURNS[lead, height: INTEGER, space, L, A, B, R, W: INTEGER] =
	BEGIN
	gc: GCell ← NARROW[node.ref];
	lead ← 0;
	height ← 0;
	L ← A ← B ← R ← W ← 0;
	FOR item: Item ← gc.items, item.next WHILE item # NIL DO
		IF item.buttonTextDirty THEN ComputeTextShape[item];
		IF item.buttonText # NIL THEN
			BEGIN
			height ← height + item.buttonHeight;
			L ← MAX[L, item.buttonL];
			A ← MAX[A, item.buttonA];
			B ← MAX[B, item.buttonB];
			R ← MAX[R, item.buttonR];
			W ← MAX[W, item.buttonW];
			END;
		ENDLOOP;
	RETURN[gc.screenLead, height, gc.screenSpace, L, A, B, R, W];
	END;

PrePaintGCell: PROCEDURE[node: StructureNodes.StructureNode, y, x, L, R, W: INTEGER] =
	BEGIN
	gc: GCell ← NARROW[node.ref];
	paramChanges: BOOLEAN ← gc.lastPrePaintY # y OR gc.lastPrePaintX # x OR gc.lastPrePaintL # L OR gc.lastPrePaintR # R OR gc.lastPrePaintW # W OR gc.unPainted;
	itemY: INTEGER ← y;
	FOR item: Item ← gc.items, item.next WHILE item # NIL DO
		IF item.buttonText # NIL THEN
			BEGIN
			IF item.buttonShapeDirty OR paramChanges OR item.viewerMode # button THEN
				PrePaintItem[gc, item, itemY, x, L, R, W];
			itemY ← itemY + item.buttonHeight;
			item.buttonShapeDirty ← FALSE;
			END;
	 	ENDLOOP;
	gc.lastPrePaintY ← y;
	gc.lastPrePaintX ← x;
	gc.lastPrePaintL ← L;
	gc.lastPrePaintR ← R;
	gc.lastPrePaintW ← W;
	gc.unPainted ← FALSE;
	END;


NoteEnclosingCellGCell: PROCEDURE[node: StructureNodes.StructureNode, dac: StructureNodes.DACell] =
	BEGIN
	END;
   

NoteCoordinatesGCell: PROCEDURE[node: StructureNodes.StructureNode, da: StructureNodes.DisplayArray, I, J: CARDINAL] = 
	BEGIN
	gc: GCell ← NARROW[node.ref];
	x: CARDINAL ← 0;
	gc.da ← da;
	gc.selfI ← I;
	gc.selfJ ← J;
	FOR item: Item ← gc.items, item.next WHILE item # NIL DO
		x ← x+1;
		NoteCoordinatesItem[item, da, I, J, x];
		ENDLOOP;
	END;




--  local procedures

GCellItemVersion: INTEGER = 3;

WriteOneItem: 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: 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;
		
		d: CARDINAL; -- comment depth, from old form
		n: INTEGER; -- number of characters in rope (new form)
		
		CProc: IO.CharProc = TRUSTED
			BEGIN
			IF d = 0 AND char # '{ THEN RETURN[include: FALSE, quit: FALSE];
			IF d = 0 AND char = '{ THEN {d ← d + 1; RETURN[include: FALSE, quit: FALSE]};
			n ← n - 1; RETURN[include: n>=0, quit: n<-1];
			END;
			
		id: Rope.ROPE;
		text: Rope.ROPE;
		zeroValText: Rope.ROPE;
		
		d ← 0; n ← IO.GetInt[from];
		id ← IO.GetSequence[from, CProc];
		d ← 0; n ← IO.GetInt[from];
		text ← IO.GetSequence[from, CProc];
		d ← 0; n ← IO.GetInt[from];
		zeroValText ← IO.GetSequence[from, CProc];
		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
	
	CProc: IO.CharProc = TRUSTED
			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.GetSequence[from, CProc];
	text: Rope.ROPE ← IO.GetSequence[from, CProc];
	zeroValText: Rope.ROPE ← IO.GetSequence[from, CProc];
   

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

ClearGCell: 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;


-- standard cells mechanism

CellDescription: TYPE = RECORD[nItems: CARDINAL, itemDescriptors: LIST OF GCellItemDescriptor];

LoadGCellWithType: PROCEDURE[gc: GCell, type: StandardGCells] =
	BEGIN -- uses SetGCellContentsAsGCell
	cellDescription: CellDescription ← 
			SELECT type FROM
				null => NullDescription,
				colTitle => ColTitleDescription,
				colSum => ColSumDescription,
				rowTitle => RowTitleDescription,
				rowSum => RowSumDescription,
				assignment => AssignmentDescription,
				slot => SlotDescription,
				ENDCASE => ERROR;
				
	nextD: LIST OF GCellItemDescriptor ← cellDescription.itemDescriptors;
	
	NextItem: PROCEDURE RETURNS[GCellItemDescriptor] =
		BEGIN
		thisOne: GCellItemDescriptor ← nextD.first;
		nextD ← nextD.rest;
		RETURN[thisOne];
		END;
		
	SetGCellContentsAsGCell[gc, cellDescription.nItems, NextItem, FALSE];
	IF nextD # NIL THEN ERROR;
	END;


NullDescription: CellDescription ← [0, NIL];

ColTitleDescription: CellDescription ← [1,  LIST[
		[showText, right, noEffect, noEffect, expression, "", "col title"]]];

ColSumDescription: CellDescription ← [1, LIST[ 
		[showValue, rightDecimal, noEffect, noEffect, colSum, "", "", " "]]];

RowTitleDescription: CellDescription ← [1,  LIST[
		[showText, left, noEffect, noEffect, expression, "", "row title"]]];

RowSumDescription: CellDescription ← [1, LIST[
		[showValue, rightDecimal, noEffect, addVal, rowSum, "", "", " "]]];

AssignmentDescription: CellDescription ← [1, LIST[
		[showExp, rightDecimal, addVal, addVal, expression, "", "0.00"]]];

SlotDescription: CellDescription ← [1, LIST[
		[showText, left, noEffect, noEffect, expression, "", "slot"]]];

	
-- local procedures, item specific

CreateItem: PROCEDURE[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[gc, item];
			AnalyzeItemId[gc, item];
			HandleItemValueChanges[gc, item];
			END;
			
		showExp =>
			BEGIN
			item.buttonText ← desc.text;
			item.buttonTextDirty ← TRUE;
			item.buttonTextAnalyzed ← FALSE;
			item.editable ← TRUE;
			AnalyzeItemExpression[gc, item];
			AnalyzeItemId[gc, item];
			HandleItemValueChanges[gc, item];
			END;
			
		showValue, noShow =>
			BEGIN
			AnalyzeItemExpression[gc, item];
			AnalyzeItemId[gc, item];
			HandleItemValueChanges[gc, item];
			IF show THEN SetItemValue[gc, item, item.value];
			END;
			
		ENDCASE => ERROR;
	END;

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

SubstituteInItem: PROCEDURE[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[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[gc, item];
			changes ← TRUE;
			END;
		END;
	IF changes THEN 
		{HandleItemValueChanges[gc, item]; DisplayNewButtonText[gc, item]};
	END;

CheckItemForDirtyData: 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			
	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
			AnalyzeItemExpression[gc, item];
			HandleItemValueChanges[gc, item];
			END;
				
		showId =>
			BEGIN
			AnalyzeItemId[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: PROCEDURE[item: Item] RETURNS[BOOLEAN] =
	BEGIN
	RETURN[ SELECT item.mode FROM
		showText, showId, showExp, showValue => TRUE,
		ENDCASE => FALSE];
	END;
	
-- item specific computation procedures

AnalyzeItemExpression: PROCEDURE[gc: GCell, item: Item] =
	BEGIN
	BEGIN
	expression: Expression;
	IF item.valueMode # expression THEN RETURN;
	IF gc.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;
				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.ncGlobal.dSet, EvalItemExpression, item];
		Dependencies.MarkActionDirty[item.evalAction];
		END;
	item.expression ← AnalyzeExpression[expression, gc.idTables, gc.ncGlobal.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: PROCEDURE[gc: GCell, item: Item] =
	BEGIN
	BEGIN
	id: Identifier;
	IF gc.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;
				ViewerTools.SetContents[item.viewer, item.buttonText];
				GOTO idBad;
				END];
		item.idBad ← FALSE;
		Dependencies.DeactivateAction[item.assignAction];
		item.assignAction ← Dependencies.DefineAction[gc.ncGlobal.dSet, AssignItemValue, item];
		item.id ← AnalyzeIdentifier[id, gc.idTables, gc.ncGlobal.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: 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.ncGlobal.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: 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: 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 Convert.ValueToRope[Convert.Value[real[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;

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: 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: 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;

NoteCoordinatesItem: PROCEDURE[item: Item, da: StructureNodes.DisplayArray, I, J, x: CARDINAL] =
	BEGIN
	item.da ← da;
	item.selfI ← I;
	item.selfJ ← J;
	item.selfItemX ← x;
	
	SELECT item.viewerMode FROM
		nil => NULL;
		button, textBox => ViewerOps.AddProp[item.viewer, $GCellItem, NEW[ItemRefDescBody ← [da, I, J, x]]];
		ENDCASE;
	END;


ComputeTextShape: 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 Rope.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 Rope.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: TRUE,
			fork: TRUE]; 
		ViewerOps.AddProp[button, $GCellItem, NEW[ItemRefDescBody ← [item.da, item.selfI, item.selfJ, item.selfItemX]]];
		item.viewerMode ← button;
		item.viewer ← button;
		END;
	END;


	

DisplayButtonTextAsTextViewer: Buttons.ButtonProc = TRUSTED
	BEGIN
	itemRefDesc: ItemRefDesc ← NARROW[ViewerOps.FetchProp[item.viewer, $GCellItem]];
	gcSn: StructureNodes.StructureNode; gc: GCell; item: Item;
	DBTATVInternal: PROCEDURE =
		BEGIN
		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]
		END;
	[gcSn, gc, item] ← DecodeItemRefDesc[itemRefDesc];
	gc.ncGlobal.executeInMonitor[gc.ncGlobal, DBTATVInternal];
	END;

ForceItemSelection: 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];
	ViewerTools.SetContents[textBox, item.buttonText];
	ViewerOps.AddProp[textBox, $GCellItem, NEW[ItemRefDescBody ← [item.da, item.selfI, item.selfJ, item.selfItemX]]];
	ViewerTools.SetSelection[textBox, NIL];
	item.viewer ← textBox;
	item.viewerMode ← textBox;
	END;

ClearItemViewer: PROCEDURE[item: Item] =
	BEGIN
	IF item.viewerMode = button THEN
		Buttons.ReLabel[item.viewer, "", TRUE];
	
	IF item.viewerMode = textBox THEN
		ViewerTools.SetContents[item.viewer, "", TRUE];
	
	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
	item: Item ← NARROW[itemRef];
	itemRefDesc: ItemRefDesc ← NARROW[ViewerOps.FetchProp[item.viewer, $GCellItem]];
	gcSn: StructureNodes.StructureNode;
	gc: GCell;
	[gcSn, gc, ] ← 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 J: CARDINAL IN [1..itemRefDesc.J) 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..

-- September 23, 1982 5:11 pm: Sturgis, started GCellImpl.mesa
-- October 1, 1982 3:35 pm: add code to create cells with standard types.
-- RET: October 1, 1982 3:50 pm: forgot to define the assorted dependency DataItems in each item.  Also, add code to deactivate them at abandon time.
-- RTE: October 1, 1982 4:12 pm: attempted to analyze an expression before the gc idtables had been set up.  Also, did not reanalyze after resetting the gcidtables.
-- RTE: October 1, 1982 4:23 pm: attempted to analyze expression text in a showText case.
-- RTE: October 1, 1982 4:36 pm: analyzeItemId passed analyzeId item.id, before item.id had been initialized.
-- RTE: October 1, 1982 4:52 pm: forgot to set lead to 0 in PrePrePaintCell.
-- RTE: October 1, 1982 5:01 pm: never called ComputeTextShape.
-- RTE: October 2, 1982 3:24 pm: appropriate data items were not being set dirty when expressions or identifiers were analyzed.  Further, seems useful to add idChanges as a datatem.
-- RTE: October 2, 1982 3:33 pm: expressoin analyze and id analyze did not set epxression (id) bad ← FALSE.
-- RTE: October 2, 1982 3:52 pm: assign item val, in the case of item mode = ShowValue should reset the screen text.
-- October 2, 1982 4:30 pm: add a call to NoteSelection.
-- October 2, 1982 5:12 pm: accept DeSelect as an action, do CheckForDirtyData;
RTE: October 2, 1982 6:17 pm: could not do a NARROW[info] in ActGCell, did not notice that GCellImpl failed to compile, ran with old version of code as binder did not notice. When trying to set a break point, BugBane said "Break NOT set: can't get source name".  Not very informative.  Finally figured it out using CoPilot, which complained explicitly about dates, exhibiting them.
-- RTE: October 2, 1982 6:26 pm: must fork the button proc for converting to a text box, so as to be able to use bugbane.  Eventually this will have to come in through a monitor, but later.
-- change: October 3, 1982 12:55 pm: modify selection mechanism, so that ln.Act returns enclosing context when it fails to perform the act.
change: October 3, 1982 2:04 pm: arrange for button proc to execute inside the NewCalc global monitor.  There may have been a race on an earlier execution, and so time to do it right.
RTE: October 3, 1982 2:28 pm: read and write item (from and to save file) did not include noShow case.
RTE: October 3, 1982 2:49 pm: saving an item left a trailing blank.  HIgher level code expected no trailing blanks duringload.  so remove the trailing blank.
RTE: October 3, 1982 3:31 pm: LoadGCell did not set gc.nItems.
RTE: October 3, 1982 4:11 pm: PreShow was supposed to set gc.textNLines, but didnot.
RTE: October 3, 1982 4:18 pm: ShowGCellLine should start lx at 1, not 0, since first lineX arguemnt is 1.
RTE: October 3, 1982 4:45 pm: ShowLine, after passing all the items, should put out W blanks.
change: October 4, 1982 2:37 pm: add code to handle "ZeroValText".  Also, add a version on saved form of  an item, so that new fields can be easily added.
RTE: October 4, 1982 2:54 pm: forgot to have GetItemDescriptor also deliver the ZeroValText field.  And fact that ropes are default NIL prevented the compiler from complaining.
RTE: October 4, 1982 4:22 pm: load did not read the version number at correct time.  Also, modify ShowValue to not use ZeroValueText if lenght of that text is 0.  (as saving and the reloading converts nil rope to zero lenght text)
RTE: October 4, 1982 4:50 pm: ReadOneItem did not return ZeroValText, again the compiler did not tell me, but returned default NIL.
RTE: October 5, 1982 4:46 pm: code for reading a rope blanks and left braces.   Want it to skip blanks and first left brace, but no characters beyond the first brace.  Added a test in InitialSkip to quit the skip on any character beyond the first left brace.
change: October 6, 1982 5:39 pm: NoteSelection is now declared in GCell, and takes an additional param.
RTE: October 7, 1982 2:57 pm: ClearGCell did not set nItems to 0, hence nItems increased each time the GCell was set.
change: October 8, 1982 1:16 pm: add slot type to standard cells.
RTE: October 8, 1982 1:52 pm: wrong count at head of descriptor for slot type standard cell.
change: October 8, 1982 4:09 pm: arrange to prepaint an item only if external params have changed, or item has changed.
RTE: October 8, 1982 4:18 pm: item.buttonHeight was not getting set for leftDecimal or rightDecimal justification.
change: October 10, 1982 11:51 am: convert to 3.4 (the button viewer is now named "parent", and is of type REF ANY). 
RTE: October 10, 1982 12:44 pm: read and write were unable to handle mode = showId.
RTE: October 10, 1982 1:07 pm: identifier in textBox form clips off trailing characters.  try increasing buttonW to +20 rather than +10.
RTE: October 10, 1982 1:15 pm: hmm, previous fix did not work; anyway, id.name is looking like a decimal point, so modify decimal point code to stop looking for one if a non digit is found during right to left scan.
RTE: October 10, 1982 1:26 pm: ah, did not find all the settings of item.buttonW.  eventually I should clean up this code so that there is only one such setting, or I use a named value.
October 10, 1982 2:09 pm: get Action from StrucutreNodes, rather than StructureNode.
October 11, 1982 1:47 pm: make (prefix) space a cell, rather than item, property.  Set TextSpace to 2 for the time being, until it becomes a modifiable property.
October 14, 1982 4:04 pm: Note selection now returns more information, which must be passed in at creation time.
October 16, 1982 3:22 pm: conditionally entry to monitor in GetGCellContents and SetGCellContents.
-- October 23, 1982 11:10 am: row sum and col sum code, also only create needed actions and dataitems.
-- RTE: October 23, 1982 12:11 pm: assorted problems handling empty ids, and getting unwanted parse errors.
-- RTE: October 23, 1982 12:27 pm: EvalExpression now has to call SetItemValue, so as to get button created if needed.  (AssignItemValue no longer does this.)
-- RTE: October 23, 1982 12:36 pm: had to add  a "firstTime" boolean to an item to get value to display the first time, since I had also placed in code not too redisplay value if it hadnt changed.
-- RTE: October 23, 1982 12:56 pm: (symptom: multiple assignment to one dataitem) (cause: analyzing item expression when value mode # expression).
-- RTE: October 23, 1982 1:05 pm: another occurance of the above, found yet another call to AnalyzeItemExpression.
-- RTE: October 23, 1982 1:12 pm: yet another, so, change code so that AnalyzeItemExpression returns if valueMOde # expression.
-- RTE: October 23, 1982 2:56 pm: attempt to read new rope form fails, must set d to 0 before each input rope.
-- change: October 24, 1982 12:48 pm: add code to conditionally add item.valueChanges dataItem.
-- RTEs(2): October 24, 1982 1:05 pm: several attempts to link dependencies to not yet created dependencies, or wrongly named dependencies.
-- change: October 24, 1982 1:13 pm: add carefully taylored test for constant expression.  Should eventually move this to expression code.
-- CTE: October 24, 1982 1:18 pm: well, it was taylored using the debugger, but I didn't notice the Ref Anys tying it together.  So, have to add some NARROWs.
-- RTE: October 24, 1982 1:30 pm: still got it wrong, one of the types as a Token, not an Expression.  Sure would have been helpful if the debugger had stated the types.
-- RTE: October 24, 1982 1:35 pm: must put the analyzed expression in the item, even if a constant.  So that it can subsequently evaluated.
-- RTE: October 24, 1982 1:50 pm: now we have to hand set outgoing actions dirty when the expression is edited to a constant.
-- RTE: October 25, 1982 4:09 pm: blank expressions produce NIL, which tripped up the non contant test.
-- change: October 26, 1982 3:21 pm: begin changes to support Next cell feature, also alow for selection of textBox form items.  Also, automatic text selection when changing to a textbox.
-- RTE: October 26, 1982 6:02 pm: ItemSelection mechanism counted items from 0 rather than 1.
-- RTE: October 26, 1982 6:08 pm: and that fouled up the move to next.
-- RTE: October 28, 1982 2:52 pm: modify ForceItemSelection to handle non editable items by building a text viewer, and then inhibiting user edit, rather than refusing to build the text viewer.  Thus the new selection mechanism that uses the Viewer selection concept will work on non editable cells.
-- RTE: October 28, 1982 2:59 pm: checkitemfordirty data generates an error if in form of text box and not editable.  remove this error;
-- RTE: October 28, 1982 3:24 pm: modify DisplayButtonTextAsButton to check to see if the item viewer is the [Viewer] selected viewer.  If so, display it as a text viewer anyway, and preserve the selection.
-- RTE: October 28, 1982 3:37 pm: add showValue as a case that will accept selection.  This prevents selection from disapearing during certain delete column or row actions.
-- RTE: October 30, 1982 4:34 pm: symptom: cells showing value do not get displayed just after begin set by celltool.  Fix: add a "show" param to CreateItem, which is looked at in showValue case, and if true, calls SetItemValue.  Also rig PrePaintGCell to PrePaint the item (in addition to previous cases) if the item.mode # button.
-- RTE: October 31, 1982 4:09 pm: remove the error from DisplayButtonTextAsTextViewer, simply return if the item.viewer is not the argument to the button proc.  Presumably some concurrent process has removed the button from the item, and is destroying the button.
-- RTEs: November 2, 1982 2:57 pm: 1) modify HandleValueChanges so that in case ValueChanges is not to exist, then hand marks assorted actions dirty.  2) add an unPainted field to a gCell.  Force building of an item viewer during prePaint if the gCell is unpainted. 3) remove search for item in Act code.  This was causing errors during DeSelect, which will be removed also.
-- change: November 2, 1982 4:10 pm: add SubstituteInGCell.
-- November 2, 1982 4:27 pm: add 2 ref any params to ActGCell.
-- RTE: November 2, 1982 5:27 pm: specified incorrect length of text to replace.
-- RTE: November 2, 1982 5:47 pm: only substituted in the visible text.
-- RTE: November 2, 1982 5:53 pm: substituted in the wrong text.
-- RTE: November 2, 1982 6:02 pm: old ItemIs dirty code expected only the displayed fields to change.  So had to put in specific code to call the appropriate anal routines.
-- change: November 3, 1982 3:33 pm: make SubstituteInGCell a logical node proc.
-- change: November 4, 1982 1:31 pm: do not create valueChangesDataItem when creating an item.
-- change: November 14, 1982 1:49 pm: conform to new selection logic
-- change: November 14, 1982 2:49 pm: add setZero mode to col and row summs.
-- RTE: November 14, 1982 4:00 pm: during create item, even in showText case, must call HandleItemValueChanges, because some of the modes may affect rowSummer or colSummer, therefore, want associated actions marked dirty.
-- CTE: February 11, 1983 10:54 am: convert to 4.0.  Major change in IO parsing mechanism.  Somewhat cleaner, Hvae to use GetSequence, and it has the property that if the char being lookd at when quit is returned is not included, then it will be seen by the next reader.
-- RTE: February 11, 1983 2:24 pm: replace calls on GetAtom by calls on NARROW[GetRefAny[]].
-- RTE: February 11, 1983 2:38 pm: CProc for current text version did not terminate so as to through away the trailing '}.
-- change: February 25, 1983 1:31 pm: add counters for nGCells and nGCellItems.
-- February 27, 1983 1:52 pm: convert from logical and display nodes to structurenodes.  Objective is to avoid the back poitners from GCell to the nodes, and the two forward poitners from the cells to GCell.  Took aout 45 minutes.
-- RTE: February 28, 1983 10:28 am: selection mechanism failed, had to modify GetContextDACell to also return the sn of the gcell contained in the DACell.
-- remark: February 28, 1983 2:04 pm: previous changes only reduced ref counts by 1, not the expected 2.  I suspect that someone is holding two pointers to gcell.sn.  Not sure where.
--remark: February 28, 1983 2:05 pm: now change the selectoin mechanism to store a pointer to the display array holding the gcell, along with coordinates, in the viewer.  This requires a co-ordinaote change notification routine.
-- remark: February 28, 1983 2:17 pm: first add dummy note coordinates procedure.
-- February 28, 1983 2:56 pm: change selection mechanism to store da and coordinates in viewer, rather than item.
-- UGH: February 28, 1983 4:47 pm: Storage overflow in pass 3.  SO have to split of items.
-- February 28, 1983 4:47 pm: remark: Also, need to put indirect addressing info in the actions associated with an item, and this will require the ability to update that info when coordinates change.