PopUpButtons.mesa
Copyright Ó 1991 by Xerox Corporation. All rights reserved.
Last tweaked by Mike Spreitzer on September 6, 1989 5:42:08 pm PDT
Willie-s, January 7, 1992 3:57 pm PST
DIRECTORY Imager, List, Rope, TIPUser, ViewerClasses;
PopUpButtons: CEDAR DEFINITIONS = {
PopUpButtons are the long-discussed and anticipated attack on the problem of overloading buttons. A PopUpButton normally looks like a Button, and can decode mouse button and control and shift keys like a Button. But if the user is too slow (i.e., not fast) in hitting the button, a pop-up menu is presented. The first 12 (or 6 or 4 or 3 or 2 or 1 or 0) entries in the pop-up menu correspond to the mouse button and control/shift key decoded possibilities.
ROPE: TYPE = Rope.ROPE;
ROPEList: TYPE = LIST OF ROPE;
Viewer: TYPE = ViewerClasses.Viewer;
PropList: TYPE = List.AList;
Class: TYPE = REF ClassPrivate;
ClassPrivate: TYPE;
Each PopUpButton is an instance of some class of PopUpButton. You must first make a class, then instantiate it. In general, a tool should make a class for every kind of button it has, and instantiate those classes at every instance of the tool.
MakeClass: PROC [spec: ClassSpec] RETURNS [class: Class];
QuaClass: PROC [REF ANY] RETURNS [MaybeClass];
MaybeClass: TYPE ~ RECORD [is: BOOL, class: Class];
GetSpec: PROC [class: Class] RETURNS [spec: ClassSpec];
AmbushClass: PROC [class: Class, spec: ClassSpec];
Redefines the class.
ClassSpec: TYPE = RECORD [
classData: REF ANY ¬ NIL,
For client use.
proc: PopUpButtonProc,
choices: ChoiceList ¬ NIL,
The order determines the coding; mouse button is least significant, then shift, then control.
decodeMouseButton, decodeShift, decodeControl: BOOL ¬ TRUE,
It's OK to leave these TRUE even if you don't have that many choices.
fork: BOOL ¬ TRUE,
guarded: BOOL ¬ FALSE,
disableDecoding: BOOL ¬ FALSE, --when decoding is disabled, quick-clicks are ineffective, but the menus are arranged according to the decoding
headMenu: BOOL ¬ TRUE, --TRUE <=> the menu is headed (by the class's image or the first choice's image)
image: Image ¬ NIL,
doc, disarmMsg: ROPE ¬ NIL,
help: Help ¬ NIL
];
PopUpButtonProc: TYPE = PROC [view: View, instanceData, classData, key: REF ANY];
For menus, the view field will contain the viewer containing the menu; for buttons, view will be the button Viewer itself; in general, view is that `visible' entity from which we are popping; view need not be a viewer. classData comes from the class, and instanceData from the instance. key indicates which choice was made.
Implementation guide: the proc that calls a button proc has an ! ABORT => CONTINUE catch phrase so that the client may make use of Process.Abort to cancel execution.
View: TYPE ~ REF ANY;
This is the TYPE used for the hook from PopUpButtons back to whatever the menu pops up from (a Button Viewer, or Gargoyle object, or whatever).
ChoiceList: TYPE = LIST OF Choice;
Choice: TYPE = RECORD [
key: REF ANY,
This is what's passed to the PopUpButtonProc to identify the choice taken.
doc: ROPE,
This describes this choice for the user. This doc should be short enough to fit in the message window. This doc may be NIL, but you don't want to shaft your users, do you?
image: Image ¬ NIL
This specifies what's displayed in the pop-up menu. If NIL, it is taken from the key, which must then be reasonably straightforward to convert to a ROPE.
];
If choices is non-empty, and a mouse button is held down in the button for more than a short while, a pop-up menu of the choices is created and selected from. Otherwise, the choice is taken from the choices according to the combination of mouse button, control, and shift keys used and the decoding instructions (decodeMouseButton, decodeShift, and decodeControl).
nullChoice: Choice = [NIL, NIL, NIL];
Indicates a no-op.
Image: TYPE = REF ImagePrivate;
ImagePrivate: TYPE = RECORD [
size: Imager.VEC,
Draw: Drawer,
data: REF ANY ¬ NIL
];
Drawer: TYPE = PROC [image: Image, context: Imager.Context, bounds: Imager.Rectangle, state: VisibleState];
bounds describes the area to paint, in the context's current coordinate system; the caller tried to make it same as image.size; it might well be bigger; it should only be smaller if there's real trouble making it big enough. state gives bits that may specialize the appearance. Do not assume the area has been cleared. Not called under DoSave, so please restore context's transformation upon exit; other context state variables may be changed (and of course you can't assume they have any particular value upon entry).
VisibleState: TYPE ~ RECORD [highlight, executing, guarded: BOOL];
highlight indicates the user is pointing at this image and wants it highlighted. executing is only relevent for button images; it is irrelevent and FALSE for pop-up menu entry images. guarded indicates a button image should be painted in a way so as to indicate it's guarded; NOT guarded indicates the button is not guarded or is armed; irrelevent and FALSE for pop-up menu entry images.
TransformImage: PROC [i: Image, t: Imager.Transformation] RETURNS [Image];
Drawing the result = drawing i with t premultiplied.
QuaTransformed: PROC [Image] RETURNS [MaybeTransformed];
MaybeTransformed: TYPE ~ RECORD [is: BOOL, i: Image, t: Imager.Transformation];
QuaTransformed[TransformImage[i, t]] = [TRUE, i, t]; is=FALSE => i=given and t=identity.
ImageForRope: PROC [rope: ROPE, colors: Colors ¬ NIL, font: Imager.Font ¬ NIL, align: Align ¬ bottomLeft] RETURNS [image: Image];
QuaRopeImage: PROC [Image] RETURNS [MaybeRope];
MaybeRope: TYPE ~ RECORD [is: BOOL, rope: ROPE, colors: Colors, font: Imager.Font, align: Align, transform: Imager.Transformation];
When is=FALSE, rope=NIL and no guarantees about the other fields.
RopeImage: TYPE = REF RopeImagePrivate;
RopeImagePrivate: TYPE = RECORD [
text: ROPE,
colors: Colors,
font: Imager.Font,
align: Align,
org: Imager.VEC
];
defaultFont: Imager.Font;
defaultColors, inverseColors: Colors;
dontPaint: READONLY Imager.SpecialColor;
Colors: TYPE = REF ColorsPrivate;
ColorsPrivate: TYPE = ARRAY --button highlight--BOOL OF ARRAY --executing--BOOL OF ARRAY --guarded--BOOL OF RECORD [foreground, background: Imager.Color, strike: Imager.Color ¬ NIL];
A defaulted strike means to use the foreground color.
Align: TYPE ~ RECORD [x, y: REAL--in [0.0 .. 1.0]--];
bottomLeft: Align ~ [0.0, 0.0];
center: Align ~ [0.5, 0.5];
Help: TYPE ~ REF HelpPrivate;
HelpPrivate: TYPE ~ RECORD [
variant: SELECT kind: HelpKind FROM
none => [],
docs => [docs: HelpDocList],
proc => [Proc: HelpProc, data: REF ANY ¬ NIL],
ENDCASE];
HelpKind: TYPE ~ {none, docs, proc};
HelpDocList: TYPE ~ LIST OF HelpDocument;
HelpDocument: TYPE ~ RECORD [
filename: ROPE,
A document to be opened; if only a short name, will look in current working directory and version maps for highest version.
searches: ROPEList
The document is split this many ways, and a word-search is done on each ROPE.
];
HelpProc: TYPE ~ PROC [view: View, instSpec: InstanceSpec, data: REF ANY];
noHelp: REF HelpPrivate[none];
HelpFromDoc: PROC [filename: ROPE, searches: ROPEList ¬ NIL] RETURNS [Help]
~ INLINE {RETURN [NEW [HelpPrivate[docs] ¬ [docs[LIST[[filename, searches]]]]]]};
HelpFromList: PROC [list: HelpDocList] RETURNS [Help]
~ INLINE {RETURN [NEW [HelpPrivate[docs] ¬ [docs[list]]]]};
HelpFromProc: PROC [Proc: HelpProc, data: REF ANY ¬ NIL] RETURNS [Help]
~ INLINE {RETURN [NEW [HelpPrivate[proc] ¬ [proc[Proc, data]]]]};
UnionHelp: PROC [a, b: Help] RETURNS [Help];
DeduceHelp: PROC [buttonName, installationDir, clientPackageName: ROPE ¬ NIL, clientPackageGlobalFrame: POINTER ¬ NIL] RETURNS [Help];
GuessPackageName: PROC [packageGlobalFrame: POINTER] RETURNS [packageName: ROPE];
DoHelp: PROC [view: View, instSpec: InstanceSpec, help: Help];
DefaultSize: PROC [viewerName: ROPE ¬ NIL, class: Class ¬ NIL, image: Image ¬ NIL, border: BOOL ¬ TRUE] RETURNS [ww, wh: INTEGER];
It is of course accurate to omit the Class only when you know the image won't come from it.
Instance: TYPE = REF InstancePrivate;
InstancePrivate: TYPE;
Instantiate: PROC [class: Class, viewerInfo: ViewerClasses.ViewerRec ¬ [], instanceData: REF ANY ¬ NIL, image: Image ¬ NIL, help: Help ¬ NIL, paint: BOOL ¬ TRUE] RETURNS [button: Viewer];
The image used for this button is the first non-NIL one found along the following search path: the image argument to Instantiate, the image of the class being instantiated, the name of the viewer being instantiated, and the image of the first choice. If the viewerInfo.wh and ww are defaulted, aesthetic values will be chosen based on the image. Defaulting the wx and wy, with parent=NIL creates a "system" button appended after the most recent system button after the message window. The help used for this button is the first one found of: the help argument to Instantiate, the help of the class, and the result of trying certain standard heuristics.
GeneralInstantiate: PROC [class: Class, Paint: PaintProc, InTest: InTestProc, name: ROPE ¬ NIL, instanceData: REF ANY ¬ NIL, image: Image ¬ NIL, help: Help ¬ NIL] RETURNS [Instance];
PaintProc: TYPE ~ PROC [view: View, Decide: PaintDecider, decisionData: REF ANY];
A PaintProc first consults Decide, while holding some locks on painting, and then conditionally paints according to the returned state. The decisionData is simply there for the PaintDecider.
PaintDecider: TYPE ~ PROC [decisionData: REF ANY] RETURNS [paint: BOOL, state: VisibleState];
InTestProc: TYPE ~ PROC [view: View, coords: TIPUser.TIPScreenCoords] RETURNS [in: BOOL];
The coordinates of mouse are relative to the screen, rather than to the view (whatever that might be).
QuaInstance: PROC [REF ANY] RETURNS [MaybeInstance];
ViewerQuaInstance: PROC [Viewer] RETURNS [MaybeInstance];
MaybeInstance: TYPE ~ RECORD [is: BOOL, inst: Instance];
InstanceToSpec: PROC [Instance] RETURNS [InstanceSpec];
ViewerToSpec: PROC [button: Viewer] RETURNS [is: InstanceSpec];
InstanceSpec: TYPE = RECORD [
class: Class,
instanceData: REF ANY,
name: ROPE,
specdImage, image: Image,
specdHelp, help: Help,
processProps: PropList
];
InstanceToState: PROC [Instance] RETURNS [VisibleState];
RawNotify: PROC [view: View, instance: Instance, actionQueue, action: REF ANY];
For PopUpButtons not anchored in Viewers, call this with the user input. In DCedar, actionQueue is a ClassIncreek.InscriptPosition and action is a REF ClassIncreek.ActionBody; in PCedar, actionQueue is a UserInput.Handle and action is a REF UserInput.ActionBody --- unless things have changed since this comment was written.
AmbushGeneralInstance: PROC [view: View, inst: Instance, class: Class ¬ NIL, instanceData: REF ANY ¬ NIL, image: Image ¬ NIL, help: Help ¬ NIL, specInstanceData, specImage, specHelp, specProcessProps: BOOL ¬ FALSE, paint: BOOL ¬ TRUE];
Changes the instance to be as if some parameters to Instantiate were different (except does not change size, even when image size changes; neither will it change view). default values mean don't change. specFoo=TRUE or foo#default means use given value of foo.
AmbushInstance: PROC [button: Viewer, class: Class ¬ NIL, instanceData: REF ANY ¬ NIL, image: Image ¬ NIL, help: Help ¬ NIL, specInstanceData, specImage, specHelp, specProcessProps: BOOL ¬ FALSE, paint: BOOL ¬ TRUE]
~ INLINE {AmbushGeneralInstance[button, ViewerQuaInstance[button].inst, class, instanceData, image, help, specInstanceData, specImage, specHelp, specProcessProps]};
BeRope: PROC [r: ROPE] RETURNS [ROPE] = INLINE {RETURN [r]};
Cedar trivia question #47: why is this procedure here?
}.