<> <> <> <<>> DIRECTORY Atom, EditSpan, InputFocus, Interminal, Menus, MessageWindow, NodeProps, Process, Rope, TEditDocument, TEditDocumentPrivate, TEditMouseImpl, TEditSelection, TextEdit, TextLooks, TextNode, TiogaButtons, TiogaExtraOps, TiogaFileOps, TiogaOps, TIPUser, ViewerClasses, ViewerOps; TiogaButtonsImpl: CEDAR PROGRAM IMPORTS Atom, EditSpan, InputFocus, MessageWindow, NodeProps, Process, Rope, TEditDocumentPrivate, TEditMouseImpl, TEditSelection, TextEdit, TextLooks, TextNode, TiogaButtons, TiogaExtraOps, TiogaFileOps, TiogaOps, ViewerOps EXPORTS TiogaButtons SHARES TEditMouseImpl~ { OPEN TiogaButtons; ROPE: TYPE ~ Rope.ROPE; TiogaButtonAtom: ATOM ~ $TiogaButtonList; TiogaButtonsAtom: ATOM ~ $TiogaButtons; <> <<>> CreateViewer: PUBLIC PROC [info: ViewerClasses.ViewerRec] RETURNS [v: ViewerClasses.Viewer] ~ { v _ ViewerOps.CreateViewer[flavor: TiogaButtonsAtom, info: info]; }; LoadViewer: PUBLIC PROC [viewer: ViewerClasses.Viewer, fileName: ROPE] ~ { name: ROPE; IF viewer = NIL OR viewer.class.flavor # TiogaButtonsAtom THEN ERROR WrongViewerClass; name _ viewer.name; -- DoLoadFile will change this on us [] _ TEditDocumentPrivate.DoLoadFile[parent: viewer, fileName: fileName]; viewer.tipTable _ viewer.class.tipTable; -- DoLoadFile does violence to this too! viewer.name _ name; ViewerOps.PaintViewer[viewer: viewer, hint: caption]; }; <<>> 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] ~ { <<>> <> IF viewer = NIL OR viewer.class.flavor # TiogaButtonsAtom THEN ERROR WrongViewerClass; <> { root: TiogaOps.Ref _ TiogaOps.ViewerDoc[viewer]; LockedCreateButton: PROC [root: TiogaOps.Ref] ~ { node: TiogaOps.Ref ~ CreateNode[root, rope]; 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] }; }; WrongViewerClass: PUBLIC ERROR = CODE; CreateButtonFromNode: PUBLIC PROC [node: TiogaOps.Ref, start: TiogaOps.Offset _ 0, end: TiogaOps.Offset _ LAST[TiogaOps.Offset], 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 viewer = NIL OR viewer.class.flavor # TiogaButtonsAtom 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 = LAST[TiogaOps.Offset] 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: TiogaOps.Offset; [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: [TextNodeLoc[button.startLoc], TextNodeLoc[button.endLoc]]]; IF button.startLoc.where # NodeItself THEN AdjustButtonProps[button]; }; CallWithLock[LockedDeleteButton, root]; }; }; GetRope: PUBLIC PROC [button: TiogaButton] RETURNS [rope: ROPE] ~ { <> IF button # NIL THEN { rope _ TiogaOps.GetRope[button.startLoc.node]; IF button.startLoc.where # NodeItself THEN rope _ rope.Substr[button.startLoc.where, button.endLoc.where-button.startLoc.where+1]; }; }; SetStyleFromRope: PUBLIC PROC [v: ViewerClasses.Viewer, styleRope: ROPE] ~ { <> root: TextNode.Ref ~ TextNodeRef[TiogaOps.ViewerDoc[v]]; TextEdit.PutProp[node: root, name: "StyleDef", value: NodeProps.DoSpecs[$StyleDef, styleRope], root: root]; }; CallWithLock: PROC [proc: PROC [root: TiogaOps.Ref], root: TiogaOps.Ref] ~ { lockedDoc: BOOL _ FALSE; Cleanup: PROC ~ { IF lockedDoc THEN TiogaOps.Unlock[root]; }; { ENABLE UNWIND => Cleanup; TiogaOps.Lock[root]; lockedDoc _ TRUE; proc[root]; }; Cleanup; MarkViewerNotEdited[root]; }; MarkViewerNotEdited: PROC [root: TiogaOps.Ref] ~ { DoOne: PROC [v: ViewerClasses.Viewer] ~ { v.newVersion _ FALSE; ViewerOps.PaintViewer[viewer: v, hint: caption]; }; v: ViewerClasses.Viewer; ref: REF ANY _ TiogaOps.GetProp[root, $Viewer]; IF ref # NIL AND ISTYPE[ref, ViewerClasses.Viewer] THEN v _ NARROW[ref]; IF v # NIL AND v.newVersion THEN { DoOne[v]; <> IF v.link # NIL THEN FOR x: ViewerClasses.Viewer _ v.link, x.link UNTIL x=v DO DoOne[x]; ENDLOOP; }; }; CreateNode: PROC [root: TiogaOps.Ref, rope: ROPE, prevLast: TiogaOps.Ref _ NIL] RETURNS [node: TiogaOps.Ref] ~ { IF TiogaOps.GetProp[root, $InitialTiogaButtons] # NIL THEN { TiogaExtraOps.RemProp[root, $InitialTiogaButtons]; node _ TiogaOpsRef[TextNodeRef[root].child]; } ELSE { <> node _ TiogaOpsRef[EditSpan.Split[TextNodeRef[root], [TextNode.LastChild[TextNodeRef[root]], NodeItself]]]; SetFormat[node, ""]; }; [] _ TextEdit.InsertRope[root: TextNodeRef[root], dest: TextNode.NarrowToTextNode[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] ~ TRUSTED { TiogaFileOps.AddLooks[TiogaFileOpsRef[node], start, len, look, TiogaFileOpsRef[root]]; }; AddButtonProp: PROC [node: TiogaOps.Ref, button: TiogaButton] ~ { list: TiogaButtonList _ NIL; ref: REF ANY _ TiogaOps.GetProp[node, TiogaButtonAtom]; 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, TiogaButtonAtom, list]; }; RemButtonProp: PROC [node: TiogaOps.Ref] ~ { TiogaExtraOps.RemProp[node, TiogaButtonAtom]; }; 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, TiogaButtonAtom]; 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, TiogaButtonAtom, propList]; }; AppendRopeToNode: PROC [root: TiogaOps.Ref, node: TiogaOps.Ref, rope: ROPE, lookVector: TextLooks.Looks] RETURNS [start, length: TiogaOps.Offset] ~ TRUSTED { [start, length] _ TextEdit.InsertRope[root: TextNodeRef[root], dest: TextNode.NarrowToTextNode[TextNodeRef[node]], rope: rope, destLoc: TextNode.MaxLen, inherit: FALSE, looks: lookVector]; }; <<>> <> InitViewer: ViewerClasses.InitProc ~ { root: TiogaOps.Ref; textViewerInitProc[self]; self.tipTable _ buttonTIPTable; root _ TiogaOps.ViewerDoc[self]; TiogaOps.PutProp[root, $InitialTiogaButtons, NEW[BOOLEAN _ 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; }; <> <> [] _ TEditMouseImpl.ResolveToChar[ selection: feedbackSel, viewer: self, tdd: NARROW[self.data, TEditDocument.TEditDocumentData], x: mouse.mouseX, y: self.ch - mouse.mouseY]; <> <> button _ FindTiogaButton[self, TiogaOpsLoc[feedbackSel.start.pos]]; 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 => ERROR; 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]] }; TextNodeLoc: PROC [loc: TiogaOps.Location] RETURNS [TextNode.Location] ~ TRUSTED INLINE { RETURN [LOOPHOLE[loc]] }; FindTiogaButton: PROC [this: ViewerClasses.Viewer, loc: TiogaOps.Location] RETURNS [button: TiogaButton] ~ { list: TiogaButtonList; ref: REF ANY _ TiogaOps.GetProp[loc.node, TiogaButtonAtom]; IF ref = NIL OR ~ISTYPE[ref, TiogaButtonList] 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]; }; 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, GetRope[button].Length] 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 ~ { button: TiogaButton _ NARROW[parent]; MessageWindow.Append[Rope.Cat["button contents ", GetRope[button]], TRUE]; MessageWindow.Blink[]; }; <> <<>> buttonTIPTable: TIPUser.TIPTable _ ViewerOps.FetchViewerClass[$Button].tipTable; tiogaButtonClass: ViewerClasses.ViewerClass _ NEW[ViewerClasses.ViewerClassRec _ ViewerOps.FetchViewerClass[$Text]^]; textViewerInitProc: ViewerClasses.InitProc _ tiogaButtonClass.init; tiogaButtonClass.init _ InitViewer; tiogaButtonClass.save _ NIL; -- avoids saving TiogaButtons properties tiogaButtonClass.notify _ Notifier; tiogaButtonClass.bltContents _ none; -- avoids dependency in ViewerBLT on $Text tiogaButtonClass.cursor _ bullseye; tiogaButtonClass.icon _ tool; tiogaButtonClass.tipTable _ buttonTIPTable; ViewerOps.RegisterViewerClass[TiogaButtonsAtom, tiogaButtonClass]; }. <> <> <> <> <> <>