Index: doc/menu.n ================================================================== --- doc/menu.n +++ doc/menu.n @@ -468,22 +468,23 @@ command. Otherwise the result is an empty string. Note: invoking a menu entry does not automatically unpost the menu; the default bindings normally take care of this before invoking the \fBinvoke\fR widget command. .TP -\fIpathName \fBpost \fIx y\fR +\fIpathName \fBpost \fIx y\fR ?\fIindex\fR? . Arrange for the menu to be displayed on the screen at the root-window -coordinates given by \fIx\fR and \fIy\fR. These coordinates are -adjusted if necessary to guarantee that the entire menu is visible on -the screen. This command normally returns an empty string. -If the \fB\-postcommand\fR option has been specified, then its value is -executed as a Tcl script before posting the menu and the result of -that script is returned as the result of the \fBpost\fR widget -command. -If an error returns while executing the command, then the error is -returned without posting the menu. +coordinates given by \fIx\fR and \fIy\fR. If an index is specified +the menu will be located so that the entry with that index is +displayed at the point. These coordinates are adjusted if necessary to +guarantee that the entire menu is visible on the screen. This command +normally returns an empty string. If the \fB\-postcommand\fR option +has been specified, then its value is executed as a Tcl script before +posting the menu and the result of that script is returned as the +result of the \fBpost\fR widget command. If an error returns while +executing the command, then the error is returned without posting the +menu. .TP \fIpathName \fBpostcascade \fIindex\fR . Posts the submenu associated with the cascade entry given by \fIindex\fR, and unposts any previously posted submenu. Index: generic/tkMenu.c ================================================================== --- generic/tkMenu.c +++ generic/tkMenu.c @@ -868,36 +868,41 @@ } result = TkInvokeMenu(interp, menuPtr, index); break; } case MENU_POST: { - int x, y; + int x, y, index = -1; - if (objc != 4) { - Tcl_WrongNumArgs(interp, 2, objv, "x y"); + if (objc != 4 && objc != 5) { + Tcl_WrongNumArgs(interp, 2, objv, "x y ?index?"); goto error; } if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK) || (Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) { goto error; } + if (objc == 5) { + if (TkGetMenuIndex(interp, menuPtr, objv[4], 0, &index) != TCL_OK) { + goto error; + } + } /* - * Tearoff menus are posted differently on Mac and Windows than - * non-tearoffs. TkpPostMenu does not actually map the menu's window - * on those platforms, and popup menus have to be handled specially. - * Also, menubar menues are not intended to be posted (bug 1567681, - * 2160206). + * Tearoff menus are the same as ordinary menus on the Mac and are + * posted differently on Windows than non-tearoffs. TkpPostMenu + * does not actually map the menu's window on those platforms, and + * popup menus have to be handled specially. Also, menubar menus are + * not intended to be posted (bug 1567681, 2160206). */ if (menuPtr->menuType == MENUBAR) { Tcl_AppendResult(interp, "a menubar menu cannot be posted", NULL); return TCL_ERROR; } else if (menuPtr->menuType != TEAROFF_MENU) { - result = TkpPostMenu(interp, menuPtr, x, y); + result = TkpPostMenu(interp, menuPtr, x, y, index); } else { - result = TkPostTearoffMenu(interp, menuPtr, x, y); + result = TkpPostTearoffMenu(interp, menuPtr, x, y, index); } break; } case MENU_POSTCASCADE: { int index; Index: generic/tkMenu.h ================================================================== --- generic/tkMenu.h +++ generic/tkMenu.h @@ -518,11 +518,11 @@ Tcl_Obj *parentNamePtr, TkMenu *menuPtr); MODULE_SCOPE int TkPostCommand(TkMenu *menuPtr); MODULE_SCOPE int TkPostSubmenu(Tcl_Interp *interp, TkMenu *menuPtr, TkMenuEntry *mePtr); MODULE_SCOPE int TkPostTearoffMenu(Tcl_Interp *interp, TkMenu *menuPtr, - int x, int y); + int x, int y); MODULE_SCOPE int TkPreprocessMenu(TkMenu *menuPtr); MODULE_SCOPE void TkRecomputeMenu(TkMenu *menuPtr); /* * These routines are the platform-dependent routines called by the common @@ -541,9 +541,11 @@ int drawingParameters); MODULE_SCOPE void TkpMenuInit(void); MODULE_SCOPE int TkpMenuNewEntry(TkMenuEntry *mePtr); MODULE_SCOPE int TkpNewMenu(TkMenu *menuPtr); MODULE_SCOPE int TkpPostMenu(Tcl_Interp *interp, TkMenu *menuPtr, - int x, int y); + int x, int y, int index); +MODULE_SCOPE int TkpPostTearoffMenu(Tcl_Interp *interp, TkMenu *menuPtr, + int x, int y, int index); MODULE_SCOPE void TkpSetWindowMenuBar(Tk_Window tkwin, TkMenu *menuPtr); #endif /* _TKMENU */ Index: generic/tkMenuDraw.c ================================================================== --- generic/tkMenuDraw.c +++ generic/tkMenuDraw.c @@ -805,13 +805,12 @@ /* *---------------------------------------------------------------------- * * TkPostTearoffMenu -- * - * Posts a menu on the screen. Used to post tearoff menus. On Unix, all - * menus are posted this way. Adjusts the menu's position so that it fits - * on the screen, and maps and raises the menu. + * Posts a tearoff menu on the screen. Adjusts the menu's position so + * that it fits on the screen, and maps and raises the menu. * * Results: * Returns a standard Tcl Error. * * Side effects: @@ -825,68 +824,11 @@ Tcl_Interp *interp, /* The interpreter of the menu */ TkMenu *menuPtr, /* The menu we are posting */ int x, int y) /* The root X,Y coordinates where we are * posting */ { - int vRootX, vRootY, vRootWidth, vRootHeight; - int result; - - TkActivateMenuEntry(menuPtr, -1); - TkRecomputeMenu(menuPtr); - result = TkPostCommand(menuPtr); - if (result != TCL_OK) { - return result; - } - - /* - * The post commands could have deleted the menu, which means we are dead - * and should go away. - */ - - if (menuPtr->tkwin == NULL) { - return TCL_OK; - } - - /* - * Adjust the position of the menu if necessary to keep it visible on the - * screen. There are two special tricks to make this work right: - * - * 1. If a virtual root window manager is being used then the coordinates - * are in the virtual root window of menuPtr's parent; since the menu - * uses override-redirect mode it will be in the *real* root window for - * the screen, so we have to map the coordinates from the virtual root - * (if any) to the real root. Can't get the virtual root from the menu - * itself (it will never be seen by the wm) so use its parent instead - * (it would be better to have an an option that names a window to use - * for this...). - * 2. The menu may not have been mapped yet, so its current size might be - * the default 1x1. To compute how much space it needs, use its - * requested size, not its actual size. - */ - - Tk_GetVRootGeometry(Tk_Parent(menuPtr->tkwin), &vRootX, &vRootY, - &vRootWidth, &vRootHeight); - vRootWidth -= Tk_ReqWidth(menuPtr->tkwin); - if (x > vRootX + vRootWidth) { - x = vRootX + vRootWidth; - } - if (x < vRootX) { - x = vRootX; - } - vRootHeight -= Tk_ReqHeight(menuPtr->tkwin); - if (y > vRootY + vRootHeight) { - y = vRootY + vRootHeight; - } - if (y < vRootY) { - y = vRootY; - } - Tk_MoveToplevelWindow(menuPtr->tkwin, x, y); - if (!Tk_IsMapped(menuPtr->tkwin)) { - Tk_MapWindow(menuPtr->tkwin); - } - TkWmRestackToplevel((TkWindow *) menuPtr->tkwin, Above, NULL); - return TCL_OK; + return TkpPostTearoffMenu(interp, menuPtr, x, y, -1); } /* *-------------------------------------------------------------- * Index: library/menu.tcl ================================================================== --- library/menu.tcl +++ library/menu.tcl @@ -231,10 +231,11 @@ } if {[$w cget -state] eq "active" && [tk windowingsystem] ne "aqua"} { $w configure -state normal } } + # ::tk::MbPost -- # Given a menubutton, this procedure does all the work of posting # its associated menu and unposting any other menu that is currently # posted. @@ -280,105 +281,21 @@ set Priv(postedMb) $w set Priv(focus) [focus] $menu activate none GenerateMenuSelect $menu - - # If this looks like an option menubutton then post the menu so - # that the current entry is on top of the mouse. Otherwise post - # the menu just below the menubutton, as for a pull-down. - update idletasks - if {[catch { - switch [$w cget -direction] { - above { - set x [winfo rootx $w] - set y [expr {[winfo rooty $w] - [winfo reqheight $menu]}] - # if we go offscreen to the top, show as 'below' - if {$y < [winfo vrooty $w]} { - set y [expr {[winfo vrooty $w] + [winfo rooty $w] + [winfo reqheight $w]}] - } - PostOverPoint $menu $x $y - } - below { - set x [winfo rootx $w] - set y [expr {[winfo rooty $w] + [winfo height $w]}] - # if we go offscreen to the bottom, show as 'above' - set mh [winfo reqheight $menu] - if {($y + $mh) > ([winfo vrooty $w] + [winfo vrootheight $w])} { - set y [expr {[winfo vrooty $w] + [winfo vrootheight $w] + [winfo rooty $w] - $mh}] - } - PostOverPoint $menu $x $y - } - left { - set x [expr {[winfo rootx $w] - [winfo reqwidth $menu]}] - set y [expr {(2 * [winfo rooty $w] + [winfo height $w]) / 2}] - set entry [MenuFindName $menu [$w cget -text]] - if {$entry eq ""} { - set entry 0 - } - if {[$w cget -indicatoron]} { - if {$entry == [$menu index last]} { - incr y [expr {-([$menu yposition $entry] \ - + [winfo reqheight $menu])/2}] - } else { - incr y [expr {-([$menu yposition $entry] \ - + [$menu yposition [expr {$entry+1}]])/2}] - } - } - PostOverPoint $menu $x $y - if {$entry ne "" \ - && [$menu entrycget $entry -state] ne "disabled"} { - $menu activate $entry - GenerateMenuSelect $menu - } - } - right { - set x [expr {[winfo rootx $w] + [winfo width $w]}] - set y [expr {(2 * [winfo rooty $w] + [winfo height $w]) / 2}] - set entry [MenuFindName $menu [$w cget -text]] - if {$entry eq ""} { - set entry 0 - } - if {[$w cget -indicatoron]} { - if {$entry == [$menu index last]} { - incr y [expr {-([$menu yposition $entry] \ - + [winfo reqheight $menu])/2}] - } else { - incr y [expr {-([$menu yposition $entry] \ - + [$menu yposition [expr {$entry+1}]])/2}] - } - } - PostOverPoint $menu $x $y - if {$entry ne "" \ - && [$menu entrycget $entry -state] ne "disabled"} { - $menu activate $entry - GenerateMenuSelect $menu - } - } - default { - if {[$w cget -indicatoron]} { - if {$y eq ""} { - set x [expr {[winfo rootx $w] + [winfo width $w]/2}] - set y [expr {[winfo rooty $w] + [winfo height $w]/2}] - } - PostOverPoint $menu $x $y [MenuFindName $menu [$w cget -text]] - } else { - PostOverPoint $menu [winfo rootx $w] [expr {[winfo rooty $w]+[winfo height $w]}] - } - } - } - } msg opt]} { + + if {[catch {PostMenubuttonMenu $w $menu} msg opt]} { # Error posting menu (e.g. bogus -postcommand). Unpost it and # reflect the error. - MenuUnpost {} return -options $opt $msg } set Priv(tearoff) $tearoff - if {$tearoff != 0} { + if {$tearoff != 0 && [tk windowingsystem] ne "aqua"} { focus $menu if {[winfo viewable $w]} { SaveGrabInfo $w grab -global $w } @@ -574,15 +491,17 @@ && $index ne $activeindex} { set mode [option get $menu clickToFocus ClickToFocus] if {[string is false $mode]} { set delay [expr {[$menu cget -type] eq "menubar" ? 0 : 50}] if {[$menu type $index] eq "cascade"} { + # Catch these postcascade commands since the menu could be + # destroyed before they run. set Priv(menuActivatedTimer) \ - [after $delay [list $menu postcascade active]] + [after $delay "catch {$menu postcascade active}"] } else { set Priv(menuDeactivatedTimer) \ - [after $delay [list $menu postcascade none]] + [after $delay "catch {$menu postcascade none}"] } } } } } @@ -1205,36 +1124,149 @@ } } } return "" } + +# ::tk::PostMenubuttonMenu -- +# +# Given a menubutton and a menu, this procedure posts the menu at the +# appropriate location. If the menubutton looks like an option +# menubutton, meaning that the indicator is on and the direction is +# neither above nor below, then the menu is posted so that the current +# entry is vertically aligned with the menubutton. On the Mac this +# will expose a small amount of the blue indicator on the right hand +# side. On other platforms the entry is centered over the button. + +if {[tk windowingsystem] eq "aqua"} { + proc ::tk::PostMenubuttonMenu {button menu} { + set entry "" + if {[$button cget -indicatoron]} { + set entry [MenuFindName $menu [$button cget -text]] + if {$entry eq ""} { + set entry 0 + } + } + set x [winfo rootx $button] + set y [expr {2 + [winfo rooty $button]}] + switch [$button cget -direction] { + above { + set entry "" + incr y [expr {4 - [winfo reqheight $menu]}] + } + below { + set entry "" + incr y [expr {2 + [winfo height $button]}] + } + left { + incr x [expr {-[winfo reqwidth $menu]}] + } + right { + incr x [winfo width $button] + } + default { + incr x [expr {[winfo width $button] - [winfo reqwidth $menu] - 5}] + } + } + PostOverPoint $menu $x $y $entry + } +} else { + proc ::tk::PostMenubuttonMenu {button menu} { + set entry "" + if {[$button cget -indicatoron]} { + set entry [MenuFindName $menu [$button cget -text]] + if {$entry eq ""} { + set entry 0 + } + } + if {$entry ne ""} { + if {$entry == [$menu index last]} { + set entryHeight [expr {[winfo reqheight $menu] \ + - [$menu yposition $entry]}] + } else { + set entryHeight [expr {[$menu yposition [expr {$entry+1}]] \ + - [$menu yposition $entry]}] + } + } + set x [winfo rootx $button] + set y [winfo rooty $button] + switch [$button cget -direction] { + above { + incr y [expr {-[winfo reqheight $menu]}] + # if we go offscreen to the top, show as 'below' + if {$y < [winfo vrooty $button]} { + set y [expr {[winfo vrooty $button] + [winfo rooty $button]\ + + [winfo reqheight $button]}] + } + set entry {} + } + below { + incr y [winfo height $button] + # if we go offscreen to the bottom, show as 'above' + set mh [winfo reqheight $menu] + if {($y + $mh) > ([winfo vrooty $button] + [winfo vrootheight $button])} { + set y [expr {[winfo vrooty $button] + [winfo vrootheight $button] \ + + [winfo rooty $button] - $mh}] + } + set entry {} + } + left { + # It is not clear why this is needed. + if {[tk windowingsystem] eq "win32"} { + incr x [expr {-4 - [winfo reqwidth $button] / 2}] + } + incr x [expr {- [winfo reqwidth $menu]}] + } + right { + incr x [expr {[winfo width $button]}] + } + default { + if {[$button cget -indicatoron]} { + incr x [expr {([winfo width $button] - \ + [winfo reqwidth $menu])/ 2}] + } else { + incr y [winfo height $button] + } + } + } + PostOverPoint $menu $x $y $entry + } +} # ::tk::PostOverPoint -- -# This procedure posts a given menu such that a given entry in the -# menu is centered over a given point in the root window. It also -# activates the given entry. +# +# This procedure posts a menu on the screen so that a given entry in +# the menu is positioned with its upper left corner at a given point +# in the root window. The procedure also activates that entry. If no +# entry is specified the upper left corner of the entire menu is +# placed at the point. # # Arguments: # menu - Menu to post. # x, y - Root coordinates of point. # entry - Index of entry within menu to center over (x,y). # If omitted or specified as {}, then the menu's # upper-left corner goes at (x,y). -proc ::tk::PostOverPoint {menu x y {entry {}}} { - if {$entry ne ""} { - if {$entry == [$menu index last]} { - incr y [expr {-([$menu yposition $entry] \ - + [winfo reqheight $menu])/2}] +if {[tk windowingsystem] ne "win32"} { + proc ::tk::PostOverPoint {menu x y {entry {}}} { + if {$entry ne ""} { + $menu post $x $y $entry + if {[$menu entrycget $entry -state] ne "disabled"} { + $menu activate $entry + GenerateMenuSelect $menu + } } else { - incr y [expr {-([$menu yposition $entry] \ - + [$menu yposition [expr {$entry+1}]])/2}] + $menu post $x $y } - incr x [expr {-[winfo reqwidth $menu]/2}] + return } - - if {[tk windowingsystem] eq "win32"} { +} else { + proc ::tk::PostOverPoint {menu x y {entry {}}} { + if {$entry ne ""} { + incr y [expr {-[$menu yposition $entry]}] + } # osVersion is not available in safe interps set ver 5 if {[info exists ::tcl_platform(osVersion)]} { scan $::tcl_platform(osVersion) %d ver } @@ -1246,11 +1278,11 @@ # entry. To be safe we subtract an extra 10. # NOTE: this issue appears to have been resolved in the Window # manager provided with Vista and Windows 7. if {$ver < 6} { set yoffset [expr {[winfo screenheight $menu] \ - - $y - [winfo reqheight $menu] - 10}] + - $y - [winfo reqheight $menu] - 10}] if {$yoffset < [winfo vrooty $menu]} { # The bottom of the menu is offscreen, so adjust upwards incr y [expr {$yoffset - [winfo vrooty $menu]}] } # If we're off the top of the screen (either because we were @@ -1258,15 +1290,15 @@ # then make the menu popup on the top edge. if {$y < [winfo vrooty $menu]} { set y [winfo vrooty $menu] } } - } - $menu post $x $y - if {$entry ne "" && [$menu entrycget $entry -state] ne "disabled"} { - $menu activate $entry - GenerateMenuSelect $menu + $menu post $x $y + if {$entry ne "" && [$menu entrycget $entry -state] ne "disabled"} { + $menu activate $entry + GenerateMenuSelect $menu + } } } # ::tk::SaveGrabInfo -- # Sets the variables tk::Priv(oldGrab) and tk::Priv(grabStatus) to record Index: macosx/tkMacOSXDefault.h ================================================================== --- macosx/tkMacOSXDefault.h +++ macosx/tkMacOSXDefault.h @@ -332,19 +332,19 @@ /* * Defaults for menubuttons: */ -#define DEF_MENUBUTTON_ANCHOR "center" +#define DEF_MENUBUTTON_ANCHOR "w" #define DEF_MENUBUTTON_ACTIVE_BG_COLOR ACTIVE_BG #define DEF_MENUBUTTON_ACTIVE_BG_MONO BLACK #define DEF_MENUBUTTON_ACTIVE_FG_COLOR ACTIVE_FG #define DEF_MENUBUTTON_ACTIVE_FG_MONO WHITE #define DEF_MENUBUTTON_BG_COLOR NORMAL_BG #define DEF_MENUBUTTON_BG_MONO WHITE #define DEF_MENUBUTTON_BITMAP "" -#define DEF_MENUBUTTON_BORDER_WIDTH "2" +#define DEF_MENUBUTTON_BORDER_WIDTH "0" #define DEF_MENUBUTTON_CURSOR "" #define DEF_MENUBUTTON_DIRECTION "below" #define DEF_MENUBUTTON_DISABLED_FG_COLOR DISABLED #define DEF_MENUBUTTON_DISABLED_FG_MONO "" #define DEF_MENUBUTTON_FONT "TkDefaultFont" @@ -356,12 +356,12 @@ #define DEF_MENUBUTTON_HIGHLIGHT_WIDTH "0" #define DEF_MENUBUTTON_IMAGE NULL #define DEF_MENUBUTTON_INDICATOR "1" #define DEF_MENUBUTTON_JUSTIFY "left" #define DEF_MENUBUTTON_MENU "" -#define DEF_MENUBUTTON_PADX "4" -#define DEF_MENUBUTTON_PADY "3" +#define DEF_MENUBUTTON_PADX "0" +#define DEF_MENUBUTTON_PADY "0" #define DEF_MENUBUTTON_RELIEF "flat" #define DEF_MENUBUTTON_STATE "normal" #define DEF_MENUBUTTON_TAKE_FOCUS "0" #define DEF_MENUBUTTON_TEXT "" #define DEF_MENUBUTTON_TEXT_VARIABLE "" Index: macosx/tkMacOSXMenu.c ================================================================== --- macosx/tkMacOSXMenu.c +++ macosx/tkMacOSXMenu.c @@ -753,14 +753,17 @@ /* *---------------------------------------------------------------------- * * TkpPostMenu -- * - * Posts a menu on the screen + * Posts a menu on the screen. If entry is < 0 then the menu is + * drawn so its top left corner is located at the point with + * screen coordinates (x, y). Otherwise the top left corner of + * the specified entry is located at that point. * * Results: - * None. + * Returns a standard Tcl result. * * Side effects: * The menu is posted and handled. * *---------------------------------------------------------------------- @@ -768,57 +771,162 @@ int TkpPostMenu( Tcl_Interp *interp, /* The interpreter this menu lives in */ TkMenu *menuPtr, /* The menu we are posting */ - int x, /* The global x-coordinate of the top, left- - * hand corner of where the menu is supposed - * to be posted. */ - int y) /* The global y-coordinate */ -{ - - - /* Get the object that holds this Tk Window.*/ - Tk_Window root; - root = Tk_MainWindow(interp); + int x, int y, /* The screen coordinates where the top left + * corner of the menu, or of the specified + * entry, will be located. */ + int index) +{ + int result; + Tk_Window root = Tk_MainWindow(interp); + if (root == NULL) { return TCL_ERROR; } - Drawable d = Tk_WindowId(root); NSView *rootview = TkMacOSXGetRootControl(d); NSWindow *win = [rootview window]; - int result; + NSView *view = [win contentView]; + NSMenu *menu = (NSMenu *) menuPtr->platformData; + NSInteger itemIndex = index; + NSInteger numItems = [menu numberOfItems]; + NSMenuItem *item = nil; + NSPoint location = NSMakePoint(x, tkMacOSXZeroScreenHeight - y); inPostMenu = 1; - result = TkPreprocessMenu(menuPtr); if (result != TCL_OK) { inPostMenu = 0; return result; } - - int oldMode = Tcl_SetServiceMode(TCL_SERVICE_NONE); - NSView *view = [win contentView]; - NSRect frame = NSMakeRect(x + 9, tkMacOSXZeroScreenHeight - y - 9, 1, 1); - - frame.origin = [view convertPoint: - [win tkConvertPointFromScreen:frame.origin] fromView:nil]; - - NSMenu *menu = (NSMenu *) menuPtr->platformData; - NSPopUpButtonCell *popUpButtonCell = [[NSPopUpButtonCell alloc] - initTextCell:@"" pullsDown:NO]; - - [popUpButtonCell setAltersStateOfSelectedItem:NO]; - [popUpButtonCell setMenu:menu]; - [popUpButtonCell selectItem:nil]; - [popUpButtonCell performClickWithFrame:frame inView:view]; - [popUpButtonCell release]; - Tcl_SetServiceMode(oldMode); + if (itemIndex >= numItems) { + itemIndex = numItems - 1; + } + if (itemIndex >= 0) { + item = [menu itemAtIndex:itemIndex]; + } + + /* + * The post commands could have deleted the menu, which means we are dead + * and should go away. + */ + + if (menuPtr->tkwin == NULL) { + return TCL_OK; + } + + [menu popUpMenuPositioningItem:item + atLocation:[win tkConvertPointFromScreen:location] + inView:view]; inPostMenu = 0; return TCL_OK; } + +/* + *---------------------------------------------------------------------- + * + * TkpPostTearoffMenu -- + * + * Tearoff menus are not supported on the Mac. This placeholder + * function, which is simply a copy of the unix function, posts a + * completely useless window with a black background on the screen. If + * entry is < 0 then the window is positioned so that its top left corner + * is located at the point with screen coordinates (x, y). Otherwise the + * window position is offset so that top left corner of the specified + * entry would be located at that point, if there actually were a menu. + * + * Mac menus steal all mouse or keyboard input from the application until + * the menu is dismissed, with or without a selection, by a mouse or key + * event. Posting a Mac menu in a regression test will cause the test to + * halt waiting for user input. This is why the TkpPostMenu function is + * not being used as the placeholder. + * + * Results: + * None. + * + * Side effects: + * A useless window is posted. + * + *---------------------------------------------------------------------- + */ + +int +TkpPostTearoffMenu( + Tcl_Interp *interp, /* The interpreter this menu lives in */ + TkMenu *menuPtr, /* The menu we are posting */ + int x, int y, int index) /* The screen coordinates where the top left + * corner of the menu, or of the specified + * entry, will be located. */ +{ + int vRootX, vRootY, vRootWidth, vRootHeight; + int result; + + if (index >= menuPtr->numEntries) { + index = menuPtr->numEntries - 1; + } + if (index >= 0) { + y -= menuPtr->entries[index]->y; + } + + TkActivateMenuEntry(menuPtr, -1); + TkRecomputeMenu(menuPtr); + result = TkPostCommand(menuPtr); + if (result != TCL_OK) { + return result; + } + + /* + * The post commands could have deleted the menu, which means we are dead + * and should go away. + */ + + if (menuPtr->tkwin == NULL) { + return TCL_OK; + } + + /* + * Adjust the position of the menu if necessary to keep it visible on the + * screen. There are two special tricks to make this work right: + * + * 1. If a virtual root window manager is being used then the coordinates + * are in the virtual root window of menuPtr's parent; since the menu + * uses override-redirect mode it will be in the *real* root window for + * the screen, so we have to map the coordinates from the virtual root + * (if any) to the real root. Can't get the virtual root from the menu + * itself (it will never be seen by the wm) so use its parent instead + * (it would be better to have an an option that names a window to use + * for this...). + * 2. The menu may not have been mapped yet, so its current size might be + * the default 1x1. To compute how much space it needs, use its + * requested size, not its actual size. + */ + + Tk_GetVRootGeometry(Tk_Parent(menuPtr->tkwin), &vRootX, &vRootY, + &vRootWidth, &vRootHeight); + vRootWidth -= Tk_ReqWidth(menuPtr->tkwin); + if (x > vRootX + vRootWidth) { + x = vRootX + vRootWidth; + } + if (x < vRootX) { + x = vRootX; + } + vRootHeight -= Tk_ReqHeight(menuPtr->tkwin); + if (y > vRootY + vRootHeight) { + y = vRootY + vRootHeight; + } + if (y < vRootY) { + y = vRootY; + } + Tk_MoveToplevelWindow(menuPtr->tkwin, x, y); + if (!Tk_IsMapped(menuPtr->tkwin)) { + Tk_MapWindow(menuPtr->tkwin); + } + TkWmRestackToplevel((TkWindow *) menuPtr->tkwin, Above, NULL); + return TCL_OK; +} /* *---------------------------------------------------------------------- * * TkpSetWindowMenuBar -- @@ -1085,18 +1193,19 @@ void TkpComputeStandardMenuGeometry( TkMenu *menuPtr) /* Structure describing menu. */ { + NSSize menuSize = [(NSMenu *)menuPtr->platformData size]; Tk_Font tkfont, menuFont; Tk_FontMetrics menuMetrics, entryMetrics, *fmPtr; int modifierCharWidth, menuModifierCharWidth; int x, y, modifierWidth, labelWidth, indicatorSpace; int windowWidth, windowHeight, accelWidth; - int i, j, lastColumnBreak, maxWidth; + int i, maxWidth; int entryWidth, maxIndicatorSpace, borderWidth, activeBorderWidth; - TkMenuEntry *mePtr, *columnEntryPtr; + TkMenuEntry *mePtr; int haveAccel = 0; if (menuPtr->tkwin == NULL) { return; } @@ -1104,11 +1213,11 @@ Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, menuPtr->borderWidthPtr, &borderWidth); Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, menuPtr->activeBorderWidthPtr, &activeBorderWidth); x = y = borderWidth; - windowHeight = maxWidth = lastColumnBreak = 0; + windowHeight = maxWidth = 0; maxIndicatorSpace = 0; /* * On the Mac especially, getting font metrics can be quite slow, so we * want to do it intelligently. We are going to precalculate them and pass @@ -1131,10 +1240,13 @@ } } for (i = 0; i < menuPtr->numEntries; i++) { mePtr = menuPtr->entries[i]; + if (mePtr->type == TEAROFF_ENTRY) { + continue; + } if (mePtr->fontPtr == NULL) { tkfont = menuFont; fmPtr = &menuMetrics; modifierCharWidth = menuModifierCharWidth; } else { @@ -1141,30 +1253,12 @@ tkfont = Tk_GetFontFromObj(menuPtr->tkwin, mePtr->fontPtr); Tk_GetFontMetrics(tkfont, &entryMetrics); fmPtr = &entryMetrics; modifierCharWidth = ModifierCharWidth(tkfont); } - - if ((i > 0) && mePtr->columnBreak) { - if (maxIndicatorSpace != 0) { - maxIndicatorSpace += 2; - } - for (j = lastColumnBreak; j < i; j++) { - columnEntryPtr = menuPtr->entries[j]; - columnEntryPtr->indicatorSpace = maxIndicatorSpace; - columnEntryPtr->width = maxIndicatorSpace + maxWidth - + 2 * activeBorderWidth; - columnEntryPtr->x = x; - columnEntryPtr->entryFlags &= ~ENTRY_LAST_COLUMN; - } - x += maxIndicatorSpace + maxWidth + 2 * activeBorderWidth; - maxWidth = maxIndicatorSpace = 0; - lastColumnBreak = i; - y = borderWidth; - } accelWidth = modifierWidth = indicatorSpace = 0; - if (mePtr->type == SEPARATOR_ENTRY || mePtr->type == TEAROFF_ENTRY) { + if (mePtr->type == SEPARATOR_ENTRY) { mePtr->height = menuSeparatorHeight; } else { /* * For each entry, compute the height required by that particular * entry, plus three widths: the width of the label, the width to @@ -1174,20 +1268,20 @@ * on the type of the entry. */ NSMenuItem *menuItem = (NSMenuItem *) mePtr->platformEntryData; int haveImage = 0, width = 0, height = 0; - if (mePtr->image) { Tk_SizeOfImage(mePtr->image, &width, &height); haveImage = 1; + height += 2; /* tweak */ } else if (mePtr->bitmapPtr) { Pixmap bitmap = Tk_GetBitmapFromObj(menuPtr->tkwin, mePtr->bitmapPtr); - Tk_SizeOfBitmap(menuPtr->display, bitmap, &width, &height); haveImage = 1; + height += 2; /* tweak */ } if (!haveImage || (mePtr->compound != COMPOUND_NONE)) { NSAttributedString *attrTitle = [menuItem attributedTitle]; NSSize size; @@ -1195,15 +1289,12 @@ size = [attrTitle size]; } else { size = [[menuItem title] sizeWithAttributes: TkMacOSXNSFontAttributesForFont(tkfont)]; } - size.width += menuTextLeadingEdgeMargin + - menuTextTrailingEdgeMargin; - if (size.height < fmPtr->linespace) { - size.height = fmPtr->linespace; - } + size.width += menuTextLeadingEdgeMargin + menuTextTrailingEdgeMargin; + size.height -= 1; /* tweak */ if (haveImage && (mePtr->compound != COMPOUND_NONE)) { int margin = width + menuIconTrailingEdgeMargin; if (margin > menuTextLeadingEdgeMargin) { margin = menuTextLeadingEdgeMargin; @@ -1215,13 +1306,15 @@ } else { width = size.width; height = size.height; } } + else { + /* image only. */ + } labelWidth = width + menuItemExtraWidth; mePtr->height = height + menuItemExtraHeight; - if (mePtr->type == CASCADE_ENTRY) { modifierWidth = modifierCharWidth; } else if (mePtr->accelLength == 0) { if (haveAccel && !mePtr->hideMargin) { modifierWidth = modifierCharWidth; @@ -1248,34 +1341,22 @@ } entryWidth = labelWidth + modifierWidth + accelWidth; if (entryWidth > maxWidth) { maxWidth = entryWidth; } + menuPtr->entries[i]->width = entryWidth; mePtr->height += 2 * activeBorderWidth; } + mePtr->x = x; mePtr->y = y; y += menuPtr->entries[i]->height + borderWidth; - if (y > windowHeight) { - windowHeight = y; - } - } - - for (j = lastColumnBreak; j < menuPtr->numEntries; j++) { - columnEntryPtr = menuPtr->entries[j]; - columnEntryPtr->indicatorSpace = maxIndicatorSpace; - columnEntryPtr->width = maxIndicatorSpace + maxWidth - + 2 * activeBorderWidth; - columnEntryPtr->x = x; - columnEntryPtr->entryFlags |= ENTRY_LAST_COLUMN; - } - windowWidth = x + maxIndicatorSpace + maxWidth - + 2 * activeBorderWidth + borderWidth; - windowHeight += borderWidth; - + } + windowWidth = menuSize.width; if (windowWidth <= 0) { windowWidth = 1; } + windowHeight = menuSize.height; if (windowHeight <= 0) { windowHeight = 1; } menuPtr->totalWidth = windowWidth; menuPtr->totalHeight = windowHeight; Index: macosx/tkMacOSXMenubutton.c ================================================================== --- macosx/tkMacOSXMenubutton.c +++ macosx/tkMacOSXMenubutton.c @@ -45,21 +45,27 @@ HIThemeButtonDrawInfo lastdrawinfo; DrawParams drawParams; } MacMenuButton; /* - * Forward declarations for procedures defined later in this file: + * Forward declarations for static functions defined later in this file: */ static void MenuButtonEventProc(ClientData clientData, XEvent *eventPtr); -static void MenuButtonBackgroundDrawCB ( MacMenuButton *ptr, SInt16 depth, Boolean isColorDev); -static void MenuButtonContentDrawCB ( ThemeButtonKind kind, const HIThemeButtonDrawInfo * info, MacMenuButton *ptr, SInt16 depth, Boolean isColorDev); +static void MenuButtonBackgroundDrawCB (MacMenuButton *ptr, SInt16 depth, + Boolean isColorDev); +static void MenuButtonContentDrawCB (ThemeButtonKind kind, + const HIThemeButtonDrawInfo * info, + MacMenuButton *ptr, SInt16 depth, + Boolean isColorDev); static void MenuButtonEventProc ( ClientData clientData, XEvent *eventPtr); -static void TkMacOSXComputeMenuButtonParams (TkMenuButton * butPtr, ThemeButtonKind* btnkind, HIThemeButtonDrawInfo* drawinfo); -static int TkMacOSXComputeMenuButtonDrawParams (TkMenuButton * butPtr, DrawParams * dpPtr); -static void TkMacOSXDrawMenuButton (MacMenuButton *butPtr, - GC gc, Pixmap pixmap); +static void TkMacOSXComputeMenuButtonParams (TkMenuButton * butPtr, + ThemeButtonKind* btnkind, + HIThemeButtonDrawInfo* drawinfo); +static void TkMacOSXComputeMenuButtonDrawParams (TkMenuButton * butPtr, + DrawParams * dpPtr); +static void TkMacOSXDrawMenuButton (MacMenuButton *butPtr, GC gc, Pixmap pixmap); static void DrawMenuButtonImageAndText(TkMenuButton* butPtr); /* * The structure below defines menubutton class behavior by means of * procedures that can be invoked from generic window code. @@ -68,15 +74,49 @@ Tk_ClassProcs tkpMenubuttonClass = { sizeof(Tk_ClassProcs), /* size */ TkMenuButtonWorldChanged, /* worldChangedProc */ }; +/* + * We use Apple's Pop-Up Button widget to represent the Tk Menubutton. + * However, we do not use the NSPopUpButton class for this control. Instead we + * render the Pop-Up Button using the HITheme library. This imposes some + * constraints on what can be done. The HITheme renderer allows only specific + * dimensions for the button. + * + * The HITheme library allows drawing a Pop-Up Button with an arbitrary bounds + * rectangle. However the button is always drawn as a rounded box which is 22 + * pixels high. If the bounds rectangle is less than 22 pixels high, the + * button is drawn at the top of the rectangle and the bottom of the button is + * clipped away. So we set a minimum height of 22 pixels for a Menubutton. If + * the bounds rectangle is more than 22 pixels high, then the button is drawn + * centered vertically in the bounds rectangle. + * + * The content rectangle of the button is inset by 14 pixels on the left and 28 + * pixels on the right. The rightmost part of the button contains the blue + * double-arrow symbol which is 28 pixels wide. + * + * To maintain compatibility with code that runs on multiple operating systems, + * the width and height of the content rectangle includes the borderWidth, the + * highlightWidth and the padX and padY dimensions of the Menubutton. However, + * to be consistent with the standard Apple appearance, the content is always + * be drawn at the left side of the content rectangle. All of the excess space + * appears on the right side of the content, and the anchor property is + * ignored. The easiest way to comply with Apple's Human Interface Guidelines + * would be to set bd = highlightthickness = padx = 0 and to specify an + * explicit width for the button. Apple also recommends using the same width + * for all Pop-Up Buttons in a given window. + */ + +#define LEFT_INSET 8 +#define RIGHT_INSET 28 +#define MIN_HEIGHT 22 /* *---------------------------------------------------------------------- * - * TkpCreateMenuButton -- + * TkpCreateMenuButton -- * * Allocate a new TkMenuButton structure. * * Results: * Returns a newly allocated TkMenuButton structure. @@ -91,17 +131,16 @@ TkpCreateMenuButton( Tk_Window tkwin) { MacMenuButton *mbPtr = (MacMenuButton *) ckalloc(sizeof(MacMenuButton)); - Tk_CreateEventHandler(tkwin, ActivateMask, - MenuButtonEventProc, (ClientData) mbPtr); + Tk_CreateEventHandler(tkwin, ActivateMask, MenuButtonEventProc, + (ClientData) mbPtr); mbPtr->flags = FIRST_DRAW; mbPtr->btnkind = kThemePopupButton; bzero(&mbPtr->drawinfo, sizeof(mbPtr->drawinfo)); bzero(&mbPtr->lastdrawinfo, sizeof(mbPtr->lastdrawinfo)); - return (TkMenuButton *) mbPtr; } /* *---------------------------------------------------------------------- @@ -163,16 +202,17 @@ *---------------------------------------------------------------------- * * TkpDestroyMenuButton -- * * Free data structures associated with the menubutton control. + * This is a no-op on the Mac. * * Results: * None. * * Side effects: - * Restores the default control state. + * None. * *---------------------------------------------------------------------- */ void @@ -202,58 +242,52 @@ void TkpComputeMenuButtonGeometry(butPtr) register TkMenuButton *butPtr; /* Widget record for menu button. */ { int width, height, avgWidth, haveImage = 0, haveText = 0; - MacMenuButton *mbPtr = (MacMenuButton*)butPtr; int txtWidth, txtHeight; Tk_FontMetrics fm; - DrawParams drawParams; - int paddingx = 0; - int paddingy = 0; + int highlightWidth = butPtr->highlightWidth > 0 ? butPtr->highlightWidth : 0; /* - * First figure out the size of the contents of the button. + * First compute the size of the contents of the button. */ width = 0; height = 0; txtWidth = 0; txtHeight = 0; avgWidth = 0; - TkMacOSXComputeMenuButtonParams(butPtr, &mbPtr->btnkind, &mbPtr->drawinfo); - if (butPtr->image != NULL) { Tk_SizeOfImage(butPtr->image, &width, &height); haveImage = 1; } else if (butPtr->bitmap != None) { Tk_SizeOfBitmap(butPtr->display, butPtr->bitmap, &width, &height); haveImage = 1; } - if (haveImage == 0 || butPtr->compound != COMPOUND_NONE) { + if (butPtr->text && strlen(butPtr->text) > 0) { + haveText = 1; Tk_FreeTextLayout(butPtr->textLayout); butPtr->textLayout = Tk_ComputeTextLayout(butPtr->tkfont, butPtr->text, -1, butPtr->wrapLength, butPtr->justify, 0, &butPtr->textWidth, &butPtr->textHeight); - txtWidth = butPtr->textWidth; txtHeight = butPtr->textHeight; avgWidth = Tk_TextWidth(butPtr->tkfont, "0", 1); Tk_GetFontMetrics(butPtr->tkfont, &fm); - haveText = (txtWidth != 0 && txtHeight != 0); } /* * If the button is compound (ie, it shows both an image and text), * the new geometry is a combination of the image and text geometry. * We only honor the compound bit if the button has both text and an * image, because otherwise it is not really a compound button. */ - if (butPtr->compound != COMPOUND_NONE && haveImage && haveText) { + if (haveImage && haveText) { switch ((enum compound) butPtr->compound) { case COMPOUND_TOP: case COMPOUND_BOTTOM: { /* * Image is above or below text @@ -291,80 +325,33 @@ if (butPtr->height > 0) { height = butPtr->height; } } else { - if (haveImage) { + if (haveImage) { /* Image only */ if (butPtr->width > 0) { width = butPtr->width; } if (butPtr->height > 0) { height = butPtr->height; } - } else { - width = txtWidth; - height = txtHeight; - if (butPtr->width > 0) { - width = butPtr->width * avgWidth; - } - if (butPtr->height > 0) { - height = butPtr->height * fm.linespace; - } - } - } - width += 2 * butPtr->padX - 2; - height += 2 * butPtr->padY - 2; - - /*Add padding for button arrows.*/ - width += 22; - - /* - * Now figure out the size of the border decorations for the button. - */ - - if (butPtr->highlightWidth < 0) { - butPtr->highlightWidth = 0; - } - butPtr->inset = 0; - butPtr->inset += butPtr->highlightWidth; - - TkMacOSXComputeMenuButtonDrawParams(butPtr,&drawParams); - - HIRect tmpRect; - HIRect contBounds; - - tmpRect = CGRectMake(0, 0, width, height); - - HIThemeGetButtonContentBounds(&tmpRect, &mbPtr->drawinfo, &contBounds); - - - - /* If the content region has a minimum height, match it. */ - if (height < contBounds.size.height) { - height = contBounds.size.height; - } - - /* If the content region has a minimum width, match it. */ - if (width < contBounds.size.width) { - width = contBounds.size.width; - } - - /* Pad to fill difference between content bounds and button bounds. */ - paddingx = tmpRect.origin.x - contBounds.origin.x; - paddingy = tmpRect.origin.y - contBounds.origin.y; - - if (paddingx > 0) { - width += paddingx; - } - if (paddingy > 0) { - height += paddingy; - } - - width += butPtr->inset*2; - height += butPtr->inset*2; - - + } else { /* Text only */ + width = txtWidth; + height = txtHeight; + if (butPtr->width > 0) { + width = butPtr->width * avgWidth + 2*butPtr->padX; + } + if (butPtr->height > 0) { + height = butPtr->height * fm.linespace + 2*butPtr->padY; + } + } + } + + butPtr->inset = highlightWidth + butPtr->borderWidth; + width += LEFT_INSET + RIGHT_INSET + 2*butPtr->inset; + height += 2*butPtr->inset; + height = height < MIN_HEIGHT ? MIN_HEIGHT : height; Tk_GeometryRequest(butPtr->tkwin, width, height); Tk_SetInternalBorder(butPtr->tkwin, butPtr->inset); } /* @@ -425,12 +412,12 @@ if (mbPtr->drawinfo.state == kThemeStatePressed) { /* Offset bitmaps by a bit when the button is pressed. */ pressed = 1; } - haveText = (butPtr->textWidth != 0 && butPtr->textHeight != 0); - if (butPtr->compound != COMPOUND_NONE && haveImage && haveText) { + haveText = (butPtr->textWidth != 0 && butPtr->textHeight != 0); + if (butPtr->compound != COMPOUND_NONE && haveImage && haveText) { int x = 0; int y = 0; textXOffset = 0; textYOffset = 0; fullWidth = 0; @@ -444,12 +431,12 @@ textYOffset = height + butPtr->padY; } else { imageYOffset = butPtr->textHeight + butPtr->padY; } fullHeight = height + butPtr->textHeight + butPtr->padY; - fullWidth = (width > butPtr->textWidth ? width : - butPtr->textWidth); + fullWidth = (width > butPtr->textWidth ? + width : butPtr->textWidth); textXOffset = (fullWidth - butPtr->textWidth)/2; imageXOffset = (fullWidth - width)/2; break; } case COMPOUND_LEFT: @@ -487,14 +474,14 @@ } case COMPOUND_NONE: {break;} } TkComputeAnchor(butPtr->anchor, tkwin, - butPtr->padX + butPtr->borderWidth, - butPtr->padY + butPtr->borderWidth, + butPtr->padX + butPtr->inset, + butPtr->padY + butPtr->inset, fullWidth, fullHeight, &x, &y); - imageXOffset += x; + imageXOffset = LEFT_INSET; imageYOffset += y; textYOffset -= 1; if (butPtr->image != NULL) { Tk_RedrawImage(butPtr->image, 0, 0, width, @@ -515,40 +502,36 @@ butPtr->textLayout, x + textXOffset, y + textYOffset, butPtr->underline); } else { if (haveImage) { - int x = 0; - int y; + int x, y; TkComputeAnchor(butPtr->anchor, tkwin, butPtr->padX + butPtr->borderWidth, butPtr->padY + butPtr->borderWidth, width, height, &x, &y); - imageXOffset += x; - imageYOffset += y; - - if (butPtr->image != NULL) { - Tk_RedrawImage(butPtr->image, 0, 0, width, height, - pixmap, imageXOffset, imageYOffset); + imageXOffset = LEFT_INSET; + imageYOffset += y; + if (butPtr->image != NULL) { + Tk_RedrawImage(butPtr->image, 0, 0, width, height, + pixmap, imageXOffset, imageYOffset); } else { XSetClipOrigin(butPtr->display, dpPtr->gc, x, y); XCopyPlane(butPtr->display, butPtr->bitmap, - pixmap, dpPtr->gc, - 0, 0, (unsigned int) width, - (unsigned int) height, - imageXOffset, imageYOffset, 1); + pixmap, dpPtr->gc, + 0, 0, (unsigned int) width, + (unsigned int) height, + imageXOffset, imageYOffset, 1); XSetClipOrigin(butPtr->display, dpPtr->gc, 0, 0); } } else { - /*Move x back by eight pixels to give the menubutton arrows room.*/ - int x = 0; - int y; - textXOffset = 8; + int x, y; + textXOffset = LEFT_INSET; TkComputeAnchor(butPtr->anchor, tkwin, butPtr->padX, butPtr->padY, butPtr->textWidth, butPtr->textHeight, &x, &y); Tk_DrawTextLayout(butPtr->display, pixmap, dpPtr->gc, - butPtr->textLayout, x - textXOffset, y, 0, -1); + butPtr->textLayout, textXOffset, y, 0, -1); y += butPtr->textHeight/2; } } } @@ -576,11 +559,10 @@ MacMenuButton *mbPtr, /* Mac menubutton. */ GC gc, /* The GC we are drawing into - needed for * the bevel button */ Pixmap pixmap) /* The pixmap we are drawing into - needed * for the bevel button */ - { TkMenuButton * butPtr = ( TkMenuButton *)mbPtr; TkWindow * winPtr; HIRect cntrRect; TkMacOSXDrawingContext dc; @@ -589,14 +571,13 @@ winPtr = (TkWindow *)butPtr->tkwin; TkMacOSXComputeMenuButtonParams(butPtr, &mbPtr->btnkind, &mbPtr->drawinfo); - cntrRect = CGRectMake(winPtr->privatePtr->xOff, winPtr->privatePtr->yOff, Tk_Width(butPtr->tkwin),Tk_Height(butPtr->tkwin)); - - cntrRect = CGRectInset(cntrRect, butPtr->inset, butPtr->inset); - + cntrRect = CGRectMake(winPtr->privatePtr->xOff, winPtr->privatePtr->yOff, + Tk_Width(butPtr->tkwin), + Tk_Height(butPtr->tkwin)); if (useNewerHITools == 1) { HIRect contHIRec; static HIThemeButtonDrawInfo hiinfo; @@ -615,21 +596,19 @@ hiinfo.animation.time.current = CFAbsoluteTimeGetCurrent(); if (hiinfo.animation.time.start == 0) { hiinfo.animation.time.start = hiinfo.animation.time.current; } - HIThemeDrawButton(&cntrRect, &hiinfo, dc.context, kHIThemeOrientationNormal, &contHIRec); - + HIThemeDrawButton(&cntrRect, &hiinfo, dc.context, + kHIThemeOrientationNormal, &contHIRec); TkMacOSXRestoreDrawingContext(&dc); - - MenuButtonContentDrawCB( mbPtr->btnkind, &mbPtr->drawinfo, (MacMenuButton *)mbPtr, 32, true); + MenuButtonContentDrawCB( mbPtr->btnkind, &mbPtr->drawinfo, + (MacMenuButton *)mbPtr, 32, true); } else { if (!TkMacOSXSetupDrawingContext(pixmap, dpPtr->gc, 1, &dc)) { return; } - - TkMacOSXRestoreDrawingContext(&dc); } mbPtr->lastdrawinfo = mbPtr->drawinfo; } @@ -694,12 +673,11 @@ Tk_Window tkwin = butPtr->tkwin; if (tkwin == NULL || !Tk_IsMapped(tkwin)) { return; } - - DrawMenuButtonImageAndText( butPtr); + DrawMenuButtonImageAndText(butPtr); } /* *-------------------------------------------------------------- * @@ -759,23 +737,22 @@ * *---------------------------------------------------------------------- */ static void -TkMacOSXComputeMenuButtonParams(TkMenuButton * butPtr, ThemeButtonKind* btnkind, HIThemeButtonDrawInfo *drawinfo) +TkMacOSXComputeMenuButtonParams( + TkMenuButton * butPtr, + ThemeButtonKind* btnkind, + HIThemeButtonDrawInfo *drawinfo) { MacMenuButton *mbPtr = (MacMenuButton *)butPtr; - if (butPtr->image || butPtr->bitmap) { + if (butPtr->image || butPtr->bitmap || butPtr->text) { /* TODO: allow for Small and Mini menubuttons. */ *btnkind = kThemePopupButton; - } else { - if (!butPtr->text || !*butPtr->text) { - *btnkind = kThemeArrowButton; - } else { - *btnkind = kThemePopupButton; - } + } else { /* This should never happen. */ + *btnkind = kThemeArrowButton; } drawinfo->value = kThemeButtonOff; if ((mbPtr->flags & FIRST_DRAW) != 0) { @@ -810,39 +787,38 @@ /* *---------------------------------------------------------------------- * * TkMacOSXComputeMenuButtonDrawParams -- * - * This procedure computes the various parameters used - * when drawing a button - * These are determined by the various tk button parameters + * This procedure selects an appropriate drawing context for + * drawing a menubutton. * * Results: - * 1 if control will be used, 0 otherwise. + * None. * * Side effects: - * Sets the button draw parameters + * Sets the button draw parameters. * *---------------------------------------------------------------------- */ -static int -TkMacOSXComputeMenuButtonDrawParams(TkMenuButton * butPtr, DrawParams * dpPtr) +static void +TkMacOSXComputeMenuButtonDrawParams( + TkMenuButton * butPtr, + DrawParams * dpPtr) { - dpPtr->hasImageOrBitmap = ((butPtr->image != NULL) - || (butPtr->bitmap != None)); + dpPtr->hasImageOrBitmap = ((butPtr->image != NULL) || + (butPtr->bitmap != None)); dpPtr->border = butPtr->normalBorder; if ((butPtr->state == STATE_DISABLED) && (butPtr->disabledFg != NULL)) { dpPtr->gc = butPtr->disabledGC; } else if (butPtr->state == STATE_ACTIVE) { dpPtr->gc = butPtr->activeTextGC; dpPtr->border = butPtr->activeBorder; } else { dpPtr->gc = butPtr->normalTextGC; } - - return 1; } /* * Local Variables: * mode: objc Index: tests/menu.test ================================================================== --- tests/menu.test +++ tests/menu.test @@ -1604,11 +1604,11 @@ menu .m1 .m1 add command -label "On Windows, hit Escape to get this menu to go away" .m1 post } -cleanup { destroy .m1 -} -returnCodes error -result {wrong # args: should be ".m1 post x y"} +} -returnCodes error -result {wrong # args: should be ".m1 post x y ?index?"} test menu-3.48 {MenuWidgetCmd procedure, "post" option} -setup { destroy .m1 } -body { menu .m1 .m1 post foo 40 Index: tests/menubut.test ================================================================== --- tests/menubut.test +++ tests/menubut.test @@ -540,11 +540,15 @@ list [info command .mb*] [winfo children .] } -cleanup { deleteWindows } -result {{} {}} - +if {[tk windowingsystem] == "aqua"} { + set extraWidth 36 +} else { + set extraWidth 0 +} test menubutton-7.1 {ComputeMenuButtonGeometry procedure} -constraints { testImageType } -setup { deleteWindows image create test image1 @@ -553,37 +557,37 @@ pack .mb list [winfo reqwidth .mb] [winfo reqheight .mb] } -cleanup { deleteWindows imageCleanup -} -result {38 23} +} -result [list [expr {38 + $extraWidth}] 23] test menubutton-7.2 {ComputeMenuButtonGeometry procedure} -constraints { testImageType } -setup { deleteWindows image create test image1 } -body { - menubutton .mb -image image1 -bd 1 -highlightthickness 2 + menubutton .mb -image image1 -bd 3 -highlightthickness 1 pack .mb list [winfo reqwidth .mb] [winfo reqheight .mb] } -cleanup { deleteWindows imageCleanup -} -result {36 21} +} -result [list [expr {38 + $extraWidth}] 23] test menubutton-7.3 {ComputeMenuButtonGeometry procedure} -constraints { testImageType } -setup { deleteWindows image create test image1 } -body { - menubutton .mb -image image1 -bd 0 -highlightthickness 2 -padx 5 -pady 5 + menubutton .mb -image image1 -bd 1 -highlightthickness 3 -padx 5 -pady 5 pack .mb list [winfo reqwidth .mb] [winfo reqheight .mb] } -cleanup { deleteWindows imageCleanup -} -result {34 19} +} -result [list [expr {38 + $extraWidth}] 23] test menubutton-7.4 {ComputeMenuButtonGeometry procedure} -constraints { testImageType } -setup { deleteWindows image create test image1 @@ -593,11 +597,11 @@ pack .mb list [winfo reqwidth .mb] [winfo reqheight .mb] } -cleanup { deleteWindows imageCleanup -} -result {48 23} +} -result [list [expr {48 + $extraWidth}] 23] test menubutton-7.5 {ComputeMenuButtonGeometry procedure} -constraints { testImageType } -setup { deleteWindows image create test image1 @@ -607,41 +611,41 @@ pack .mb list [winfo reqwidth .mb] [winfo reqheight .mb] } -cleanup { deleteWindows imageCleanup -} -result {38 38} +} -result [list [expr {38 + $extraWidth}] 38] test menubutton-7.6 {ComputeMenuButtonGeometry procedure} -setup { deleteWindows } -body { menubutton .mb -bitmap question -bd 2 -relief raised \ -highlightthickness 2 pack .mb list [winfo reqwidth .mb] [winfo reqheight .mb] } -cleanup { deleteWindows -} -result {25 35} +} -result [list [expr {25 + $extraWidth}] 35] test menubutton-7.7 {ComputeMenuButtonGeometry procedure} -setup { deleteWindows } -body { menubutton .mb -bitmap question -bd 2 -relief raised -width 40 \ -highlightthickness 1 pack .mb list [winfo reqwidth .mb] [winfo reqheight .mb] } -cleanup { deleteWindows -} -result {46 33} +} -result [list [expr {46 + $extraWidth}] 33] test menubutton-7.8 {ComputeMenuButtonGeometry procedure} -setup { deleteWindows } -body { menubutton .mb -bitmap question -bd 2 -relief raised -height 50 \ -highlightthickness 1 pack .mb list [winfo reqwidth .mb] [winfo reqheight .mb] } -cleanup { deleteWindows -} -result {23 56} +} -result [list [expr {23 + $extraWidth}] 56] test menubutton-7.9 {ComputeMenuButtonGeometry procedure} -constraints { fonts } -setup { deleteWindows } -body { Index: unix/tkUnixMenu.c ================================================================== --- unix/tkUnixMenu.c +++ unix/tkUnixMenu.c @@ -887,11 +887,14 @@ /* *---------------------------------------------------------------------- * * TkpPostMenu -- * - * Posts a menu on the screen + * Posts a menu on the screen so that the top left corner of the + * specified entry is located at the point (x, y) in screen coordinates. + * If the entry parameter is negative, the upper left corner of the + * menu itself is placed at the point. * * Results: * None. * * Side effects: @@ -902,13 +905,108 @@ int TkpPostMenu( Tcl_Interp *interp, TkMenu *menuPtr, - int x, int y) + int x, int y, int index) +{ + return TkpPostTearoffMenu(interp, menuPtr, x, y, index); +} + +/* + *---------------------------------------------------------------------- + * + * TkpPostTearoffMenu -- + * + * Posts a tearoff menu on the screen so that the top left corner of the + * specified entry is located at the point (x, y) in screen coordinates. + * If the index parameter is negative, the upper left corner of the menu + * itself is placed at the point. On unix this is called when posting + * any menu. Adjusts the menu's position so that it fits on the screen, + * and maps and raises the menu. + * + * Results: + * Returns a standard Tcl Error. + * + * Side effects: + * The menu is posted. + * + *---------------------------------------------------------------------- + */ + +int +TkpPostTearoffMenu( + Tcl_Interp *interp, /* The interpreter of the menu */ + TkMenu *menuPtr, /* The menu we are posting */ + int x, int y, int index) /* The root X,Y coordinates where the + * specified entry will be posted */ { - return TkPostTearoffMenu(interp, menuPtr, x, y); + int vRootX, vRootY, vRootWidth, vRootHeight; + int result; + + if (index >= menuPtr->numEntries) { + index = menuPtr->numEntries - 1; + } + if (index >= 0) { + y -= menuPtr->entries[index]->y; + } + + TkActivateMenuEntry(menuPtr, -1); + TkRecomputeMenu(menuPtr); + result = TkPostCommand(menuPtr); + if (result != TCL_OK) { + return result; + } + + /* + * The post commands could have deleted the menu, which means we are dead + * and should go away. + */ + + if (menuPtr->tkwin == NULL) { + return TCL_OK; + } + + /* + * Adjust the position of the menu if necessary to keep it visible on the + * screen. There are two special tricks to make this work right: + * + * 1. If a virtual root window manager is being used then the coordinates + * are in the virtual root window of menuPtr's parent; since the menu + * uses override-redirect mode it will be in the *real* root window for + * the screen, so we have to map the coordinates from the virtual root + * (if any) to the real root. Can't get the virtual root from the menu + * itself (it will never be seen by the wm) so use its parent instead + * (it would be better to have an an option that names a window to use + * for this...). + * 2. The menu may not have been mapped yet, so its current size might be + * the default 1x1. To compute how much space it needs, use its + * requested size, not its actual size. + */ + + Tk_GetVRootGeometry(Tk_Parent(menuPtr->tkwin), &vRootX, &vRootY, + &vRootWidth, &vRootHeight); + vRootWidth -= Tk_ReqWidth(menuPtr->tkwin); + if (x > vRootX + vRootWidth) { + x = vRootX + vRootWidth; + } + if (x < vRootX) { + x = vRootX; + } + vRootHeight -= Tk_ReqHeight(menuPtr->tkwin); + if (y > vRootY + vRootHeight) { + y = vRootY + vRootHeight; + } + if (y < vRootY) { + y = vRootY; + } + Tk_MoveToplevelWindow(menuPtr->tkwin, x, y); + if (!Tk_IsMapped(menuPtr->tkwin)) { + Tk_MapWindow(menuPtr->tkwin); + } + TkWmRestackToplevel((TkWindow *) menuPtr->tkwin, Above, NULL); + return TCL_OK; } /* *---------------------------------------------------------------------- * @@ -1719,11 +1817,10 @@ menuPtr->entries[j]->x = x; menuPtr->entries[j]->entryFlags |= ENTRY_LAST_COLUMN; } windowWidth = x + indicatorSpace + labelWidth + accelWidth + 2 * activeBorderWidth + borderWidth; - windowHeight += borderWidth; /* * The X server doesn't like zero dimensions, so round up to at least 1 (a * zero-sized menu should never really occur, anyway). Index: win/tkWinMenu.c ================================================================== --- win/tkWinMenu.c +++ win/tkWinMenu.c @@ -741,11 +741,14 @@ /* *---------------------------------------------------------------------- * * TkpPostMenu -- * - * Posts a menu on the screen + * Posts a menu on the screen so that the top left corner of the + * specified entry is located at the point (x, y) in screen coordinates. + * If the entry parameter is negative, the upper left corner of the + * menu itself is placed at the point. * * Results: * None. * * Side effects: @@ -756,11 +759,11 @@ int TkpPostMenu( Tcl_Interp *interp, TkMenu *menuPtr, - int x, int y) + int x, int y, int index) { HMENU winMenuHdl = (HMENU) menuPtr->platformData; int result, flags; RECT noGoawayRect; POINT point; @@ -768,18 +771,24 @@ int oldServiceMode = Tcl_GetServiceMode(); ThreadSpecificData *tsdPtr = Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); tsdPtr->inPostMenu++; - CallPendingReconfigureImmediately(menuPtr); result = TkPreprocessMenu(menuPtr); if (result != TCL_OK) { tsdPtr->inPostMenu--; return result; } + + if (index >= menuPtr->numEntries) { + index = menuPtr->numEntries - 1; + } + if (index >= 0) { + y -= menuPtr->entries[index]->y; + } /* * The post commands could have deleted the menu, which means * we are dead and should go away. */ @@ -835,10 +844,104 @@ if (tsdPtr->inPostMenu) { tsdPtr->inPostMenu = 0; } return TCL_OK; } + +/* + *---------------------------------------------------------------------- + * + * TkpPostTearoffMenu -- + * + * Posts a tearoff menu on the screen so that the top left corner of the + * specified entry is located at the point (x, y) in screen coordinates. + * If the index parameter is negative, the upper left corner of the menu + * itself is placed at the point. Adjusts the menu's position so that it + * fits on the screen, and maps and raises the menu. + * + * Results: + * Returns a standard Tcl Error. + * + * Side effects: + * The menu is posted. + * + *---------------------------------------------------------------------- + */ + +int +TkpPostTearoffMenu( + Tcl_Interp *interp, /* The interpreter of the menu */ + TkMenu *menuPtr, /* The menu we are posting */ + int x, int y, int index) /* The root X,Y coordinates where we are + * posting */ +{ + int vRootX, vRootY, vRootWidth, vRootHeight; + int result; + + if (index >= menuPtr->numEntries) { + index = menuPtr->numEntries - 1; + } + if (index >= 0) { + y -= menuPtr->entries[index]->y; + } + + TkActivateMenuEntry(menuPtr, -1); + TkRecomputeMenu(menuPtr); + result = TkPostCommand(menuPtr); + if (result != TCL_OK) { + return result; + } + + /* + * The post commands could have deleted the menu, which means we are dead + * and should go away. + */ + + if (menuPtr->tkwin == NULL) { + return TCL_OK; + } + + /* + * Adjust the position of the menu if necessary to keep it visible on the + * screen. There are two special tricks to make this work right: + * + * 1. If a virtual root window manager is being used then the coordinates + * are in the virtual root window of menuPtr's parent; since the menu + * uses override-redirect mode it will be in the *real* root window for + * the screen, so we have to map the coordinates from the virtual root + * (if any) to the real root. Can't get the virtual root from the menu + * itself (it will never be seen by the wm) so use its parent instead + * (it would be better to have an an option that names a window to use + * for this...). + * 2. The menu may not have been mapped yet, so its current size might be + * the default 1x1. To compute how much space it needs, use its + * requested size, not its actual size. + */ + + Tk_GetVRootGeometry(Tk_Parent(menuPtr->tkwin), &vRootX, &vRootY, + &vRootWidth, &vRootHeight); + vRootWidth -= Tk_ReqWidth(menuPtr->tkwin); + if (x > vRootX + vRootWidth) { + x = vRootX + vRootWidth; + } + if (x < vRootX) { + x = vRootX; + } + vRootHeight -= Tk_ReqHeight(menuPtr->tkwin); + if (y > vRootY + vRootHeight) { + y = vRootY + vRootHeight; + } + if (y < vRootY) { + y = vRootY; + } + Tk_MoveToplevelWindow(menuPtr->tkwin, x, y); + if (!Tk_IsMapped(menuPtr->tkwin)) { + Tk_MapWindow(menuPtr->tkwin); + } + TkWmRestackToplevel((TkWindow *) menuPtr->tkwin, Above, NULL); + return TCL_OK; +} /* *---------------------------------------------------------------------- * * TkpMenuNewEntry -- @@ -2895,11 +2998,10 @@ menuPtr->entries[i]->height = height; } else if (menuPtr->entries[i]->type == TEAROFF_ENTRY) { GetTearoffEntryGeometry(menuPtr, menuPtr->entries[i], tkfont, fmPtr, &width, &height); menuPtr->entries[i]->height = height; - } else { /* * For each entry, compute the height required by that particular * entry, plus three widths: the width of the label, the width to * allow for an indicator to be displayed to the left of the label @@ -2953,12 +3055,10 @@ menuPtr->entries[j]->x = x; menuPtr->entries[j]->entryFlags |= ENTRY_LAST_COLUMN; } windowWidth = x + indicatorSpace + labelWidth + accelWidth + 2 * activeBorderWidth + borderWidth; - - windowHeight += borderWidth; /* * The X server doesn't like zero dimensions, so round up to at least 1 (a * zero-sized menu should never really occur, anyway).