ViewerInputQueue.mesa
Last Edited by: Pausch, August 15, 1983 9:35 am
DIRECTORY
ViewerInputQueue,
Process USING [Detach, DisableTimeout, EnableAborts],
Rope USING [ROPE],
SafeStorage USING [NewZone],
ViewerClasses USING [NotifyProc, Viewer];
ViewerQueueImpl: CEDAR MONITOR LOCKS q USING q: Queue
IMPORTS
Process, SafeStorage
EXPORTS ViewerInputQueue = BEGIN
Viewer: TYPE = ViewerClasses.Viewer;
ROPE: TYPE = Rope.ROPE;
Action: TYPE = ViewerInputQueue.Action;
ClientProgrammingError: ERROR = CODE;
InternalProgrammingError: ERROR = CODE;
zone: ZONE = SafeStorage.NewZone[];
When input actions to notify procs in the viewer package must be synchronous, they are pushed onto queues and later read off by this package. Each input action is described by a triplet of the notify proc to call, and the viewer and list of REF ANY (from a tip table, menu definition, etc.) to pass to the noify proc. Each queue contains a list of pending events. If pushModel is TRUE, then notifierRunning is a BOOL that indicates whether or not a process is currently processing an event. (notifierRunning is ignored if NOT pushModel. If NOT pushModel, then newEvent is a CONDITION on which a client process may wait for the next event to occur. (newEvent is ignored if pushModel).
Queue: TYPE = REF QueueObj;
QueueObj: PUBLIC TYPE = MONITORED RECORD [
firstEvent: Event ← NIL,
pushModel: BOOL,
newEvent: CONDITION,
notifierRunning: BOOLFALSE
];
Event: TYPE = LIST OF ViewerInputQueue.Action;
Create: PUBLIC PROC [pushModel: BOOL] RETURNS [queue: Queue] = TRUSTED {
queue ← zone.NEW[QueueObj← [pushModel: pushModel]];
Process.DisableTimeout[@queue.newEvent];
Process.EnableAborts[@queue.newEvent];
RETURN[queue];
};
EnQueue: PUBLIC PROC [q: Queue, action: ViewerInputQueue.Action] = {
newEvent: Event ← zone.CONS[first: action, rest: NIL];
InternalEnqueue[q, newEvent];
};
DequeueAction: PUBLIC ENTRY PROC [q: Queue] RETURNS [ViewerInputQueue.Action] = {
event: Event;
IF q.pushModel THEN RETURN WITH ERROR ClientProgrammingError;
WHILE q.firstEvent=NIL DO WAIT q.newEvent ENDLOOP;
event ← q.firstEvent;
q.firstEvent← q.firstEvent.rest;
RETURN [event.first];
};
Flush: PUBLIC ENTRY PROC [q: Queue, proc: PROC[Action]← NIL] = {
Flushes all pending button presses and menu selections in the context of q. This procedure can be called, e.g., when some illegal actions suggests the user is confused an further mouse-ahead should be ignored. Proc will get called for each Action in the queue, to allow cleanup, NOTIFY's, etc.
IF proc = NIL THEN q.firstEvent← NIL
ELSE FOR event: Event ← q.firstEvent, event.rest UNTIL event = NIL DO
proc[event.first];
ENDLOOP;
};
InternalEnqueue: ENTRY PROC [q: Queue, e: Event] = {
Adds another event e to the end of q's event list, and set a notifier running again if there wasn't one. Note that the caller of this procedure does the NEW of the Event, outside the monitor, but leaves the rest field NIL to be assigned by this procedure.
qEnd: Event;
IF q.firstEvent=NIL THEN
q.firstEvent← e
ELSE {
FOR qEnd← q.firstEvent, qEnd.rest UNTIL qEnd.rest=NIL DO ENDLOOP;
qEnd.rest← e
};
IF q.pushModel THEN {
IF q.notifierRunning THEN RETURN;
q.notifierRunning← TRUE;
TRUSTED {Process.Detach[FORK Notifier[q]]};
}
ELSE NOTIFY q.newEvent
};
Notifier: PROC [q: Queue] = {
This process is not under the queue monitor. There are exactly one or zero notifiers per queue, so that no more than one event happens at a time. FORKed from InternalEnqueue.
event: Event;
UNTIL (event ← Dequeue[q]) = NIL DO
ENABLE ABORTED => {Flush[q]; LOOP};
should be doing greying type things with the buttons ...
event.first.notify[event.first.viewer,event.first.list];
should be doing UNgreying type things with the buttons ...
ENDLOOP;
};
Dequeue: ENTRY PROC [q: Queue] RETURNS [event: Event] = {
Removes the first event on q's event list, returning NIL if list empty. Called from Notifier.
IF NOT q.pushModel THEN RETURN WITH ERROR InternalProgrammingError;
IF q.firstEvent=NIL THEN {q.notifierRunning← FALSE; RETURN[NIL]};
event← q.firstEvent;
q.firstEvent← q.firstEvent.rest;
};
END.