-- File: NutViewerMiscImpl.mesa
-- Contents: Implementation of most of Squirrel Viewers interface (NutViewer).
-- Last edited by:
-- Cattell on June 7, 1983 3:01 pm
-- Willie-Sue on January 21, 1983 8:48 am
-- Donahue, April 5, 1983 11:18 am

DIRECTORY
Buttons,
Containers USING [ChildXBound, ChildYBound],
DB,
IO,
Labels USING [Create],
MessageWindow,
Menus,
MBQueue,
Nut,
NutOps,
NutViewer,
Process USING [Detach],
Rope,
Rules USING [Create],
SquirrelTool USING [squirrel],
ViewerOps,
ViewerClasses,
ViewerTools USING [MakeNewTextViewer, SetContents],
ViewerSpecs USING [openLeftTopY],
VFonts USING[defaultFont, Font];

NutViewerMiscImpl: CEDAR PROGRAM
IMPORTS
Buttons, DB, IO, Nut, NutOps, MessageWindow,
  Containers, Labels, MBQueue, Menus, Process,
  Rope, Rules, SquirrelTool, NutViewer, ViewerOps, ViewerTools, VFonts
EXPORTS NutViewer =

BEGIN OPEN NutViewer, DB;

-- Types and global variables

Queue: TYPE = MBQueue.Queue;

entryHeight: INTEGER = 14;

-- yTextFudge: INTEGER = 0;
xTextFudge: INTEGER = 8;
xFudge: INTEGER = 4;
-- yFudge: INTEGER = 0;

Viewer: TYPE = ViewerClasses.Viewer;
ViewerRec: TYPE = ViewerClasses.ViewerRec;
ROPE: TYPE = Rope.ROPE;

defaultDBQueue: Queue;

-- Exported procedures

DBQueue: PUBLIC PROC RETURNS[Queue] =
{RETURN[defaultDBQueue]};

Initialize: PUBLIC PROC[parent: Viewer] RETURNS [nV: Viewer] =
-- Makes a label which is the first one in a viewer, at standard Y value
BEGIN
CheckAborted[parent];
nV← ViewerOps.CreateViewer[
flavor: $Label, info: [parent: parent, ww: 0, wh: 0, wy: 1,
  wx: IF parent.scrollable THEN 0 ELSE xFudge, border: FALSE]]
END;

MakeTextViewer
: PUBLIC PROC[
sib: Viewer, w: INTEGER, fullLine: BOOL] RETURNS [nV: Viewer] =
BEGIN
IF fullLine THEN RETURN[NextTextViewer[sib]]
ELSE RETURN[NextRightTextViewer[sib, w]];
END;

NextTextViewer
: PUBLIC PROC[sib: Viewer] RETURNS [nV: Viewer] =
-- Creates text viewer on line after sib, full width of parent
BEGIN
CheckAborted[sib];
nV← ViewerTools.MakeNewTextViewer[
info: [parent: sib.parent, wy: sib.wy+sib.wh, ww: sib.parent.ww,
  wh: ViewerSpecs.openLeftTopY]];
Containers.ChildXBound[sib.parent, nV];
Containers.ChildYBound[sib.parent, nV];
END;

NextRightTextViewer: PUBLIC PROC[sib: Viewer, w: INTEGER] RETURNS [nV: Viewer] =
-- creates a text viewer, next right on the same line as v
-- v must be a Viewer, not NIL
BEGIN
CheckAborted[sib];
nV← ViewerTools.MakeNewTextViewer[
info: [parent: sib.parent, wx: sib.wx+sib.ww+xFudge, wy: sib.wy,
ww: w, wh: entryHeight, border: FALSE]];
END;

MakeLabel: PUBLIC PROC[
name: ROPE, sib: Viewer← NIL, newLine: BOOLFALSE] RETURNS [nV: Viewer] =
BEGIN
-- ly: INTEGER← sib.wy;
-- IF sib.class.flavor = $Text THEN ly← ly - yTextFudge;
info: ViewerRec← [name: name, parent: sib.parent, wy: sib.wy, wh: entryHeight,
  border: FALSE];
CheckAborted[sib];
IF newLine THEN
{ info.wx← IF sib.parent.scrollable THEN 0 ELSE xFudge;
  info.wy← info.wy + sib.wh;
}
ELSE info.wx← sib.wx+sib.ww;
nV← Labels.Create[info: info];
END;

MakeButton: PUBLIC PROC[q: Queue, name: ROPE, proc: Buttons.ButtonProc,
 sib: Viewer, data: REF ANYNIL, border: BOOLFALSE, width: INTEGER← 0,
 guarded: BOOLFALSE, font: VFonts.Font ← VFonts.defaultFont, newLine: BOOLFALSE]
RETURNS [nV: Viewer] =
-- The sib is a viewer to the left or above the button to be made.
-- If q#NIL THEN button will be serialized with other buttons/menus on q,
-- otherwise it uses defaultDBQueue
-- makes a button which is the first one in a viewer
BEGIN
info: ViewerRec← [name: name, wy: sib.wy, ww: width, wh: entryHeight,
  parent: sib.parent, border: border];
CheckAborted[sib];
IF newLine THEN -- first button on new line
{ info.wy← sib.wy + sib.wh + (IF border THEN 1 ELSE 0); -- extra bit
info.wx← IF sib.parent.scrollable THEN 0 ELSE xFudge;
  }
ELSE -- next button right on same line as previous
info.wx← sib.wx+sib.ww+xFudge;
IF q=NIL THEN RETURN[Buttons.Create[
info: info, proc: proc, clientData: data, fork: FALSE, font: font, guarded: guarded]]
ELSE RETURN[ MBQueue.CreateButton[
q: q, info: info, proc: proc, clientData: data, font: font, guarded: guarded]]
END;

MakeMenuEntry: PUBLIC PROC[q: Queue, name: ROPE, proc: Menus.MenuProc,
clientData: REF ANYNIL, documentation: REF ANYNIL, fork: BOOLTRUE,
guarded: BOOLFALSE] RETURNS[Menus.MenuEntry] = {
IF q=NIL THEN RETURN[
Menus.CreateEntry[name, proc, clientData, documentation, fork, guarded]]
ELSE RETURN
[
MBQueue.CreateMenuEntry[q, name, proc, clientData, documentation, fork, guarded]]
};

MakeTypescript: PUBLIC PROC[sib: Viewer] RETURNS [ts: Viewer] =
BEGIN
y: INTEGER← sib.wy+sib.wh+xFudge;
CheckAborted[sib];
ts← ViewerOps.CreateViewer[flavor: $Typescript,
info: [parent: sib.parent, ww: sib.cw, wy: y, wh: sib.parent.ch - y, border: FALSE] ];
Containers.ChildYBound[sib.parent, ts];
Containers.ChildXBound[sib.parent, ts];
END;

MakeBigTextBox: PUBLIC PROC[sib: Viewer, contents: ROPE] RETURNS [nV: Viewer]=
-- Makes editable text viewer taking rest of sib.parent's viewer,
-- suitable for msg body or whatever.
BEGIN
y: INTEGER← 0;
oldV: Viewer← sib.parent;
h: INTEGER;
CheckAborted[sib];
IF oldV = NIL THEN oldV← sib ELSE y← sib.wy+sib.wh+xFudge;
h← oldV.ch - y;
nV ← ViewerTools.MakeNewTextViewer[
  info: [parent: oldV, wy: y, border: FALSE, ww: oldV.ww, wh: h],
  paint: FALSE];
ViewerTools.SetContents[nV, contents];
Containers.ChildYBound[oldV, nV];
Containers.ChildXBound[oldV, nV];
END;

MakeRuler: PUBLIC PROC[sib: Viewer, h: INTEGER← 1] RETURNS [nV: Viewer] =
-- Make a one-bit wide line after sib
BEGIN
CheckAborted[sib];
nV← Rules.Create[
info: [parent: sib.parent, wy: sib.wy+sib.wh+1, ww: sib.parent.ww, wh: h]];
Containers.ChildXBound[sib.parent, nV];
END;

CheckAborted: PROCEDURE[sib: Viewer] =
BEGIN
state: ATOM;
IF sib = NIL THEN RETURN;
IF sib.parent # NIL THEN sib ← sib.parent;
state ← NARROW[ViewerOps.FetchProp[sib, $NutViewerState]];
IF state = $beingDestroyed THEN ERROR ABORTED; -- caught by NutImpl.Display
END;

ProcessSelection: PUBLIC Buttons.ButtonProc =
-- A standard ButtonProc that assumes the button's REF ANY data is the FieldHandle above.
-- Insures attribute of tuple is entity-valued and non-NIL, then calls Nut.Display on it.
BEGIN OPEN DB;
otherEntity: Entity;
fd: FieldHandle← NARROW[clientData];
viewer: Viewer← NARROW[parent];
IF fd.attribute=NIL THEN -- it was the relation itself
{ otherEntity ← RelationOf[fd.tuple];
[]← Nut.Display[e: otherEntity, parent: viewer.parent, seg: SegmentOf[otherEntity]] }
ELSE IF NutOps.EntityValued[fd.attribute] THEN -- attribute refs another entity
IF Null[fd.tuple] THEN
Message[viewer, "That relationship has been deleted!"]
ELSE IF Null[otherEntity←V2E[GetF[fd.tuple, fd.attribute]]] THEN
Message[viewer, "That entity has been deleted!"]
ELSE
{ otherEntity ← V2E[GetF[fd.tuple, fd.attribute]];
[]← Nut.Display[e: otherEntity, parent: viewer.parent, seg: SegmentOf[otherEntity]] }
ELSE -- attribute is a string or number
Message[viewer, "Not an entity-valued field"];
END;

DefaultFreezeProc
: PUBLIC Menus.MenuProc =
BEGIN
 self: Viewer = NARROW[parent];
 frozen: Menus.MenuEntry = Menus.FindEntry[self.menu, "Freeze"];
IF frozen = NIL THEN RETURN;
 Menus.ReplaceMenuEntry[self.menu, frozen];
 ViewerOps.PaintViewer[self, menu];
 ViewerOps.AddProp[self, $Frozen, self];
END;

DBNotifier: PUBLIC PROC [q: MBQueue.Queue] = {
-- Just like Notifier provided by MBQueue, except catches generic database errors
v: REF ANY; -- the viewer in which the action occurred, or NIL if client-defined action
DO -- forever
ENABLE BEGIN
ABORTED
=>
{MBQueue.Flush[q]; LOOP};
DB.Aborted =>
TRUSTED {MBQueue.Flush[q]; Process.Detach[FORK NutOps.TryRestart[trans]]; LOOP};
DB.Failure =>
{MBQueue.Flush[q]; NutViewer.Error[NARROW[v], "Alpine problem."]; LOOP};
DB.Error =>
IF
code=NullifiedArgument THEN
{NutViewer.Message[NARROW[v], "Sorry, that entity has been nullified!"]; LOOP}
ELSE IF code=TransactionNotOpen THEN
{NutViewer.Message[NARROW[v], "There is no transaction open!"]; LOOP}
END;
event: MBQueue.Action← MBQueue.DequeueAction[q];
WITH event SELECT FROM
e1: MBQueue.Action.user =>
{v← e1.parent; e1.proc[e1.parent, e1.clientData, e1.mouseButton, e1.shift, e1.control]};
e2: MBQueue.Action.client =>
{v← NIL; e2.proc[e2.data]};
ENDCASE => ERROR;
ENDLOOP;
};

Message: PUBLIC PROC[v: Viewer, msg1, msg2, msg3, msg4: ROPE← NIL] =
-- Print msgs in v's typescript if there is one, else in Squirrel window if there is one,
-- else in message window.
BEGIN out: IO.Handle ← GetTypescript[v];
IF out=NIL THEN
{MessageWindow.Clear[]; MessageWindow.Append[Rope.Cat[msg1, msg2, msg3, msg4]]}
ELSE {
out.Put[IO.rope[msg1], IO.rope[msg2], IO.rope[Rope.Concat[msg3,msg4]]]; out.PutChar[IO.CR]};
END;

MessageRope: PUBLIC PROC[v: Viewer, msg: ROPE← NIL] =
BEGIN out: IO.Handle← GetTypescript[v];
IF out=NIL THEN {MessageWindow.Append[msg]}
ELSE {out.Put[IO.rope[msg]]};
END;

Error: PUBLIC PROC[v: Viewer, msg1, msg2, msg3, msg4: ROPE← NIL] =
-- Same as Message, but blinks to get user's attention.
{MessageWindow.Blink[]; Message[v, msg1, msg2, msg3, msg4]};

GetTypescript
: PROC [v: Viewer] RETURNS [out: IO.Handle] = {
IF v=NIL THEN
IF (v← SquirrelTool.squirrel)=NIL THEN RETURN[NIL]
ELSE IF v.iconic THEN ViewerOps.BlinkIcon[v]
ELSE
v← GetTopLevel[v];
RETURN[NARROW[ViewerOps.FetchProp[GetTopLevel[v], $Typescript]]];
};

GetTopLevel
: PROC [v: Viewer] RETURNS [top: Viewer] =
{FOR top← v, top.parent UNTIL top.parent=NIL DO ENDLOOP};

StartTrap: PROC = TRUSTED {
defaultDBQueue← MBQueue.Create[pushModel: FALSE];
Process.Detach[FORK DBNotifier[defaultDBQueue]]};


StartTrap[];

END
.

-- Change log

WS 23-Feb-82 16:37:41: took out paint: FALSE on all viewer creations
   made all Buttons have border: FALSE

Rick 1—March or so: fixed bug in ProcessSelection: check that tuple is not null (deleted).

Rick March 25, 1982 4:01 pm: Moved CommitEditableTuple here since it was only proc in NutEditorImpl.

Rick August 3, 1982 4:11 pm: catch AttributeValueTypeMismatch in CommitEditableTuple & recover.

Cattell June 3, 1983 11:27 am: Added fancy Message stuff here, completely reorganized NextXViewer stuff to simplify.

Cattell June 6, 1983 3:24 pm: Attempt to recover from aborted transactions in DBNotifier.