PopUpSelection2Impl.Mesa
Copyright © 1983, 1986 by Xerox Corporation. All rights reserved.
Created by Christian Jacobi, November 9, 1983 12:14 pm
Last Edited by: Christian Jacobi, August 26, 1986 10:31:27 am PDT
Last Edited by: Pier, June 23, 1986 11:39:21 am PDT
Last tweaked by Mike Spreitzer on October 9, 1990 2:36:23 pm PDT
Mike Spreitzer February 12, 1987 5:36:04 pm PST
DIRECTORY
BasicTime USING [GMT, Now, Period, Update],
CedarProcess USING [Priority, SetPriority],
Cursors USING [SetCursor],
Imager,
ImagerBackdoor USING [DiscardBuffer, MakeStipple, RestoreBuffer, SaveBufferRectangle, ViewReset],
ImagerColor,
ImagerFont USING [Extents, FontBoundingBox, RopeBoundingBox],
InputFocus USING [CaptureButtons, PopInputFocus, PushInputFocus, ReleaseButtons],
MachineDependentPopping USING [maySetMouse, ScreenCoords, SetMouse, ToScreenCoords],
PopUpButtons,
PopUpSelection2,
PopUpSelection2Private,
Process USING [Detach, InitializeCondition, MsecToTicks, SecondsToTicks],
Real USING [Fix, Round],
Rope USING [InlineIsEmpty, ROPE],
RuntimeError USING [UNCAUGHT],
TIPUser USING [InstantiateNewTIPTable, TIPScreenCoords, TIPTable],
UserProfile USING [Boolean, CallWhenProfileChanges, Number, ProfileChangedProc, Token],
VFonts USING [DefaultFont, EstablishFont],
ViewerClasses USING [NotifyProc],
ViewerLocks USING [CallUnderColumnLock, CallUnderViewerTreeLock],
ViewerPrivate USING [CreateContext, Screen],
ViewerSpecs USING [colorScreenHeight, colorScreenWidth, bwScreenHeight, bwScreenWidth];
PopUpSelection2Impl:
CEDAR
MONITOR
IMPORTS BasicTime, CedarProcess, Cursors, Imager, ImagerBackdoor, ImagerColor, ImagerFont, InputFocus, MachineDependentPopping, Process, Real, Rope, RuntimeError, TIPUser, UserProfile, VFonts, ViewerLocks, ViewerPrivate, ViewerSpecs
EXPORTS PopUpSelection2 =
BEGIN OPEN MDP:MachineDependentPopping, PopUpSelection2;
Menu: TYPE = REF MenuPrivate;
MenuPrivate: PUBLIC TYPE = PopUpSelection2Private.MenuPrivate;
RopeImage: TYPE ~ REF RopeImagePrivate;
RopeImagePrivate: TYPE ~ PopUpButtons.RopeImagePrivate;
Colors: TYPE ~ REF ColorsPrivate;
ColorsPrivate: TYPE ~ PopUpButtons.ColorsPrivate;
State: TYPE ~ {idle, preparing, selecting, recovering};
--data of the basic monitor
reCheck: CONDITION;
state: State ← idle;
repositioned: BOOL ← FALSE;
timedOut: BOOL ← FALSE;
timeOutKey: REF BasicTime.GMT; --NIL means no time out expected; SetUpTimeOutWatcher is only writer
gSelection: NAT ← 0;
gMB: MouseButton ← red;
gMouse: REF ← NIL;
gHelpOn: INTEGER ← -1;
gHelpMode: BOOL ← FALSE;
gXOrg, gYOrg: INTEGER ← 0; -- initial mouse position, relative to [mx, my], 0 at bottom
--data of the constructed monitor
sHeight, sWidth: INTEGER ← 0; -- size of screen, in pixels; [screen containing menu]
mx, my: INTEGER ← 0; -- menu position, 0 at bottom
context: Imager.Context ← NIL; -- menu context
mOnColor: BOOL ← FALSE; -- menu [NOT cursor] on color screen
lastMouse: MDP.ScreenCoords; -- at start; remember between calls is creature comfort; 0 at bottom
NotifyConsumer: Consumer ← NIL;
notifyConsumerData: REF ANY ← NIL;
gm: Menu ← NIL;
gDefault: NAT;
--unmonitored data
CallerBug: ERROR = CODE;
Bug: ERROR = CODE;
bordThick: NAT = 2;
bordSep: NAT = 2;
borderW: NAT = bordThick+bordSep;
border2W: NAT = borderW+borderW;
debug: BOOL ← FALSE;
tipTable: TIPUser.TIPTable = TIPUser.InstantiateNewTIPTable["PopUpSelection2.tip"];
font: Imager.Font ← NIL;
--for scheduling interaction menu
Reason: TYPE ~ {done, reposition, allup};
SetDoneAndNotify:
PROC [reason: Reason, mb: MouseButton]
--must only be called from the notifier process, because it passes result to NotifyConsumer-- = {
Consume: Consumer;
sel: INT;
data: REF ANY;
[Consume, sel, data] ← SetDone[reason, mb];
IF Consume # NIL THEN Consume[sel, mb, data];
RETURN};
SetDone:
ENTRY
PROC [reason: Reason, mb: MouseButton]
RETURNS [
Consume: Consumer, sel:
INT, data:
REF
ANY] = {
ENABLE UNWIND => NULL;
IF reason=allup AND gm.allMayBeUp THEN RETURN [NIL, -1, NIL];
IF state#selecting THEN RETURN [NIL, INT.FIRST, NIL];
state ← recovering;
repositioned ← SELECT reason FROM done, allup => FALSE, reposition => TRUE, ENDCASE => ERROR;
IF repositioned THEN Consume ← NIL ELSE Consume ← NotifyConsumer;
sel ← IF timedOut THEN -1 ELSE gSelection;
gMB ← mb;
data ← notifyConsumerData;
BROADCAST reCheck;
RETURN};
Wait:
ENTRY
PROC
RETURNS [reposd:
BOOL] = {
ENABLE UNWIND => NULL;
IF state#preparing THEN RETURN WITH ERROR Bug;
state ← selecting;
BROADCAST reCheck;
WHILE state#recovering DO WAIT reCheck ENDLOOP;
IF (reposd ← repositioned) THEN state ← preparing;
RETURN};
--Enter and Leave for scheduling module entrance
Enter:
ENTRY
PROC [] = {
ENABLE UNWIND => NULL;
WHILE state#idle DO WAIT reCheck ENDLOOP;
state ← preparing;
BROADCAST reCheck;
RETURN};
Leave:
ENTRY
PROC [] = {
ENABLE UNWIND => NULL;
IF state#recovering THEN RETURN WITH ERROR Bug;
state ← idle;
BROADCAST reCheck;
RETURN};
-- saving and restoring screen contents
Restore:
PROC [s:
ATOM] = {
ImagerBackdoor.RestoreBuffer[context, s];
IF discard THEN ImagerBackdoor.DiscardBuffer[context, s];
};
discard: BOOL ← TRUE;
--timeout nonsense
SetUpTimeOutWatcher:
ENTRY
PROC [i:
NAT] = {
ENABLE UNWIND => NULL;
timedOut ← FALSE;
IF i<=0 OR i>10000 THEN timeOutKey ← NIL --upper limit to prevent overflow
ELSE {
timeOutKey ← NEW[BasicTime.GMT ← BasicTime.Update[BasicTime.Now[], i]];
TRUSTED {Process.Detach[FORK TimeOutWatcherProcess[key: timeOutKey]]};
}
};
TimeOutWatcherProcess:
ENTRY
PROC [key:
REF BasicTime.
GMT] = {
--does not modify timeOutKey
--never called with key=NIL
ENABLE UNWIND => NULL;
cond: CONDITION;
CedarProcess.SetPriority[excited];
TRUSTED {Process.InitializeCondition[@cond, Process.SecondsToTicks[MIN[MAX[BasicTime.Period[from: BasicTime.Now[], to: key^], 1], 10]]]};
WHILE timeOutKey=key AND state=selecting AND BasicTime.Period[from: BasicTime.Now[], to: key^]>0 DO WAIT cond ENDLOOP;
IF timeOutKey=key
AND state=selecting
THEN {
state ← recovering;
timedOut ← TRUE;
BROADCAST reCheck};
};
--help and move
helpSave: ATOM ← NIL;
Box:
PROC [x, y, w, h, border:
INTEGER] = {
Imager.SetColor[context, Imager.white];
Imager.MaskRectangleI[context, x, y, w, h];
Imager.SetColor[context, Imager.black];
Imager.MaskRectangleI[context, x, y, border, h];
Imager.MaskRectangleI[context, x+w-border, y, border, h];
Imager.MaskRectangleI[context, x, y, w, border];
Imager.MaskRectangleI[context, x, y+h-border, w, border];
};
HelpMessage:
PROC [n:
INTEGER] = {
IF n#gHelpOn
THEN {
msg: ROPE ← NIL;
gHelpOn ← n;
IF helpSave#NIL THEN {Restore[helpSave]; helpSave ← NIL};
msg ← IF n = 0 THEN gm.doc ELSE IF n <= gm.choices.length AND gm.choices[n-1] # nullChoice THEN gm.choices[n-1].doc ELSE "no-op";
IF ~Rope.InlineIsEmpty[msg]
THEN {
bordThick: NAT ~ 2; bordDist: NAT ~ 4; below: NAT ~ 2;
e: ImagerFont.Extents ← ImagerFont.RopeBoundingBox[font, msg];
h: INTEGER ← Real.Fix[MIN[e.descent+e.ascent+2*bordDist, sHeight]]; --text height
w: INTEGER ← Real.Fix[MIN[e.rightExtent+e.leftExtent+2*bordDist+1, sWidth]]; --text width
ty: INTEGER ← my-h-below; --text box's lower y in upright coordinates
tx: INTEGER ← MIN[MAX[mx + (gm.width - w)/2, 0], sWidth-w]; --text box's left x in screen coordinates
IF ty<0
THEN {
ty ← my+gm.height+below;
IF ty+h>=sHeight THEN RETURN;
};
ImagerBackdoor.SaveBufferRectangle[context, helpSave ← $Help, [tx, ty, w, h]];
Box[tx, ty, w, h, bordThick];
Imager.SetColor[context, Imager.black];
Imager.SetXY[context, [(tx+bordDist)+e.leftExtent, ty+bordDist+e.descent]];
Imager.SetFont[context, font];
Imager.ShowRope[context, msg];
};
}
};
MoveCursor:
ENTRY
PROC [x, y:
INTEGER, onCol:
BOOL] = {
ENABLE UNWIND => NULL;
row, col, sel: NAT ← 0;
--Cursors.GetCursorInfo[].hotX.. is already included in x, y by window package...
IF state#selecting THEN RETURN;
IF onCol=mOnColor
THEN {
dx: INTEGER ~ x - mx;
dy: INTEGER ~ y - my;
SELECT
TRUE
FROM
dx
IN [borderW .. gm.width-border2W)
AND dy
IN (gm.fullRowsBot .. gm.headerBot] => {
sel ← (gm.headerBot-dy) / gm.fullRowHeight + 1;
};
dx
IN [gm.arrayLeft .. gm.arrayRight)
AND dy
IN (gm.arrayBot .. gm.arrayTop] => {
row ← (gm.arrayTop-dy)/gm.rowHeight;
col ← (dx-gm.arrayLeft)/gm.colWidth;
sel ← gm.fullRows + row*gm.cols + col + 1;
};
ENDCASE => {
gHelpMode ← TRUE;
HelpMessage[0];
};
};
IF gSelection#sel
THEN {
ll: INTEGER ~ mx;
by: INTEGER ~ my;
IF gSelection#0 THEN PaintEntry[ll, by, gSelection-1, FALSE];
gSelection ← sel;
IF gSelection#0 THEN PaintEntry[ll, by, gSelection-1, TRUE];
IF gHelpMode THEN HelpMessage[gSelection];
};
};
PopUpNotify: ViewerClasses.NotifyProc = {
--made sequential by viewer package
ProtectedNotify:
PROC [input:
LIST
OF
REF
ANY] = {
IF state=selecting
THEN {
mb: MouseButton ← red;
FOR list:
LIST
OF
REF
ANY ← input, list.rest
WHILE list#
NIL
DO
WITH list.first
SELECT
FROM
coords: TIPUser.TIPScreenCoords => MoveCursor[coords^.mouseX, coords^.mouseY, coords^.color];
atom:
ATOM =>
SELECT atom
FROM
$Red => mb ← red;
$Yellow => mb ← yellow;
$Blue => mb ← blue;
$Done => SetDoneAndNotify[done, mb];
$Reposition => SetDoneAndNotify[reposition, mb];
$AllUp => SetDoneAndNotify[allup, mb];
ENDCASE => ERROR;
ENDCASE => ERROR;
ENDLOOP
};
};
ProtectedNotify[input ! RuntimeError.UNCAUGHT => GOTO failed];
EXITS failed => {gSelection ← 0; SetDoneAndNotify[reposition, red]};
};
defaultFont: PUBLIC Imager.Font ← VFonts.DefaultFont[NIL];
dontPaint: PUBLIC Imager.Color ← ImagerColor.ColorFromStipple[
word: 0, function: [xor, null]];
sparseGrey: Imager.Color ~ ImagerBackdoor.MakeStipple[stipple: 00208H];
denseGrey: Imager.Color ~ ImagerBackdoor.MakeStipple[stipple: 0FDF7H];
defaultColors:
PUBLIC Colors ←
NEW [ColorsPrivate ← [
[ALL[[Imager.black, Imager.white]], ALL[[Imager.black, sparseGrey]]],
[ALL[[Imager.white, Imager.black]], ALL[[Imager.white, denseGrey]]]
]];
inverseColors:
PUBLIC Colors ←
NEW [ColorsPrivate ← [
[ALL[[Imager.white, Imager.black]], ALL[[Imager.white, denseGrey]]],
[ALL[[Imager.black, Imager.white]], ALL[[Imager.black, sparseGrey]]]
]];
leftMargin: INTEGER ← 3;
rightMargin: INTEGER ← 2;
bottomMargin: INTEGER ← 0;
topMargin: INTEGER ← 1;
ImageForRope:
PUBLIC
PROC [rope:
ROPE, colors: Colors ←
NIL, font: Imager.Font ←
NIL, align: Align ← bottomLeft]
RETURNS [image: Image] = {
IF font = NIL THEN font ← defaultFont;
IF colors = NIL THEN colors ← defaultColors;
{
e: ImagerFont.Extents = ImagerFont.RopeBoundingBox[font, rope];
f: ImagerFont.Extents = ImagerFont.FontBoundingBox[font];
ri: RopeImage =
NEW [RopeImagePrivate ← [
rope,
colors,
font,
align,
[Real.Round[e.leftExtent] + leftMargin, Real.Round[f.descent]+bottomMargin]
]];
image ←
NEW [ImagePrivate ← [
size: [ri.org.x + Real.Round[e.rightExtent] + rightMargin, ri.org.y + Real.Round[f.ascent] + topMargin],
Draw: DrawRope,
data: ri]];
}};
DrawRope:
PROC [image: Image, context: Imager.Context, bounds: Imager.Rectangle, state: PopUpButtons.VisibleState]
--Drawer-- = {
ri: RopeImage = NARROW[image.data];
ybot: REAL = bounds.y + ri.org.y + (bounds.h - image.size.y)*ri.align.y;
xbot: REAL ~ bounds.x + ri.org.x + (bounds.w - image.size.x)*ri.align.x;
foreground, background, strike: Imager.Color;
[foreground, background, strike] ← ri.colors[state.highlight][state.executing][state.guarded];
Imager.SetColor[context, background];
Imager.MaskRectangle[context, bounds];
Imager.SetColor[context, foreground];
Imager.SetXY[context, [xbot, ybot]];
Imager.SetFont[context, ri.font];
Imager.ShowRope[context, ri.text];
IF state.guarded
THEN {
guardOffset: REAL = ri.font.FontBoundingBox[].ascent*(1.0/3);
IF strike = NIL THEN strike ← foreground;
Imager.SetColor[context, strike];
Imager.MaskRectangle[context, [bounds.x, ybot+guardOffset, bounds.w, 1]];
};
};
Sequify:
PUBLIC
PROC [list: ChoiceList]
RETURNS [seq: ChoiceS] ~ {
len: NAT ← 0;
FOR l: ChoiceList ← list, l.rest WHILE l#NIL DO len ← len+1 ENDLOOP;
seq ← NEW [ChoiceSequence[len]];
FOR i:
NAT
IN [0 .. len)
DO
seq[i] ← [ImageForRope[list.first.key], list.first.doc];
list ← list.rest;
ENDLOOP;
IF list#NIL THEN ERROR;
RETURN};
-- the main stuff
PopRopes:
PUBLIC
PROC
[
choices: ChoiceList,
doc: ROPE,
allMayBeUp: BOOL,
default: NAT ← 0,
header: ROPE ← NIL,
left, top: Label ← nullLabel,
columns: NAT ← 1,
fullRows: NAT ← 0,
timeOut: NAT ← 0,
position: REF ← NIL,
InNotifier: Consumer ← NIL,
notifyData: REF ANY ← NIL]
RETURNS [sel: INT, mb: MouseButton]
~ {
menu: Menu ~ Create[Sequify[choices], doc, allMayBeUp, IF header#NIL THEN ImageForRope[header] ELSE NIL, left, top, columns, fullRows, timeOut];
[sel, mb] ← Pop[menu, default, position, InNotifier, notifyData];
RETURN};
Create:
PUBLIC
PROC [choices: ChoiceS, doc:
ROPE, allMayBeUp:
BOOL, header: Image ←
NIL, left, top: Label ← nullLabel, columns:
NAT ← 1, fullRows:
NAT ← 0, timeOut:
NAT ← 0]
RETURNS [m: Menu] = {
fullWidth: NAT ← 0;
m ←
NEW [MenuPrivate ← [
choices: choices,
header: header,
doc: doc,
allMayBeUp: allMayBeUp,
left: left,
top: top,
fullRows: fullRows,
arrayRows: (choices.length - fullRows + columns-1) / columns,
cols: columns,
timeOut: timeOut
]];
FOR i:
NAT
IN [0 .. fullRows)
DO
IF choices[i] # nullChoice
THEN {
m.fullRowHeight ← MAX[m.fullRowHeight, Ceiling[choices[i].image.size.y]];
fullWidth ← MAX[fullWidth, Ceiling[choices[i].image.size.x]];
};
ENDLOOP;
m.fullRowsSize ← m.fullRowHeight * fullRows;
FOR i:
NAT
IN [fullRows .. choices.length)
DO
IF choices[i] # nullChoice
THEN {
m.rowHeight ← MAX[m.rowHeight, Ceiling[choices[i].image.size.y]];
m.colWidth ← MAX[m.colWidth, Ceiling[choices[i].image.size.x]];
};
ENDLOOP;
IF top # nullLabel
THEN {
m.colWidth ← MAX[m.colWidth, top.minSpacing];
m.topLabelSize ← top.minWidth;
};
IF left # nullLabel
THEN {
m.rowHeight ← MAX[m.rowHeight, left.minSpacing];
m.leftLabelSize ← left.minWidth;
};
IF header #
NIL
THEN {
m.headerSize ← Ceiling[header.size.y];
fullWidth ← MAX[fullWidth, Ceiling[header.size.x]];
};
m.arrayWidth ← m.cols * m.colWidth;
IF fullWidth > m.arrayWidth+m.leftLabelSize
THEN {
IF m.arrayWidth # 0
THEN {
m.colWidth ← Real.Round[fullWidth/REAL[m.arrayWidth+m.leftLabelSize] * m.colWidth];
m.arrayWidth ← m.cols * m.colWidth;
};
m.leftLabelSize ← fullWidth - m.arrayWidth;
};
m.arrayHeight ← m.arrayRows * m.rowHeight;
m.arrayLeft ← borderW + m.leftLabelSize;
m.arrayRight ← m.arrayLeft + m.arrayWidth;
m.arrayBot ← borderW;
m.arrayTop ← m.arrayBot + m.arrayHeight;
m.fullRowsBot ← m.arrayTop + m.topLabelSize;
m.headerBot ← m.fullRowsBot + m.fullRowsSize;
m.height ← m.headerBot + m.headerSize + borderW;
m.width ← m.arrayRight + borderW;
};
Pop:
PUBLIC
PROC [menu: Menu, default:
NAT ← 0, position:
REF ←
NIL,
InNotifier: Consumer ←
NIL, notifyData:
REF
ANY ←
NIL]
RETURNS [selection:
INT ← 0, mouseButton: MouseButton ← red] = {
IF InNotifier#NIL AND menu.timeOut IN (0..10000] THEN ERROR CallerBug--InNotifier can't be called from the TIP notifier in the case of a timeout--;
Enter[];
{ENABLE UNWIND => {NotifyConsumer ← NIL; Leave[]};
reposd: BOOL ← FALSE;
WithViewerLock:
PROC [] = {
saved: ATOM ← PrepareMenu[];
Imager.DoSave[context, PaintMenu];
HelpMessage[default];
CheatMousePos[];
SetUpTimeOutWatcher[gm.timeOut];
reposd ← Wait[];
RemoveMenu[saved];
};
gm ← menu;
gDefault ← default;
gMouse ← position;
repositioned ← FALSE;
NotifyConsumer ← InNotifier;
notifyConsumerData ← notifyData;
SELECT gSelection ← default
FROM
IN [1 .. menu.fullRows] => {
gXOrg ← menu.width/2;
gYOrg ← menu.headerBot - (gSelection-1)*menu.fullRowHeight - menu.fullRowHeight/2;
};
IN (menu.fullRows .. menu.choices.length] => {
gRow: NAT ~ (gSelection-menu.fullRows-1)/menu.cols;
gCol: NAT ~ gSelection-menu.fullRows-1 - gRow * menu.cols;
gXOrg ← menu.arrayLeft + gCol * menu.colWidth + menu.colWidth / 2;
gYOrg ← menu.arrayTop - gRow * menu.rowHeight - menu.rowHeight/2;
};
ENDCASE => {
gXOrg ← menu.width/2;
gYOrg ← menu.headerBot + menu.headerSize/2;
};
gHelpMode ← TRUE;
InputFocus.PushInputFocus[];
InputFocus.CaptureButtons[PopUpNotify, tipTable];
Cursors.SetCursor[menu];
DO
ComputeMenuPosition[];
gHelpOn ← -1;
IF debug THEN WithViewerLock[]
ELSE IF mOnColor THEN ViewerLocks.CallUnderColumnLock[WithViewerLock, color]
ELSE ViewerLocks.CallUnderViewerTreeLock[WithViewerLock];
IF ~reposd THEN EXIT;
gMouse ← NIL;
gSelection ← default;
repositioned ← FALSE;
ENDLOOP;
InputFocus.ReleaseButtons[];
InputFocus.PopInputFocus[];
NotifyConsumer ← NIL;
IF timedOut THEN selection ← -1 ELSE selection ← gSelection;
mouseButton ← gMB;
gMouse ← NIL;
};
Leave[];
};
ComputeMenuPosition:
PROC [] =
INLINE {
-- sets variables lastMouse and mOnColor
lastMouse ← MDP.ToScreenCoords[gMouse];
mOnColor ← lastMouse.color;
};
PrepareMenu:
PROC []
RETURNS [saved:
ATOM] =
INLINE {
IF mOnColor
THEN {
sHeight ← ViewerSpecs.colorScreenHeight;
sWidth ← ViewerSpecs.colorScreenWidth;
context ← ViewerPrivate.CreateContext[color];
}
ELSE {
sHeight ← ViewerSpecs.bwScreenHeight;
sWidth ← ViewerSpecs.bwScreenWidth;
context ← ViewerPrivate.CreateContext[ViewerPrivate.Screen.FIRST];
};
ImagerBackdoor.ViewReset[context];
mx ← MIN[MAX[0, lastMouse.x - gXOrg], sWidth-gm.width];
my ← MIN[MAX[0, lastMouse.y - gYOrg], sHeight-gm.height];
ImagerBackdoor.SaveBufferRectangle[context, saved ← $Menu, [mx, my, gm.width, gm.height]];
context.SetFont[font];
};
RemoveMenu:
PROC [saved:
ATOM] =
INLINE {
IF helpSave#
NIL
THEN {
Restore[helpSave];
helpSave ← NIL
};
Restore[saved];
};
PaintMenu:
PROC [] = {
--must be called with DoSave; changes state
ll: INTEGER ~ mx;
by: INTEGER ~ my;
Imager.ClipRectangleI[context, ll, by, gm.width, gm.height];
Box[ll, by, gm.width, gm.height, bordThick];
IF gm.header # NIL THEN gm.header.Draw[gm.header, context, [ll + borderW, by + gm.headerBot, gm.width - 2*borderW, gm.headerSize], [FALSE, FALSE, FALSE]];
IF gm.left # NIL THEN gm.left.Draw[context, [ll + gm.arrayLeft, by + gm.arrayTop], gm.arrayRows, gm.rowHeight, gm.leftLabelSize, gm.left.data];
IF gm.top # NIL THEN gm.top.Draw[context, [ll + gm.arrayLeft, by + gm.arrayTop], gm.cols, gm.colWidth, gm.topLabelSize, gm.top.data];
FOR i:
NAT
IN [0 .. gm.choices.length)
DO
PaintEntry[ll, by, i, gSelection = i+1];
ENDLOOP;
};
PaintEntry:
PROC [ll, by:
INTEGER, i:
NAT, highlight:
BOOL] = {
image: Image = IF i < gm.choices.length THEN gm.choices[i].image ELSE NIL;
IF image #
NIL
THEN
SELECT i
FROM
< gm.fullRows => image.Draw[image, context, [ll + borderW, by + gm.headerBot - (i+1) * gm.fullRowHeight, gm.width-border2W, gm.fullRowHeight], [highlight, FALSE, FALSE]];
ENDCASE => {
row: NAT ~ (i - gm.fullRows) / gm.cols;
col: NAT ~ i - gm.fullRows - row*gm.cols;
image.Draw[image, context, [ll + gm.arrayLeft + col * gm.colWidth, by + gm.arrayTop - (row+1) * gm.rowHeight, gm.colWidth, gm.rowHeight], [highlight, FALSE, FALSE]];
};
};
CheatMousePos:
PROC [] = {
--sets the cursor such that it points into the default field, or header
IF MDP.maySetMouse THEN MDP.SetMouse[[x: mx + gXOrg, y: my + gYOrg, color: mOnColor]];
};
InitFontGlobals: UserProfile.ProfileChangedProc = {
font ← VFonts.EstablishFont[
family: UserProfile.Token["PopUpSelection.FontFamily", "Helvetica"],
size: UserProfile.Number["PopUpSelection.FontSize", 10],
bold: UserProfile.Boolean["PopUpSelection.FontBold", FALSE],
italic: UserProfile.Boolean["PopUpSelection.FontItalic", FALSE],
defaultOnFailure: TRUE
];
};
Ceiling:
PROC [r:
REAL]
RETURNS [i:
INTEGER] = {
d: INT = Real.Fix[r] + 1;
i ← Real.Fix[r-d]+d};
Floor:
PROC [r:
REAL]
RETURNS [i:
INTEGER] = {
d: INT = Real.Fix[r] - 1;
i ← Real.Fix[r-d]+d};
TRUSTED {Process.InitializeCondition[@reCheck, Process.MsecToTicks[1000]]};
UserProfile.CallWhenProfileChanges[InitFontGlobals];
TRUSTED {Process.Detach[FORK DebugProcess[]]};
END.