(* Copyright (C) 1989-1992, Digital Equipment Corporation *) (* All rights reserved. *) (* See the file COPYRIGHT for a full description. *) (* *) (* Last modified on Sat Jan 2 23:49:24 PST 1993 by meehan *) (* modified on Mon Jun 15 22:38:47 1992 by mhb *) (* modified on Fri Mar 20 22:34:09 1992 by steveg *) (* modified on Fri Feb 8 15:36:58 PST 1991 by brooks *) (* modified on Wed May 17 17:06:31 PDT 1989 by gidi *) <* PRAGMA LL *> (* A "TextPort" is a "VBT" that allows the user to type and edit single- or multi-line text. The "TextPortClass" interface defines ``models'' for keybindings and text-selections; the "TextPort" interface itself supports four models: Ivy, Emacs, Xterm, and Mac. The default depends on the environment variable "TextPortModel", which defaults to "ivy". See the "IvyModel", "EmacsModel", "XtermModel", and "MacModel" interfaces in Appendices \ref{IvyModel}--\ref{XtermModel} for details on keybindings, mouse clicks, and selections. "TextPort" uses the terms {\it primary} and {\it secondary} for selections; the model may support only one, and it defines the correspondence between the selection-name (e.g., "primary") with Trestle selections (e.g., "VBT.Target"). The locking level for all procedures is "LL <= VBT.mu" except as noted. *) INTERFACE TextPort; IMPORT Font, PaintOp, ScrollerVBT, VBT, VText; TYPE Points = REAL; Model = {Default, Ivy, Emacs, Mac, Xterm}; SpecificModel = [Model.Ivy .. Model.Xterm]; T <: Public; Public = VBT.Leaf OBJECT METHODS init (singleLine := FALSE; hMargin, vMargin: Points := 1.5; font := Font.BuiltIn; colorScheme : PaintOp.ColorScheme := NIL; expandOnDemand := FALSE; wrap := TRUE; readOnly := FALSE; turnMargin : Points := 2.0; model := Model.Default ): T; <* LL = VBT.mu *> getFont (): Font.T; setFont (font: Font.T); getColorScheme (): PaintOp.ColorScheme; setColorScheme (c: PaintOp.ColorScheme); getModel (): SpecificModel; setModel (model: Model); getReadOnly (): BOOLEAN; setReadOnly (flag: BOOLEAN); scrollUpdate (); (* callbacks *) returnAction (READONLY cd: VBT.KeyRec); tabAction (READONLY cd: VBT.KeyRec); defaultAction (READONLY cd: VBT.KeyRec); focus (gaining: BOOLEAN; time: VBT.TimeStamp); filter (VAR cd: VBT.KeyRec); modified (); error (msg: TEXT); END; (* The call "v.init(...)" initializes "v" as a "TextPort" and returns it. If "singleLine" is "TRUE", then the "TextPort"'s height is set to the height of the tallest character in the current font, and it does not expand vertically. Its default width is 30 times the width of the widest character in the current font. This is intended for ``type-in'' boxes. The parameters "hMargin" and "vMargin" indicate how much whitespace to leave around the text. If "colorScheme" is "NIL", then "PaintOp.bgFg" will be used. If "expandOnDemand" is "TRUE", then multi-line and scrollable "TextPort"s will expand (and contract) vertically as the text requires, so that the entire text is visible in the window. This parameter is ignored for single-line or nonscrollable "TextPort"s. If "wrap" is "TRUE" and "singleLine" is "FALSE", then text will be wrapped across line boundaries; otherwise it will be clipped. If it is wrapped, then "turnMargin" specifies the width of the gray bar placed at the end of the first line and the beginning of the second, indicating that the text has been wrapped. If "readOnly" is "TRUE", then the text cannot be changed through the user interface (keyboard and mouse). The procedures "Replace", "Insert", "SetText", and "PutText" bypass the read-only protection, but these are not called by internal routines. In all other descriptions in this interface, the words ``replace'', ``insert'', ``delete'', and so on should be understood as having the restriction that "v" is not read-only. If "model" is "Model.Default", then "init" will use the current value of "DefaultModel", defined below. "v.getModel()" returns the name of the current model; note that the return value cannot be "Model.Default". The call "v.setModel(...)" changes the current model; its parameter may be "Model.Default", in which case the value of "DefaultModel" will be used. The implementation calls "v.scrollUpdate" whenever "v"'s state has changed in a way that might change the associated scrollbar, if any. The default method calls "ScrollerVBT.Update" with the "TextPort"'s scrollbar, if it has one. The call "v.setFont(font)" changes the font used for displaying the text. The call "v.setColorScheme(c)" changes the colors used for displaying the text. The implementation calls "v.focus(gaining, ...)" whenever "v" gains or loses the keyboard focus. If "gaining" is "TRUE", then "v" is about to gain the keyboard focus; i.e., this method is called {\em before} the selection feedback is established, so it is reasonable to call "Select" (below) or put up some other indication. If "gaining" is "FALSE", then "v" has just lost the keyboard focus, so it reasonable to take down whatever indicated that the focus had been acquired. It is not within the power of the "focus" method to prevent "v" from gaining or losing the focus. The default for this method is a no-op. The implementation calls "v.error(msg)" whenever an exception is raised for which there is no particular remedy, such as an "Rd.Failure". The value of "msg" will be a short description of the error, typically the name of the procedure where the exception was raised. No method or procedure defined in this interface raises exceptions, but the client may wish to report the error or take some other action, such as beeping or flashing the screen. The default for this method is a procedure that tests whether the environment-variable named "TextPortDebug" is set (to any value); if so, it writes the message to "SmallIO.stderr". \subsubsection {Keybindings} The "TextPort" interface allows clients a great deal of flexibility in handling keystrokes. "TextPort.T.key(v, cd)" proceeds in four steps. In step 1, it tests whether "cd.wentDown" is true, whether "v" has the keyboard focus, and whether "v"'s domain is non-empty. If all three conditions are true, it proceeds to step 2. In step 2, it calls "v.filter(cd)", passing "cd" as a "VAR" parameter. The default method for "filter" is a no-op, but clients may override this. In addition to calling other procedures in the "TextPort" interface, a "filter" method may: \begin{itemize} \item{leave "cd" untouched,} \item{translate it into some other keystroke, by changing "cd.whatChanged" and/or "cd.modifiers", or} \item{suppress it entirely by setting "cd.whatChanged" to "VBT.NoKey".} \end{itemize} If the client's "filter" method handles a key directly, by taking some action, then it should usually set "cd.whatChanged" to "VBT.NoKey", which prevents further processing of the key. In step 3, we call the model's filter, which follows the same conventions as the "TextPort"'s filter. (The EmacsModel, for example, has a filter that implements quoted-insert.) In step 4, there are several mutually exclusive possibilities, tested in this order: \begin{itemize} \item{If the key is Return, then if the "shift" bit is on, we insert a newline; if the "option" bit is on, we insert a newline but leave the cursor in place; otherwise, we invoke "v.returnAction(cd)", another callback method. Its default method calls "v.defaultAction(cd)" if this is a ``single-line'' "TextPort"; otherwise it calls "NewlineAndIndent(v, cd)".} \item{If the key is Tab, we invoke "v.tabAction(cd)". This is a callback that the client can override. The default method inserts 4 spaces.} \item{If the "control" bit is on (i.e., if | VBT.Modifier.Control IN cd.modifiers we call the model's "controlChord" method.} \item{If the "option" bit is on, we call the model's "optionChord" method.} \item{If the key is an ``arrow'' key, we call the model's "arrowKey" method.} \item{If the key is Backspace or Delete, we delete the previous character.} \item{If it's a graphic character, we insert it into the text.} \item{Otherwise, we invoke "v.defaultAction(cd)", a callback. Its default is a no-op.} \end{itemize} Finally, we call "Normalize(v)", except in the "controlChord", "optionChord", and "defaultAction" cases. Clients can specialize the handling of keys, therefore, by overriding the "TextPort"'s "key", "filter", "returnAction", "tabAction", or "defaultAction" methods, and by overriding the model's "controlChord", "optionChord", or "arrowKey" methods. \subsubsection{Other state information} A "TextPort" maintains some internal information that is accessed indirectly: \begin{itemize} \item a ``modified'' flag. Any operation that changes the text sets this flag to "TRUE". If it was previously "FALSE", then the implementation calls "v.modified()", {\bf after} the change has already happened to "v". The default is a no-op. The "SetModified" and "IsModified" procedures set and test this flag, respectively. \item a scrollbar (optional). See the "TextEditVBT" interface in Section~\ref{TextEditVBTSection}. \end{itemize} \subsection {Interface routines} *) VAR DefaultModel: SpecificModel; (* The initial value of "DefaultModel" is determined by the environment-variable named "TextPortModel"; if that is set to "ivy", "emacs", "mac", or "xterm", then "DefaultModel" will be set accordingly. If it is not defined, or is defined as some other value, then the initial value of "DefaultModel" will be "Model.Ivy". *) PROCEDURE ChangeAllTextPorts (newModel := Model.Default); (* For each "TextPort" "p", call "p.setModel(newModel)". (The implementation maintains a list of weak references to all initialized "TextPort"s.) The principal client is "FormsVBT"; see the "MForm" interface. *) TYPE Scroller <: ScrollerVBT.T; (* A scrollbar that is to be used with a "TextPort" must be one of these. *) PROCEDURE SetScrollBar (v: T; scrollBar: Scroller); (* Set the scrollbar associated with "v". *) PROCEDURE MarkAndUpdate (v: T); (* Call "VBT.Mark(v)" and then update the scrollbar. This is like "Normalize", except that no scrolling is actually performed. *) PROCEDURE SetWrap (v: T; wrap: BOOLEAN); (* Set the ``wrap'' property of "v", and redisplay "v". *) PROCEDURE Length (v: T): CARDINAL; (* Return the number of characters in "v"'s text. *) PROCEDURE Width (v: T): CARDINAL; (* Return the number of characters that will fit on a line, given the current size of "v". If "v" has no width (because its window is iconic, for example), return 0. If "v"'s font is not fixed-pitch, the result will be computed in terms of the width of the widest character in the font. *) PROCEDURE Height (v: T): CARDINAL; (* Equivalent to "v.shape(Axis.T.Ver, 0)". *) PROCEDURE ShapeInfo (v: T; VAR lineCount, lineLength: INTEGER); (* Return the number of lines in the text and the number of characters in the longest line (excluding the newline). A client may use this information in conjunction with "VBTShape.Set" to shape "v" to fit its text. Having done so, it should be prepared to do it again on a rescreen to a screen of different density. *) PROCEDURE Index (v: T): CARDINAL; (* Return the current ``type-in'' position. *) PROCEDURE Seek (v: T; n: CARDINAL); (* Set the ``type-in'' position. *) PROCEDURE Newline (v: T); (* Insert a newline character at the type-in point. *) PROCEDURE NewlineAndIndent (v: T); (* Insert a newline character and enough spaces to match the indentation of the previous line. As it leaves a blank line, it will delete all spaces from that line so as to leave it truly empty. *) (* \subsubsection{Editing commands} *) PROCEDURE GetText (v : T; begin: CARDINAL := 0; end : CARDINAL := LAST (CARDINAL)): TEXT; <* LL = VBT.mu *> (* Returns a sub-sequence of the text in "v". The result will be empty if "begin >= Length(v)"; otherwise the range of indexes of the subsequence is | [begin .. MIN (end, Length (v)) - 1] *) (* Note that the following four procedures do not test the "readOnly" status of "v". *) PROCEDURE SetText (v: T; t: TEXT); <* LL <= VBT.mu *> (* Replace the current contents of "v" with "t". *) PROCEDURE PutText (v: T; t: TEXT); <* LL <= VBT.mu *> (* Append "t" to the current contents of "v". *) PROCEDURE Replace (v: T; begin, end: CARDINAL; newText: TEXT); (* Replace the text between positions "begin" and "end" in "v" with "newText". If "begin" and "end" are beyond the end of the text, they are taken to refer to the end of the text. *) PROCEDURE Insert (v: T; text: TEXT); (* If there is a replace-mode selection (see Section~\ref{ReplaceMode}, page~\pageref{ReplaceMode}), replace it with "text"; otherwise insert "text" at the type-in point. In either case, this is a no-op if "text" is the empty string. *) (* \subsubsection{Selection} *) (* "TextPort" defines two text-selections, referred to as {\it primary} and {\it secondary}. The "TextPort"'s model determines whether these types are supported, and if so, how they correspond to "VBT" selections. In the Ivy model, for example, primary and secondary correspond to "VBT.Target" and "VBT.Source", respectively. User actions that establish the primary selection cause "VBT.Target" to be acquired; likewise for the secondary selection and "VBT.Source". In the Emacs, Mac, and Xterm models, only primary selection is supported, and it corresponds to "VBT.Source". Operations involving secondary selections are effectively no-ops. *) TYPE SelectionType = {Primary, Secondary}; (* A selection is represented by a pair of indexes ("begin" and "end") into the text. A selection may or may not correspond to a "VBT.Selection"; that is determined by the model. The current selection-indices can be retrieved via the "GetSelection" procedure. *) PROCEDURE Select (v : T; time : VBT.TimeStamp; begin, end: CARDINAL; sel := SelectionType.Primary; replaceMode := FALSE; caretEnd := VText.WhichEnd.Right ); (* Make a selection in "v", at event-time "time". If "begin" and/or "end" are beyond the end of the text, they will be clipped to the end of the text. Acquire ownership of the corresponding "VBT.Selection", and, in case of "SelectionType.Primary", of the keyboard focus as well. The values "replaceMode" and "caretEnd" are relevant only if "sel" is "SelectionType.Primary". If "replaceMode" is "TRUE", then "Insert" and "VBT.Write" will {\em replace} the selected text; otherwise, they cause the new text to be {\em inserted} at whichever end of the primary selection is specified by "caretEnd". *) PROCEDURE IsReplaceMode (v: T): BOOLEAN; (* Return "TRUE" if the primary selection is in ``replace mode''. *) PROCEDURE HasSelection (v: T; sel := SelectionType.Primary): BOOLEAN; <* LL.sup = VBT.mu *> (* Return "TRUE" if "v" currently owns the "VBT.Selection" corresponding to "sel". *) TYPE Extent = RECORD l, r: CARDINAL END; CONST NotFound = Extent {LAST (CARDINAL), LAST (CARDINAL)}; PROCEDURE GetSelection (v: T; sel := SelectionType.Primary): Extent; (* Return the extent of the most recent selection in "v". If there is no such selection, return "NotFound". *) PROCEDURE GetSelectedText (v: T; sel := SelectionType.Primary): TEXT; <* LL.sup = VBT.mu *> (* Return the text of the most recent selection in "v" if there is one, or the empty string otherwise. *) PROCEDURE PutSelectedText (v : T; t : TEXT; sel := SelectionType.Primary); <* LL.sup = VBT.mu *> (* Replace the text of the most recent selection in "v", if there is one, with "t". If there is no such selection, this is a no-op. *) PROCEDURE TryFocus (v: T; t: VBT.TimeStamp): BOOLEAN; (* Try to acquire the keyboard focus and the primary selection, and report whether it succeeded. *) PROCEDURE HasFocus (v: T): BOOLEAN; <* LL.sup = VBT.mu *> (* Test whether "v" has the keyboard focus. *) PROCEDURE Normalize (v: T; to := -1); (* Scroll "v" if necessary to ensure that position "to" is visible. If "to < 0", it refers to the current type-in point. If "to" is larger than the length of the text, normalizes to the end of the text. *) PROCEDURE IsVisible (v: T; pos: CARDINAL): BOOLEAN; (* Test whether character position "pos" is visible. *) PROCEDURE IsModified (v: T): BOOLEAN; (* Return the value of the ``modified'' flag for "v". Any change to the text will cause the flag to be set to "TRUE". *) PROCEDURE SetModified (v: T; value: BOOLEAN); (* Set the value of the ``modified'' flag for "v". This will not invoke "v.modified", even if "value" is "TRUE". *) PROCEDURE GetVText (v: T): VText.T; (* For wizards only: extract the underlying "vtext". It is legal to create and manipulate highlighting intervals on it. It is legal to run readers on it, provided you can be sure that you are locking out concurrent change (for example, by holding "VBT.mu"). It is not legal to modify it directly. It is not legal to scroll it directly either, because that will leave the scrollbar incorrect. *) END TextPort.