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]; TiogaButtonsImpl: CEDAR PROGRAM IMPORTS EditNotify, EditSpan, InputFocus, Menus, MessageWindow, NodeProps, Process, Rope, TEditDisplay, TEditDocumentPrivate, TEditSelection, TEditSelectionPrivate, TextEdit, TextLooks, TextNode, TiogaExtraOps, TiogaFileOps, TiogaMenuOps, TiogaOps, ViewerOps EXPORTS TiogaButtons, TiogaButtonsExtras, TiogaButtonsToo ~ BEGIN OPEN TiogaButtons, TiogaButtonsExtras; ROPE: TYPE ~ Rope.ROPE; NodeItself: INT ~ -1; 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; { 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; { 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]; { 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]; { 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] ~ { 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] ~ { 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]; 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.Place_before] RETURNS [node: TiogaOps.Ref] ~ { 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 { IF last = NIL THEN propList _ propList.rest ELSE last.rest _ list.rest; propListModified _ TRUE; }; }; last _ list; list _ list.rest; ENDLOOP; IF propListModified THEN 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; }; TextNodeRef: PUBLIC PROC [ref: REF] RETURNS [TextNode.Ref] ~ TRUSTED { RETURN [LOOPHOLE[ref]]; }; TiogaOpsRef: PUBLIC PROC [ref: REF] RETURNS [TiogaOps.Ref] ~ TRUSTED { RETURN [LOOPHOLE[ref]]; }; TiogaFileOpsRef: PUBLIC PROC [ref: REF] RETURNS [TiogaFileOps.Ref] ~ TRUSTED { RETURN [LOOPHOLE[ref]]; }; 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: TiogaOps.Ref = 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: TextNode.Ref] ~ { 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[TiogaOpsRef[root]]; IF v # NIL AND TiogaOps.GetProp[TiogaOpsRef[root], $EditingTB] = NIL THEN { DoOne[v]; FOR x: ViewerClasses.Viewer _ v.link, x.link WHILE x # v AND x # NIL DO DoOne[x]; ENDLOOP; }; }; WITH change SELECT FROM ct: REF READONLY EditNotify.Change.ChangingText => { CheckRoot[ct.root]; }; cf: REF READONLY EditNotify.Change.ChangingFormat => { CheckRoot[cf.root]; }; cp: REF READONLY EditNotify.Change.ChangingProp => { CheckRoot[cp.root]; }; mn: REF READONLY EditNotify.Change.MovingNodes => { CheckRoot[mn.destRoot]; IF mn.sourceRoot # mn.destRoot THEN CheckRoot[mn.sourceRoot]; }; nn: REF READONLY EditNotify.Change.NodeNesting => { CheckRoot[nn.root]; }; in: REF READONLY EditNotify.Change.InsertingNode => { CheckRoot[in.root]; }; ENDCASE => NULL; }; 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; }; [] _ TEditSelectionPrivate.ResolveToChar[ selection: feedbackSel, viewer: self, tdd: NARROW[self.data, TEditDocument.TEditDocumentData], x: mouse.mouseX, y: self.ch - mouse.mouseY]; 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 => { 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[]; }; editButtonName: Rope.ROPE = "Edit"; buttonButtonName: Rope.ROPE = "Buttons"; textClass: ViewerClasses.ViewerClass = ViewerOps.FetchViewerClass[$Text]; 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]; EditNotify.AddNotifyProc[EditNotification]; END. š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 Create TiogaButtons Creates a button as the last child of the document in viewer. Creates a button from the node. Appends to a button. Deletes a button. Provides the text of the button. Can be used in place of clientData for many purposes. This crock is necessary because TiogaOps.PutProp uses too low an abstraction for adding properties to a node. Aren't we nice, we get all the split viewers too! 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 t is either the deleted button or a proper subset of it, so delete t from the list Put back the modified list Type Conversion Routines (for the client and the implementation) Editable Tioga The $TiogaButtons viewer class Mouse coordinates are guaranteed to be in viewer space and we have captured the buttons for future $Marks and $Hits Selection info returned in feedbackSel.start.pos as TextNode.Location Find button within this selection Turn off reverse video Initialization Create the TiogaButton class. Turn on the edit notifier 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 สG˜code™KšœQ™QK™(K™/K™˜>K˜K˜—š  œž œ*žœ˜JKšœžœ˜ Kšœžœžœ˜K˜Kšžœžœžœžœ˜:Kšœ4˜4Kšœ+žœฯcF˜xKšœก6˜KKšœI˜IKšœ.˜.Kšœ1˜1Kšœ)ก(˜QK˜K˜5Kšœ˜K™—š œžœžœ0žœžœžœžœžœžœžœžœžœžœ˜ัš œžœ˜Kšœ6˜6K˜K˜—š  œžœ"žœžœ žœ˜gKšœV˜VK˜K˜—š  œžœžœžœžœ˜DKš œžœžœžœ+žœ˜EK˜—š   œžœžœžœžœ˜9Kšœžœ žœ˜KK˜—š œžœž˜"šœ˜Kšœ˜Kš œžœžœžœ˜(Kšœžœ˜$Kšœžœžœžœ˜)—Kšžœžœ˜Kšžœžœžœžœ˜5K˜,Kšžœ žœžœ˜+Kš žœžœžœžœžœ$˜AKšœE˜EKšœ>˜>šžœ:žœžœž˜LKšœžœ˜Kš œžœžœžœžœ˜7Kš œžœžœžœ žœ˜5Kšœžœ*˜Hšžœ%žœžœž˜7Kš œžœžœ-žœžœ˜]Kš œžœžœ+žœžœ˜[Kšžœ*žœ/žœžœ˜hKš žœ žœ žœžœžœžœ˜?Kšžœ˜—Kšžœžœžœ˜Kšžœ˜—Kšžœžœ˜K˜—š  œžœ.˜AKšœžœ˜Kšœžœžœ,˜8Kš žœžœžœžœžœžœ˜Fšžœž˜ Kšžœžœ˜Kšžœžœ˜—Kšœ/˜/K˜K˜—š  œžœ˜,Kšœ.˜.K˜K˜—š œžœ˜0š œžœ˜2šžœLžœžœž˜bKšœ˜Kšžœ˜—K˜—Kšœ(˜(K˜K˜—š œžœ˜1Kšœ žœ3˜GKšœžœžœ˜"Kšœ&˜&Kšœžœžœ<˜HKš žœžœžœžœžœžœ˜:Kšœžœ˜šžœžœž˜Kšœ˜šžœ(žœžœ˜RK˜"K˜šžœžœ0žœ˜KKšœ(˜(—šžœžœ'žœ˜4Kšœ2˜2K˜—šžœžœ0žœ˜IKšœ&˜&—šžœžœ%žœ˜2Kšœ.˜.K˜—šžœ"ž˜)Kšœ!˜!—šžœ˜K™Ršžœž˜ Kšžœ˜Kšžœ˜—Kšœžœ˜Kšœ˜—Kšœ˜—K˜ K˜Kšž˜—šžœž˜K™KšœC˜C—K˜K˜—š  œžœ0žœžœžœ˜‰Kšœ‡žœ˜กK˜K˜—š  œžœžœ#˜RKšœžœžœ#˜/Kšœ žœžœžœžœžœžœžœžœ˜VK˜——™@š   œžœžœžœžœžœ˜FJšžœžœ˜Kšœ˜K˜—š   œžœžœžœžœžœ˜FJšžœžœ˜Kšœ˜K˜—š  œžœžœžœžœžœ˜NJšžœžœ˜Kšœ˜K˜——™š   œžœžœ!žœžœ˜IKšžœžœžœžœ˜5šžœ žœžœก.˜HKš œžœžœžœžœ˜YKšœI˜IK˜šžœ˜šžœก˜"KšœJ˜JK˜—šžœก&˜.šžœžœžœ˜!Kšœ2˜2K˜—K˜——Kšœ-˜-K˜—K˜K˜—š œžœžœ*˜?Kšžœžœžœžœ˜5šžœžœžœก˜KK˜Kšœ'˜'Kšœ˜šžœ2ž˜8Kšœ˜—Kšœ˜šžœ žœžœ˜KšœA˜AKšœE˜EK˜Kšœ(˜(Kšœ-˜-K˜—K˜K˜—šžœžœžœก˜NK˜Kšœ ˜ K˜šžœ žœžœ˜Kšœ?˜?KšœG˜GK˜Kšœ(˜(Kšœ-˜-K˜—K˜—K˜K˜—š œžœžœžœ ˜CKšžœžœžœžœ˜5Kšžœžœžœ žœ ˜CK˜K˜—š œžœžœ5˜VKšžœžœžœžœ˜5Kšœ&žœ˜CKšœ˜K˜—š  œžœ˜0Kšœ+˜+Kšœžœžœ*˜LK˜šžœžœžœ˜K˜Kšœ ˜ K˜—Kšœ#žœ˜(K˜K˜—š  œ˜Kšœžœ ˜)K˜Kšœ žœžœ žœ ˜DK˜K˜—š œ˜/š  œžœ˜(š œžœ˜)š žœžœžœ(žœžœ˜iKšœ*˜*Kšžœžœ˜1K˜—K˜—K˜:K˜š žœžœžœ3žœžœ˜KKšœ ˜ š žœ*žœžœžœž˜GKšœ ˜ Kšžœ˜—K˜—K˜—šžœžœž˜šœžœžœ$˜4K˜K˜—šœžœžœ&˜6K˜K˜—šœžœžœ$˜4K˜K˜—šœžœžœ#˜3Kšœ˜šžœž˜#Kšœ˜—K˜—šœžœžœ#˜3Kšœ˜K˜—šœžœžœ%˜5Kšœ˜K˜—Kšžœžœ˜—K˜——šœ™š  œ˜&K˜K˜Kšฯbะbkขะbc#˜8Kšœ˜Kšœ*˜*š žœžœžœ žœž˜-KšœAžœก~˜ศ—Kšœ0˜0K˜K˜—š œ˜&K˜%Kšœžœžœ˜Kšœ˜Kšœžœก˜-Kšœ>˜>šžœžœžœžœžœžœžœž˜@šžœ žœž˜šœžœžœž˜K˜K˜ K˜Kšœžœ˜Kšœžœ˜šœ ˜ K˜K˜3šžœžœžœ˜Kšœ?ก#˜b—šžœžœžœžœ˜-K˜K˜Kšžœ˜Kšœ˜—K™:K™8šœ)˜)Kšœ˜Kšœ ˜ Kšœžœ-˜8Kšœ˜Kšœ˜—KšœE™EK™!Kšœ>žœ˜Dšžœ žœžœ˜Kšžœžœžœก˜8Kšžœ ˜$—šžœ˜K˜K˜Kšœ˜—Kšœ˜—šœ ˜ K™Kšœ3˜3šžœžœžœ˜K˜Kšœ˜šžœžœžœ˜%Kšœžœ]˜uKšœ˜—Kšžœ]˜aKšœ˜—Kšœ˜—Kšžœžœ˜—šœ˜K˜ Kšœ˜Kšœ˜Kšœ˜—Kšžœžœ˜—Kšžœ˜—K˜!Kšœ˜K˜—š  œžœ>žœžœ˜fK˜Kšœžœ˜Kšœ2˜2Kšžœžœ ˜"K˜K˜—š  œžœžœžœ˜YKšžœžœ˜K˜K˜—š  œžœ!žœžœ˜]Kšžœžœ žœ˜*K˜K˜—š œžœžœ˜SKšœžœžœA˜Mš žœžœžœžœž˜.Kšœ žœ˜—K˜K˜—š œžœ6˜MKšœI˜I˜Kšœ ˜ šœžœ#˜,Kšžœ˜Kšžœ˜—šœžœ!˜(KšžœE˜IKšžœ˜—K˜ Kšœ žœ˜Kšœžœ˜K˜—K˜K˜—š œžœ!˜5KšœAžœ˜FK˜#K˜K˜—š œ˜KšœDžœ˜JK˜K˜——™Kšœžœ ˜#Kšœžœ ˜(K˜IK˜™Kšœ-žœ,˜]Kšœ#˜#Kšœ#˜#Kšœ#˜#Kšœ˜KšœI˜IKšœ?˜?K™—™K˜+K˜——Kšžœ˜K˜™6K™^K™JK™#K™-Kšœ?™?——…—`ฎ„