<> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <<>> 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. <> <> <> <> <> <>