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],
Geom2D USING
[Vec],
ImagerTransformation USING
[Transform],
InputFocus USING
[SetInputFocus],
Interminal USING
[GetCursorOffset, SetCursorOffset],
MessageWindow USING
[Append],
Real USING [RoundI],
Rope USING
[ROPE],
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: BOOLFALSE,
caretsShouldChangeNow: BOOLFALSE,
Information for gridding the cursor:
see GridTheCursor for explanation of the need for each field.
gridSpacing: NAT ← SilUserInput.defaultGridSpacing,
gridding: BOOLFALSE, --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: BOOLFALSE; -- set by ControlS
restoreCursor: BOOLTRUE; -- 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.