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];
TiogaButtonsImpl: CEDAR PROGRAM
IMPORTS Atom, EditNotify, EditSpan, InputFocus, Menus, MessageWindow, NodeProps, Process, Rope, TEditDisplay, TEditDocumentPrivate, TEditSelection, TEditSelectionPrivate, TextEdit, TextLooks, TextNode, TiogaExtraOps, TiogaMenuOps, TiogaOps, ViewerOps
EXPORTS TiogaButtons
~ BEGIN OPEN TiogaButtons;
ROPE: TYPE ~ Rope.ROPE;
Node: TYPE ~ Tioga.Node;
Location: TYPE ~ Tioga.Location;
NodeItself: INT ~ Tioga.NodeItself;
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[];
};
Initialization
editButtonName: Rope.ROPE = "Edit";
buttonButtonName: Rope.ROPE = "Buttons";
textClass: ViewerClasses.ViewerClass = ViewerOps.FetchViewerClass[$Text];
Create the TiogaButton class.
tiogaButtonClass: ViewerClasses.ViewerClass = NEW[ViewerClasses.ViewerClassRec ¬ textClass­];
tiogaButtonClass.init ¬ InitViewer;
tiogaButtonClass.notify ¬ Notifier;
tiogaButtonClass.cursor ¬ bullseye;
tiogaButtonClass.icon ¬ tool;
tiogaButtonClass.tipTable ¬ ViewerOps.FetchViewerClass[$Button].tipTable;
ViewerOps.RegisterViewerClass[$TiogaButtons, tiogaButtonClass];
Turn on the edit notifier
EditNotify.AddNotifyProc[EditNotification];
END.
Some Neat things to add to TiogaButtons in the future:
a) box style attributes that Tioga would draw boxes around the run of text with this attribute
b) rule style attribute that Tioga would draw horizontal or vertical rules
c) background color style attribute
d) cursor changes when mouse is over a button
e) secondary selection to still work from $TiogaButtons viewers