SlidersImpl.mesa
Copyright © 1983, 1984 Xerox Corporation. All rights reserved.
Written by Darlene Plebon on June 22, 1983 1:16 pm
Last Edited by: Beach, June 22, 1983 6:15 pm
Doug Wyatt, September 5, 1984 3:13:38 pm PDT
DIRECTORY
Cursors USING [CursorType],
GraphicsBasic USING [Color, white],
Imager USING [Box, Color, Context, MaskBox, SetColor],
ImagerOps USING [Color, ColorFromGraphicsColor, ImagerFromGraphics],
InputFocus USING [CaptureButtons, ReleaseButtons],
Process USING [Detach, Milliseconds, MsecToTicks, SetTimeout],
Sliders USING [FilterProc, NormalizedSliderValue, Reason, Slider, sliderGray, SliderOrientation, SliderProc],
TIPUser USING [InstantiateNewTIPTable, TIPScreenCoords, TIPScreenCoordsRec],
ViewerClasses USING [GetProc, NotifyProc, PaintProc, SetProc, Viewer, ViewerClass, ViewerClassRec, ViewerRec],
ViewerOps USING [CreateViewer, GetViewer, MouseInViewer, PaintViewer, RegisterViewerClass, SetViewer, UserToScreenCoords],
WindowManager USING [RestoreCursor];
SlidersImpl: CEDAR MONITOR
IMPORTS Imager, ImagerOps, InputFocus, Process, TIPUser, ViewerOps, WindowManager
EXPORTS Sliders
= BEGIN OPEN Sliders;
SliderState: TYPE = {inputDisabled, inputEnabled};
SliderValue: TYPE = REAL;     -- in range [0.0, 1.0]
SliderData: TYPE = REF SliderDataRec;
SliderDataRec: TYPE = RECORD [
sliderProc: SliderProc ← NIL,   -- client procedure called when slider value changed
filterProc: FilterProc ← NIL,   -- client procedure for filtering slider updates
orientation: SliderOrientation,   -- orientation of slider image (horizontal or vertical)
foreground: Imager.Color,    -- color of slider foreground
background: Imager.Color,    -- color of slider background
normalizedSliderValue: NormalizedSliderValue ← 0.0, -- in range [0.0, 1.0]
mouseValue: SliderValue ← 0.0,  -- mouse position in slider
sliderValue: SliderValue ← 0.0,  -- slider value in viewer screen coordinates
maxSliderValue: SliderValue0.0, -- maximum slider value in viewer screen coordinates
oldSliderValue: SliderValue ← 0.0,  -- previous value in viewer screen coordinates
savedSliderValue: SliderValue ← 0.0, -- value to rollback to if operation aborted
reason: Reason ← move,     -- reason for calling client proc
oldReason: Reason ← move,    -- reason for calling client proc the last time
state: SliderState ← inputDisabled,  -- indicates whether mouse input is being accepted
clientData: REF ANY
];
Create: PUBLIC PROCEDURE [info: ViewerClasses.ViewerRec ← [], sliderProc: SliderProc ← NIL, filterProc: FilterProc ← NIL, orientation: SliderOrientation ← vertical, foreground: GraphicsBasic.Color ← sliderGray, background: GraphicsBasic.Color ← GraphicsBasic.white, value: NormalizedSliderValue ← 0.0, clientData: REF ANYNIL, paint: BOOLTRUE] RETURNS [slider: Slider] = {
sliderData: SliderData ← NEW[SliderDataRec ← [
sliderProc: sliderProc, filterProc: filterProc, orientation: orientation,
foreground: ImagerOps.ColorFromGraphicsColor[foreground],
background: ImagerOps.ColorFromGraphicsColor[background],
normalizedSliderValue: value, clientData: clientData]];
info.data ← sliderData;
info.scrollable ← FALSE;
RETURN[ViewerOps.CreateViewer[flavor: $Slider, info: info, paint: paint]];
};
GetContents: PUBLIC PROCEDURE [slider: Slider] RETURNS [contents: NormalizedSliderValue] = {
RETURN[NARROW[ViewerOps.GetViewer[viewer: slider], REF NormalizedSliderValue]^];
};
SetContents: PUBLIC PROCEDURE [slider: Slider, contents: NormalizedSliderValue] = {
value: REF NormalizedSliderValue ← NEW[NormalizedSliderValue ← contents];
ViewerOps.SetViewer[viewer: slider, data: value];
};
SlidersPaint: PRIVATE ViewerClasses.PaintProc = {
min, max: REAL;
imager: Imager.Context ~ ImagerOps.ImagerFromGraphics[context];
sliderData: SliderData ← NARROW[self.data];
box: Imager.Box ← [0, 0, self.cw, self.ch];
SELECT whatChanged FROM
NIL => {
Imager.SetColor[imager, sliderData.background];
Imager.MaskBox[imager, box];
SetMaxSliderValue[self];
SetSliderValue[sliderData, sliderData.normalizedSliderValue];
min ← 0.0;
max ← sliderData.sliderValue;
Imager.SetColor[imager, sliderData.foreground];
};
$sliderValue => {
IF sliderData.oldSliderValue <= sliderData.sliderValue THEN {
min ← sliderData.oldSliderValue;
max ← sliderData.sliderValue;
Imager.SetColor[imager, sliderData.foreground];
}
ELSE {
min ← sliderData.sliderValue;
max ← sliderData.oldSliderValue;
Imager.SetColor[imager, sliderData.background];
};
};
ENDCASE => ERROR;
IF sliderData.orientation = horizontal THEN {
box.xmin ← min;
box.xmax ← max;
}
ELSE {
box.ymin ← min;
box.ymax ← max;
};
Imager.MaskBox[imager, box];
sliderData.oldSliderValue ← sliderData.sliderValue;
};
SetMaxSliderValue: PROCEDURE [slider: Slider] = {
sliderData: SliderData ← NARROW[slider.data];
sliderData.maxSliderValue ← IF sliderData.orientation = horizontal THEN slider.cw ELSE slider.ch;
IF sliderData.maxSliderValue < 1.0 THEN sliderData.maxSliderValue ← 1.0;
};
SlidersGet: PRIVATE ViewerClasses.GetProc = {
sliderData: SliderData ← NARROW[self.data];
RETURN[NEW[NormalizedSliderValue ← sliderData.normalizedSliderValue]];
};
SlidersSet: PRIVATE ENTRY ViewerClasses.SetProc = {
dataValue: REF NormalizedSliderValue ← NARROW[data];
sliderData: SliderData ← NARROW[self.data];
SetSliderValue[sliderData, dataValue^];
ViewerOps.PaintViewer[viewer: self, hint: client, clearClient: FALSE, whatChanged: $sliderValue];
};
SetSliderValue: PRIVATE PROCEDURE [sliderData: SliderData, normalizedValue: NormalizedSliderValue] = {
SELECT normalizedValue FROM
< 0.0 => normalizedValue ← 0.0;
> 1.0 => normalizedValue ← 1.0;
ENDCASE;
sliderData.normalizedSliderValue ← normalizedValue;
sliderData.sliderValue ← sliderData.normalizedSliderValue * sliderData.maxSliderValue;
};
SlidersNotify: PRIVATE ViewerClasses.NotifyProc = TRUSTED {
ENABLE UNWIND => { InputFocus.ReleaseButtons[]; WindowManager.RestoreCursor[] };
v: ViewerClasses.Viewer;
c: BOOLEAN;
mouse: TIPUser.TIPScreenCoords;
mouseValue: SliderValue;
mouseScreenCoords: TIPUser.TIPScreenCoords ← NEW[TIPUser.TIPScreenCoordsRec];
sliderData: SliderData ← NARROW[self.data];
FOR list: LIST OF REF ANY ← input, list.rest UNTIL list = NIL DO
WITH list.first SELECT FROM
x: ATOM => SELECT x FROM
$Enable => {
InputFocus.CaptureButtons[proc: SlidersNotify, tip: slidersClass.tipTable, viewer: self];
sliderData.state ← inputEnabled;
sliderData.savedSliderValue ← sliderData.sliderValue;
mouseValue ← IF sliderData.orientation = horizontal THEN mouse.mouseX ELSE mouse.mouseY;
SetNewSliderValue[sliderData: sliderData, value: mouseValue, reason: move];
CreateSliderProcess[self];
};
$Abort => {
InputFocus.ReleaseButtons[];
sliderData.state ← inputDisabled;
SetNewSliderValue[sliderData: sliderData, value: sliderData.savedSliderValue, reason: abort];
sliderProcessData.state ← dying;
};
$Move => IF sliderData.state = inputEnabled THEN {
mouseScreenCoords^ ← mouse^;
[v, c] ← ViewerOps.MouseInViewer[mouse];
IF v = self AND c THEN
mouseValue ←
IF sliderData.orientation = horizontal THEN mouse.mouseX ELSE mouse.mouseY
ELSE IF MouseAboveSlider[self: self, coords: mouseScreenCoords] THEN
mouseValue ← sliderData.maxSliderValue
ELSE IF MouseBelowSlider[self: self, coords: mouseScreenCoords] THEN
mouseValue ← 0.0
ELSE mouseValue ← sliderData.mouseValue;
SetNewSliderValue[sliderData: sliderData, value: mouseValue, reason: move];
};
$Set => IF sliderData.state = inputEnabled THEN {
reason: Reason ← set;
InputFocus.ReleaseButtons[];
sliderData.state ← inputDisabled;
mouseScreenCoords^ ← mouse^;
[v, c] ← ViewerOps.MouseInViewer[mouse];
IF v = self AND c THEN
mouseValue ←
IF sliderData.orientation = horizontal THEN mouse.mouseX ELSE mouse.mouseY
ELSE IF MouseAboveSlider[self: self, coords: mouseScreenCoords] THEN
mouseValue ← sliderData.maxSliderValue
ELSE IF MouseBelowSlider[self: self, coords: mouseScreenCoords] THEN
mouseValue ← 0.0
ELSE {
mouseValue ← sliderData.savedSliderValue;
reason ← abort;
};
SetNewSliderValue[sliderData: sliderData, value: mouseValue, reason: reason];
sliderProcessData.state ← dying;
};
ENDCASE => NULL;
z: TIPUser.TIPScreenCoords => mouse ← z;
ENDCASE => ERROR;
ENDLOOP;
};
SetNewSliderValue: PRIVATE ENTRY PROCEDURE [sliderData: SliderData, value: SliderValue, reason: Reason] = {
sliderData.mouseValue ← value;
sliderData.reason ← reason;
};
MouseAboveSlider: PRIVATE PROCEDURE [self: Slider, coords: TIPUser.TIPScreenCoords] RETURNS [above: BOOLEAN] = {
cornerX, cornerY: INTEGER;
sliderData: SliderData ← NARROW[self.data];
IF sliderData.orientation = horizontal THEN {
[cornerX, cornerY] ← ViewerOps.UserToScreenCoords[self: self, vx: self.ww-2, vy: 0];
IF coords.mouseX > cornerX AND coords.mouseY >= cornerY AND coords.mouseY < cornerY + self.wh THEN RETURN[TRUE];
}
ELSE {
[cornerX, cornerY] ← ViewerOps.UserToScreenCoords[self: self, vx: 0, vy: self.wh-2];
IF coords.mouseY > cornerY AND coords.mouseX >= cornerX AND coords.mouseX < cornerX + self.ww THEN RETURN[TRUE];
};
RETURN[FALSE];
};
MouseBelowSlider: PRIVATE PROCEDURE [self: Slider, coords: TIPUser.TIPScreenCoords] RETURNS [below: BOOLEAN] = {
cornerX, cornerY: INTEGER;
sliderData: SliderData ← NARROW[self.data];
[cornerX, cornerY] ← ViewerOps.UserToScreenCoords[self: self, vx: 0, vy: 0];
IF sliderData.orientation = horizontal THEN {
IF coords.mouseX < cornerX AND coords.mouseY >= cornerY AND coords.mouseY < cornerY + self.wh THEN RETURN[TRUE];
}
ELSE {
IF coords.mouseY < cornerY AND coords.mouseX >= cornerX AND coords.mouseX < cornerX + self.ww THEN RETURN[TRUE];
};
RETURN[FALSE];
};
SliderProcessData: TYPE = REF SliderProcessDataRec;
SliderProcessDataRec: TYPE = RECORD [
slider: Slider ← NIL,
timerCondition: CONDITION,
state: SliderProcessState ← alive
];
SliderProcessState: TYPE = {alive, dying};
sliderProcessData: SliderProcessData ← NIL;
CreateSliderProcess: PRIVATE PROCEDURE [slider: Slider] = {
sliderProcessData ← NEW[SliderProcessDataRec];
sliderProcessData.slider ← slider;
TRUSTED {Process.Detach[FORK SliderProcess[sliderProcessData]]};
};
SliderProcess: PRIVATE PROCEDURE [myData: SliderProcessData] = {
UpdateSliderValue: PROCEDURE [self: Slider] = {
sliderData: SliderData ← NARROW[self.data];
notifyClient: BOOLEAN;
reason: Reason;
normalizedValue: NormalizedSliderValue;
[notifyClient, reason, normalizedValue] ← UpdateSlider[self];
IF sliderData.sliderProc # NIL AND notifyClient THEN sliderData.sliderProc[slider: self, reason: reason, value: normalizedValue, clientData: sliderData.clientData];
};
timerPeriod: Process.Milliseconds = 40;
WHILE myData.state = alive DO
UpdateSliderValue[myData.slider];
Wait[myData, timerPeriod];
ENDLOOP;
UpdateSliderValue[myData.slider];
};
UpdateSlider: PRIVATE ENTRY PROCEDURE [self: Slider] RETURNS [notifyClient: BOOLEAN, reason: Reason, normalizedValue: NormalizedSliderValue] = {
sliderData: SliderData ← NARROW[self.data];
value: NormalizedSliderValue;
notifyClient ← FALSE;
IF sliderData.mouseValue # sliderData.oldSliderValue THEN {
notifyClient ← TRUE;
value ← sliderData.mouseValue / sliderData.maxSliderValue;
IF sliderData.filterProc # NIL THEN value ← sliderData.filterProc[value, sliderData.clientData];
SetSliderValue[sliderData, value];
IF sliderData.sliderValue # sliderData.oldSliderValue THEN {
notifyClient ← TRUE;
ViewerOps.PaintViewer[viewer: self, hint: client, clearClient: FALSE, whatChanged: $sliderValue];
};
};
IF sliderData.reason # sliderData.oldReason THEN notifyClient ← TRUE;
sliderData.oldReason ← sliderData.reason;
RETURN[notifyClient, sliderData.reason, sliderData.normalizedSliderValue];
};
Wait: PRIVATE ENTRY PROCEDURE [data: SliderProcessData, milliseconds: Process.Milliseconds] = {
TRUSTED {Process.SetTimeout[@data.timerCondition, Process.MsecToTicks[milliseconds]]};
WAIT data.timerCondition;
};
slidersClass is a record containing the procedures and data common to all slidersClass viewer instances (class record).
slidersClass: ViewerClasses.ViewerClass ← NEW[ViewerClasses.ViewerClassRec ← [
paint: SlidersPaint,
get: SlidersGet,
set: SlidersSet,
notify: SlidersNotify,
tipTable: TIPUser.InstantiateNewTIPTable["Slider.tip"],
cursor: crossHairsCircle
]];
Register the Sliders class of viewer with the Window Manager
ViewerOps.RegisterViewerClass[$Slider, slidersClass];
END.