-- File: NutImpl.mesa
-- Contents: Implementation of Nut.mesa.
-- Created by: Rick Cattell on January, 1982
-- Last edited by:
-- Cattell on June 3, 1983 12:30 pm
-- Willie-Sue on January 21, 1983 8:55 am
-- Maxwell on July 15, 1982 12:26 pm
-- Donahue on May 23, 1983 12:22 pm

DIRECTORY
Atom, DB, Nut, NutPrivate, Rope,
DBNames USING[NameToEntity],
MessageWindow USING[ Append ],
Process USING [InitializeCondition, SecondsToTicks],
NutOps,
NutViewer,
SystemNuts,
ViewerClasses,
ViewerOps;

NutImpl: CEDAR MONITOR
IMPORTS
Atom, DB, DBNames, Nut, NutPrivate, NutViewer, Rope, Process,
ViewerOps, SystemNuts, NutOps, MessageWindow
EXPORTS Nut =

BEGIN OPEN Nut, DB;

DomainOf: PROC[e: Entity] RETURNS[d: Domain] =
{IF e=NIL THEN RETURN[NIL] ELSE RETURN[DB.DomainOf[e]]};

-- Types and global variables

implementations: PUBLIC ImplementationList ← NIL;

ImplementationList: TYPE = LIST OF ImplementationRecord ← NIL;
ImplementationRecord: TYPE = REF ImplementationRecordObject;
ImplementationRecordObject: TYPE = RECORD[
domain: ROPE, -- name of domain
segment: DB.Segment, -- the segment of the domain
display: DisplayProc, -- nut displayer for this domain
edit: EditProc, -- nut editor for this domain
query: QueryProc, -- nut queryer for this domain
create: CreateProc, -- nut creater for this domain
update: UpdateProc, -- nut updater for this domain
transaction: TransactionProc]; -- transaction notification for this domain

defaultNutViewerProcs: ImplementationRecord← NEW[ImplementationRecordObject ← [
display: DefaultDisplay, edit: DefaultEdit,
query: DefaultQuery, create: DefaultCreate ]];

domainProcs: ImplementationRecord ← NEW[ImplementationRecordObject ← [
display: SystemNuts.DomainDisplayer, edit: SystemNuts.DomainEditor,
query: SystemNuts.DomainQueryer, create: SystemNuts.DomainCreate ]];

relationProcs: ImplementationRecord ← NEW[ImplementationRecordObject ← [
display: SystemNuts.RelationDisplayer, edit: SystemNuts.RelationEditor,
query: SystemNuts.RelationQueryer, create: SystemNuts.RelationCreate ]];

viewerSynchronizer: CONDITION;
vSynchronizerPtr: LONG POINTER TO CONDITION;
NutViewerState: TYPE = ATOM; -- {$quiescent, $beingDisplayed, $beingDestroyed};

-- Exported procedures

Display: PUBLIC PROC[ e: DB.Entity, seg: DB.Segment ← NIL,
        parent: Viewer← NIL, method: Method ← oneOnly] RETURNS[v: Viewer] =
-- Creates a new nut viewing entity e. If a nut implementation has
-- registered itself for e's type, that implementation will be called,
-- else the standard NutViewer browser will be used. Replace caller's nut.
BEGIN
IF Null[e] THEN {MessageWindow.Append["Entity has been deleted!"]; RETURN};
{ eName: ROPE = NameOf[e];
d: Domain = DomainOf[e];
dName: ROPE = NameOf[d];
segment: DB.Segment = IF DB.IsSystemEntity[e] THEN seg ELSE SegmentOf[e];
implRec: ImplementationRecord = FindImpl[dName, segment];
oldV: Viewer = FindExistingViewer[displayer, d, eName, segment, parent, method];
newV: Viewer = CreateNut[oldV, displayer, implRec, d, eName, segment, e];
IF newV=NIL THEN RETURN[NIL];
IF method # replace THEN NutPrivate.SetSpawned[parent, newV];
newV.newVersion ← TRUE;
ViewerOps.PaintViewer[newV, caption];
implRec.display[e, newV, segment ! ABORTED => CONTINUE];
SetViewerState[newV, $quiescent];
newV.newVersion ← FALSE;
ViewerOps.PaintViewer[newV, caption];
RETURN[newV] }
END;

Edit: PUBLIC PROC[ d: DB.Domain, eName: ROPE, seg: DB.Segment ← NIL,
      parent: Viewer← NIL, method: Method ← oneOnly] RETURNS[v: Viewer] =
BEGIN
dName: ROPE = GetName[d];
e: Entity = FetchEntity[d, eName, seg];
segment: DB.Segment = IF NutOps.IsSystemDomain[d] THEN seg ELSE SegmentOf[d];
implRec: ImplementationRecord = FindImpl[dName, segment];
oldV: Viewer = FindExistingViewer[editor, d, eName, segment, parent, method];
newV: Viewer = CreateNut[oldV, editor, implRec, d, eName, segment, e];
IF newV=NIL THEN RETURN[NIL];
IF method # replace THEN NutPrivate.SetSpawned[parent, newV];
newV.newVersion ← TRUE;
ViewerOps.PaintViewer[newV, caption];
implRec.edit[d, eName, newV, segment ! ABORTED => CONTINUE];
SetViewerState[newV, $quiescent];
newV.newVersion ← FALSE;
ViewerOps.PaintViewer[newV, caption];
RETURN[newV]
END;

Query: PUBLIC PROC[d: DB.Domain, seg: DB.Segment ← NIL] RETURNS[v: Viewer] =
-- Creates a new nut for querying entities in d with given initial query.
BEGIN dName: ROPE = GetName[d];
segment: DB.Segment = IF NutOps.IsSystemDomain[d] THEN seg ELSE SegmentOf[d];
implRec: ImplementationRecord = FindImpl[dName, seg];
newV: Viewer = CreateNut[NIL, queryer, implRec, d, "?", segment];
IF newV=NIL THEN RETURN[NIL];
implRec.query[d, newV, segment ! ABORTED => CONTINUE];
SetViewerState[newV, $quiescent];
RETURN[newV]
END;

Update: PUBLIC PROC[updateType: UpdateType, tuple: Relship] =
-- calls any registered proc for e's domain
BEGIN
FOR iL: ImplementationList← implementations, iL.rest UNTIL iL=NIL DO
-- if were smart, could just call guys that could be effected by this tuple
IF iL.first.update#NIL THEN iL.first.update[updateType, tuple];
ENDLOOP;
END;

FindExistingViewer: PROC[ type: Nut.NutType, d: Domain, eName: ROPE, seg: DB.Segment,
          parent: Viewer← NIL, method: Method ← oneOnly]
  RETURNS[oldV: Viewer] =
BEGIN
-- look for an existing viewer
IF method = replace THEN oldV ← parent ELSE oldV ← NutPrivate.FindSpawned[parent];
IF oldV # NIL AND oldV.newVersion THEN oldV ← NIL;
IF oldV = NIL AND method = oneOnly THEN
oldV ← NutPrivate.FindViewer[type, d, eName, seg];
IF oldV # NIL AND oldV.newVersion THEN oldV ← NIL;
END;

CreateNut: PROC[ oldV: Viewer, type: NutType, implRec: ImplementationRecord,
      d: Domain, eName: ROPE, segment: Segment, e: Entity← NIL]
RETURNS [newV: Viewer] =
-- Used by Display, Edit, and Create procs: sets up a new nut in newV according to the rules.
-- Returns the new viewer and the nut record for it, or NIL if nut creation should be aborted.
-- Sets viewer state to $beingDisplayed, caller must set to $quiescent when done displaying.

BEGIN
name: ROPE = Rope.Cat[Atom.GetPName[segment], "!", DB.NameOf[d], "!", eName];
newV ← implRec.create[type, d, eName, segment, IF oldV = NIL THEN left ELSE oldV.column];
IF newV=NIL THEN RETURN;
ViewerOps.AddProp[ newV, $Entity, name ];
IF eName#NIL THEN
ViewerOps.AddProp[ newV, $EntityHandle, DBNames.NameToEntity[name] ];
IF newV.icon = tool THEN
-- Set the icon if the create proc left it as default (tool) icon
newV.icon ←
IF e#NIL THEN NutViewer.GetIcon[e, segment] -- use icon for specific entity
ELSE NutViewer.GetIcon[d, segment]; -- use icon for domain
SetViewerState[newV, $beingDisplayed];
IF newV = oldV THEN RETURN;
IF oldV#NIL THEN
{ IF oldV.iconic AND NOT newV.iconic THEN ViewerOps.OpenIcon[oldV];
SetViewerState[oldV, $beingDestroyed];
ViewerOps.ReplaceViewer[new: newV, old: oldV] }
ELSE IF ~newV.iconic THEN ViewerOps.ComputeColumn[newV.column];
END;

BeingDestroyed: PROCEDURE[v: Viewer] RETURNS[BOOLEAN] =
BEGIN
state: NutViewerState;
IF v.destroyed THEN RETURN[TRUE];
state ← NARROW[ViewerOps.FetchProp[v, $NutViewerState]];
RETURN[state = $beingDestroyed];
END;

SetViewerState: ENTRY PROC[v: Viewer, newState: NutViewerState] =
-- Used for synchronization of nut viewers as the change states. The states are
-- 1. NIL: container created but client DisplayProc (which creates subviewers) not yet called.
-- 2. $beingDisplayed: client DisplayProc in process of setting up the viewer.
-- 3. $quiescent: viewer has been created and displayed and is ready to go.
-- 4. $beingDestroyed: user or client has requested destruction of this viewer, not yet finished.
-- If client requests destroy, waits until finished creating before doing so.
-- Sets inhibitDestroy bit in ViewerRec so Viewers won't allow user to destroy it until finish create.
BEGIN OPEN ViewerOps;
 oldState: NutViewerState = NARROW[FetchProp[v, $NutViewerState]];
 AddProp[v, $NutViewerState, newState];
SELECT newState FROM
  $quiescent =>
SELECT oldState FROM
  $beingDisplayed => v.inhibitDestroy← FALSE; -- normal case: finished display without destroy
  $beingDestroyed => BROADCAST viewerSynchronizer; -- wake up pending destroy
  $quiescent => NULL; -- although we don't think this can ever happen
ENDCASE => ERROR;
  $beingDisplayed => -- we assume this MUST be a new viewer if come in with this
  v.inhibitDestroy ← TRUE;
  $beingDestroyed => -- wait for finish of display if being displayed
SELECT oldState FROM
NIL => RETURN;
  $quiescent => RETURN;
  $beingDestroyed => ERROR;
  $beingDisplayed => {
WHILE NARROW[FetchProp[v, $NutViewerState], ATOM] # $quiescent DO
    WAIT viewerSynchronizer; ENDLOOP;
  v.inhibitDestroy← FALSE;
  AddProp[v, $NutViewerState, $beingDestroyed];
    };
   ENDCASE => ERROR;
ENDCASE => ERROR;
END;

GetViewerState: PUBLIC ENTRY PROC[v: Viewer] RETURNS [NutViewerState] =
{RETURN[NARROW[ViewerOps.FetchProp[v, $NutViewerState], ATOM]]};

Register: PUBLIC PROC[
domain: ROPE,
segment: DB.Segment,
display: DisplayProc← NIL,
edit: EditProc← NIL,
query: QueryProc← NIL,
create: CreateProc← NIL,
update: UpdateProc← NIL,
transaction: TransactionProc← NIL ] =
-- Registers a display, create, query, and/or notify proc for given domain. These will
-- supersede any previous non-NIL registrations for this domain.
BEGIN implRec: ImplementationRecord← FindImpl[domain, segment, TRUE];
IF display#NIL THEN implRec.display← display;
IF edit#NIL THEN implRec.edit← edit;
IF query#NIL THEN implRec.query← query;
IF create#NIL THEN implRec.create← create;
IF display#NIL THEN implRec.display← display;
IF update#NIL THEN implRec.update← update;
IF transaction#NIL THEN implRec.transaction← transaction;
END;

DeRegister: PUBLIC PROC[segment: DB.Segment, domain: ROPE] =
BEGIN
 prev: ImplementationList← NIL;
FOR iL: ImplementationList← implementations, iL.rest UNTIL iL=NIL DO
IF Rope.Equal[iL.first.domain, domain] AND iL.first.segment = segment THEN
{ IF prev = NIL THEN implementations← iL.rest
ELSE prev.rest← iL.rest;
EXIT;
};
prev← iL;
ENDLOOP;
END;

-- Support procedures

debug: PUBLIC BOOLEANFALSE;

FindImpl: PROC[d: ROPE, seg: Segment, create: BOOLEANFALSE] RETURNS[ImplementationRecord] =
-- Try to find an implementation for domain d. If not found, return the default one.
BEGIN
IF Rope.Equal[d, "Domain"] THEN RETURN[ domainProcs ];
IF Rope.Equal[d, "Relation"] THEN RETURN[ relationProcs ];
IF ~debug THEN
FOR iL: ImplementationList ← implementations, iL.rest UNTIL iL=NIL DO
IF Rope.Equal[iL.first.domain, d] AND seg = iL.first.segment
THEN RETURN[iL.first] ENDLOOP;
IF NOT create THEN RETURN[ defaultNutViewerProcs ]
ELSE
BEGIN implRec: ImplementationRecord = NEW[ImplementationRecordObject];
implRec^ ← defaultNutViewerProcs^; implRec.domain← d;
implRec.segment ← seg;
implementations← CONS[ implRec, implementations ];
RETURN[implementations.first];
END END;

Initialize: PROC = TRUSTED
{ vSynchronizerPtr← @viewerSynchronizer;
Process.InitializeCondition[vSynchronizerPtr, Process.SecondsToTicks[1200]]; -- 20 minutes ??
};

--* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-- one-time start code
Initialize[];

END.

Last edited by:

Rick on April 3, 1982 5:36 pm: Added new CreateProc feature so clients can create their own viewers. Added DefaultCreateProc here. Started using ReplaceViewer.

WS on April 5, 1982: Took out ReplaceViewer (can't work anyway, Scott says)

WS on April 22: changed CreateProc to pass dName&eName, return vIsOld.

Rick on April 23, 1982 9:03 am: CreateProc returns NIL if it doesn't want NutImpl to create new nut.

WS on May 6, 1982 4:08 pm: Display returns newV

Rick on May 6, 1982 6:58 pm: Check for Null entity in Display, just as defensive programming.

Maxwell on June 15, 1982 9:30 am: Changed algorithm for displaying viewers.

WS on June 23, 1982: added DBNotify stuff.

Rick & Willie-Sue on July 8, 1982: viewerSynchronizer stuff

Maxwell July 15, 1982 12:28 pm: Added "debug", NutViewer.StartTrap[]; catch ABORTED

Cattell August 3, 1982 4:40 pm: don't repaint viewers or recompute columns in Display, Edit, and Query procs.

Cattell August 5, 1982 6:54 pm: Cleaned up Display, Edit, and Query procs considerably, by putting much of their shared logic in new proc CreateNut. We no longer do any extra paint of the viewer. We no longer open the oldV if BOTH oldV and newV are iconic; this means the client wants to leave the nut iconic. We now create icons for Editors and Queryers as well as Displayers; we use the icon for the domain since there is not (necessarily) an entity associated with an editor or queryer. Simultaneously changed ViewerNutImpl so that the default icon for the domain will be used in conjuction with the domain itself. We now recompute the left column if newV is on the left, right if on the right, and neither if iconic. Added "init" parameter to Display procs and to Create procs so client can pass info through to both of these as well as Edit and Query procs. This last change involves changing Nut.

Cattell October 12, 1982 12:27 pm: Halfway fixed problem with new Viewers (3.4) destroying nuts before they are created: set "filler" field to non-zero until viewer is fully created. Added PUBLIC GetViewerState, would like to put this in NutViewer interface when possible.

WS on October 28, 1982 4:26 pm: now have inhibitDestroy bit in ViewerRec

Cattell on April 13, 1983 11:45 am: should have passed segment, not seg, in call to implRec.display in Display proc. Same with Edit and Query procs.

Cattell on May 25, 1983 1:41 pm: should have used seg of entity, not domain in Display.