don@AMANDA.CS.UMD.EDU (Don Hopkins) (03/10/89)
This is a new version of pie menus for NeWS, with several bugs fixed, some defaults changed, and some new features added. They don't use retained canvas by default, and they don't use savebehind on a monochrome display, since it seems to occasionaly tickle some server bug and leave turds on the screen. Now pie menus support mouse ahead display supression based on mouse motion (instead of a timeout like before), and "Direct Pac-Manipulation" feedback. If you press the menu button down, without moving the mouse, the menu should pop up almost immediatly. If you're moving the mouse around, the menu display is supressed until you stop moving or make a selection. If you "mouse ahead", and make a selection quickly before the menu is displayed, then you will see a pac-man blink onto the screen where the menu would have been and chomp the slice you selected. You can tell you're getting the hang of it and really racking up them points when you can make the pac-man blink all the way through several levels of nested menus! But other people can look at the screen and see what menu selections you're making, so if you'd rather that your menu selections be invisible and mysterious, you can set the class variable /Wocka to false. If you can tolerate it, you can increate /WockaTime to get a slower pac-man. (PS: Lawyers, please note, this uses a non-profit, non-denominational, generic pac-man, whose green card is on file. All disclaimers in the universe apply, but are not listed here due to space limitations. "moveto", "arc", and "closepath" are trademarks of Adobe. ;-) -Don %! %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % @(#)piemenu.ps % % Pie menu class implementation. % Copyright (C) 1987. % By Don Hopkins. % All rights reserved. % % Simple Simon popped a Pie Men- % u upon the screen; % With directional selection, % all is peachy keen! % % Pie Menus are provided for UNRESTRICTED use provided that this % copyright message is preserved on all copies and derivative works. % This is provided without any warranty. No author or distributor % accepts any responsibility whatsoever to any person or any entity % with respect to any loss or damage caused or alleged to be caused % directly or indirectly by this program. This includes, but is not % limited to, any interruption of service, loss of business, loss of % information, loss of anticipated profits, core dumps, abuses of the % virtual memory system, or any consequential or incidental damages % resulting from the use of this program. % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % May 28 1987 Don Hopkins % First cut, based on LitePullRightMenu. % % May 30 1987 Don Hopkins % Uses "Thing"s from liteitem.ps for key labels. A thing can be a % string, or a keyword. The string is shown in MenuFont. The % keyword can be either the name of an icon in icondict, or bound % on the dict stack to an executable function. The function takes % a boolean as input; if true, it draws itsself; if false, it % returns its width and height. % NOTE: in NeWS 1.1, a Thing is either: a string, a keyword (icon % name only), an executable array (taking /draw or /size as % input), or an Object dict (sent a /draw and /size messages). % See the colornames demo! % Better label positioning scheme: top or bottom justify labels at % at the very bottom or top of the menu, and left or right justify % labels on the right or left sides of the menu. The points % relative to which the labels are justified are positioned at % evenly spaced angles in a circle around the menu center. The % instance variable PieInitialAngle is the angle of the first % point. LabelRadius is the distance from the menu center to each % point, calculated as: % LabelMinRadius + LabelRadiusPerKey * <the number of menu keys> % NOTE: LabelRadiusPerKey is obsolete now. LabelRadius is automatically % pushed out until no labels overlap. % If the menu can't be centered on the location of the button % event that invoked it, then warp the cursor to the menu center % plus how much it has moved since the button down event, so that % pop up menus near the screen edge and static menus work % correctly. But ARRRGH FOO: setcursorlocation is broken!!! It % moves the cursor, but next time you move the mouse, the cursor % pops back to where it used to be! The Sun X server used to have % the same problem with XWarpMouse. Makes you wonder. Well, % anyway, I commented it out, because it's more confusing with % setcursorlocation broken than it is not warping at all. % NOTE: It's fixed now, so it works right! % % July 13 1987 Don Hopkins % Fixed up handling of retained canvases. Changed SliceLines to % SliceWedges, and made it draw wedges inside of LabelRadius. % Put in MoveMenu, which moves the menu, making sure that it's % completely on the screen, and the mouse is in the menu center. % (The latter part should be uncommented when setcursorlocation % is fixed.) Changed slice highlighting. % Implemented an oops function. Pressing the adjust button moves % the top menu so the cursor's back in its center. (Well, % setcursorlocation is still broken ...) If the mouse is already % in the menu center, then the menu is popped down and the % one below it is moved so its center is at the cursor. % NOTE: Oops works much better now that setcursorlocation is fixed! % On AdjustButton Down (Ker), the cursor moves to the menu center. % On AdjustButtonUp (Chunk), if the cursor is still in the menu % center, the menu is popped down, leaving you in the previous % menu (if any), at the location you invoked this menu from. % % July 24 1987 Don Hopkins % Changed to work with NeWS 1.1 litemenu.ps ... (just in time for SIGGRAPH!) % % August 20, 1987 Don Hopkins % Uncommented out and fixed the mouse warping code. Added display % interruption, so that if the events that would make the menu % selection are already in the event queue, then the menu is not % displayed. I'm not sure if the way I'm doing it is the best way, % but it seems to work. I'm still not sure that the way mouse warping % near the screen edge and display interruption are interacting is % really correct. It should not warp the mouse if the events are % already in the queue, so maybe warping should be defered, as well. % There was also a problem with /Damaged events generated when the % canvas is reshaped, being put into the queue before the /MapMenu % event is. This was causing the menu to be painted before the % defered mapping took place, which is not the way I think it should % work. So I kludged around it. There's got to be a safer way to % make it work right. % NOTE: This kludge has been flushed in favor of drawing the menu % before it's mapped. % A delay has been added to the map event, to facilitate mouse-ahead % display suppression. If you click down and up, without moving out % of the menu center, you will get the menu as soon you let up, but % if you click down and move, without letting up, there will be a % delay before it is mapped, during which time if you let up in an % active slice region, the mapping of the menu will be suppressed % (unless there is a submenu), and the selection you have chosen % acted upon immediatly. The submenu delay is shorter than the delay % of a menu with no parent, so that when you mouse-ahead quickly % into a submenu, you will see the submenu mapped first. (Because % the parent menu is less important than the active submenu, now % that you've already made the selection.) This may sound quite % bizarre, but it seems to work pretty nicely for me. % % March 29, 1988 Don Hopkins % Lots of changes have been made, too many to go into excruciating % detail, but I've put notes in the above comments to bring them % somewhat up to date. Please destroy any evil old copies of % piemenu.ps and replace them with this!!! % % August 28, 1988 Don Hopkins % Fixed "go!" so the framebuffer's event manager would not end up % with the currentcanvas of the process from which it was invoked. % (This was causing damage on the framebuffer not to be repainted % if piemenu.ps was run from a menu.) % Added the DontSetDefaultMenu flag. % % February 17 1989 Don Hopkins % Changed MapMenuEvent handler so that mapping is defered until % the mouse stops moving around. % % March 7 1989 Don Hopkins % Finally figure out some sort of light-weight feedback to use with % mouse-ahead display suppression, short of mapping the menu. When % popping down a menu whose display was supressed, draw a circle % where the menu would have been, with the selected slice cut out. % (Direct Pac-Manipulation feedback.) % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Things to do: % % Teach it to use items as menu keys. Create PieItems like buttons, % cycles, sliders and pull-out menus based on the distance, % etc... (Use Things that are Objects!) % % Make each slice a canvas, and map just the choosen slices. Leave % a trail of wedges to the current active submenu. % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% systemdict begin systemdict /Item known not { (NeWS/liteitem.ps) run } if systemdict /LiteMenu known not { (NeWS/litemenu.ps) run } if %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Utilities % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Replace the go! function with one that starts a root event manager % that listens for (and ignores) menu button up events. This is so they % don't get dropped on the floor before a pie menu can express interest % in them. (Crucial for effective mouse-ahead!) /go! { verbose? { (Starting root eventmgr\n) print } if /rooteventmgr where { pop rooteventmgr type /processtype eq { rooteventmgr killprocess } if } if { countdictstack 1 sub {end} repeat framebuffer setcanvas /rooteventmgr [ /rootmenu where { pop MenuButton { {newprocessgroup /showat rootmenu send} fork pop } /DownTransition framebuffer eventmgrinterest MenuButton { CurrentEvent redistributeevent } null null eventmgrinterest dup /Priority -5 put AdjustButton { CurrentEvent redistributeevent } null null eventmgrinterest dup /Priority -5 put } if /Damaged {newprocessgroup damagepath clipcanvas PaintRoot newpath clipcanvas} null framebuffer eventmgrinterest ] forkeventmgr def } fork pop } def /rooteventmgr where { pop rooteventmgr type /processtype eq { go! } if } if % Coerce an angle to be >=0 and <360. % Note: mod returns integers, so's no good. /NormalAngle { % angle => angle dup 0 lt { dup 360 sub 360 idiv 360 mul sub } if dup 360 ge { dup 360 idiv 360 mul sub } if } def % From demomenu.ps % Fake method to send to a menu that returns a copy of the menu in the % new menu style. Recursivly changes all sub-menus. One thing to look % out for is that it does not change variables bound to the sub-menus % that were changed, so setting /rootmenu to the result of sending % /flipstyle to rootmenu will give you a new root menu, with a new % terminal sub-menu, but /terminalmenu will still be bound to the old % one, so sending messages to terminalmenu will not change the % terminal menu you get under the new rootmenu. But sending /flipstyle % to terminalwindow would not update the terminal menu under rootmenu. % So get your changes in before you flip styles! Or use /searchkey to % find the new menu, and re-def it in systemdict. /flipstyle { % - => newmenu 0 1 MenuActions length 1 sub { dup getmenuaction % fixed to use getmenuaction! dup type /dicttype eq { /flipstyle exch send % i menu' MenuActions 3 1 roll put % - } {pop pop} ifelse } for MenuKeys MenuActions /new DefaultMenu send } def % Override flipdefaultmenustyle, a function invoked from the user % interface menu. /flipdefaultmenustyle { % - => - (Flips default menu style) /DefaultMenu DefaultMenu SunViewMenu eq {PieMenu} {SunViewMenu} ifelse store } def %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % SimplePieMenu class % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% /SimplePieMenu LiteMenu %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Instance variables % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% dictbegin % The slice currently painted. /PaintedValue null def % Inner radius around which labels are positioned. Based LabelMinRadius, % LabelRadiusPerKey, and the length of MenuKeys. /LabelRadius null def % Pie menu outer radius. Based on LabelRadius and the bounding boxes of % the Key Things. /PieRadius null def % The number of degrees a slice takes up. Based on length of MenuKeys. /PieSliceWidth null def % The current direction in degrees from the menu center to the cursor. /PieDirection null def % The current distance from the menu center to the cursor. /PieDistance null def % Angle used in loops. /ThisAngle null def % Amount to move the menu so that it fits entirely on the screen. /DeltaX null def /DeltaY null def % Flag to remember if we've gotten a menu button down event before. /GotDown false def % Interruptable display event /MapMenuEvent null def /CurX 0 def /CurY 0 def dictend %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Class variables % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% classbegin % Highlight: true strokes, false fills. /StrokeSelection false def % Width of border just inside PieRadius perimiter. /Border 3 def % Gap between outermost label edge and border. /Gap 9 def % Radius of numb hole in menu center that makes no menu selection. /NumbRadius 14 def % Fudge factors for menu positioning. /MouseXDelta 0 def /MouseYDelta -3 def % Draw lines delimiting slices. /SliceWedges true def % Draw arrows in the directions of slices. /SliceArrows false def % Drill a hole through the menu center, as big as NumbRadius. /NumbHole false def % Save the bits so pop-up is fast. % /RetainCanvas? true def /RetainCanvas? false def % Nice menu font... /MenuFont /Helvetica-Bold findfont 12 scalefont def % Draw arrow pointing to current selection? /HiLiteWithArrow? true def % Menu line attributes /MenuLineWidth 0 def /MenuLineCap 1 def /MenuArrowWidth 1 def /MenuArrowCap 1 def % Minimum radius for label positioning. /LabelMinRadius 25 def % Radius to step by when sizing menu /LabelRadiusStep 5 def % Extra radius to add when sizing menu /LabelRadiusExtra 10 def % Direction in which the keys are laid out around the circle. /Clockwise true def % The angle at which the first key is placed. /PieInitialAngle 90 def % up % Don't ask. /SplatFactor 0 def % Delays to use before mapping, if a button up has not happened yet. /MapLongDelay .6 60 div def % root menu popup delay /MapShortDelay .25 60 div def % submenu popup delay /NoMapDist 10 def % Direct Pac-Manupulation Feedback /Wocka true def /WockaTime .05 60 div def %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Class methods % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Calculate and set the menu % LabelRadius, PieRadius, MenuWidth, and MenuHeight. Shape the canvas % and set the cursor. /layout { gsave MenuFont setfont initmatrix /PieSliceWidth 360 MenuKeys length 1 max div store % Get the size of all the keys, and point them in the right direction /ThisAngle PieInitialAngle store MenuItems { begin w null eq {/Key load ThingSize /h exch def /w exch def} if /ang ThisAngle def /dx ang cos def /dy ang sin def dx abs .05 lt { % top or bottom /xoffset w -.5 mul def /yoffset ang 180 gt {h neg} {0} ifelse def } { % left or right /xoffset ang 90 gt ang 270 lt and {w neg} {0} ifelse def /yoffset h -.5 mul def } ifelse /ThisAngle ThisAngle PieSliceWidth Clockwise {sub} {add} ifelse NormalAngle store end } forall % Push the keys out so none of them overlap /LabelRadius LabelMinRadius def MenuItems length 1 gt { 0 1 MenuItems length 1 sub { /i exch def /nexti i 1 add MenuItems length mod def { i calcrect nexti calcrect rectsoverlap not {exit} if /LabelRadius LabelRadius LabelRadiusStep add def } loop } for } if /LabelRadius LabelRadius LabelRadiusExtra add def /PieRadius LabelRadius dup mul def MenuItems { begin /x dx LabelRadius cvr mul def % XXX: cvr is for NeWS math bug /y dy LabelRadius cvr mul def /X x xoffset add def /Y y yoffset add def dx abs .05 lt { % top or bottom x abs w 2 div add dup mul y abs h add dup mul add } { % left or right x abs w add dup mul y abs h 2 div add dup mul add } ifelse PieRadius max /PieRadius exch store end } forall /PieRadius PieRadius sqrt Gap add Border add round store /MenuWidth PieRadius dup add store /MenuHeight MenuWidth store grestore } def /calcrect { % item_number => x y w h MenuItems exch get begin LabelRadius dx mul xoffset add LabelRadius dy mul yoffset add w h end } def /reshape { MenuGSave framebuffer setcanvas newpath PieRadius dup dup 0 360 arc closepath NumbHole { PieRadius dup NumbRadius 1 sub 360 0 arcn closepath } if SplatFactor { 6 { PieRadius dup add random mul } repeat curveto } repeat MenuCanvas eoreshapecanvas /beye /beye_m MenuCanvas setstandardcursor % So retained canvases don't have their old image upon popup: RetainCanvas? { MenuCanvas setcanvas MenuFillColor fillcanvas } if grestore } def % Make sure nothing's highlighted if there's a retained canvas. % Layout the menu, make the canvas, and reshape it, as needed. Try to % center the menu on (XLocation, YLocation) (the location of the event % or the (X, Y) arguments), but if needed, move it so that it's % completely on the screen, remembering the distance moved in (DeltaX, % DeltaY), for repositioning the mouse later. Set up the canvas. Send % out a MapMenuEvent with a delay, so that we can supress the mapping % if we receive the events that complete the selection right away. % (This is mouse-ahead display suppression.) (Submenus have a shorter % delay than parentless menus, because if you mouse quickly into a % submenu, then wait, you're more immediatly interested in seeing the % submenu than the parent.) Finally, reset the menu value, and % activate the menu event manager. /showat { % event => - PaintedValue null ne MenuCanvas null ne and MenuWidth null ne and { MenuGSave PaintedValue PaintSlice grestore } if /PaintedValue null store MenuEventMgr null ne {MenuEventMgr waitprocess pop} if MenuWidth null eq { /layout self send MenuCanvas null ne {/reshape self send} if } if MenuCanvas null eq { /MenuCanvas ParentCanvas newcanvas def MenuCanvas /Retained RetainCanvas? put MenuCanvas /SaveBehind ColorDisplay? put % MenuCanvas /SaveBehind true put /reshape self send } if MapMenuEvent null eq { /MapMenuEvent createevent def MapMenuEvent begin /Name /MapMenu def end % MapMenuEvent } if MapMenuEvent /Canvas MenuCanvas put gsave framebuffer setcanvas dup type /eventtype eq { begin XLocation YLocation end } if PieRadius sub MouseYDelta add /MenuY exch def PieRadius sub MouseXDelta add /MenuX exch def currentcursorlocation /CurY exch def /CurX exch def clippath pathbbox /DeltaY exch def /DeltaX exch def pop pop /DeltaY MenuY MenuHeight add dup DeltaY ge { DeltaY exch sub } { dup MenuHeight lt { MenuHeight exch sub } { pop 0 } ifelse } ifelse def /DeltaX MenuX MenuWidth add dup DeltaX ge { DeltaX exch sub } { dup MenuWidth lt { MenuWidth exch sub } { pop 0 } ifelse } ifelse def /MenuX MenuX DeltaX add store /MenuY MenuY DeltaY add store % MenuCanvas savebehindcanvas MenuCanvas setcanvas MenuX MenuY movecanvas MenuCanvas canvastotop grestore % Defer the mapping till events already in the input queue % have been processed. MapMenuEvent recallevent % So active submenu pops up before already choosen parent! MapMenuEvent /TimeStamp currenttime MapShortDelay add put MapMenuEvent sendevent /MenuValue null def /GotDown false def /activate self send } def /paint { MenuGSave PaintMenuFrame PaintMenuItems grestore } def /PaintMenuFrame { MenuGSave MenuFillColor fillcanvas PieRadius dup translate newpath 0 0 PieRadius 0 360 arc closepath 0 0 PieRadius Border sub 0 360 arc closepath % 0 0 NumbRadius 0 360 arc closepath MenuBorderColor setcolor eofill grestore } def /PaintMenuItems { MenuGSave false setprintermatch PieRadius dup translate MenuItems { % item begin MenuTextColor setcolor /Key load X Y ShowThing % There seems to be a NeWS line clipping bug with lines with one % endpoint the right of the hole in the center of the menu ... 2 setlinequality % Solves SOME of the line glitches ... MenuLineWidth setlinewidth MenuLineCap setlinecap SliceWedges { gsave newpath ang PieSliceWidth 2 div sub rotate NumbRadius 0 moveto LabelRadius Gap sub 0 lineto MenuBorderColor setcolor stroke grestore } if SliceArrows { gsave MenuArrowWidth setlinewidth MenuArrowCap setlinecap newpath ang rotate NumbRadius 0 moveto LabelRadius .5 mul 0 lineto currentpoint LabelRadius .4 mul LabelRadius .04 mul lineto moveto LabelRadius .4 mul LabelRadius -.04 mul lineto MenuBorderColor setcolor stroke grestore } if end } forall grestore } def % Handle drag events. If there's not a child menu up, then track the % mouse movement, updating the menu value according the the event % location; if it has changed, then update the highlighting. /DragProc { ChildMenu null eq { MenuGSave PieRadius dup translate CurrentEvent begin XLocation DeltaX add YLocation DeltaY add end SetMenuValue MenuValue PaintedValue ne { PaintMenuValue } if grestore } if } def % Handle enter canvas events. Just call DragProc to keep the menu % value updated. /EnterProc { DragProc } def % Handle exit canvas events. Same as above. Here we keep tracking even % when you're off the menu edge (due to expressing interest in events % on the null canvas). But if it really turns you on, going off the % edge could mean no selection (like when you're within the numb % radius - look at SetMenuValue), or select the slice, or pop up a % submenu, or drag the menu around, or give more info about the slice, % or whatever. /ExitProc { DragProc } def % Pop back to the center of the menu. /KerProc { MenuGSave DragProc framebuffer setcanvas MenuX PieRadius add MouseXDelta sub MenuY PieRadius add MouseYDelta sub setcursorlocation grestore } def % Pop back to the previous menu, if we're in this menu's center. /ChunkProc { MenuGSave DragProc MenuValue null eq { popdown } if grestore } def % Map the menu on the screen. This is invoked when we get a /MapMenu % event, so that we can interrupt the display of the menu (by % recalling the event) if the events that would complete the selection % are already in the input queue. /MapMenu { gsave DeltaX 0 ne DeltaY 0 ne or { framebuffer setcanvas currentcursorlocation exch DeltaX add exch DeltaY add setcursorlocation /DeltaX 0 def /DeltaY 0 def } if % MenuCanvas /SaveBehind ChildMenu null eq put MenuCanvas /Mapped true put grestore } def /MaybeMapMenu { gsave framebuffer setcanvas CurX CurY currentcursorlocation /CurY exch def /CurX exch def CurY sub dup mul exch CurX sub dup mul add NoMapDist gt { MapMenuEvent /TimeStamp currenttime ChildMenu null eq MapShortDelay MapLongDelay ifelse add put MapMenuEvent sendevent } { MapMenu } ifelse grestore } def /popdown { % Direct Pac-Manipulation Feedback Wocka MenuCanvas /Mapped get not and { MenuValue null ne { gsave MenuItems MenuValue get begin fboverlay setcanvas overlayerase erasepage 0 setgray MenuX PieRadius add MenuY PieRadius add translate ang rotate 0 0 moveto 0 0 PieRadius % x y r PieSliceWidth 2 div dup neg arc closepath fill CurrentEvent /TimeStamp get WockaTime add { pause dup currenttime lt { exit } if } loop pop overlayerase erasepage end % Item grestore } if } if MapMenuEvent recallevent MenuCanvas null ne {MenuCanvas unmapcanvas} if % spin needs this?? RetainCanvas? not { /MenuCanvas null store /MenuInterests null store % /MenuWidth null store } if % framebuffer setcanvas? ChildMenu null ne { /popdown ChildMenu send } if ParentMenu null ne { ParentMenu /ChildMenu null put /ParentMenu null store } if MenuEventMgr null ne { MenuEventMgr /MenuEventMgr null store killprocess } if } def % Calculate and set the menu value from the cursor x y location. % Updates /PieDistance and /PieDirection instance variables. /SetMenuValue { % x y => - (Sets /MenuValue) /PieDistance 2 index cvr dup mul 2 index cvr dup mul add sqrt def exch atan /PieDirection exch def /MenuValue PieDistance NumbRadius le % It could be that when the cursor is out past the menu radius, % nothing is selected. But I don't do it that way, because it wins % to be able to get arbitrarily more precision by moving out further. % PieDistance PieRadius gt or { null } { PieSliceWidth 2 div PieInitialAngle Clockwise { add PieDirection sub } { sub PieDirection add } ifelse NormalAngle PieSliceWidth idiv } ifelse def } def % Update the highlighted slice to show the current menu value. /PaintMenuValue { % - => - (Hilite current item, un-hilite prev one.) PaintedValue PaintSlice MenuValue PaintSlice /PaintedValue MenuValue store } def % Paint highlighting on a menu slice. If it's null, then do nothing. % Draw an arrow, and a box around the key. /PaintSlice { % key => - dup null ne { % key MenuGSave PieRadius dup translate % Draw an arrow pointing out in the direction of the slice. MenuItems exch get begin % overlayerase MenuBorderColor setcolor 5 setrasteropcode HiLiteWithArrow? { gsave ang rotate newpath NumbRadius 0 moveto LabelRadius Gap sub % r dup .6 mul dup PieSliceWidth 3 div sin mul lineto dup .9 mul 0 lineto .6 mul dup PieSliceWidth -3 div sin mul lineto % closepath StrokeSelection {stroke} {fill} ifelse grestore } if % Highlight the key Thing. -4 2 X Y w h insetrrect rrectpath StrokeSelection {stroke} {fill} ifelse end grestore } {pop} ifelse % } def % Handle button up events. If we have children, then let the leaf % child menu handle the button up event. Otherwise, we handle it: If % it's a menu dictionary, then make it the child menu and show it. % Otherwise, execute the associated menu action, and send a /popdown % message to the root parent menu. /UpProc { DragProc MenuValue getmenuaction dup type /dicttype eq { /DeltaX 0 def /DeltaY 0 def % selection already made -- don't warp! /ChildMenu exch def ChildMenu /ParentMenu self put CurrentEvent /showat ChildMenu send } { pop % Ignore first mouse up if we're still in center of first menu ParentMenu null ne MenuValue null ne GotDown or or { /DeltaX 0 def /DeltaY 0 def % don't warp! { % Find the parent menu self { dup /ParentMenu get dup null eq { pop exit } { exch pop } ifelse } loop % ^?^? (toodles [tm]!) /popdown exch send domenu } fork waitprocess % doesn't return } { % If we are still in menu center then map immediatly! MapMenuEvent recallevent MapMenu } ifelse } ifelse } def % Handle menu button down events. /DownProc { /GotDown true store DragProc } def % Handle damage events. Gotta make sure the highlighted slice is % re-highlighted. /DamageProc { MenuGSave damagepath clipcanvas /paint self send PaintedValue PaintSlice newpath clipcanvas grestore } def % Construct menu event interests. Use exclusivity so only the % top-most menu sees the events. /makeinterests { /MenuInterests [ MenuButton /UpProc UpTransition null eventmgrinterest dup /Exclusivity true put dup /Priority 5 put MenuButton /DownProc DownTransition null eventmgrinterest dup /Exclusivity true put MouseDragged /DragProc null null eventmgrinterest dup /Exclusivity true put /EnterEvent /EnterProc null MenuCanvas eventmgrinterest dup /Exclusivity true put /ExitEvent /ExitProc null MenuCanvas eventmgrinterest dup /Exclusivity true put /Damaged /DamageProc null MenuCanvas eventmgrinterest dup /Exclusivity true put dup /Priority -5 put AdjustButton /KerProc DownTransition null eventmgrinterest dup /Exclusivity true put AdjustButton /ChunkProc UpTransition null eventmgrinterest dup /Exclusivity true put % Kludge to refresh messed up retained menu canvases. Ssssh! Don't tell anyone. PointButton {} DownTransition null eventmgrinterest PointButton /DamageProc UpTransition MenuCanvas eventmgrinterest /MapMenu /MaybeMapMenu null MenuCanvas eventmgrinterest dup /Priority -5 put ] def } def /getmenuaction { % index => action dup null ne { MenuActions 1 index MenuActions length 1 sub min get % Execute actions that are names! (This is so we can have references % to submenus (executable names) as actions, as opposed to having the % submenu object dict itsself!) dup type /nametype eq { exec } if } {nullproc} ifelse exch pop } def classend def /PieMenu SimplePieMenu def %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% /LayeredPieMenu SimplePieMenu dictbegin /MenuArgs [] def /MenuArg null def /PaintedArg null def dictend classbegin % Need to make flipstyle a no-op because /new takes a different number % of args, and actions might depend on MenuArg! Scratch that. % Instead, let's just make a new instance of ourselves, of % the same class. /flipstyle { 0 1 MenuActions length 1 sub { dup getmenuaction % fixed to use getmenuaction! dup type /dicttype eq { /flipstyle exch send % i menu' MenuActions 3 1 roll put % - } {pop pop} ifelse } for MenuArgs MenuKeys MenuActions /new ClassName load send dup /LabelMinRadius LabelMinRadius put % hack } def /new { % args keys actions => menu % -or- args keys/actions (one array) => menu /new super send begin /MenuArgs exch def currentdict end } def /showat { /MenuArg null def PaintedArg null ne MenuCanvas null ne and MenuWidth null ne and { MenuGSave PaintedArg PaintMenuArg grestore } if /PaintedArg null store /showat super send } def /DragProc { ChildMenu null eq { MenuGSave PieRadius dup translate CurrentEvent begin XLocation DeltaX add YLocation DeltaY add end SetMenuValue MenuValue PaintedValue ne { PaintMenuValue } if MenuArg PaintedArg ne { PaintMenuArg } if grestore } if } def /DamageProc { MenuGSave damagepath clipcanvas /paint self send PaintedValue PaintSlice PaintedArg PaintArg newpath clipcanvas grestore } def /PaintMenuArg { PaintedArg PaintArg MenuArg PaintArg /PaintedArg MenuArg store } def /PaintArg { dup null ne { MenuGSave PieRadius dup translate MenuBorderColor setcolor 5 setrasteropcode 100 string cvs dup stringbbox points2rect -.5 mul exch -.5 mul exch moveto pop pop show grestore } if } def /SetMenuValue { % x y => - /SetMenuValue super send /MenuArg MenuValue null eq MenuArgs length 0 eq or { null } { PieDistance PieRadius 1 sub min NumbRadius sub PieRadius NumbRadius sub div MenuArgs length mul floor MenuArgs exch get } ifelse def } def /getmenuarg { MenuArg } def classend def %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% /setdefaultmenu { % class => - /DefaultMenu exch store systemdict /rootmenu known { /rootmenu /flipstyle rootmenu send store } if } def systemdict /DontSetDefaultMenu known not { % Death to pulldown menus! PieMenu setdefaultmenu } if end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%