SlidersImpl.mesa
Written by Darlene Plebon on June 22, 1983 1:16 pm
Last Edited by: Beach, June 22, 1983 6:15 pm
DIRECTORY
Cursors USING [CursorType],
Graphics USING [Box, DrawBox, GetBounds, SetColor],
GraphicsBasic USING [Color, white],
InputFocus USING [CaptureButtons, ReleaseButtons],
Process USING [Detach, Milliseconds, MsecToTicks, SetTimeout],
Sliders,
TIPUser USING [InstantiateNewTIPTable, TIPScreenCoords, TIPScreenCoordsRec],
ViewerClasses USING [GetProc, NotifyProc, PaintProc, SetProc, Viewer, ViewerRec, ViewerClass, ViewerClassRec],
ViewerOps USING [CreateViewer, GetViewer, MouseInViewer, PaintViewer, RegisterViewerClass, SetViewer, UserToScreenCoords],
WindowManager USING [RestoreCursor];
SlidersImpl:
CEDAR
MONITOR
IMPORTS Graphics, 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: GraphicsBasic.Color, -- color of slider foreground
background: GraphicsBasic.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: SliderValue ← 0.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
ANY ←
NIL, paint:
BOOL ←
TRUE]
RETURNS [slider: Slider] = {
sliderData: SliderData ← NEW[SliderDataRec ← [sliderProc: sliderProc, filterProc: filterProc, orientation: orientation, foreground: foreground, background: 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;
sliderData: SliderData ← NARROW[self.data];
box: Graphics.Box ← Graphics.GetBounds[context];
SELECT whatChanged
FROM
NIL => {
Graphics.SetColor[context, sliderData.background];
Graphics.DrawBox[context, box];
SetMaxSliderValue[self];
SetSliderValue[sliderData, sliderData.normalizedSliderValue];
min ← 0.0;
max ← sliderData.sliderValue;
Graphics.SetColor[context, sliderData.foreground];
};
$sliderValue => {
IF sliderData.oldSliderValue <= sliderData.sliderValue
THEN {
min ← sliderData.oldSliderValue;
max ← sliderData.sliderValue;
Graphics.SetColor[context, sliderData.foreground];
}
ELSE {
min ← sliderData.sliderValue;
max ← sliderData.oldSliderValue;
Graphics.SetColor[context, sliderData.background];
};
};
ENDCASE => ERROR;
IF sliderData.orientation = horizontal
THEN {
box.xmin ← min;
box.xmax ← max;
}
ELSE {
box.ymin ← min;
box.ymax ← max;
};
Graphics.DrawBox[context, 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];