(* Copyright (C) 1992, Digital Equipment Corporation *) (* All rights reserved. *) (* See the file COPYRIGHT for a full description. *) (* *) (* Last modified on Tue Jan 5 13:25:53 PST 1993 by meehan *) <* PRAGMA LL *> MODULE IvyModel; IMPORT Char, MTextUnit, PaintOp, Rd, Text, TextPort, TextPortClass, Thread, VBT, VTDef, VText; REVEAL T = TextPortClass.Model BRANDED OBJECT pivotL, pivotR: CARDINAL; dragButton : VBT.Button; dragSel : TextPort.SelectionType; selection: ARRAY TextPort.SelectionType OF TextPortClass.SelectionRecord; swapping := FALSE; clipboard := ""; sourceInClipboard := TRUE; pattern := "" (* For searches *) METHODS highlight (sel: TextPort.SelectionType; left, middle, right: CARDINAL) := Highlight OVERRIDES controlChord := ControlChord; copy := Copy; getSelection := GetSelection; hasVBTselection := HasVBTselection; init := Init; isReplaceMode := IsReplaceMode; misc := Misc; mouse := Mouse; optionChord := OptionChord; position := Position; putSelectedText := PutSelectedText; read := Read; select := Select; takeSelection := TakeSelection; walkIntervals := WalkIntervals; write := Write; END; CONST Primary = TextPort.SelectionType.Primary; Secondary = TextPort.SelectionType.Secondary; standardStyle = ARRAY TextPort.SelectionType OF VText.IntervalStyle { VText.IntervalStyle.UnderlineStyle, VText.IntervalStyle.GrayUnderlineStyle}; replaceModeStyle = ARRAY TextPort.SelectionType OF VText.IntervalStyle { VText.IntervalStyle.InverseStyle, VText.IntervalStyle.GrayUnderlineStyle}; VAR SwapHighlights := VBT.GetMiscCodeType ("SwapHighlights"); PROCEDURE Init (m: T; colorScheme: PaintOp.ColorScheme): TextPortClass.Model = BEGIN TRY m.swapping := FALSE; m.selection [Primary].interval := VText.CreateInterval ( vtext := m.v.vtext, indexL := 0, indexR := 0, options := VText.MakeIntervalOptions ( style := VText.IntervalStyle.UnderlineStyle, whiteBlack := colorScheme, whiteStroke := colorScheme, leading := colorScheme.bg)); m.selection [Secondary].interval := VText.CreateInterval ( vtext := m.v.vtext, indexL := 0, indexR := 0, options := VText.MakeIntervalOptions ( style := VText.IntervalStyle.GrayUnderlineStyle, whiteBlack := colorScheme, whiteStroke := colorScheme, leading := colorScheme.bg)) EXCEPT | VTDef.Error (ec) => m.v.vterror ("Model Init", ec) END; RETURN m END Init; PROCEDURE ControlChord (m: T; ch: CHAR; READONLY cd: VBT.KeyRec) = CONST name = "Control Key"; VAR time := cd.time; v := m.v; PROCEDURE delete (READONLY e: TextPort.Extent) = BEGIN IF e # TextPort.NotFound THEN m.select (time, e.l, e.r) END END delete; PROCEDURE toWord (READONLY e: TextPort.Extent; forward := TRUE) = BEGIN IF e # TextPort.NotFound THEN m.select (time, e.l, e.r, Primary, caretEnd := VAL (ORD (forward), VText.WhichEnd)) END END toWord; BEGIN TRY CASE ch OF | ' ' => (* Just normalize. *) | ',' => find (m, time, FALSE, TextPortClass.Direction.Forward, TextPortClass.Where.Here) | ';' => ToEndOfLine (m, time) | 'a' => delete (TextPortClass.DeletePrevChar (v)) | 'b' => delete (TextPortClass.DeleteCurrentLine (v)) | 'c' => delete (TextPortClass.DeleteToStartOfLine (v)) | 'd' => delete (TextPortClass.DeleteToStartOfWord (v)) | 'e' => Move (m, time) | 'f' => delete (TextPortClass.DeleteToEndOfWord (v)) | 'g' => delete (TextPortClass.DeleteCurrentWord (v)) | 'h' => ExchangeSelections (m, time); | 'i' => toWord (TextPortClass.FindNextWord (v)) | 'j' => TextPortClass.ToPrevChar (v); sci (m, time) | 'k' => TextPortClass.ToNextChar (v); sci (m, time) | 'l' => ToStartOfLine (m, time) | 'm' => find (m, time, FALSE, TextPortClass.Direction.Backward, TextPortClass.Where.Here) | 'n' => (* Find Next Selected *) find (m, time, TRUE, TextPortClass.Direction.Forward, TextPortClass.Where.Here) | 'o' => TextPortClass.UpOneLine (v); sci (m, time) | 'p' => TextPortClass.DownOneLine (v); sci (m, time) | 'q' => m.clear () | 'r' => Swap (m, time) | 's' => delete (TextPortClass.DeleteNextChar (v)) | 'u' => toWord (TextPortClass.FindPrevWord (v), FALSE) | 'v' => delete (TextPortClass.DeleteToEndOfLine (v)) | 'w' => m.paste (time) | 'y' => TextPortClass.ToOtherEnd (v); sci (m, time) | 'z' => TextPortClass.Undo (v) | 'Z' => TextPortClass.Redo (v) ELSE (* Don't normalize if unknown chord, including just ctrl itself. *) m.v.ULdefaultAction (cd); RETURN END EXCEPT | VTDef.Error (ec) => m.v.vterror (name, ec) | Rd.Failure (ref) => m.v.rdfailure (name, ref) | Rd.EndOfFile => m.v.rdeoferror (name) | Thread.Alerted => END; m.v.normalize (-1) END ControlChord; PROCEDURE OptionChord (m: T; ch: CHAR; READONLY cd: VBT.KeyRec) = CONST name = "Option Key"; VAR time := cd.time; v := m.v; BEGIN TRY CASE ch OF | 'c' => m.copy (time) | 'm' => find (m, time, TRUE, TextPortClass.Direction.Backward, TextPortClass.Where.Here) | 'n' => find (m, time, TRUE, TextPortClass.Direction.Forward, TextPortClass.Where.Beginning) | 'v' => m.paste (time) | 'x' => m.cut (time) | Char.BS, Char.DEL => TextPortClass.SwapChars (v); WITH index = m.v.index () DO m.select (time, index - 2, index) END | Char.NL => TextPortClass.InsertNewline (v); sci (m, time) ELSE (* Don't normalize if unknown chord, including just option itself. *) m.v.ULdefaultAction (cd); RETURN END EXCEPT | VTDef.Error (ec) => m.v.vterror (name, ec) | Rd.Failure (ref) => m.v.rdfailure (name, ref) | Rd.EndOfFile => m.v.rdeoferror (name) | Thread.Alerted => RETURN END; m.v.normalize (-1) END OptionChord; PROCEDURE Copy (m: T; time: VBT.TimeStamp) = CONST name = "Copy"; VAR t := m.getSelectedText (Primary); BEGIN IF NOT Text.Empty (t) AND m.takeSelection (Secondary, time) THEN m.clipboard := t; m.sourceInClipboard := TRUE; TRY VText.SwitchInterval ( m.selection [Secondary].interval, VText.OnOffState.Off) EXCEPT | VTDef.Error (ec) => m.v.vterror (name, ec) | Rd.Failure (ref) => m.v.rdfailure (name, ref) | Rd.EndOfFile => m.v.rdeoferror (name) | Thread.Alerted => END END END Copy; PROCEDURE sci (m: T; time: VBT.TimeStamp) = (* Select Current Index *) BEGIN WITH index = m.v.index () DO m.select (time, index, index) END END sci; PROCEDURE find (m : T; time : VBT.TimeStamp; selected : BOOLEAN; direction: TextPortClass.Direction; whence : TextPortClass.Where ) = VAR t := m.pattern; BEGIN IF selected THEN TRY t := m.read (VBT.Source, time) EXCEPT | VBT.Error (ec) => m.v.vbterror ("find", ec); RETURN END END; WITH ext = TextPortClass.Find (m.v, t, direction, whence) DO IF ext # TextPort.NotFound THEN m.pattern := t; m.select (time, ext.l, ext.r, replaceMode := TRUE, caretEnd := VAL (1 - ORD (direction), VText.WhichEnd)) END END END find; PROCEDURE ExchangeSelections (m: T; time: VBT.TimeStamp) = CONST name = "Exchange"; VAR primary := m.selection [Primary]; intvl_1 := primary.interval; secondary := m.selection [Secondary]; intvl_2 := secondary.interval; index := m.v.index (); pLeft := intvl_1.left (); pRight := intvl_1.right (); sLeft := intvl_2.left (); sRight := intvl_2.right (); BEGIN TRY IF NOT primary.owned THEN RETURN END; (* This VBT owns the primary. Does it own both? *) IF secondary.owned THEN IF index = pLeft THEN index := sLeft ELSE index := sRight END; m.highlight (Primary, sLeft, index, sRight) ELSE (* This is more complex. Must tell the owner of the secondary that this is going to be a swap, and then grab the secondary. *) VBT.Put (m.v, VBT.Source, time, SwapHighlights); VText.SwitchInterval (intvl_1, VText.OnOffState.Off); EVAL m.takeSelection (Secondary, time) END; (* Now, move the secondary selection to what used to be the primary interval. *) m.highlight (Secondary, pLeft, pRight, pRight) EXCEPT | VTDef.Error (ec) => m.v.vterror (name, ec) | VBT.Error (ec) => m.v.vbterror (name, ec) | Rd.Failure (ref) => m.v.rdfailure (name, ref) | Rd.EndOfFile => m.v.rdeoferror (name) | Thread.Alerted => END END ExchangeSelections; PROCEDURE Mouse (m: T; READONLY cd: VBT.MouseRec) = CONST name = "Mouse"; BEGIN TRY CASE cd.clickType OF | VBT.ClickType.FirstDown => IF VBT.Modifier.Control IN cd.modifiers THEN (* Secondary selection *) IF m.takeSelection (Secondary, cd.time) THEN m.sourceInClipboard := FALSE; MouseExtend (m, cd, Secondary) END ELSE (* Primary selection *) IF m.takeSelection (Primary, cd.time) AND m.v.getKFocus (cd.time) THEN MouseExtend (m, cd, Primary) END END ELSE m.dragging := FALSE END EXCEPT | VTDef.Error (ec) => m.v.vterror (name, ec) | VBT.Error (ec) => m.v.vbterror (name, ec) | Rd.Failure (ref) => m.v.rdfailure (name, ref) | Rd.EndOfFile => m.v.rdeoferror (name) | Thread.Alerted => END END Mouse; PROCEDURE MouseExtend ( m : T; READONLY cd : VBT.MouseRec; sel: TextPort.SelectionType) RAISES {Rd.EndOfFile, Rd.Failure, Thread.Alerted, VTDef.Error} = VAR r : TextPortClass.IRange; options: VText.IntervalOptions; BEGIN WITH rec = m.selection [sel], interval = rec.interval DO options := interval.getOptions (); CASE cd.whatChanged OF | VBT.Modifier.MouseL, VBT.Modifier.MouseM => rec.mode := selectionModes [cd.whatChanged, MIN (cd.clickCount, 4)]; options.style := standardStyle [sel]; VText.ChangeIntervalOptions (interval, options); r := TextPortClass.GetRange (m.v, cd.cp, rec.mode); IF rec.mode = VText.SelectionMode.CharSelection THEN r.left := r.middle; r.right := r.middle END; m.highlight (sel, r.left, r.middle, r.right) | VBT.Modifier.MouseR => IF cd.clickCount < 2 THEN (* single click *) IF interval.left () >= m.v.typeinStart AND NOT m.v.readOnly AND options.style # replaceModeStyle [sel] THEN options.style := replaceModeStyle [sel]; VText.ChangeIntervalOptions (interval, options) END; (* The current interval-bounds become the pivots. *) m.pivotL := interval.left (); m.pivotR := interval.right () ELSIF rec.mode > FIRST (VText.SelectionMode) THEN (* multi-clicking: make the selection-mode smaller. *) DEC (rec.mode) END; (* IF *) r := TextPortClass.GetRange (m.v, cd.cp, rec.mode); IF r.left < m.pivotL THEN m.fixed := MAX (r.right, m.pivotR); m.highlight (sel, r.left, r.left, m.fixed) ELSE m.fixed := MIN (r.left, m.pivotL); m.highlight (sel, m.fixed, r.right, r.right) END ELSE END (* CASE *) END; (* WITH *) m.dragButton := cd.whatChanged; m.dragSel := sel; m.dragging := TRUE END MouseExtend; PROCEDURE Highlight (m : T; sel : TextPort.SelectionType; left, middle, right: CARDINAL ) = CONST name = "Highlight"; BEGIN TRY IF sel = Primary THEN VText.MoveCaret (m.v.vtext, middle) END; VText.MoveInterval (m.selection [sel].interval, left, right); VText.SwitchInterval (m.selection [sel].interval, VText.OnOffState.On); VBT.Mark (m.v) EXCEPT | VTDef.Error (ec) => m.v.vterror (name, ec) | Rd.EndOfFile => m.v.rdeoferror (name) | Rd.Failure (ref) => m.v.rdfailure (name, ref) | Thread.Alerted => END END Highlight; PROCEDURE Position (m: T; READONLY cd: VBT.PositionRec) = BEGIN IF NOT m.dragging THEN (* skip *) ELSIF cd.cp.gone THEN VBT.SetCage (m.v, VBT.GoneCage) ELSE VAR sel := m.dragSel; mode := m.selection [sel].mode; r := TextPortClass.GetRange (m.v, cd.cp, mode); BEGIN IF mode = VText.SelectionMode.CharSelection THEN r.left := r.middle; r.right := r.middle END; CASE m.dragButton OF | VBT.Modifier.MouseL, VBT.Modifier.MouseM => m.highlight (sel, r.left, r.middle, r.right) | VBT.Modifier.MouseR => IF r.left < m.fixed THEN m.highlight (sel, r.left, r.left, m.fixed) ELSE m.highlight (sel, m.fixed, r.right, r.right) END ELSE END END END END Position; (*********************** Reading ****************************) PROCEDURE Read (m: T; READONLY s: VBT.Selection; time: VBT.TimeStamp): TEXT RAISES {VBT.Error} = BEGIN IF s = VBT.Source AND m.selection [Secondary].owned THEN IF m.sourceInClipboard THEN RETURN m.clipboard ELSE RETURN m.getSelectedText (Secondary) END ELSIF s = VBT.Target AND m.selection [Primary].owned THEN RETURN m.getSelectedText (Primary) ELSE RETURN TextPortClass.Model.read (m, s, time) END END Read; (*********************** Writing ****************************) PROCEDURE PutSelectedText (m: T; t: TEXT; sel: TextPort.SelectionType) = CONST name = "PutSelectedText"; VAR interval := m.selection [sel].interval; left := interval.left (); options := interval.getOptions (); BEGIN TRY IF m.v.replace (interval.left (), interval.right (), t) = TextPort.NotFound THEN RETURN END; (* NB: Replace changes interval! *) options.style := standardStyle [sel]; VText.ChangeIntervalOptions (interval, options); VText.MoveInterval (interval, left, left + Text.Length (t)) EXCEPT | VTDef.Error (ec) => m.v.vterror (name, ec) | Rd.Failure (ref) => m.v.rdfailure (name, ref) | Rd.EndOfFile => m.v.rdeoferror (name) | Thread.Alerted => END END PutSelectedText; PROCEDURE Write (m: T; READONLY s: VBT.Selection; time: VBT.TimeStamp; t: TEXT) RAISES {VBT.Error} = PROCEDURE write (t: TEXT; sel: TextPort.SelectionType) RAISES {VBT.Error} = BEGIN IF m.selection [sel].interval.left () < m.v.typeinStart THEN RAISE VBT.Error (VBT.ErrorCode.Unwritable) ELSE m.putSelectedText (t, sel) END END write; BEGIN IF s = VBT.Source AND m.selection [Secondary].owned THEN IF m.sourceInClipboard THEN m.clipboard := t ELSE write (t, Secondary) END ELSIF s = VBT.Target AND m.selection [Primary].owned THEN write (t, Primary) ELSE TextPortClass.Model.write (m, s, time, t) END END Write; (***************** Other things *************************) PROCEDURE Misc (m: T; READONLY cd: VBT.MiscRec) = CONST name = "Misc"; BEGIN TRY IF cd.type = SwapHighlights THEN m.swapping := TRUE ELSIF cd.type = VBT.Lost THEN IF cd.selection = VBT.KBFocus AND m.v.hasFocus THEN m.v.hasFocus := FALSE; VBT.Release (m.v, VBT.Target); VText.SwitchCaret (m.v.vtext, VText.OnOffState.Off); m.v.ULfocus (FALSE, cd.time) ELSIF cd.selection = VBT.Target AND m.selection [Primary].owned THEN (* VBT.Release (m.v, VBT.KBFocus); *) VText.SwitchInterval ( m.selection [Primary].interval, VText.OnOffState.Off); m.selection [Primary].owned := FALSE ELSIF cd.selection = VBT.Source AND m.selection [Secondary].owned THEN WITH i = m.selection [Secondary].interval DO VText.SwitchInterval (i, VText.OnOffState.Off); m.selection [Secondary].owned := FALSE; IF m.swapping THEN (* After losing the secondary, grab primary! *) IF m.takeSelection (Primary, cd.time) THEN m.highlight (Primary, i.left (), i.right (), i.right ()) END; m.swapping := FALSE END (* IF m.swapping *) END (* WITH *) END; (* IF cd.selection *) VBT.Mark (m.v) ELSIF cd.type = VBT.TakeSelection AND cd.selection = VBT.KBFocus THEN EVAL m.takeSelection (Primary, cd.time, TRUE) END (* IF cd.type *) EXCEPT | VTDef.Error (ec) => m.v.vterror (name, ec) | VBT.Error (ec) => m.v.vbterror (name, ec) | Rd.Failure (ref) => m.v.rdfailure (name, ref) | Rd.EndOfFile => m.v.rdeoferror (name) | Thread.Alerted => END (* TRY *) END Misc; PROCEDURE TakeSelection (m : T; sel : TextPort.SelectionType; time : VBT.TimeStamp; highlight := FALSE): BOOLEAN = CONST name = "TakeSelection"; BEGIN WITH rec = m.selection [sel], i = rec.interval, s = Map [sel] DO IF NOT rec.owned THEN TRY VBT.Acquire (m.v, s, time); IF sel = Primary THEN rec.owned := TRUE ELSIF m.v.getKFocus (time) THEN rec.owned := TRUE ELSE VBT.Release (m.v, VBT.Source) END; VBT.Mark (m.v) EXCEPT | VBT.Error (ec) => m.v.vbterror (name, ec) END END; IF rec.owned AND highlight THEN m.highlight (sel, i.left (), i.right (), i.right ()) END; RETURN rec.owned END END TakeSelection; VAR Map := ARRAY TextPort.SelectionType OF VBT.Selection {VBT.Target, VBT.Source}; PROCEDURE Select (m : T; time : VBT.TimeStamp; begin, end : CARDINAL; sel := Primary; replaceMode := FALSE; caretEnd := VText.WhichEnd.Right) = CONST name = "Select"; VAR interval := m.selection [sel].interval; options := interval.getOptions (); BEGIN IF m.takeSelection (sel, time) THEN TRY CASE sel OF | Primary => IF replaceMode AND NOT m.v.readOnly THEN options.style := replaceModeStyle [sel] ELSE options.style := standardStyle [sel] END; IF caretEnd = VText.WhichEnd.Right THEN m.seek (end) ELSE m.seek (begin) END; | Secondary => options.style := standardStyle [sel] END; VText.ChangeIntervalOptions (interval, options); VText.MoveInterval (interval, begin, end); VText.SwitchInterval (interval, VText.OnOffState.On); VBT.Mark (m.v) EXCEPT | VTDef.Error (ec) => m.v.vterror (name, ec) | VBT.Error (ec) => m.v.vbterror (name, ec) | Rd.Failure (ref) => m.v.rdfailure (name, ref) | Rd.EndOfFile => m.v.rdeoferror (name) | Thread.Alerted => END END END Select; PROCEDURE IsReplaceMode (v: T): BOOLEAN = BEGIN RETURN v.selection [Primary].interval.getOptions ().style = replaceModeStyle [Primary] END IsReplaceMode; PROCEDURE GetSelection (m: T; sel: TextPort.SelectionType): TextPort.Extent = BEGIN WITH interval = m.selection [sel].interval DO RETURN TextPort.Extent {interval.left (), interval.right ()} END END GetSelection; PROCEDURE HasVBTselection (m: T; sel: TextPort.SelectionType): BOOLEAN = BEGIN RETURN m.selection [sel].owned END HasVBTselection; PROCEDURE ToStartOfLine (m: T; time: VBT.TimeStamp) = VAR index := m.v.index (); mtext := m.v.vtext.mtext; line := MTextUnit.LineInfo (mtext, index); x := m.selection [Primary].interval; BEGIN IF x.left () = line.left AND x.right () = line.right AND index = line.left AND line.left > 0 THEN line := MTextUnit.LineInfo (mtext, line.left - 1) END; m.select (time, line.left, line.right, caretEnd := VText.WhichEnd.Left) END ToStartOfLine; PROCEDURE ToEndOfLine (m: T; time: VBT.TimeStamp) = VAR index := m.v.index (); mtext := m.v.vtext.mtext; line := MTextUnit.LineInfo (mtext, index); x := m.selection [Primary].interval; BEGIN IF x.left () = line.left AND x.right () = line.rightEnd AND index = line.rightEnd AND line.rightEnd < m.v.length () THEN line := MTextUnit.LineInfo (mtext, line.right) END; m.select (time, line.left, line.rightEnd) END ToEndOfLine; PROCEDURE Swap (m: T; time: VBT.TimeStamp) = BEGIN TRY WITH primaryValue = m.read (VBT.Target, time), secondaryValue = m.read (VBT.Source, time) DO m.write (VBT.Target, time, secondaryValue); m.write (VBT.Source, time, primaryValue) END EXCEPT | VBT.Error (ec) => m.v.vbterror ("Swap", ec) END END Swap; PROCEDURE Move (m: T; time: VBT.TimeStamp) = BEGIN TRY m.write (VBT.Target, time, m.read (VBT.Source, time)); m.write (VBT.Source, time, "") EXCEPT | VBT.Error (ec) => m.v.vbterror ("Move", ec) END END Move; PROCEDURE WalkIntervals (m: T; p: TextPortClass.IProc) RAISES {VTDef.Error} = BEGIN p (m.selection [Primary].interval); p (m.selection [Secondary].interval) END WalkIntervals; VAR selectionModes: ARRAY [VBT.Modifier.MouseL .. VBT.Modifier.MouseR], [0 .. 4] OF VText.SelectionMode; BEGIN selectionModes [VBT.Modifier.MouseL, 0] := VText.SelectionMode.CharSelection; selectionModes [VBT.Modifier.MouseL, 1] := VText.SelectionMode.CharSelection; selectionModes [VBT.Modifier.MouseL, 2] := VText.SelectionMode.LineSelection; selectionModes [VBT.Modifier.MouseL, 3] := VText.SelectionMode.LineSelection; selectionModes [VBT.Modifier.MouseL, 4] := VText.SelectionMode.AllSelection; selectionModes [VBT.Modifier.MouseM, 0] := VText.SelectionMode.WordSelection; selectionModes [VBT.Modifier.MouseM, 1] := VText.SelectionMode.WordSelection; selectionModes [VBT.Modifier.MouseM, 2] := VText.SelectionMode.ParagraphSelection; selectionModes [VBT.Modifier.MouseM, 3] := VText.SelectionMode.ParagraphSelection; selectionModes [VBT.Modifier.MouseM, 4] := VText.SelectionMode.AllSelection; END IvyModel.