SilUserInputImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Tracy Larrabee, April 23, 1984 5:43:05 pm PST
Last Edited by: Ken Pier, May 7, 1986 10:14:59 am PDT
This module implements procedures needed for User Input retreival and inventory: things that handle input from Viewers (including placement of the input focus), deal with TIPTables, query and append to the Sil UserAction queue, and grid the cursor.
The way that user input works: Actions by the user will cause one or more User Input Records (UInputRec - defined below) to be placed on the User Input Queue (UInput - defined below). The display module will take the records off, whenever it has an opportunity to do so, and process them.
DIRECTORY
Ascii
USING
[BS, ControlA, ControlW, ControlQ, ControlS, CR, DEL, ESC],
BiScrollers USING [BiScroller, ClientCoords, ClientDataOf, QuaBiScroller, QuaViewer],
ImagerTransformation
USING
[Transform],
InputFocus
USING
[SetInputFocus],
Interminal
USING
[GetCursorOffset, SetCursorOffset],
MessageWindow
USING
[Append],
Real USING [RoundI],
Terminal
USING
[Current, Position, SetBWCursorPosition, SetColorCursorPosition, Virtual],
TIPUser
USING
[TIPTable, TIPScreenCoords],
ViewerClasses
USING
[ModifyProc, NotifyProc, Viewer],
ViewerOps
USING
[UserToScreenCoords],
Cursors
USING
[SetCursor, CursorType],
SilUserInput,
SilKernel,
SilFile
;
SilUserInputImpl:
CEDAR
MONITOR
LOCKS uiData
USING uiData: SilUIData
IMPORTS BiScrollers, MessageWindow, ImagerTransformation, InputFocus, Real, ViewerOps, Interminal, Terminal, Cursors
EXPORTS SilUserInput, SilKernel = BEGIN
ROPE: TYPE = Rope.ROPE;
SilData: TYPE = SilKernel.SilData;
UInput: TYPE = SilUserInput.UInput;
UInputRec: TYPE = SilUserInput.UInputRec;
TIPScreenCoords: TYPE = TIPUser.TIPScreenCoords;
This is the concrete representation of an opaque type defined in SilKernel. The SilUIDataRec contains all the state that the User Input module needs to operate (the input queue, the cursor gridding details, the current mode).
SilUIData: TYPE = REF SilUIDataRec;
SilUIDataRec:
PUBLIC
TYPE =
MONITORED
RECORD [
Information about the state of this viewer and its input
queue: UInput ← NIL,
gotInput: CONDITION,
innerViewer: ViewerClasses.Viewer, --two viewers to use BiScrollers properly
outerViewer: ViewerClasses.Viewer,
bs: BiScrollers.BiScroller,
gotInputFocus: BOOL ← FALSE,
caretsShouldChangeNow: BOOL ← FALSE,
Information for gridding the cursor:
see GridTheCursor for explanation of the need for each field.
gridSpacing: NAT ← SilUserInput.defaultGridSpacing,
gridding: BOOL ← FALSE, --is cursor gridding in this viewer at this moment?
hotX: INTEGER ← 0,
hotY: INTEGER ← 0,
Transform from Sil coordinates to Client coordinates:
Client Coords = (Sil Coords + offset) * scale
xScale = gridMagnification,
yScale = - gridMagnification,
xOffset, yOffset:
REAL ← 0.0,
Client Coords = Sil Coords * scale + postOffset
xPostOffset, yPostOffset: INTEGER ← 0,
gridMagnification: NAT ← 1,
Information for correctly interpretting control characters:
in text input mode ^W is backup word - else it is change line width.
mode: SilUserInput.InputCharMode ← NotInputingRope,
InputWaitingForArg: UInput ← NIL
];
GLOBAL VARIABLES:
only 1 tip table for all sil instances
silTipTable: TIPUser.TIPTable;
Only 1 virtual terminal for all sil instances
terminal: Terminal.Virtual;
Only1 input focus for all sil instances
SilHasInputFocus:
BOOL;
Only1 sil cursor for all sil instances
silCursorHasBitmap: BOOL ← FALSE; -- set by ControlS
restoreCursor: BOOL ← TRUE; -- set by move or copy commands
savedCursor: Cursors.CursorType ← textPointer; --restored after ControlS, move, copy
InitTipTable:
PUBLIC
PROC [tipTable: TIPUser.TIPTable] = {
Set the tip table so it can be referenced later on. Also set up the virtual terminal
silTipTable ← tipTable;
terminal ← Terminal.Current[];
};
InitUserInput:
PUBLIC
PROC [data: SilData, viewer: ViewerClasses.Viewer] = {
Initialize all the state needed to process user input
uiData: SilUIData ← data.uiData ← NEW[SilUIDataRec];
uiData.bs ← BiScrollers.QuaBiScroller[viewer];
uiData.innerViewer ← BiScrollers.QuaViewer[bs: uiData.bs, inner: TRUE];
uiData.outerViewer ← BiScrollers.QuaViewer[bs: uiData.bs, inner: FALSE];
savedCursor ← uiData.innerViewer.class.cursor;
};
DestroyUserInput:
PUBLIC
ENTRY
PROC [uiData: SilUIData] = {
Perform necessary actions for the destruction of this user input.
QuitGridding[uiData];
uiData.innerViewer ← NIL;
uiData.outerViewer ← NIL;
};
SilNotify:
PUBLIC ViewerClasses.NotifyProc = {
[self: Viewer, input: LIST OF REF ANY]
The notify proc which the viewers package will be asked to call. This process will be placing user input records on the user input queue.
mx, my: INTEGER ← 0; --for saving mouse coordinates.
data: SilData ← NARROW[BiScrollers.QuaBiScroller[self].ClientDataOf[]];
uiData: SilUIData ← data.uiData;
FOR list:
LIST
OF
REF
ANY ← input, list.rest
WHILE list #
NIL
DO
WITH list.first
SELECT
FROM
z: BiScrollers.ClientCoords => {
[mx, my] ← GridTheCursor[self, uiData, z];
IF NOT uiData.gridding THEN RETURN;
};
C: REF CHAR => RouteInputChars[C^, uiData];
A:
ATOM => {
SELECT A FROM
What happens when mouse leaves BiScroller client TIPtable:
$Exit =>
QuitGridding[uiData];
Tracking the mouse for the command line:
$TrackMouse =>
Enque[ [TrackMouse[x: mx, y: my] ], uiData];
Sil MARK buttons (they cause the mouse to track for the command line)
$MoveMark => {
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ESC, uiData];
Enque[ [SetCaret[caret: mark, mode: absolute, x: mx, y: my] ], uiData];
Enque[ [TrackMouse[x: mx, y: my] ], uiData];
};
$MoveStretch => {
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ESC, uiData];
Enque[ [OperateOnSelected[op: IF silCursorHasBitmap THEN moveNoStretch ELSE moveStretch, rel: absolute, x: mx, y: my] ], uiData];
Enque[ [TrackMouse[x: mx, y: my] ], uiData];
};
$MoveNoStretch => {
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ESC, uiData];
Enque[ [OperateOnSelected[op: moveNoStretch, rel: absolute, x: mx, y: my] ], uiData];
Enque[ [TrackMouse[x: mx, y: my] ], uiData];
};
$MoveOrigin => {
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ESC, uiData];
Enque[ [SetCaret[caret: origin, mode: absolute, x: mx, y: my] ], uiData];
Enque[ [TrackMouse[x: mx, y: my] ], uiData];
};
Sil DRAW buttons
$DrawLine => {
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ESC, uiData];
Enque[ [DrawBox[markRel: absolute, originRel: relative, x: mx, y: my] ], uiData];
};
$DeleteObject => {
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ESC, uiData];
Enque[ [SelectWithPos[x: mx, y: my, mode: delete] ], uiData];
};
$Copy => {
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ESC, uiData];
Enque[ [OperateOnSelected[op: copy, rel: absolute, x: mx, y: my] ], uiData];
};
$Undelete => {
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ESC, uiData];
Enque[ [Undelete[] ], uiData];
};
Sil SELECT buttons
$SelectObject => {
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ESC, uiData];
Enque[ [SelectWithPos[x: mx, y: my] ], uiData];
};
$AddSelected => {
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ESC, uiData];
Enque[ [SelectWithPos[x: mx, y: my, mode: add] ], uiData];
};
$SelectArea => {
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ESC, uiData];
Enque[ [SelectWithPos[x: mx, y: my, mode: relative] ], uiData];
};
$DeselectObject => {
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ESC, uiData];
Enque[ [SelectWithPos[x: mx, y: my, mode: remove] ], uiData];
};
Sil Control characters
$DrawBox =>
Enque[ [DrawBox[markRel: absolute, originRel: absolute] ], uiData];
$DrawBackBox =>
Enque[ [DrawBox[markRel: absolute, originRel: absolute, background: TRUE] ], uiData];
$CopySelected =>
Enque[ [OperateOnSelected[op: copy, rel: relative] ], uiData];
$DeleteSelected =>
Enque[ [OperateOnSelected[op: delete] ], uiData];
$CenterOnMark =>
Enque[ [CenterOnMark[] ], uiData];
$ComplimentMagnify =>
Enque[ [Compliment[mode: magnification] ], uiData];
$SetDefault => {
uiData.mode ← waitingFor1CharArg;
uiData.InputWaitingForArg ← LIST[ [SetDefaultAttribs[]] ];
MessageWindow.Append["Attribute which is to be default: ", TRUE];
};
$SetGrid => {
uiData.mode ← waitingFor1CharArg;
uiData.InputWaitingForArg ← LIST[ [SetDetails[detail: gridSize]] ];
MessageWindow.Append["Exponent for new grid size (0-9): ", TRUE];
};
$InputFile =>
Enque[ [InputFile[mode: absolute] ], uiData];
$InputFileRelative =>
Enque[ [InputFile[mode: relative] ], uiData];
$Jam => {
uiData.mode ← waitingFor1CharArg;
uiData.InputWaitingForArg ← LIST[ [ChangeSelected[]] ];
MessageWindow.Append["Attribute by which to change selection: ", TRUE];
};
$Kill =>
Enque[ [KillPicture[] ], uiData];
$DefineMacro => {
uiData.mode ← waitingFor1CharArg;
uiData.InputWaitingForArg ← LIST[ [ManipulateMacro[mode: define]] ];
MessageWindow.Append["Name for new macro: ", TRUE];
};
$ComplimentOneLevel =>
Enque[ [Compliment[mode: oneLevel] ], uiData];
$OutputFile =>
Enque[ [StoreFile[clip: TRUE, large: FALSE] ], uiData];
$OutputFileUnclipped =>
Enque[ [StoreFile[clip: FALSE, large: FALSE] ], uiData];
$PutFile =>
Enque[ [StoreFile[clip: FALSE, large: TRUE] ], uiData];
$DeleteMacros =>
Enque[ [ManipulateMacro[mode: clear] ], uiData];
$ShowTics =>
Enque[ [Compliment[mode: tics] ], uiData];
$ShowFrames =>
Enque[ [Compliment[mode: frames] ], uiData];
$View => {
uiData.mode ← waitingFor1CharArg;
uiData.InputWaitingForArg ← LIST[ [ShowMacros[]] ];
MessageWindow.Append["Font for macro description or attribute for selection: ", TRUE];
};
$ReduceSelection => {
uiData.mode ← waitingFor1CharArg;
uiData.InputWaitingForArg ← LIST[ [SelectForAttrib[mode: reduce]] ];
MessageWindow.Append["Attribute to which to reduce selection: ", TRUE];
};
$MoveRelativeStretch => {
Enque[ [OperateOnSelected[op: IF silCursorHasBitmap THEN moveNoStretch ELSE moveStretch, rel: relative, x: mx, y: my] ], uiData];
};
$MoveRelativeNoStretch => {
Enque[ [OperateOnSelected[op: moveNoStretch, rel: relative, x: mx, y: my] ], uiData];
};
$Ylock =>
Enque[ [Compliment[mode: yInc] ], uiData];
$Hardcopy => {
uiData.mode ← waitingFor1CharArg;
uiData.InputWaitingForArg ← LIST[ [HardCopy[]] ];
MessageWindow.Append["Hardcopy Device: Type ", TRUE];
MessageWindow.Append["R,H,M,U,C for IP file (H for Stinger); P for Press file)", FALSE];
};
$Help => -- HELP FEATURE DISABLED. SIL Help.sil gets desired effect.
data.createHelpWindow ← TRUE;
$ControlA =>
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ControlA, uiData]
ELSE Enque[ [SwapFonts[] ], uiData];
$ControlH =>
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ControlA, uiData]
ELSE {
uiData.mode ← waitingFor1CharArg;
uiData.InputWaitingForArg ← LIST[ [ManipulateMacro[mode: expand]] ];
MessageWindow.Append["Name for macro to be expanded: ", TRUE];
};
$ControlQ =>
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ControlQ, uiData]
ELSE MessageWindow.Append["Use viewer Destroy button to Quit", TRUE];
$ControlW =>
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ControlW, uiData]
ELSE {
uiData.mode ← waitingFor1CharArg;
uiData.InputWaitingForArg ← LIST[ [SetDetails[detail: boxWidth]] ];
MessageWindow.Append["New line width (1-9): ", TRUE];
};
$ControlS =>
IF uiData.mode = InputingRope
THEN
RouteInputChars[Ascii.ControlS, uiData]
ELSE {
Enque[ [SetCursor[caret: origin] ], uiData];
silCursorHasBitmap ← TRUE;
};
$Del =>
RouteInputChars[Ascii.DEL, uiData];
$Escape =>
RouteInputChars[Ascii.ESC, uiData];
$Return =>
SELECT uiData.mode
FROM
InputingRope =>
RouteInputChars[Ascii.CR, uiData]
ENDCASE =>
Enque[ [SetCaret[caret: mark, mode: relative] ], uiData];
$Backspace =>
RouteInputChars[Ascii.BS, uiData];
ENDCASE => NULL;
restoreCursor ←
SELECT
A
FROM
$TrackMouse, $ControlS, $MoveMark, $MoveStretch, $MoveNoStretch, $MoveOrigin, $Copy, $CopySelected, $ShowTics, $MoveRelativeStretch, $MoveRelativeNoStretch => FALSE,
ENDCASE => TRUE;
};
ENDCASE => ERROR;
IF silCursorHasBitmap
AND restoreCursor
THEN
TRUSTED {
Cursors.SetCursor[blank]; -- clear special cursor bits
Cursors.SetCursor[savedCursor]; -- restore normal cursor
Interminal.SetCursorOffset[deltaX: 0, deltaY: 0, enableTracking: FALSE]; -- and disable automatic tracking !!!!
silCursorHasBitmap ← FALSE;
};
ENDLOOP;
};
SilModifyInputFocus:
PUBLIC ViewerClasses.ModifyProc = {
[self: Viewer, change: ModifyAction]
Call me when the user input focus changes.
data: SilData ← NARROW[BiScrollers.QuaBiScroller[self].ClientDataOf[]];
uiData: SilUIData ← data.uiData;
SELECT change
FROM
kill, push => {
uiData.gotInputFocus ← FALSE;
SilHasInputFocus ← FALSE;
};
set, pop => {
uiData.gotInputFocus ← TRUE;
SilHasInputFocus ← TRUE;
};
ENDCASE;
};
GetInputFocus:
PUBLIC
ENTRY PROC[uiData: SilUIData] = {
Make sure that we have the input Focus.
IF NOT uiData.gotInputFocus THEN InputFocus.SetInputFocus[uiData.innerViewer];
};
HasInputFocus:
PUBLIC
PROC[]
RETURNS [hasIt:
BOOL] = {
Do we have the input Focus?
RETURN[SilHasInputFocus];
};
Enque:
PUBLIC
PROC [
UI: UInputRec, uiData: SilUIData] = {
Enque the user input so that it can be processed sequentially with all the other user inputs.
Does a notify on gotInput.
EnqueWithList[LIST[UI], uiData];
};
EnqueWithList:
ENTRY
PROC [
UIL: UInput, uiData: SilUIData] = {
Enque the user input so that it can be processed sequentially with all the other user inputs.
Does a notify on gotInput.
ENABLE
UNWIND =>
NULL;
IF uiData.queue = NIL THEN uiData.queue ← UIL
ELSE
FOR q: UInput ← uiData.queue, q.rest
WHILE q #
NIL
DO
IF q.rest =
NIL
THEN {
q.rest ← UIL;
EXIT;
};
ENDLOOP;
NOTIFY uiData.gotInput;
};
Deque:
PUBLIC
ENTRY
PROC [uiData: SilUIData]
RETURNS [UInputRec] = {
Deque a user input so that it can be processed.
ENABLE
UNWIND =>
NULL;
UI: UInputRec ← uiData.queue.first;
uiData.queue ← uiData.queue.rest;
RETURN [UI];
};
InputAvailable:
PUBLIC
ENTRY
PROC [uiData: SilUIData]
RETURNS [yes:
BOOL] = {
True if there is user input available, else false.
ENABLE
UNWIND =>
NULL;
RETURN[uiData.queue # NIL];
};
SetCaretChange:
PUBLIC
ENTRY
PROC [uiData: SilUIData] = {
Set the information which will tell the display process to blink the caret.
ENABLE
UNWIND =>
NULL;
doesn't really matter whether or not it was already on
uiData.caretsShouldChangeNow ← TRUE;
NOTIFY uiData.gotInput;
};
CaretHasChanged:
PUBLIC
ENTRY
PROC [uiData: SilUIData] = {
Make uiData reflect the fact that the caret has been blinked.
ENABLE
UNWIND =>
NULL;
doesn't really matter whether or not it was already on
uiData.caretsShouldChangeNow ← FALSE;
};
ShouldChangeCaret:
PUBLIC
ENTRY
PROC [uiData: SilUIData]
RETURNS [changeIt:
BOOL] = {
True if the caret should be blinked.
ENABLE
UNWIND =>
NULL;
RETURN[uiData.caretsShouldChangeNow];
};
AwaitUserInput:
PUBLIC
ENTRY
PROC [uiData: SilUIData] = {
Wait until somebody typed something.
ENABLE
UNWIND =>
NULL;
WAIT uiData.gotInput;
};
GridTheCursor:
PROC [innerViewer: ViewerClasses.Viewer, uiData: SilUIData, cc: BiScrollers.ClientCoords]
RETURNS [mx, my:
INTEGER ← 0] =
TRUSTED {
Make the cursor Grid. This is a pretty low level routine. Modification of this routine is potentially hazardous to your peace of mind.
Grid:
PROC [raw, grid:
INTEGER]
RETURNS [gridded:
INTEGER] =
CHECKED {
rem: INTEGER ← raw MOD grid;
IF rem >= grid/2 THEN rem ← rem - grid;
gridded ← raw - rem};
gridSpacing: NAT ← uiData.gridSpacing * uiData.gridMagnification;
viewerPos: Geom2D.Vec;
sx, sy: INTEGER;
mx ← Real.RoundI[cc.x];
my ← Real.RoundI[cc.y];
IF
NOT uiData.gridding
THEN {
1st mouse input for viewer: coords will be viewer relative; save Cursor Offset so can reset when we release buttons.
[uiData.hotX, uiData.hotY, ] ← Interminal.GetCursorOffset[];
uiData.gridding ← TRUE;
Steal input events & keep the cursor from tracking. . .
uiData.bs.style.SetButtonsCapturedness[uiData.bs, TRUE];
InputFocus.CaptureButtons[SilNotify, silTipTable, viewer];
Interminal.SetCursorOffset[0, 0, FALSE];
};
mx ← Grid[mx - uiData.xPostOffset, gridSpacing] + uiData.xPostOffset;
my ← Grid[my - uiData.yPostOffset, gridSpacing] + uiData.yPostOffset;
viewerPos ← uiData.bs.style.GetTransforms[uiData.bs].clientToViewer.Transform[[mx, my]];
[sx, sy] ← ViewerOps.UserToScreenCoords[innerViewer, Real.RoundI[viewerPos.x], Real.RoundI[viewerPos.y]];
IF innerViewer.column#color
THEN {
sy ← terminal.bwHeight - sy;
Terminal.SetBWCursorPosition[terminal, [sx + uiData.hotX, sy + uiData.hotY]];
}
ELSE {
sy ← terminal.colorHeight - sy;
Terminal.SetColorCursorPosition[terminal, [sx + uiData.hotX, sy + uiData.hotY]];
};
};
QuitGridding:
PROC [uiData: SilUIData] =
TRUSTED {
Perform necessary actions for the destruction of this user input.
uiData.gridding ← FALSE;
uiData.bs.style.SetButtonsCapturedness[uiData.bs, FALSE];
Interminal.SetCursorOffset[uiData.hotX, uiData.hotY, TRUE]; --reset remembered offset
};
ChangeGridInterval:
PUBLIC
ENTRY
PROC [uiData: SilUIData, gridSpacing:
NAT] = {
Change the cursor grid spacing
ENABLE UNWIND => NULL;
uiData.gridSpacing ← gridSpacing;
};
MagnifyGrid:
PUBLIC
ENTRY
PROC [uiData: SilUIData, xOffset, yOffset:
REAL, magnification:
NAT] = {
Change the cursor grid spacing
Make the cursor gridding reflect the current magnification.
ENABLE UNWIND => NULL;
uiData.xOffset ← xOffset;
uiData.yOffset ← yOffset;
uiData.gridMagnification ← magnification;
uiData.xPostOffset ← Real.RoundI[xOffset * magnification];
uiData.yPostOffset ← Real.RoundI[yOffset * -magnification];
};
NotInputingRope:
PUBLIC
ENTRY
PROC [uiData: SilUIData] = {
Tell the userinput module that Sil is no longer in Rope Editing Mode.
ENABLE
UNWIND =>
NULL;
uiData.mode ← NotInputingRope;
};
InputingRope:
PUBLIC
ENTRY
PROC [uiData: SilUIData] = {
Tell the userinput module that Sil is now in Rope Editing Mode.
ENABLE
UNWIND =>
NULL;
uiData.mode ← InputingRope;
};
RouteInputChars:
PROC [c:
CHAR, uiData: SilUIData] = {
SELECT uiData.mode
FROM
NotInputingRope, InputingRope => Enque[ [UserChar[c] ], uiData];
waitingFor1CharArg => {
TRUSTED {
WITH temp: uiData.InputWaitingForArg.first
SELECT
FROM
ChangeSelected => temp.char ← c;
HardCopy => temp.char ← c;
ManipulateMacro => temp.char ← c;
SelectForAttrib => temp.char ← c;
SetDefaultAttribs => temp.char ← c;
SetDetails => temp.char ← c;
ShowMacros =>
SELECT c
FROM
Only show macros if char was macro font
ELSE select all items of given attribute
'4, '5, '6, '7, '8, '9 => temp.char ← c;
ENDCASE => uiData.InputWaitingForArg ← LIST[ [SelectForAttrib[mode: change, char: c]] ];
ENDCASE;
};
uiData.mode ← NotInputingRope;
Only printing characters are appropriate here.
IF c IN SilFile.PrintingChars THEN EnqueWithList[uiData.InputWaitingForArg, uiData];
uiData.InputWaitingForArg ← NIL;
};
waitingForConfirmation => NULL;
ENDCASE;
};
GetBiScroller:
PUBLIC
PROC [uiData: SilUIData]
RETURNS [bs: BiScrollers.BiScroller] =
{bs ← uiData.bs};
END.