(* Copyright (C) 1992, Digital Equipment Corporation *) (* All rights reserved. *) (* See the file COPYRIGHT for a full description. *) (* *) (* Last modified on Tue Jan 5 22:46:42 PST 1993 by meehan *) <* PRAGMA LL *> MODULE MacModel; IMPORT KeyboardKey, PaintOp, Rd, Text, TextPort, TextPortClass, Thread, VBT, VTDef, VText; (* The "anchor point" is implemented by "m.fixed". The "active end" and the "insertion point" are implemented as "m.v.index()", and set with "m.seek(...)". *) REVEAL T = TextPortClass.Model BRANDED OBJECT selection: TextPortClass.SelectionRecord; selectionP := FALSE; (* Is there a (non-empty) selection? *) clipboard := ""; (* Source selection *) screen: VBT.ScreenID; METHODS highlight (left, right: CARDINAL) := Highlight OVERRIDES arrowKey := ArrowKey; controlChord := ControlChord; copy := Copy; getSelection := GetSelection; hasVBTselection := HasVBTselection; init := Init; isReplaceMode := IsReplaceMode; 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 | 'c' => m.copy (cd.time) | 'v' => m.paste (cd.time) | 'x' => m.cut (cd.time) | 'Z' => TextPortClass.Redo (m.v) | '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) = VAR r: TextPortClass.IRange; BEGIN IF cd.whatChanged # VBT.Modifier.MouseL THEN RETURN END; IF NOT m.v.getKFocus (cd.time) THEN RETURN END; m.screen := cd.cp.screen; CASE cd.clickType OF | VBT.ClickType.FirstDown => WITH rec = m.selection, interval = rec.interval DO IF cd.modifiers = VBT.Modifiers {} THEN IF cd.clickCount < 2 THEN (* single-click *) rec.mode := VText.SelectionMode.CharSelection; r := TextPortClass.GetRange (m.v, cd.cp, rec.mode); m.seek (r.middle); m.fixed := r.middle; CancelHighlight (m) ELSE (* double-click *) rec.mode := VText.SelectionMode.WordSelection; r := TextPortClass.GetRange (m.v, cd.cp, rec.mode); m.seek (r.right); m.fixed := r.left; m.highlight (r.left, r.right) END; m.dragging := TRUE ELSIF VBT.Modifier.Shift IN cd.modifiers THEN (* shift-click => extend selection *) IF NOT m.selectionP THEN RETURN END; r := TextPortClass.GetRange (m.v, cd.cp, rec.mode); IF r.left < interval.left () THEN m.seek (r.left); m.fixed := MAX (r.right, interval.right ()); m.highlight (r.left, m.fixed) ELSE m.seek (r.right); 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 m.dragging := FALSE 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 r.left < m.fixed THEN m.seek (r.left); m.highlight (r.left, m.fixed) ELSE m.seek (r.right); m.highlight (m.fixed, 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); m.selectionP := TRUE; 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.clipboard ELSE RETURN TextPortClass.Model.read (m, s, time) END END Read; (*********************** Writing ****************************) PROCEDURE PutSelectedText (m: T; t: TEXT; sel: TextPort.SelectionType) = BEGIN IF sel = Primary AND m.selectionP THEN WITH interval = m.selection.interval, left = interval.left (), right = interval.right () DO EVAL m.v.replace (left, right, t); IF Text.Empty (t) THEN CancelHighlight (m) ELSE m.highlight (left, left + Text.Length (t)) END END 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 m.clipboard := t 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); IF m.selectionP THEN VText.SwitchInterval (m.selection.interval, VText.OnOffState.Off) END; 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); IF m.selectionP THEN VText.SwitchInterval (m.selection.interval, VText.OnOffState.On) END 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 AND m.selectionP 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) = VAR t := m.getSelectedText (Primary); BEGIN IF NOT Text.Empty (t) AND m.selectionP AND m.takeSelection (Primary, time) THEN m.clipboard := t END END Copy; PROCEDURE Paste (m: T; time: VBT.TimeStamp) = BEGIN IF NOT m.v.readOnly THEN TextPortClass.Model.paste (m, time); CancelHighlight (m) END END Paste; (* PROCEDURE Insert (m: T; t: TEXT) = VAR len := Text.Length (t); BEGIN IF m.selectionP THEN VAR i := m.selection.interval; left := i.left (); BEGIN EVAL m.v.replace (left, i.right (), t); CancelHighlight (m); m.seek (left + len) END ELSIF len > 0 THEN VAR p := m.v.index (); BEGIN EVAL m.v.replace (p, p, t) (* m.seek (p + len) *) END END; END Insert; *) PROCEDURE CancelHighlight (m: T) = BEGIN TRY VText.SwitchInterval (m.selection.interval, VText.OnOffState.Off); EXCEPT | VTDef.Error (ec) => m.v.vterror ("CancelHighlight", ec) END; m.selectionP := FALSE END CancelHighlight; PROCEDURE IsReplaceMode (m: T): BOOLEAN = BEGIN RETURN m.selectionP (* If there's a selection, it's in replace-mode. *) END IsReplaceMode; PROCEDURE ArrowKey (m: T; ch: TextPortClass.Arrow; READONLY cd: VBT.KeyRec) = BEGIN IF NOT VBT.Modifier.Shift IN cd.modifiers THEN CancelHighlight (m); TextPortClass.Model.arrowKey (m, ch, cd) ELSE CONST name = "Arrow Key"; TYPE End = {Left, Right}; VAR v := m.v; here := v.index (); oldr, newr: TextPortClass.IRange; PROCEDURE extentToIRange (READONLY x: TextPort.Extent; end: End): TextPortClass.IRange = BEGIN IF end = End.Left THEN RETURN TextPortClass.IRange {x.l, x.l, x.r} ELSE RETURN TextPortClass.IRange {x.l, x.r, x.r} END END extentToIRange; PROCEDURE getIRange (): TextPortClass.IRange RAISES {Rd.EndOfFile, Rd.Failure, Thread.Alerted, VTDef.Error} = VAR cp: VBT.CursorPosition; BEGIN (* We need to synthesize a CursorPosition from the new location, so that we can pass it to GetRange. *) cp.screen := m.screen; cp.gone := FALSE; cp.offScreen := FALSE; VText.Locate (m.v.vtext, 0, m.v.index (), cp.pt.h, cp.pt.v); RETURN TextPortClass.GetRange ( m.v, cp, VText.SelectionMode.WordSelection) END getIRange; BEGIN TRY IF m.selectionP THEN WITH ext = m.getSelection (Primary) DO oldr := extentToIRange (ext, VAL (ORD (here = ext.r), End)) END ELSE oldr := TextPortClass.IRange {here, here, here} END; IF VBT.Modifier.Option IN cd.modifiers THEN m.selection.mode := VText.SelectionMode.WordSelection; CASE ch OF | KeyboardKey.Left => newr := extentToIRange (TextPortClass.FindPrevWord (m.v), End.Left) | KeyboardKey.Right => newr := extentToIRange (TextPortClass.FindNextWord (m.v), End.Right) | KeyboardKey.Up => TextPortClass.UpOneLine (m.v); newr := getIRange () | KeyboardKey.Down => TextPortClass.DownOneLine (m.v); newr := getIRange () END ELSE m.selection.mode := VText.SelectionMode.CharSelection; TextPortClass.Model.arrowKey (m, ch, cd); here := v.index (); newr := TextPortClass.IRange {here, here, here} END; IF newr.left < oldr.left THEN m.seek (newr.left); m.fixed := MAX (newr.right, oldr.right); m.highlight (newr.left, m.fixed) ELSE m.seek (newr.right); m.fixed := MIN (newr.left, oldr.left); m.highlight (m.fixed, newr.right) END 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 END END ArrowKey; PROCEDURE WalkIntervals (m: T; p: TextPortClass.IProc) RAISES {VTDef.Error} = BEGIN p (m.selection.interval); END WalkIntervals; BEGIN END MacModel.