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; 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; { 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; { 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]; { 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]; { 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] ~ { 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] ~ { 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]; 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] ~ { 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 { 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: 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; }; 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; }; 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; }; [] ¬ TEditSelectionPrivate.ResolveToChar[ selection: feedbackSel, viewer: self, tdd: NARROW[self.data, TEditDocument.TEditDocumentData], x: mouse.mouseX, y: self.ch - mouse.mouseY]; 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 => { 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[]; }; 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, 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 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 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 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 Κ£–(cedarcode) style•NewlineDelimiter ™code™Kšœ ΟeœR™]K™(K™/K™K˜K˜—š  œŸ œ*Ÿœ˜JKšœŸœ˜ KšœŸœŸœ˜K˜KšŸœŸœŸœŸœ˜:K˜4Kšœ+ŸœΟcF˜xKšœ‘6˜KK˜IKšœ.˜.Kšœ1˜1Kšœ)‘(˜QK˜K˜5Kšœ˜K™—š œŸœŸœ0ŸœŸœŸœŸœŸœŸœŸœŸœŸœŸœ˜Ρš œŸœ˜:K˜-šŸœŸœŸ˜šœŸœ˜,Kšœ˜Kšœ ŸœŸœŸœ˜RKšœ/˜/Kšœ˜—šŸœŸœ˜.KšŸœ˜—šŸœŸœ˜1KšŸœ#˜'—KšŸœ˜—K˜—KšŸœŸœŸœŸœ˜:KšœG˜GKšœ˜K˜—š œŸ ˜šœ˜Kšœ˜KšœŸœ˜K˜KšœŸœŸœ˜KšœŸœŸœ˜KšœŸœŸœ˜KšœŸœ˜Kšœ ŸœŸœŸœ˜KšœŸœŸ˜Kšœ˜—šŸœ˜!KšŸœŸœŸœŸœ˜:K˜K™=šœ˜K˜.š œŸœ˜/K˜JKšŸœŸœŸœ1˜KšŸœŸœŸœ˜K˜"šŸœŸœŸœŸ˜"KšœŸœ˜KšŸœ˜—K˜BK˜—Kšœ Ÿœ~˜ŠšŸœŸœŸœ˜Kšœ˜—šŸ˜Kšœ˜—K˜—Kšœ&˜&Kšœ˜—Kšœ˜K˜——š  œŸ œ&ŸœŸœ ŸœŸœ ŸœŸœŸœŸœŸœŸœŸœŸœŸœ˜αKšŸœŸœ8˜]Kšœ˜K˜—šœŸœŸœŸœ˜&K˜—š œŸœŸœŸœ ŸœŸœŸœŸœŸœŸœŸœŸœŸœ˜ΙK˜'KšœŸœŸœ#˜/KšœŸœŸœŸœŸœŸœŸœŸœŸœ˜lKšŸœŸœŸœŸœ˜:K˜K™šœ˜š œŸœ˜7Kšœ Ÿœr˜~šŸœŸœŸœ˜Kšœ˜—šŸ˜Kšœ˜—K˜—š Ÿœ ŸœŸœŸœŸœ˜%K˜—KšŸœŸœŸœ/˜AKšœ˜—Kšœ˜K˜—š œŸ œŸœŸœ ŸœŸœŸœŸœŸœŸœŸœŸœŸœ˜ΎK˜(K˜'K™K™šœ˜š œŸœ˜1K˜;KšœŸœ˜K˜AKšœ Ÿœ}˜‰šŸœŸœŸœ˜Kšœ˜—K˜—Kšœ)˜)KšŸœ ˜K˜—Kšœ˜K™—š  œŸ œ˜3šŸœ ŸœŸœ˜K˜7K˜K™˜š œŸœ˜/˜K˜ K˜'—šŸœ#Ÿœ˜+K˜—K˜—Kšœ'˜'K˜—Kšœ˜—K˜K˜—š œŸœŸœ-Ÿœ˜jKšŸœŸœ˜%K˜K˜—š  œŸœ˜Kšœ?ŸœŸœ˜fKšœ˜KšœŸœŸœ0˜Kšœ ˜ š Ÿœ*ŸœŸœŸœŸ˜GKšœ ˜ KšŸœ˜—K˜—K˜—K˜—š œŸœ˜Kšœ!˜!Kšœ˜Kšœ˜—š œŸœ˜"Kšœ#˜#Kšœ#˜#Kšœ˜KšŸœ Ÿœ˜%Kšœ˜—šŸœŸœŸ˜KšœŸœŸœ/˜>KšœŸœŸœ/˜>KšœŸœŸœ6˜EKšœŸœŸœ/˜>KšœŸœŸœ/˜>KšŸœ˜—K˜——šœ™š  œ˜&K˜K˜KšœŸœ‘#˜@K˜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šœ<™ŸœŸœ˜fK˜KšœŸœ˜K˜2KšŸœŸœ ˜"K˜K˜—š œŸœŸœ˜SKšœŸœŸœA˜Mš ŸœŸœŸœŸœŸ˜.Kšœ Ÿœ˜—K˜K˜—š œŸœ6˜MKšœI˜I˜Kšœ ˜ šœŸœ#˜,KšŸœ˜KšŸœ˜—šœŸœ!˜(KšŸœ8˜