(* Copyright (C) 1992, Digital Equipment Corporation *) (* All rights reserved. *) (* See the file COPYRIGHT for a full description. *) (* *) (* Last modified on Wed Dec 30 17:29:57 PST 1992 by meehan *) <* PRAGMA LL *> MODULE XtermModel; IMPORT PaintOp, Rd, Text, TextPort, TextPortClass, Thread, VBT, VTDef, VText; REVEAL T = TextPortClass.Model BRANDED OBJECT selection: TextPortClass.SelectionRecord METHODS highlight (left, right: CARDINAL) := Highlight OVERRIDES controlChord := ControlChord; copy := Copy; getSelection := GetSelection; hasVBTselection := HasVBTselection; init := Init; misc := Misc; mouse := Mouse; optionChord := OptionChord; paste := Paste; position := Position; putSelectedText := PutSelectedText; read := Read; select := Select; takeSelection := TakeSelection; walkIntervals := WalkIntervals; write := Write; END; CONST Primary = TextPort.SelectionType.Primary; PROCEDURE Init (m: T; colorScheme: PaintOp.ColorScheme): TextPortClass.Model = BEGIN TRY m.selection.interval := VText.CreateInterval ( vtext := m.v.vtext, indexL := 0, indexR := 0, options := VText.MakeIntervalOptions ( style := VText.IntervalStyle.InverseStyle, 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) = BEGIN CASE ch OF | ' ' => (* Just normalize. *) | 'Z' => TextPortClass.Redo (m.v) | 'u' => WITH ext = TextPortClass.DeleteToStartOfLine (m.v) DO IF ext # TextPort.NotFound THEN m.seek (ext.l) END END | 'z' => TextPortClass.Undo (m.v) ELSE (* Don't normalize if unknown chord, including just ctrl itself. *) m.v.ULdefaultAction (cd) END END ControlChord; PROCEDURE OptionChord (m: T; ch: CHAR; READONLY cd: VBT.KeyRec) = BEGIN CASE ch OF | 'c' => m.copy (cd.time) | 'v' => m.paste (cd.time) | 'x' => m.cut (cd.time) ELSE (* Don't normalize if unknown chord, including just option itself. *) m.v.ULdefaultAction (cd) END END OptionChord; PROCEDURE Mouse (m: T; READONLY cd: VBT.MouseRec) = CONST MODS = VBT.Modifiers {VBT.Modifier.Control, VBT.Modifier.Option}; EMPTY = VBT.Modifiers {}; VAR r: TextPortClass.IRange; BEGIN m.dragging := FALSE; CASE cd.clickType OF | VBT.ClickType.LastUp => IF cd.whatChanged = VBT.Modifier.MouseM THEN m.paste (cd.time) END | VBT.ClickType.FirstDown => IF cd.modifiers * MODS # EMPTY THEN RETURN END; IF NOT m.v.getKFocus (cd.time) THEN RETURN END; WITH rec = m.selection, interval = rec.interval DO CASE cd.whatChanged OF | VBT.Modifier.MouseL => rec.mode := selectionModes [cd.whatChanged, cd.clickCount DIV 2 MOD 3]; r := TextPortClass.GetRange (m.v, cd.cp, rec.mode); m.fixed := r.middle; IF rec.mode = VText.SelectionMode.CharSelection THEN (* Single left-click => Move only the typein point. Don't highlight, since the highlighted interval IS the Source selection, and we don't want to change that. *) m.seek (r.middle); m.dragging := TRUE ELSIF m.takeSelection (Primary, cd.time) THEN m.highlight (r.left, r.right); m.dragging := TRUE END | VBT.Modifier.MouseR => IF NOT m.takeSelection (Primary, cd.time) THEN RETURN END; r := TextPortClass.GetRange (m.v, cd.cp, rec.mode); (* extend: move one end, keeping the other fixed *) IF r.left < interval.left () THEN m.fixed := MAX (r.right, interval.right ()); m.highlight (r.left, m.fixed) ELSE m.fixed := MIN (r.left, interval.left ()); m.highlight (m.fixed, r.right) END; m.dragging := TRUE ELSE END (* CASE cd.whatChanged *) END (* WITH rec *) ELSE END (* CASE cd.clickType *) END Mouse; 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 rec := m.selection; mode := rec.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; IF NOT m.takeSelection (Primary, cd.time) THEN (* skip *) ELSIF r.left < m.fixed THEN m.highlight (r.left, m.fixed); m.seek (r.left) ELSE m.highlight (m.fixed, r.right); m.seek (r.right) END END END END Position; PROCEDURE Highlight (m: T; left, right: CARDINAL) = CONST name = "Highlight"; BEGIN TRY VText.MoveInterval (m.selection.interval, left, right); VText.SwitchInterval (m.selection.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; (*********************** Reading ****************************) PROCEDURE Read (m: T; READONLY s: VBT.Selection; time: VBT.TimeStamp): TEXT RAISES {VBT.Error} = BEGIN IF s = VBT.Source AND m.selection.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) = VAR interval := m.selection.interval; left := interval.left (); BEGIN IF sel = Primary AND m.v.replace (left, interval.right (), t) # TextPort.NotFound THEN (* NB: Replace changes interval! *) m.highlight (left, left + Text.Length (t)) END END PutSelectedText; PROCEDURE Write (m: T; READONLY s: VBT.Selection; time: VBT.TimeStamp; t: TEXT) RAISES {VBT.Error} = BEGIN IF s = VBT.Source AND m.selection.owned THEN IF m.selection.interval.left () >= m.v.typeinStart THEN m.putSelectedText (t, Primary) ELSE RAISE VBT.Error (VBT.ErrorCode.Unwritable) END 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 = VBT.Lost THEN IF cd.selection = VBT.KBFocus AND m.v.hasFocus THEN m.v.hasFocus := FALSE; VText.SwitchCaret (m.v.vtext, VText.OnOffState.Off); m.v.ULfocus (FALSE, cd.time) ELSIF cd.selection = VBT.Source AND m.selection.owned THEN VText.SwitchInterval (m.selection.interval, VText.OnOffState.Off); m.selection.owned := FALSE END ELSIF cd.type = VBT.TakeSelection AND cd.selection = VBT.KBFocus THEN EVAL m.v.getKFocus (cd.time) END; 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 Misc; PROCEDURE Select ( m : T; time : VBT.TimeStamp; begin, end : CARDINAL; sel := Primary; <* UNUSED *> replaceMode: BOOLEAN; caretEnd := VText.WhichEnd.Right) = BEGIN IF sel = Primary AND m.takeSelection (sel, time) THEN m.highlight (begin, end); IF caretEnd = VText.WhichEnd.Right THEN m.seek (end) ELSE m.seek (begin) END END END Select; PROCEDURE GetSelection (m: T; sel: TextPort.SelectionType): TextPort.Extent = BEGIN IF sel = Primary THEN WITH interval = m.selection.interval DO RETURN TextPort.Extent {interval.left (), interval.right ()} END ELSE RETURN TextPort.NotFound END END GetSelection; PROCEDURE HasVBTselection (m: T; sel: TextPort.SelectionType): BOOLEAN = BEGIN RETURN sel = Primary AND m.selection.owned END HasVBTselection; PROCEDURE TakeSelection (m : T; sel : TextPort.SelectionType; time : VBT.TimeStamp; highlight := FALSE): BOOLEAN = CONST name = "TakeSelection"; BEGIN IF sel # Primary THEN RETURN FALSE END; WITH rec = m.selection, i = rec.interval DO IF NOT rec.owned THEN TRY VBT.Acquire (m.v, VBT.Source, time); IF 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 (i.left (), i.right ()) END; RETURN rec.owned END END TakeSelection; PROCEDURE Copy (m: T; time: VBT.TimeStamp) = (* This is almost a no-op. The selection *IS* the highlighted region.*) BEGIN IF m.selection.interval.left () # m.selection.interval.right () THEN EVAL m.takeSelection (Primary, time) END END Copy; PROCEDURE Paste (m: T; time: VBT.TimeStamp) = BEGIN TRY WITH t = m.read (VBT.Source, time), p = m.v.index (), len = Text.Length (t) DO IF len # 0 AND m.v.replace (p, p, t) # TextPort.NotFound THEN m.select (time, p, p + len) END END EXCEPT | VBT.Error (ec) => m.v.vbterror ("Paste", ec) END END Paste; PROCEDURE WalkIntervals (m: T; p: TextPortClass.IProc) RAISES {VTDef.Error} = BEGIN p (m.selection.interval); END WalkIntervals; VAR selectionModes: ARRAY [VBT.Modifier.MouseL .. VBT.Modifier.MouseR], [0 .. 2] OF VText.SelectionMode; BEGIN selectionModes [VBT.Modifier.MouseL, 0] := VText.SelectionMode.CharSelection; selectionModes [VBT.Modifier.MouseL, 1] := VText.SelectionMode.WordSelection; selectionModes [VBT.Modifier.MouseL, 2] := VText.SelectionMode.LineSelection; selectionModes [VBT.Modifier.MouseR, 0] := VText.SelectionMode.CharSelection; selectionModes [VBT.Modifier.MouseR, 1] := VText.SelectionMode.WordSelection; selectionModes [VBT.Modifier.MouseR, 2] := VText.SelectionMode.LineSelection; END XtermModel.