WindowManagerImpl.mesa; Written by S. McGregor
Edited by McGregor on August 16, 1983 4:49 pm
Last Edited by: Maxwell, February 8, 1983 3:58 pm
Last Edited by: Pausch, August 17, 1983 5:05 pm
Last Edited by: Wyatt, November 9, 1983 4:58 pm
DIRECTORY
Carets USING [ResumeCarets, SuspendCarets],
ColorWorld USING [TurnOffColor, TurnOnColor],
Cursors USING [CursorType, GetCursor, SetCursor],
InputFocus USING [CaptureButtons, inputEnabled, ReleaseButtons, WindowManagerTIPTable],
InterminalExtra USING [InsertAction],
Imager USING [black, Color, Context, DoSave, IntegerClipRectangle, IntegerMaskRectangle, MakeStipple, SetColor, white],
Menus USING [ActionRec, allShifts, allTriggers, EntryRec, Menu, MenuRec, RegisterMenu],
MenusPrivate USING [ClearMenu, DrawMenu, HitMenu, MarkMenu, ReComputeWindowMenus, ViewerlessAddMenu, ViewerMenus],
MessageWindow USING [Append, Blink],
TIPUser USING [TIPScreenCoords, TIPScreenCoordsRec],
UserTerminal USING [BlinkDisplay],
ViewerClasses USING [Column, NotifyProc, ScrollOp, Viewer],
ViewerOps USING [AcquireContext, Adjust, ChangeColumn, CloseViewer, DestroyViewer, EnumerateViewers, EnumProc, GrowViewer, InitialiseColorPainting, InvisiblePaint, MouseInViewer, PaintViewer, ReleaseContext, TopViewer],
ViewerSpecs USING [captionHeight, scrollBarW, windowBorderSize],
WindowManager USING [ScreenPos],
WindowManagerPrivate USING [];
WindowManagerImpl: CEDAR MONITOR
IMPORTS Carets, ColorWorld, Cursors, Imager, InputFocus, InterminalExtra, Menus, MenusPrivate, MessageWindow, UserTerminal, ViewerOps
EXPORTS WindowManager, WindowManagerPrivate
SHARES InputFocus, ViewerClasses, ViewerOps
= BEGIN OPEN ViewerSpecs;
Viewer: TYPE = ViewerClasses.Viewer;
Zone: TYPE = {none, menu, scroll, hscroll, caption, client};
CursorZone: PROC[v: Viewer, mousePos: TIPUser.TIPScreenCoords] RETURNS[Zone] = {
Note: mousePos is relative to (v.wx, v.wy)!
x: INTEGER = mousePos.mouseX;
y: INTEGER = mousePos.mouseY;
IF v.parent=NIL AND v.column#static THEN {
ymax: INTEGER = v.wh; -- top of caption (= top of window)
ymin: INTEGER = ymax-captionHeight; -- bottom of caption
IF y IN[ymin..ymax) THEN RETURN[caption];
};
IF v.scrollable THEN {
ymin: INTEGER = v.cy-v.wy; -- bottom of scrollbar (= bottom of client area)
ymax: INTEGER = ymin+v.ch; -- top of scrollbar (= top of client area)
IF x IN[0..scrollBarW) AND y IN[ymin..ymax) THEN RETURN[scroll];
};
IF v.hscrollable THEN {
ymin: INTEGER = v.cy-v.wy+v.ch; -- bottom of scrollbar (= top of client area)
ymax: INTEGER = ymin+scrollBarW; -- top of scrollbar
IF y IN[ymin..ymax) THEN RETURN[hscroll];
};
WITH v.menu SELECT FROM
m: MenusPrivate.MenuInfo =>
IF x IN[m.x..m.x+m.w) AND y IN[m.y..m.y+m.h) THEN RETURN[menu];
ENDCASE;
RETURN[none];
};
feedbackZone: Zone ← none;
feedbackViewer: Viewer ← NIL;
PostNewFeedback: PROC[viewer: Viewer, zone: Zone] = {
old, new: BOOLTRUE;
SELECT feedbackZone FROM
scroll => RemoveScrollFeedback[feedbackViewer];
hscroll => RemoveHScrollFeedback[feedbackViewer];
menu => RemoveMenuFeedback[feedbackViewer];
caption => RemoveCaptionFeedback[feedbackViewer];
ENDCASE => old ← FALSE; -- no old feedback
IF old THEN {
InputFocus.ReleaseButtons[];
feedbackViewer ← NIL;
};
SELECT feedbackZone ← zone FROM
scroll => PostScrollFeedback[viewer];
hscroll => PostHScrollFeedback[viewer];
menu => PostMenuFeedback[viewer];
caption => PostCaptionFeedback[viewer];
ENDCASE => { SetC[textPointer]; new ← FALSE }; -- no new feedback
IF new THEN {
InputFocus.CaptureButtons[
ProcessWindowResults, InputFocus.WindowManagerTIPTable, viewer];
feedbackViewer ← viewer;
};
};
MarkMenu: PROC[viewer: Viewer, mousePos: TIPUser.TIPScreenCoords] = {
MenusPrivate.MarkMenu[NARROW[viewer.menu], viewer, mousePos]
};
HitMenu: PROC[viewer: Viewer, mousePos: TIPUser.TIPScreenCoords, trigger: [0..6)] = {
MenusPrivate.HitMenu[NARROW[viewer.menu], viewer, mousePos, trigger]
};
MarkWindowMenu: PROC[viewer: Viewer, mousePos: TIPUser.TIPScreenCoords] = {
MenusPrivate.MarkMenu[windowMenu, viewer, mousePos]
};
HitWindowMenu: PROC[viewer: Viewer, mousePos: TIPUser.TIPScreenCoords, trigger: [0..6)] = {
MenusPrivate.HitMenu[windowMenu, viewer, mousePos, trigger]
};
ProcessWindowResults: PUBLIC ViewerClasses.NotifyProc = {
[self: Viewer, input: LIST OF REF ANY] RETURNS[BOOL]
not monitored since notifier is synchronous
zone: Zone ← feedbackZone;
newZone: BOOLFALSE;
shift, control: BOOLFALSE;
mousePos: TIPUser.TIPScreenCoords;
viewer: Viewer ← self; -- the current viewer
FOR l: LIST OF REF ANY ← input, l.rest UNTIL l=NIL DO WITH l.first SELECT FROM
z: TIPUser.TIPScreenCoords => {
client: BOOLFALSE;
mousePos ← z;
IF feedbackZone#none THEN [viewer, client] ← ViewerOps.MouseInViewer[mousePos];
zone ← IF client OR viewer=NIL THEN none ELSE CursorZone[viewer, mousePos];
newZone ← zone#feedbackZone OR (viewer#feedbackViewer AND feedbackViewer#NIL);
};
z: ATOM => {
IF newZone THEN PostNewFeedback[viewer, zone];
SELECT zone FROM-- zone specific ops
scroll => SELECT z FROM
$M   => SetC[scrollUpDown];
$RU   => HitScroll[viewer, mousePos.mouseY, up, shift, control];
$RD, $RM => SetC[scrollUp];
$YU   => HitScroll[viewer, mousePos.mouseY, thumb, shift, control];
$YD, $YM => SetC[scrollRight];
$BU   => HitScroll[viewer, mousePos.mouseY, down, shift, control];
$BD, $BM => SetC[scrollDown];
$Control  => control ← TRUE;
$Shift   => shift ← TRUE;
ENDCASE  => NULL;
menu => SELECT z FROM
$RD, $RM => MarkMenu[viewer, mousePos];
$RU   => HitMenu[viewer, mousePos, IF shift THEN 3 ELSE 0];
$YD, $YM => MarkMenu[viewer, mousePos];
$YU   => HitMenu[viewer, mousePos, IF shift THEN 4 ELSE 1];
$BD, $BM => MarkMenu[viewer, mousePos];
$BU   => HitMenu[viewer, mousePos, IF shift THEN 5 ELSE 2];
$Control  => control ← TRUE;
$Shift   => shift ← TRUE;
ENDCASE  => NULL;
caption => SELECT z FROM
$RM, $RD => MarkWindowMenu[viewer, mousePos];
$RU   => HitWindowMenu[viewer, mousePos, IF shift THEN 3 ELSE 0];
$YM, $YD => MarkWindowMenu[viewer, growPos];
$YU   => HitWindowMenu[viewer, growPos, IF shift THEN 4 ELSE 1];
$BM, $BD => MarkWindowMenu[viewer, closePos];
$BU   => HitWindowMenu[viewer, closePos, IF shift THEN 5 ELSE 2];
$Control  => control ← TRUE;
$Shift   => shift ← TRUE;
ENDCASE  => NULL;
ENDCASE;
};
ENDCASE => TRUSTED {UserTerminal.BlinkDisplay[]};
ENDLOOP;
};
AlterColumn: PROC [v: Viewer, mx: INTEGER] = {
right: BOOL ~ mx >= v.ww/2;
column: ViewerClasses.Column ← SELECT v.column FROM
left => IF right THEN right ELSE IF colorDisplayOn THEN color ELSE right,
right => IF ~right THEN left ELSE IF colorDisplayOn THEN color ELSE left,
color => IF right THEN right ELSE left,
ENDCASE => ERROR;
ViewerOps.ChangeColumn[v, column];
};
SetC: PROC [cursor: Cursors.CursorType] = INLINE
{IF waitCount=0 AND Cursors.GetCursor[]#cursor THEN Cursors.SetCursor[cursor]};
HitScroll: PROC [v: Viewer, y: INT, op: ViewerClasses.ScrollOp, shift, control: BOOL] = {
RemoveScrollFeedback[v];
IF v.parent=NIL OR v.class.coordSys#top THEN
y ← v.ch-y; -- client coords
IF v.class.scroll#NIL THEN [] ← v.class.scroll[v, op,
SELECT op FROM
up, down => y,
ENDCASE => (100*(y+1))/v.ch, -- percent
shift, control];
PostScrollFeedback[v];
};
scrollVisible: Imager.Color = Imager.MakeStipple[122645B];
scrollInvisible: Imager.Color = Imager.MakeStipple[100040B];
PostScrollFeedback: PROC[v: Viewer] = {
top, bottom: INTEGER;
IF v.class.scroll=NIL THEN RETURN;
IF ~v.init THEN RETURN; -- avoid a race condition bug
Carets.SuspendCarets[];
[top, bottom] ← v.class.scroll[v, query, 0];
IF top IN[0..100] AND bottom IN[0..100] THEN {
context: Imager.Context ← ViewerOps.AcquireContext[v.parent, v.column=color
! ViewerOps.InvisiblePaint => GOTO Punt];
vbs: INTEGER = (IF v.border THEN windowBorderSize ELSE 0);
baseY, baseX: INTEGER;
relY1, relY2: INT; -- so won't overflow
IF v.parent#NIL AND v.parent.class.coordSys=top THEN { -- flip origin
Imager.TranslateT[context, 0, v.parent.ch];
Imager.Scale2T[context, 1, -1];
baseY ← v.parent.ch - v.wy - v.wh + vbs;
} ELSE
baseY ← v.wy + vbs;
baseX ← v.wx + vbs;
relY1 ← baseY;
relY2 ← (LONG[100-bottom]*v.ch)/100+baseY;
Imager.SetColor[context, scrollInvisible];
Imager.IntegerMaskRectangle[context, baseX, relY1, scrollBarW, relY2-relY1];
relY1 ← relY2;
relY2 ← relY2 + (LONG[bottom-top]*v.ch)/100;
Imager.SetColor[context, scrollVisible];
Imager.IntegerMaskRectangle[context, baseX, relY1, scrollBarW, relY2-relY1];
Imager.SetColor[context, scrollInvisible];
Imager.IntegerMaskRectangle[context, baseX, relY2, scrollBarW, baseY+v.ch-relY2];
ViewerOps.ReleaseContext[context];
EXITS Punt => NULL;
};
Carets.ResumeCarets[];
SetC[scrollUpDown];
};
RemoveScrollFeedback: PROC[v: Viewer] = {
Carets.SuspendCarets[];
IF v#NIL THEN {
context: Imager.Context ← ViewerOps.AcquireContext[v.parent, v.column=color
! ViewerOps.InvisiblePaint => GOTO Punt];
baseX, baseY: INTEGER;
vbs: INTEGER = (IF v.border THEN windowBorderSize ELSE 0);
IF v.parent#NIL AND v.parent.class.coordSys=top THEN {
flip origin
Imager.TranslateT[context, 0, v.parent.ch];
Imager.Scale2T[context, 1, -1];
baseY ← v.parent.ch - v.wy - v.wh + vbs;
} ELSE
baseY ← v.wy + vbs;
baseX ← v.wx + vbs;
Imager.SetColor[context, Imager.white];
Imager.IntegerMaskRectangle[context, baseX, baseY, scrollBarW, v.ch];
ViewerOps.ReleaseContext[context];
EXITS Punt => NULL;
};
Carets.ResumeCarets[];
};
InternalDrawCaptionMenu: INTERNAL PROC[v: Viewer, guard: BOOL] = {
IF v.visible AND NOT v.iconic THEN {
context: Imager.Context ← ViewerOps.AcquireContext[v.parent, v.column=color
! ViewerOps.InvisiblePaint => GOTO Punt];
DrawCaption: PROC = {
x: INTEGER ← v.wx+windowBorderSize;
y: INTEGER ← v.wy+v.wh-captionHeight;
guardDestroy: BOOLFALSE;
IF guard THEN guardDestroy ← v.guardDestroy
OR (v.newVersion AND v.link=NIL AND NOT v.saveInProgress);
Imager.IntegerClipRectangle[context, x, y, v.ww-(2*windowBorderSize), captionHeight];
Imager.SetColor[context, Imager.white];
Imager.IntegerMaskRectangle[context, x, y, v.ww, captionHeight];
Imager.SetColor[context, Imager.black];
MenusPrivate.ReComputeWindowMenus[v, guardDestroy, colorDisplayOn];
MenusPrivate.DrawMenu[v, windowMenu, context];
};
Imager.DoSave[context, DrawCaption];
ViewerOps.ReleaseContext[context];
EXITS Punt => NULL;
};
};
DrawCaptionMenu: PUBLIC ENTRY PROC[v: Viewer, guard: BOOL] = {
ENABLE UNWIND => NULL;
IF v=feedbackViewer THEN InternalDrawCaptionMenu[v, guard];
};
PostCaptionFeedback: ENTRY PROC[v: Viewer] = {
ENABLE UNWIND => NULL;
InternalDrawCaptionMenu[v, TRUE];
SetC[bullseye];
};
RemoveCaptionFeedback: ENTRY PROC[v: Viewer] = {
ENABLE UNWIND => NULL;
IF v#NIL THEN {
MenusPrivate.ClearMenu[windowMenu, v, FALSE];
ViewerOps.PaintViewer[v, caption];
};
};
PostMenuFeedback: PROC[v: Viewer] = {
SetC[bullseye];
};
RemoveMenuFeedback: PROC[v: Viewer] = {
menu: MenusPrivate.MenuInfo = NARROW[v.menu];
MenusPrivate.ClearMenu[menu, v];
};
waitCount: PUBLIC INTEGER ← 0;
WaitCursor: PUBLIC ENTRY PROC [cursor: Cursors.CursorType ← hourGlass] = {
ENABLE UNWIND => NULL;
IF InputFocus.inputEnabled THEN Cursors.SetCursor[cursor];
waitCount ← waitCount + 1;
};
UnWaitCursor: PUBLIC ENTRY PROC = {
ENABLE UNWIND => NULL;
waitCount ← MAX[0, waitCount - 1];
IF waitCount=0 THEN RestoreCursor[];
};
RestoreCursor: PUBLIC PROC = {
IF InputFocus.inputEnabled THEN
TRUSTED{InterminalExtra.InsertAction[[contents: deltaMouse[[0,0]]]]}
};
StartColorViewers: PUBLIC PROC [screenPos: WindowManager.ScreenPos,
bitsPerPixel: CARDINAL] = {
IF colorDisplayOn THEN StopColorViewers[];
colorDisplayOn ← ColorWorld.TurnOnColor[bitsPerPixel, (screenPos=left)];
IF ~colorDisplayOn THEN {
MessageWindow.Append["Sorry, you don't have a color display.", TRUE];
MessageWindow.Blink[];
RETURN;
};
ViewerOps.InitialiseColorPainting[];
};
StopColorViewers: PUBLIC PROC = {
DoColorViewer: ViewerOps.EnumProc = {
IF v.column=color THEN {
ViewerOps.CloseViewer[v];
ViewerOps.ChangeColumn[v, left];
};
};
IF ~colorDisplayOn THEN RETURN;
ViewerOps.EnumerateViewers[DoColorViewer];
ColorWorld.TurnOffColor[];
colorDisplayOn ← FALSE;
};
BuildWindowMenus: PROC = {
Menus.RegisterMenu[windowDestroyMenu];
Menus.RegisterMenu[windowGuardedDestroyMenu];
Menus.RegisterMenu[windowMovementMenu];
Menus.RegisterMenu[windowColorMenu];
Menus.RegisterMenu[windowSizeMenu];
Menus.RegisterMenu[windowDebugMenu];
MenusPrivate.ViewerlessAddMenu[$windowDestroyMenu];
MenusPrivate.ViewerlessAddMenu[$windowGuardedDestroyMenu];
MenusPrivate.ViewerlessAddMenu[$windowMovementMenu];
MenusPrivate.ViewerlessAddMenu[$windowColorMenu];
MenusPrivate.ViewerlessAddMenu[$windowSizeMenu];
MenusPrivate.ViewerlessAddMenu[$windowDebugMenu];
we have to actually instantiate these pointers to start pointing at SOMETHING
growPos ← NEW[TIPUser.TIPScreenCoordsRec ← [0, FALSE, 0]];
closePos ← NEW[TIPUser.TIPScreenCoordsRec ← [0, FALSE, 0]];
};
colorDisplayOn: PUBLIC BOOLFALSE; -- color display status
windowMenu: PUBLIC MenusPrivate.MenuInfo;
growPos: PUBLIC TIPUser.TIPScreenCoords;
closePos: PUBLIC TIPUser.TIPScreenCoords;
windowDestroyMenu: Menus.Menu = NEW[Menus.MenuRec ← [
name: $windowDestroyMenu,
beginsActive: TRUE, breakBefore: FALSE, breakAfter: FALSE,
notify: ViewerMenuNotifier,
entries: LIST[
NEW[Menus.EntryRec ← [
name: "Destroy",
actions: LIST[NEW[Menus.ActionRec ← [
triggers: Menus.allTriggers,
input: LIST[$DestroyViewer]
]]]
]]
]
]];
windowGuardedDestroyMenu: Menus.Menu = NEW[Menus.MenuRec ← [
name: $windowGuardedDestroyMenu,
beginsActive: TRUE, breakBefore: FALSE, breakAfter: FALSE,
notify: ViewerMenuNotifier,
entries: LIST[
NEW[Menus.EntryRec ← [
name: "Destroy",
guarded: TRUE,
actions: LIST[NEW[Menus.ActionRec ← [
triggers: Menus.allTriggers,
input: LIST[$DestroyViewer],
popupDoc: "Destroy the Viewer",
guardResponse: "Edits will be discarded..."
]]]
]]
]
]];
windowMovementMenu: Menus.Menu = NEW[Menus.MenuRec ← [
name: $windowMovementMenu,
beginsActive: TRUE, breakBefore: FALSE, breakAfter: FALSE,
notify: ViewerMenuNotifier,
entries: LIST[
NEW[Menus.EntryRec ← [
name: "Adjust",
actions: LIST[NEW[Menus.ActionRec ← [
triggers: Menus.allTriggers,
input: LIST[$AdjustViewer],
popupDoc: "Adjust Viewer Size"
]]]
]],
NEW[Menus.EntryRec ← [
name: "Top",
actions: LIST[NEW[Menus.ActionRec ← [
triggers: Menus.allTriggers,
input: LIST[$MoveViewerToTop],
popupDoc: "Move Viewer To Top Of Column"
]]]
]],
NEW[Menus.EntryRec ← [
name: "Left",
displayData: "—",
actions: LIST[
NEW[Menus.ActionRec ← [
triggers: Menus.allShifts,
input: LIST[$MoveViewerToLeftColumn, $GrowViewer],
popupDoc: "Move To Left Column and Grow"
]],
NEW[Menus.ActionRec ← [
triggers: Menus.allTriggers,
input: LIST[$MoveViewerToLeftColumn],
popupDoc: "Move Viewer To Left Column"
]]
]
]],
NEW[Menus.EntryRec ← [
name: "Right",
displayData: "—",
actions: LIST[
NEW[Menus.ActionRec ← [
triggers: Menus.allShifts,
input: LIST[$MoveViewerToRightColumn, $GrowViewer],
popupDoc: "Move To Right Column and Grow"
]],
NEW[Menus.ActionRec ← [
triggers: Menus.allTriggers,
input: LIST[$MoveViewerToRightColumn],
popupDoc: "Move Viewer To Right Column"
]]
]
]]
]
]];
windowColorMenu: Menus.Menu = NEW[Menus.MenuRec ← [
name: $windowColorMenu,
beginsActive: TRUE, breakBefore: FALSE, breakAfter: FALSE,
notify: ViewerMenuNotifier,
entries: LIST[
NEW[Menus.EntryRec ← [
name: "Color",
actions: LIST[
NEW[Menus.ActionRec ← [
triggers: Menus.allShifts,
input: LIST[$MoveViewerToColorColumnAndGrow, $GrowViewer],
popupDoc: "Move To Color Display and Grow"
]],
NEW[Menus.ActionRec ← [
triggers: Menus.allTriggers,
input: LIST[$MoveViewerToColorColumn],
popupDoc: "Move Viewer To Color Display"
]]
]
]]
]
]];
windowSizeMenu: Menus.Menu = NEW[Menus.MenuRec ← [
name: $windowSizeMenu,
beginsActive: TRUE, breakBefore: FALSE, breakAfter: FALSE,
notify: ViewerMenuNotifier,
entries: LIST[
NEW[Menus.EntryRec ← [
name: "Grow",
actions: LIST[
NEW[Menus.ActionRec ← [
triggers: Menus.allTriggers,
input: LIST[$GrowViewer],
popupDoc: "Grow the Viewer"
]]
]
]],
NEW[Menus.EntryRec ← [
name: "Close",
actions: LIST[
NEW[Menus.ActionRec ← [
triggers: Menus.allTriggers,
input: LIST[$CloseViewer],
popupDoc: "Close the Viewer"
]]
]
]]
]
]];
windowDebugMenu: Menus.Menu =
[name: $windowDebugMenu,
beginsActive: TRUE,
breakBefore: FALSE,
breakAfter: FALSE,
notify: ViewerMenuNotifier,
entries: LIST[
["+Debug", FALSE, NIL, LIST[
[LIST[all],LIST[$IncreaseDebug],"Increase debugging level", "", NIL,NIL,NIL]
] ],
["-Debug", FALSE, NIL, LIST[
[LIST[all],LIST[$DecreaseDebug],"Decrease debugging level", "", NIL,NIL,NIL]
] ],
["NoBug", FALSE, NIL, LIST[
[LIST[all],LIST[$NoDebug],"Turn Off Debugging", "", NIL,NIL,NIL]
] ]
]
];
ViewerMenuNotifier: ViewerClasses.NotifyProc = {
[self: Viewer, input: LIST OF REF ANY]
FOR current: LIST OF REF ANY ← input, current.rest UNTIL current = NIL DO
SELECT current.first FROM
$DestroyViewer => Destroy[self];
$CloseViewer => ViewerOps.CloseViewer[self];
$GrowViewer => ViewerOps.GrowViewer[self];
$MoveViewerToTop => ViewerOps.TopViewer[self];
$AdjustViewer => ViewerOps.Adjust[self];
$MoveViewerToLeftColumn => ViewerOps.ChangeColumn[self, left];
$MoveViewerToRightColumn => ViewerOps.ChangeColumn[self, right];
$MoveViewerToColorColumn => MoveToColorColumn[viewer: self, doGrow: FALSE];
$MoveViewerToColorColumnAndGrow => MoveToColorColumn[viewer: self, doGrow:TRUE];
$IncreaseDebug => MenusPrivate.AlterDebuggingLevel[amount: 1, relative: TRUE];
$DecreaseDebug => MenusPrivate.AlterDebuggingLevel[amount: -1, relative: TRUE];
$NoDebug => MenusPrivate.AlterDebuggingLevel[amount: 0, relative: FALSE];
ENDCASE => ERROR;
ENDLOOP;
};
Destroy: PROC[viewer: Viewer] = {
IF ~viewer.inhibitDestroy THEN ViewerOps.DestroyViewer[viewer]
ELSE {MessageWindow.Append["Sorry, this viewer can not be destroyed.", TRUE];
MessageWindow.Blink[]};
};
MoveToColorColumn: PRIVATE PROC[viewer: Viewer, doGrow: BOOLFALSE] = {
IF colorDisplayOn THEN {
ViewerOps.ChangeColumn[viewer, color];
IF doGrow THEN ViewerOps.GrowViewer[viewer]}
ELSE {
MessageWindow.Append["Sorry, the color display is not available.", TRUE];
MessageWindow.Blink[];
};
};
BuildWindowMenus[];
END.