TiogaButtonsImpl.mesa
Copyright Ó 1984, 1985, 1986, 1988, 1989 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
Doug Wyatt, November 17, 1989 10:45:02 am PST
Chauser, October 25, 1991 4:30 pm PDT
DIRECTORY
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, 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 [InsertRope, PutProp, Size],
TextLooks USING [Looks, RopeToLooks],
TextNode USING [Forward, LastChild, Location, MaxLen, NodeItself, NodeRope, Ref, Span],
TiogaButtons USING [TiogaButton, TiogaButtonList, TiogaButtonProc, TiogaButtonRec],
TiogaButtonsExtras USING [State, WasModifiedProc],
TiogaButtonsToo,
TiogaExtraOps USING [RemProp],
TiogaFileOps USING [AddLooks, Ref, SetFormat],
TiogaMenuOps USING [tiogaMenu],
TiogaOps USING [CancelSelection, FirstChild, GetProp, GetRope, LastChild, LastLocWithin, LastWithin, Location, Lock, LockSel, Next, PutProp, Ref, 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: TiogaOps.Ref] ~ {
node: TiogaOps.Ref ← 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: TiogaOps.Ref ← TiogaOps.ViewerDoc[viewer];
LockedCreateButton:
PROC [root: TiogaOps.Ref] ~ {
node: TiogaOps.Ref ~ LockedCreateNode[root, rope, viewer, oldButton, where];
IF NOT format.IsEmpty THEN SetFormat[node, format];
IF
NOT looks.IsEmpty
THEN
FOR i:
INT
IN [0..looks.Length)
DO
AddLooks[node, 0, TextNode.MaxLen, looks.Fetch[i], root];
ENDLOOP;
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: TiogaOps.Ref,
start:
INT ← 0, end:
INT ←
INT.LAST,
proc: TiogaButtonProc ←
NIL, clientData:
REF
ANY ←
NIL, fork:
BOOLEAN ←
TRUE]
RETURNS [button: TiogaButton] ~ {
root: TiogaOps.Ref ~ 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: TiogaOps.Ref] ~ {
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: TiogaOps.Ref ~ button.startLoc.node;
root: TiogaOps.Ref ~ TiogaOps.Root[node];
Appends to a button.
{
LockedAppendToButton:
PROC [root: TiogaOps.Ref] ~ {
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: TiogaOps.Ref ~ TiogaOps.Root[button.startLoc.node];
Deletes a button.
{
LockedDeleteButton:
PROC [root: TiogaOps.Ref] ~ {
EditSpan.Delete[
root: TextNodeRef[root],
del: TextNodeSpan[button.startLoc, button.endLoc]];
IF button.startLoc.where # NodeItself
THEN
AdjustButtonProps[button];
};
CallWithLock[LockedDeleteButton, root];
};
};
};
FindTiogaButton:
PUBLIC
PROC [this: ViewerClasses.Viewer, loc: TiogaOps.Location]
RETURNS [button: TiogaButton] ~ {
RETURN[FindButton[this, loc, FALSE]];
};
FindButton:
PROC [
this: ViewerClasses.Viewer, loc: TiogaOps.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: TiogaOps.Ref ← 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 ← otherRope.Cat[ TiogaOps.GetRope[currentNode], " " ];
currentNode ← TiogaOps.StepForward[currentNode];
ENDLOOP;
IF button.endLoc.where # NodeItself
THEN {
ropeForNode: ROPE ← TiogaOps.GetRope[button.endLoc.node];
rope ← rope.Cat[otherRope, ropeForNode.Substr[0, button.endLoc.where+1]];
}
ELSE rope ← rope.Cat[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: TiogaOps.Ref ~ TiogaOps.ViewerDoc[v];
LockedSetStyle:
PROC [root: TiogaOps.Ref] ~ {
TextEdit.PutProp[node: TextNodeRef[root], name: $StyleDef, value: NodeProps.DoSpecs[$StyleDef, styleRope], root: TextNodeRef[root]];
};
CallWithLock[LockedSetStyle, root];
};
ChangeButtonLooks:
PUBLIC
PROC [button: TiogaButton,
addLooks, removeLooks:
ROPE ←
NIL] ~ {
IF button #
NIL
THEN {
root: TiogaOps.Ref ← TiogaOps.Root[button.startLoc.node];
InnerSetLooks:
PROC [root: TiogaOps.Ref] ~ {
add: TextLooks.Looks ← TextLooks.RopeToLooks[addLooks];
remove: TextLooks.Looks ← TextLooks.RopeToLooks[removeLooks];
EditSpan.ChangeLooks[TextNodeRef[root], TextNodeSpan[button.startLoc, button.endLoc], remove, add];
};
CallWithLock[InnerSetLooks, root];
};
};
CallWithLock:
PROC [proc:
PROC [root: TiogaOps.Ref], root: TiogaOps.Ref] ~ {
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: TiogaOps.Ref]
RETURNS [edited:
BOOL] ~ {
v: ViewerClasses.Viewer ← RootToViewer[root];
edited ← v # NIL AND v.newVersion;
};
MarkViewerNotEdited:
PUBLIC
PROC [root: TiogaOps.Ref] ~ {
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: TiogaOps.Ref, rope:
ROPE, viewer: ViewerClasses.Viewer, oldButton: TiogaButton←
NIL, where: EditSpan.Placeore]
RETURNS [node: TiogaOps.Ref] ~ {
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: TiogaOps.Ref = TiogaOps.FirstChild[root];
IF oldButton#
NIL
THEN {
oldNode: TiogaOps.Ref ← oldButton.startLoc.node;
node ← TiogaOpsRef[EditSpan.Insert[root: TextNodeRef[root], old: TextNodeRef[oldNode], where: where]];
}
ELSE
IF child =
NIL
THEN {
node ← TiogaOpsRef[EditSpan.Insert[root: TextNodeRef[root], old: TextNodeRef[root], where: child]];
TEditDisplay.InvalidateBranch[viewer: viewer, node: TextNodeRef[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 ← TiogaOpsRef[EditSpan.Split[TextNodeRef[root], [TextNode.LastChild[TextNodeRef[root]], NodeItself]]];
};
SetFormat[node, ""];
};
[] ← TextEdit.InsertRope[root: TextNodeRef[root], dest: TextNodeRef[node], rope: rope, destLoc: TextNode.MaxLen];
};
SetFormat:
PROC [node: TiogaOps.Ref, format:
ROPE] ~
TRUSTED {
TiogaFileOps.SetFormat[TiogaFileOpsRef[node], format];
};
AddLooks:
PROC [node: TiogaOps.Ref, start, len:
INT, look:
CHAR ['a..'z],
root: TiogaOps.Ref ←
NIL] ~ {
TiogaFileOps.AddLooks[TiogaFileOpsRef[node], start, len, look, TiogaFileOpsRef[root]];
};
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: TiogaOps.Location ← [NIL, 0],
end: TiogaOps.Location ← [NIL, INT.LAST]]
RETURNS [BOOL] ~ {
IF NOT IsTiogaButtons[v] THEN ERROR WrongViewerClass;
{root: TiogaOps.Ref ~ TiogaOps.ViewerDoc[v];
IF start = [NIL, 0] THEN start ← [root, 0];
IF end = [NIL, INT.LAST] THEN end ← TiogaOps.LastLocWithin[root];
{tnStart: TextNode.Location ~ [TextNodeRef[start.node], start.where];
tnEnd: TextNode.Location ~ [TextNodeRef[end.node], end.where];
FOR tn: TextNode.Ref ← tnStart.node, TextNode.Forward[tn].nx
WHILE tn#
NIL
DO
len: INT ~ tn.NodeRope.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=TextNode.NodeItself THEN 0 ELSE bl.first.startLoc.where;
be: INT ~ IF bl.first.endLoc.where=TextNode.NodeItself THEN len ELSE bl.first.endLoc.where;
IF TextNodeRef[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: TiogaOps.Ref, 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: TiogaOps.Ref] ~ {
TiogaExtraOps.RemProp[node, $TiogaButtonList];
};
RemAllButtonProps:
PROC [root: TiogaOps.Ref] ~ {
LockedRemoveButtons:
PROC [root: TiogaOps.Ref] ~ {
FOR node: TiogaOps.Ref ← 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: TiogaOps.Ref, node: TiogaOps.Ref, rope:
ROPE,
lookVector: TextLooks.Looks]
RETURNS [start, length:
INT] ~ {
[start, length] ← TextEdit.InsertRope[root: TextNodeRef[root],
dest: TextNodeRef[node], destLoc: TextNode.MaxLen,
rope: rope, inherit: FALSE, looks: lookVector];
};
RootToViewer:
PROC [root: TiogaOps.Ref]
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;
};
The $TiogaButtons viewer class
InitViewer: ViewerClasses.InitProc ~ {
self.class ← textClass;
textClass.init[self];
self.spare6 ← 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 TextNode.Location
Find button within this selection
button ← FindButton[self, TiogaOpsLoc[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];
};
TiogaOpsLoc:
PROC [loc: TextNode.Location]
RETURNS [TiogaOps.Location] ~
TRUSTED INLINE {
RETURN [LOOPHOLE[loc]]
};
TextNodeSpan:
PROC [start, end: TiogaOps.Location]
RETURNS [TextNode.Span] ~
TRUSTED INLINE {
RETURN [[LOOPHOLE[start], LOOPHOLE[end]]];
};
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[TextNodeRef[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.Cat["button contents ", GetRope[button]], TRUE];
MessageWindow.Blink[];
};