TiogaButtonsImpl.mesa
Copyright Ó 1984, 1985, 1986, 1988, 1989, 1991, 1992 Xerox Corporation. All rights reserved.
Rick Beach, May 14, 1986 11:56:37 am PDT
Russ Atkinson (RRA) May 21, 1985 0:03:26 am PDT
Last edited by: Mik Lamming - August 25, 1986 5:13:58 pm PDT
Hal Murray, December 22, 1986 10:59:07 am PST
Willie-Sue, January 23, 1987 3:55:22 pm PST
Last tweaked by Mike Spreitzer on June 8, 1988 1:07:59 pm PDT
Polle Zellweger (PTZ) June 14, 1988 5:48:20 pm PDT
Don Baker, August 12, 1988 4:27:07 pm PDT
Doug Terry, October 13, 1988 4:36:50 pm PDT
Swinehart, February 3, 1991 0:35 am PST
Bill Jackson (bj) June 29, 1989 5:04:59 pm PDT
Chauser, October 25, 1991 4:30 pm PDT
Willie-s, January 29, 1992 5:24 pm PST
Doug Wyatt, January 31, 1992 12:56 pm PST
Michael Plass, March 10, 1992 4:12 pm PST
DIRECTORY
Atom USING [MakeAtom],
EditNotify USING [AddNotifyProc, Change, EditNotifyProc],
EditSpan USING [ChangeLooks, Delete, Insert, Place, Split],
InputFocus USING [CaptureButtons, ReleaseButtons],
Menus USING [AppendMenuEntry, CopyMenu, CreateEntry, FindEntry, MouseButton, MenuProc, MenuEntry, ReplaceMenuEntry],
MessageWindow USING [Append, Blink],
NodeProps USING [DoSpecs, GetProp],
Process USING [Detach],
Rope USING [Cat, Concat, Fetch, IsEmpty, Length, MaxLen, ROPE, Substr],
TEditDisplay USING [InvalidateBranch],
TEditDocument USING [Selection, TEditDocumentData],
TEditDocumentPrivate USING [DoLoadFile],
TEditSelectionPrivate USING [ResolveToChar],
TEditSelection USING [Alloc, Free],
TextEdit USING [ChangeLooks, PutComment, PutFormat, PutProp, ReplaceByRope, Size],
TextLooks USING [Looks, RopeToLooks],
TextNode USING [Forward, LastChild, MaxLen, Root],
Tioga USING [Location, Looks, noLooks, Node, NodeItself],
TiogaButtons,
TiogaExtraOps USING [RemProp],
TiogaMenuOps USING [tiogaMenu],
TiogaOps USING [CancelSelection, FirstChild, GetProp, GetRope, LastChild, LastLocWithin, LastWithin, Lock, LockSel, Next, PutProp, Root, SetSelection, SelectionRoot, StepForward, Unlock, UnlockSel, ViewerDoc],
TIPUser USING [TIPScreenCoords],
ViewerClasses USING [InitProc, SaveProc, Lock, NotifyProc, Viewer, ViewerClass, ViewerClassRec, ViewerRec],
ViewerOps USING [AddProp, CreateViewer, FetchProp, FetchViewerClass, MouseInViewer, PaintViewer, RegisterViewerClass, SetMenu];
Create TiogaButtons
CreateViewer:
PUBLIC PROC [info: ViewerClasses.ViewerRec]
RETURNS [v: ViewerClasses.Viewer] ~ {
v ¬ ViewerOps.CreateViewer[flavor: $TiogaButtons, info: info];
};
LoadViewer:
PUBLIC PROC [viewer: ViewerClasses.Viewer, fileName:
ROPE] ~ {
name: ROPE;
proc: REF ANY;
IF NOT IsTiogaButtons[viewer] THEN ERROR WrongViewerClass;
proc ¬ ViewerOps.FetchProp[viewer, $ModifiedProcTB];
ViewerOps.AddProp[viewer, $ModifiedProcTB, NIL]; -- Inhibit notifications by temporarily removing the notification proc
name ¬ viewer.name; -- DoLoadFile will change this on us, so save it first
[] ¬ TEditDocumentPrivate.DoLoadFile[parent: viewer, fileName: fileName];
RemAllButtonProps[TiogaOps.ViewerDoc[viewer]];
ViewerOps.AddProp[viewer, $ModifiedProcTB, proc];
viewer.tipTable ¬ viewer.class.tipTable; -- DoLoadFile does violence to this too!
viewer.name ¬ name;
ViewerOps.PaintViewer[viewer: viewer, hint: caption];
};
CreateButtonForEachNode:
PUBLIC
PROC [viewer: ViewerClasses.Viewer,
firstLevelOnly:
BOOL ¬
FALSE, subtreeAsButton:
BOOL ¬
TRUE,
proc: TiogaButtonProc ¬
NIL, clientData:
REF
ANY ¬
NIL, fork:
BOOLEAN ¬
TRUE] ~ {
LockedCreateButtonForEachNode:
PROC [root: Tioga.Node] ~ {
node: Tioga.Node ¬ TiogaOps.FirstChild[root];
WHILE node #
NIL
DO
button: TiogaButton ~
NEW[TiogaButtonRec ¬ [
startLoc: [node, NodeItself],
endLoc: [IF subtreeAsButton THEN TiogaOps.LastWithin[node] ELSE node, NodeItself],
proc: proc, clientData: clientData, fork: fork]
];
IF proc #
NIL THEN AddButtonProp[node, button]
ELSE RemButtonProp[node];
IF firstLevelOnly
THEN node ¬ TiogaOps.Next[node]
ELSE node ¬ TiogaOps.StepForward[node];
ENDLOOP;
};
IF NOT IsTiogaButtons[viewer] THEN ERROR WrongViewerClass;
CallWithLock[LockedCreateButtonForEachNode, TiogaOps.ViewerDoc[viewer]]
};
CreateButtonAtNode:
PUBLIC PROC
[
viewer: ViewerClasses.Viewer,
oldButton: TiogaButton ¬ NIL,
where: EditSpan.Place ¬ before,
rope: ROPE ¬ NIL,
format: ROPE ¬ NIL,
looks: ROPE ¬ NIL,
proc: TiogaButtonProc ¬ NIL,
clientData: REF ANY ¬ NIL,
fork: BOOLEAN ¬ TRUE
]
RETURNS [button: TiogaButton] ~ {
IF NOT IsTiogaButtons[viewer] THEN ERROR WrongViewerClass;
Creates a button as the last child of the document in viewer.
{
root: Tioga.Node ¬ TiogaOps.ViewerDoc[viewer];
LockedCreateButton:
PROC [root: Tioga.Node] ~ {
node: Tioga.Node ~ LockedCreateNode[root, rope, viewer, oldButton, where];
IF NOT format.IsEmpty THEN TextEdit.PutFormat[node, Atom.MakeAtom[format]];
IF
NOT looks.IsEmpty
THEN {
this: Tioga.Looks ¬ Tioga.noLooks;
FOR i:
INT
IN [0..looks.Length)
DO
this[looks.Fetch[i]] ¬ TRUE;
ENDLOOP;
TextEdit.ChangeLooks[root: root, text: node, add: this, start: 0];
};
button ¬ NEW[TiogaButtonRec ¬ [startLoc: [node, NodeItself], endLoc: [node, NodeItself], proc: proc, clientData: clientData, fork: fork]];
IF proc #
NIL
THEN
AddButtonProp[node, button]
ELSE
RemButtonProp[node];
};
CallWithLock[LockedCreateButton, root]
};
};
CreateButton:
PUBLIC PROC [viewer: ViewerClasses.Viewer,
rope:
ROPE ¬
NIL, format:
ROPE ¬
NIL, looks:
ROPE ¬
NIL,
proc: TiogaButtonProc ¬
NIL, clientData:
REF
ANY ¬
NIL, fork:
BOOLEAN ¬
TRUE]
RETURNS [button: TiogaButton] ~ {
RETURN[CreateButtonAtNode[viewer, NIL, before, rope, format, looks, proc, clientData, fork]];
};
WrongViewerClass:
PUBLIC
ERROR =
CODE;
CreateButtonFromNode:
PUBLIC
PROC [node: Tioga.Node,
start:
INT ¬ 0, end:
INT ¬
INT.LAST,
proc: TiogaButtonProc ¬
NIL, clientData:
REF
ANY ¬
NIL, fork:
BOOLEAN ¬
TRUE]
RETURNS [button: TiogaButton] ~ {
root: Tioga.Node ~ TiogaOps.Root[node];
ref: REF ANY ¬ TiogaOps.GetProp[root, $Viewer];
viewer: ViewerClasses.Viewer ¬ IF ref # NIL AND ISTYPE[ref, ViewerClasses.Viewer] THEN NARROW[ref] ELSE NIL;
IF NOT IsTiogaButtons[viewer] THEN ERROR WrongViewerClass;
Creates a button from the node.
{
LockedCreateButtonFromNode:
PROC [root: Tioga.Node] ~ {
button ¬ NEW[TiogaButtonRec ¬ [startLoc: [node, start], endLoc: [node, end], proc: proc, clientData: clientData, fork: fork]];
IF proc #
NIL
THEN
AddButtonProp[node, button]
ELSE
RemButtonProp[node];
};
IF start = 0
AND end =
INT.
LAST
THEN
start ¬ end ¬ NodeItself;
IF node # NIL THEN CallWithLock[LockedCreateButtonFromNode, root]
};
};
AppendToButton:
PUBLIC PROC [button: TiogaButton, rope:
ROPE ¬
NIL, looks:
ROPE ¬
NIL,
proc: TiogaButtonProc ¬
NIL, clientData:
REF
ANY ¬
NIL, fork:
BOOLEAN ¬
TRUE]
RETURNS [TiogaButton] ~ {
node: Tioga.Node ~ button.startLoc.node;
root: Tioga.Node ~ TiogaOps.Root[node];
Appends to a button.
{
LockedAppendToButton:
PROC [root: Tioga.Node] ~ {
lookVector: TextLooks.Looks ~ TextLooks.RopeToLooks[looks];
start, length: INT;
[start, length] ¬ AppendRopeToNode[root, node, rope, lookVector];
button ¬ NEW[TiogaButtonRec ¬ [startLoc: [node, start], endLoc: [node, start+length-1], proc: proc, clientData: clientData, fork: fork]];
IF proc #
NIL
THEN
AddButtonProp[node, button];
};
CallWithLock[LockedAppendToButton, root];
RETURN [button];
};
};
DeleteButton:
PUBLIC PROC [button: TiogaButton] ~ {
IF button #
NIL
THEN {
root: Tioga.Node ~ TiogaOps.Root[button.startLoc.node];
Deletes a button.
{
LockedDeleteButton:
PROC [root: Tioga.Node] ~ {
EditSpan.Delete[
root: root,
del: [button.startLoc, button.endLoc]];
IF button.startLoc.where # NodeItself
THEN
AdjustButtonProps[button];
};
CallWithLock[LockedDeleteButton, root];
};
};
};
FindTiogaButton:
PUBLIC
PROC [this: ViewerClasses.Viewer, loc: Location]
RETURNS [button: TiogaButton] ~ {
RETURN[FindButton[this, loc, FALSE]];
};
FindButton:
PROC [
this: ViewerClasses.Viewer, loc: Location, onlyIfNotNotifying: BOOL]
RETURNS [button: TiogaButton] ~ {
list: TiogaButtonList;
ref: REF ANY ¬ TiogaOps.GetProp[loc.node, $TiogaButtonList];
IF NOT IsTiogaButtons[this] THEN ERROR WrongViewerClass;
IF ref =
NIL
OR ~
ISTYPE[ref, TiogaButtonList]
OR
(onlyIfNotNotifying AND ViewerOps.FetchProp[this, $NotifyingTB] # NIL) THEN
RETURN [NIL];
list ¬ NARROW[ref];
WHILE list #
NIL
DO
t: TiogaButton ~ list.first;
IF t.startLoc.node # loc.node THEN RETURN [NIL];
IF t.startLoc.where = NodeItself THEN RETURN [t];
IF loc.where >= t.startLoc.where AND t.endLoc.where >= loc.where THEN RETURN [t];
list ¬ list.rest;
ENDLOOP;
RETURN [NIL];
};
GetRope:
PUBLIC PROC [button: TiogaButton]
RETURNS [rope:
ROPE] ~ {
Provides the text of the button. Can be used in place of clientData for many purposes.
IF button #
NIL
THEN {
rope ¬ TiogaOps.GetRope[button.startLoc.node];
IF button.startLoc.node = button.endLoc.node
THEN {
IF button.startLoc.where # NodeItself
THEN
rope ¬ rope.Substr[button.startLoc.where, button.endLoc.where-button.startLoc.where+1];
}
ELSE {
otherRope: ROPE ¬ " ";
currentNode: Tioga.Node ¬ TiogaOps.StepForward[button.startLoc.node];
IF button.startLoc.where # NodeItself
THEN
rope ¬ rope.Substr[button.startLoc.where, rope.Length-button.startLoc.where];
WHILE currentNode # button.endLoc.node
DO
otherRope ¬ Rope.Cat[ otherRope, TiogaOps.GetRope[currentNode], " " ];
currentNode ¬ TiogaOps.StepForward[currentNode];
ENDLOOP;
IF button.endLoc.where # NodeItself
THEN {
ropeForNode: ROPE ¬ TiogaOps.GetRope[button.endLoc.node];
rope ¬ Rope.Cat[rope, otherRope, ropeForNode.Substr[0, button.endLoc.where+1]];
}
ELSE rope ¬ Rope.Cat[rope, otherRope, TiogaOps.GetRope[button.endLoc.node]];
};
};
};
SetStyleFromRope:
PUBLIC PROC [v: ViewerClasses.Viewer, styleRope:
ROPE] ~ {
This crock is necessary because TiogaOps.PutProp uses too low an abstraction for adding properties to a node.
root: Tioga.Node ~ TiogaOps.ViewerDoc[v];
LockedSetStyle:
PROC [root: Tioga.Node] ~ {
TextEdit.PutProp[node: root, name: $StyleDef, value: NodeProps.DoSpecs[$StyleDef, styleRope]];
};
CallWithLock[LockedSetStyle, root];
};
ChangeButtonLooks:
PUBLIC
PROC [button: TiogaButton,
addLooks, removeLooks:
ROPE ¬
NIL] ~ {
IF button #
NIL
THEN {
root: Tioga.Node ¬ TiogaOps.Root[button.startLoc.node];
InnerSetLooks:
PROC [root: Tioga.Node] ~ {
add: TextLooks.Looks ¬ TextLooks.RopeToLooks[addLooks];
remove: TextLooks.Looks ¬ TextLooks.RopeToLooks[removeLooks];
EditSpan.ChangeLooks[root, [button.startLoc, button.endLoc], remove, add];
};
CallWithLock[InnerSetLooks, root];
};
};
CallWithLock:
PROC [proc:
PROC [root: Tioga.Node], root: Tioga.Node] ~ {
IF root #
NIL
THEN {
Cleanup:
PROC ~ {
TiogaExtraOps.RemProp[root, $EditingTB];
TiogaOps.Unlock[root];
};
edited: BOOL = IsViewerEdited[root];
TiogaOps.Lock[root];
TiogaOps.PutProp[root, $EditingTB, $TRUE];
proc[root ! UNWIND => Cleanup[]];
Cleanup[];
IF NOT edited THEN MarkViewerNotEdited[root];
};
};
IsViewerEdited:
PUBLIC
PROC [root: Tioga.Node]
RETURNS [edited:
BOOL] ~ {
v: ViewerClasses.Viewer ¬ RootToViewer[root];
edited ¬ v # NIL AND v.newVersion;
};
MarkViewerNotEdited:
PUBLIC
PROC [root: Tioga.Node] ~ {
DoOne:
PROC [v: ViewerClasses.Viewer] ~ {
IF v.newVersion
THEN {
v.newVersion ¬ FALSE;
ViewerOps.PaintViewer[viewer: v, hint: caption];
};
};
v: ViewerClasses.Viewer ¬ RootToViewer[root];
IF v #
NIL
AND v.newVersion
THEN {
DoOne[v];
Aren't we nice, we get all the split viewers too!
FOR x: ViewerClasses.Viewer ¬ v.link, x.link
WHILE x # v
AND x #
NIL
DO
DoOne[x];
ENDLOOP;
};
};
LockedCreateNode:
PROC [root: Tioga.Node, rope:
ROPE, viewer: ViewerClasses.Viewer, oldButton: TiogaButton¬
NIL, where: EditSpan.Place¬before]
RETURNS [node: Tioga.Node] ~ {
If oldButton exists, create node in specified relationship to the node containing the button.
Otherwise, create a node as last child of root. Document is already locked
child: Tioga.Node = TiogaOps.FirstChild[root];
IF oldButton#
NIL
THEN {
oldNode: Tioga.Node ¬ oldButton.startLoc.node;
node ¬ EditSpan.Insert[root: root, old: oldNode, where: where];
}
ELSE
IF child =
NIL
THEN {
node ¬ EditSpan.Insert[root: root, old: root, where: child];
TEditDisplay.InvalidateBranch[viewer: viewer, node: root];
}
ELSE {
IF child = TiogaOps.LastChild[root]
AND TiogaOps.FirstChild[child] =
NIL
AND TiogaOps.GetRope[child].IsEmpty
THEN {
-- An empty document (hopefully), use the child node
node ¬ child;
}
ELSE {
-- Create node as sibling of last child of root
node ¬ EditSpan.Split[root, [TextNode.LastChild[root], NodeItself]];
};
TextEdit.PutFormat[node, NIL];
TextEdit.PutComment[node, FALSE];
};
[] ¬ TextEdit.ReplaceByRope[root: root, dest: node, rope: rope, start: TextNode.MaxLen, len: 0];
};
IsTiogaButtons:
PUBLIC
PROC [v: ViewerClasses.Viewer]
RETURNS [
BOOL]
~ {RETURN [v#NIL AND ViewerOps.FetchProp[v, $IsTiogaButtons] # NIL]};
IsEntireNode:
PUBLIC
PROC [b: TiogaButton]
RETURNS [
BOOL]
~ {RETURN [b.startLoc.where = NodeItself AND b.endLoc.where = NodeItself]};
EnumerateTiogaButtons:
PUBLIC
PROC
[
v: ViewerClasses.Viewer,
Test: PROC [TiogaButton] RETURNS [BOOL],
start: Location ¬ [NIL, 0],
end: Location ¬ [NIL, INT.LAST]]
RETURNS [BOOL] ~ {
IF NOT IsTiogaButtons[v] THEN ERROR WrongViewerClass;
{root: Tioga.Node ~ TiogaOps.ViewerDoc[v];
IF start = [NIL, 0] THEN start ¬ [root, 0];
IF end = [NIL, INT.LAST] THEN end ¬ TiogaOps.LastLocWithin[root];
{tnStart: Location ~ [start.node, start.where];
tnEnd: Location ~ [end.node, end.where];
FOR tn: Tioga.Node ¬ tnStart.node, TextNode.Forward[tn].nx
WHILE tn#
NIL
DO
len: INT ~ tn.rope.Length;
ns: INT ~ IF tn=tnStart.node THEN tnStart.where ELSE 0;
ne: INT ~ IF tn=tnEnd.node THEN tnEnd.where ELSE len;
list: TiogaButtonList ~ NARROW[NodeProps.GetProp[tn, $TiogaButtonList]];
FOR bl: TiogaButtonList ¬ list, bl.rest
WHILE bl#
NIL
DO
bs: INT ~ IF bl.first.startLoc.where=NodeItself THEN 0 ELSE bl.first.startLoc.where;
be: INT ~ IF bl.first.endLoc.where=NodeItself THEN len ELSE bl.first.endLoc.where;
IF bl.first.startLoc.node # tn OR bl.first.startLoc.node # bl.first.endLoc.node THEN ERROR;
IF bs <= ne AND ns <= be AND Test[bl.first] THEN RETURN [TRUE];
ENDLOOP;
IF tn = tnEnd.node THEN EXIT;
ENDLOOP;
RETURN [FALSE]}}};
AddButtonProp:
PROC [node: Tioga.Node, button: TiogaButton] ~ {
list: TiogaButtonList ¬ NIL;
ref: REF ANY ¬ TiogaOps.GetProp[node, $TiogaButtonList];
IF ref # NIL AND ISTYPE[ref, TiogaButtonList] THEN list ¬ NARROW[ref];
IF list =
NIL
THEN list ¬ LIST[button]
ELSE list ¬ CONS[button, list];
TiogaOps.PutProp[node, $TiogaButtonList, list];
};
RemButtonProp:
PROC [node: Tioga.Node] ~ {
TiogaExtraOps.RemProp[node, $TiogaButtonList];
};
RemAllButtonProps:
PROC [root: Tioga.Node] ~ {
LockedRemoveButtons:
PROC [root: Tioga.Node] ~ {
FOR node: Tioga.Node ¬ TiogaOps.FirstChild[root], TiogaOps.StepForward[node]
WHILE node #
NIL
DO
RemButtonProp[node];
ENDLOOP;
};
CallWithLock[LockedRemoveButtons, root];
};
AdjustButtonProps:
PROC [button: TiogaButton] ~ {
buttonDelta: INTEGER ~ button.endLoc.where - button.startLoc.where + 1;
propListModified: BOOLEAN ¬ FALSE;
propList, list, last: TiogaButtonList;
ref: REF ANY ~ TiogaOps.GetProp[button.startLoc.node, $TiogaButtonList];
IF ref = NIL OR ~ISTYPE[ref, TiogaButtonList] THEN RETURN;
propList ¬ list ¬ NARROW[ref];
WHILE list #
NIL
DO
t: TiogaButton ~ list.first;
IF t.startLoc.node = button.startLoc.node
AND t.startLoc.where # NodeItself
THEN {
t.endLoc.where ¬ t.endLoc.where+1;
IF t.startLoc.where
IN [button.startLoc.where .. button.endLoc.where]
THEN
t.startLoc.where ¬ button.startLoc.where
ELSE
IF t.startLoc.where > button.endLoc.where
THEN
t.startLoc.where ¬ t.startLoc.where - buttonDelta;
IF t.endLoc.where
IN [button.startLoc.where .. button.endLoc.where]
THEN
t.endLoc.where ¬ button.startLoc.where
ELSE
IF t.endLoc.where > button.endLoc.where
THEN
t.endLoc.where ¬ t.endLoc.where - buttonDelta;
IF t.startLoc.where # t.endLoc.where
THEN
t.endLoc.where ¬ t.endLoc.where-1
ELSE {
t is either the deleted button or a proper subset of it, so delete t from the list
IF last =
NIL
THEN propList ¬ propList.rest
ELSE last.rest ¬ list.rest;
propListModified ¬ TRUE;
};
};
last ¬ list;
list ¬ list.rest;
ENDLOOP;
IF propListModified
THEN
Put back the modified list
TiogaOps.PutProp[button.startLoc.node, $TiogaButtonList, propList];
};
AppendRopeToNode:
PROC [root: Tioga.Node, node: Tioga.Node, rope:
ROPE,
lookVector: TextLooks.Looks]
RETURNS [start, length:
INT] ~ {
[start, length] ¬ TextEdit.ReplaceByRope[root: root,
dest: node, start: TextNode.MaxLen, len: 0,
rope: rope, looks: lookVector];
};
RootToViewer:
PROC [root: Tioga.Node]
RETURNS [viewer: ViewerClasses.Viewer] ~ {
ref: REF ANY ¬ TiogaOps.GetProp[root, $Viewer];
viewer ¬ IF ref # NIL AND ISTYPE[ref, ViewerClasses.Viewer] THEN NARROW[ref] ELSE NIL;
};
Editable Tioga
StateButton:
PUBLIC
PROC [v: ViewerClasses.Viewer, show:
BOOL ¬
TRUE] = {
IF NOT IsTiogaButtons[v] THEN ERROR WrongViewerClass;
IF v.parent =
NIL
THEN {
-- Can't have a menu if not a top level viewer
buttonText: Rope.ROPE = IF v.class = textClass THEN buttonButtonName ELSE editButtonName;
existingEditEntry: Menus.MenuEntry = Menus.FindEntry[v.menu, buttonText];
IF show
THEN {
-- Add the edit menu entry
Menus.AppendMenuEntry[v.menu, Menus.CreateEntry[buttonText, ToggleState]];
}
ELSE {
-- Remove the edit entry from the menu
IF existingEditEntry #
NIL
THEN {
Menus.ReplaceMenuEntry[v.menu, existingEditEntry];
};
};
ViewerOps.PaintViewer[viewer: v, hint: menu];
};
};
SetState:
PUBLIC
PROC [v: ViewerClasses.Viewer, val: State] ~ {
IF NOT IsTiogaButtons[v] THEN ERROR WrongViewerClass;
IF val = buttoning
AND v.class = textClass
THEN {
-- Go to buttoning state
v.class ¬ tiogaButtonClass;
v.tipTable ¬ tiogaButtonClass.tipTable;
TiogaOps.LockSel[];
IF TiogaOps.SelectionRoot[] = TiogaOps.ViewerDoc[v]
THEN
TiogaOps.CancelSelection[];
TiogaOps.UnlockSel[];
IF v.menu #
NIL
THEN {
was: Menus.MenuEntry = Menus.FindEntry[v.menu, buttonButtonName];
is: Menus.MenuEntry = Menus.CreateEntry[editButtonName, ToggleState];
Menus.ReplaceMenuEntry[v.menu, was, is];
ViewerOps.PaintViewer[viewer: v, hint: menu];
};
NotifyClient[v];
};
IF val = editing
AND v.class = tiogaButtonClass
THEN {
-- Go to editing state
v.class ¬ textClass;
v.tipTable ¬ textClass.tipTable;
CancelFeedback[v];
IF v.menu #
NIL
THEN {
was: Menus.MenuEntry = Menus.FindEntry[v.menu, editButtonName];
is: Menus.MenuEntry = Menus.CreateEntry[buttonButtonName, ToggleState];
Menus.ReplaceMenuEntry[v.menu, was, is];
ViewerOps.PaintViewer[viewer: v, hint: menu];
};
};
};
GetState:
PUBLIC
PROC [v: ViewerClasses.Viewer]
RETURNS [State] ~ {
IF NOT IsTiogaButtons[v] THEN ERROR WrongViewerClass;
RETURN [IF v.class = tiogaButtonClass THEN buttoning ELSE editing];
};
RegisterModifiedProc:
PUBLIC
PROC [v: ViewerClasses.Viewer, proc: WasModifiedProc] ~ {
IF NOT IsTiogaButtons[v] THEN ERROR WrongViewerClass;
ViewerOps.AddProp[v, $ModifiedProcTB, NEW[WasModifiedProc ¬ proc]];
};
NotifyClient:
PROC [v: ViewerClasses.Viewer] ~ {
root: Tioga.Node = TiogaOps.ViewerDoc[v];
proc: REF WasModifiedProc = NARROW[ViewerOps.FetchProp[v, $ModifiedProcTB]];
IF proc # NIL THEN { RemAllButtonProps[root]; proc[v] };
ViewerOps.AddProp[v, $NotifyingTB, NIL];
};
ToggleState: Menus.MenuProc ~ {
v: ViewerClasses.Viewer = NARROW[parent];
SetState[v, IF GetState[v] = buttoning THEN editing ELSE buttoning];
};
EditNotification: EditNotify.EditNotifyProc ~ {
CheckRoot:
PROC [root: Tioga.Node] ~ {
IF root#
NIL
THEN {
DoOne:
PROC [v: ViewerClasses.Viewer] ~ {
IF IsTiogaButtons[v]
AND v.class = tiogaButtonClass
AND ViewerOps.FetchProp[v, $NotifyingTB] =
NIL
THEN {
ViewerOps.AddProp[v, $NotifyingTB, $TRUE];
TRUSTED { Process.Detach[FORK NotifyClient[v]] };
};
};
v: ViewerClasses.Viewer ~ RootToViewer[root];
IF v #
NIL
AND TiogaOps.GetProp[root, $EditingTB] =
NIL
THEN {
DoOne[v];
FOR x: ViewerClasses.Viewer ¬ v.link, x.link
WHILE x # v
AND x #
NIL
DO
DoOne[x];
ENDLOOP;
};
};
};
Do1:
PROC [node: Node] ~ {
root: Node ~ TextNode.Root[node];
CheckRoot[root];
};
Do2:
PROC [node1, node2: Node] ~ {
root1: Node ~ TextNode.Root[node1];
root2: Node ~ TextNode.Root[node2];
CheckRoot[root1];
IF root2#root1 THEN CheckRoot[root2];
};
WITH change
SELECT
FROM
x: REF READONLY EditNotify.Change.ChangingText => Do1[x.text];
x: REF READONLY EditNotify.Change.ChangingProp => Do1[x.node];
x: REF READONLY EditNotify.Change.MovingNodes => Do2[x.dest, x.pred];
x: REF READONLY EditNotify.Change.NodeNesting => Do1[x.first];
x: REF READONLY EditNotify.Change.InsertingNode => Do1[x.new];
ENDCASE;
};
The $TiogaButtons viewer class
InitViewer: ViewerClasses.InitProc ~ {
self.class ¬ textClass;
textClass.init[self];
self.transparentTIP ¬ FALSE; -- don't use transparent tip table!
self.class ¬ tiogaButtonClass;
self.tipTable ¬ tiogaButtonClass.tipTable;
IF self.parent =
NIL
AND self.menu =
NIL
THEN
ViewerOps.SetMenu[ self, Menus.CopyMenu[TiogaMenuOps.tiogaMenu], TRUE ]; -- force each instance to have its own copy so we can have an optional EDIT button for each instance (top level viewers only).
ViewerOps.AddProp[self, $IsTiogaButtons, $TRUE];
};
Notifier: ViewerClasses.NotifyProc ~ {
mouseButton: Menus.MouseButton ¬ red;
shift, control: BOOL ¬ FALSE;
mouse: TIPUser.TIPScreenCoords;
mouseX, mouseY: INT; -- for archival purposes
feedbackSel: TEditDocument.Selection ¬ TEditSelection.Alloc[];
FOR list:
LIST
OF
REF
ANY ¬ input, list.rest
UNTIL list =
NIL
DO
WITH list.first
SELECT
FROM
x:
ATOM =>
SELECT x
FROM
$Red => mouseButton ¬ red;
$Yellow => mouseButton ¬ yellow;
$Blue => mouseButton ¬ blue;
$Shift => shift ¬ TRUE;
$Control => control ¬ TRUE;
$Mark => {
button: TiogaButton;
selectedButton: TiogaButton ¬ SelectedButton[self];
IF selectedButton =
NIL
THEN
InputFocus.CaptureButtons[Notifier, self.class.tipTable, self] -- to track feedback out of buttons
ELSE
IF
NOT MouseInViewer[self, mouse]
THEN {
InputFocus.ReleaseButtons[];
CancelFeedback[self];
LOOP;
};
Mouse coordinates are guaranteed to be in viewer space and
we have captured the buttons for future $Marks and $Hits
[] ¬ TEditSelectionPrivate.ResolveToChar[
selection: feedbackSel,
viewer: self,
tdd: NARROW[self.data, TEditDocument.TEditDocumentData],
x: mouse.mouseX,
y: self.ch - mouse.mouseY];
Selection info returned in feedbackSel.start.pos as Location
Find button within this selection
button ¬ FindButton[self, feedbackSel.start.pos, TRUE];
IF button #
NIL
THEN
IF button = selectedButton THEN LOOP -- optimizing check
ELSE EstablishFeedback[self, button]
ELSE {
InputFocus.ReleaseButtons[];
CancelFeedback[self];
};
};
$Hit => {
Turn off reverse video
selectedButton: TiogaButton ¬ SelectedButton[self];
IF selectedButton #
NIL
THEN {
InputFocus.ReleaseButtons[];
CancelFeedback[self];
IF selectedButton.fork
THEN
TRUSTED {
[] ¬ Process.Detach[FORK selectedButton.proc[selectedButton, selectedButton.clientData, mouseButton, shift, control]]
}
ELSE selectedButton.proc[selectedButton, selectedButton.clientData, mouseButton, shift, control];
};
};
ENDCASE => NULL;
z: TIPUser.TIPScreenCoords => {
mouse ¬ z;
mouseX ¬ mouse.mouseX;
mouseY ¬ mouse.mouseY;
};
ENDCASE => NULL;
ENDLOOP;
TEditSelection.Free[feedbackSel];
};
MouseInViewer:
PROC [this: ViewerClasses.Viewer, mouse: TIPUser.TIPScreenCoords]
RETURNS [
BOOLEAN] ~ {
viewer: ViewerClasses.Viewer;
client: BOOLEAN;
[viewer, client] ¬ ViewerOps.MouseInViewer[mouse];
RETURN [viewer = this AND client];
};
SelectedButton:
PROC [this: ViewerClasses.Viewer]
RETURNS [button: TiogaButton] ~ {
val: REF ANY ~ ViewerOps.FetchProp[viewer: this, prop: $TiogaButtonSelected];
IF val #
NIL
AND
ISTYPE[val, TiogaButton]
THEN
button ¬ NARROW[val];
};
EstablishFeedback:
PROC [this: ViewerClasses.Viewer, button: TiogaButton] ~ {
ViewerOps.AddProp[viewer: this, prop: $TiogaButtonSelected, val: button];
TiogaOps.SetSelection[
viewer: this,
start:
IF button.startLoc.where = NodeItself
THEN [button.startLoc.node, 0]
ELSE button.startLoc,
end:
IF button.endLoc.where = NodeItself
THEN [button.endLoc.node, TextEdit.Size[button.endLoc.node]]
ELSE button.endLoc,
level: char,
caretBefore: TRUE,
pendingDelete: TRUE,
which: feedback];
};
CancelFeedback:
PROC [this: ViewerClasses.Viewer] ~ {
ViewerOps.AddProp[viewer: this, prop: $TiogaButtonSelected, val: NIL];
TiogaOps.CancelSelection[feedback];
};
Hack: TiogaButtonProc ~ {
MessageWindow.Append[Rope.Concat["button contents ", GetRope[button]], TRUE];
MessageWindow.Blink[];
};