3 # This file defines the default bindings for Tk menus and menubuttons.
4 # It also implements keyboard traversal of menus and implements a few
5 # other utility procedures related to menus.
7 # SCCS: @(#) menu.tcl 1.103 97/10/31 15:26:08
9 # Copyright (c) 1992-1994 The Regents of the University of California.
10 # Copyright (c) 1994-1997 Sun Microsystems, Inc.
12 # See the file "license.terms" for information on usage and redistribution
13 # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
16 #-------------------------------------------------------------------------
17 # Elements of tkPriv that are used in this file:
19 # cursor - Saves the -cursor option for the posted menubutton.
20 # focus - Saves the focus during a menu selection operation.
21 # Focus gets restored here when the menu is unposted.
22 # grabGlobal - Used in conjunction with tkPriv(oldGrab): if
23 # tkPriv(oldGrab) is non-empty, then tkPriv(grabGlobal)
24 # contains either an empty string or "-global" to
25 # indicate whether the old grab was a local one or
27 # inMenubutton - The name of the menubutton widget containing
28 # the mouse, or an empty string if the mouse is
29 # not over any menubutton.
30 # menuBar - The name of the menubar that is the root
31 # of the cascade hierarchy which is currently
32 # posted. This is null when there is no menu currently
33 # being pulled down from a menu bar.
34 # oldGrab - Window that had the grab before a menu was posted.
35 # Used to restore the grab state after the menu
36 # is unposted. Empty string means there was no
37 # grab previously set.
38 # popup - If a menu has been popped up via tk_popup, this
39 # gives the name of the menu. Otherwise this
41 # postedMb - Name of the menubutton whose menu is currently
42 # posted, or an empty string if nothing is posted
43 # A grab is set on this widget.
44 # relief - Used to save the original relief of the current
46 # window - When the mouse is over a menu, this holds the
47 # name of the menu; it's cleared when the mouse
49 # tearoff - Whether the last menu posted was a tearoff or not.
50 # This is true always for unix, for tearoffs for Mac
52 # activeMenu - This is the last active menu for use
53 # with the <<MenuSelect>> virtual event.
54 # activeItem - This is the last active menu item for
55 # use with the <<MenuSelect>> virtual event.
56 #-------------------------------------------------------------------------
58 #-------------------------------------------------------------------------
60 # This file is tricky because there are five different ways that menus
63 # 1. As a pulldown from a menubutton. In this style, the variable
64 # tkPriv(postedMb) identifies the posted menubutton.
65 # 2. As a torn-off menu copied from some other menu. In this style
66 # tkPriv(postedMb) is empty, and menu's type is "tearoff".
67 # 3. As an option menu, triggered from an option menubutton. In this
68 # style tkPriv(postedMb) identifies the posted menubutton.
69 # 4. As a popup menu. In this style tkPriv(postedMb) is empty and
70 # the top-level menu's type is "normal".
71 # 5. As a pulldown from a menubar. The variable tkPriv(menubar) has
72 # the owning menubar, and the menu itself is of type "normal".
74 # The various binding procedures use the state described above to
75 # distinguish the various cases and take different actions in each
77 #-------------------------------------------------------------------------
79 #-------------------------------------------------------------------------
80 # The code below creates the default class bindings for menus
82 #-------------------------------------------------------------------------
84 bind Menubutton <FocusIn> {}
85 bind Menubutton <Enter> {
88 bind Menubutton <Leave> {
92 if {$tkPriv(inMenubutton) != ""} {
93 tkMbPost $tkPriv(inMenubutton) %X %Y
96 bind Menubutton <Motion> {
97 tkMbMotion %W up %X %Y
99 bind Menubutton <B1-Motion> {
100 tkMbMotion %W down %X %Y
102 bind Menubutton <ButtonRelease-1> {
105 bind Menubutton <space> {
107 tkMenuFirstEntry [%W cget -menu]
110 # Must set focus when mouse enters a menu, in order to allow
111 # mixed-mode processing using both the mouse and the keyboard.
112 # Don't set the focus if the event comes from a grab release,
113 # though: such an event can happen after as part of unposting
114 # a cascaded chain of menus, after the focus has already been
115 # restored to wherever it was before menu selection started.
117 bind Menu <FocusIn> {}
120 set tkPriv(window) %W
121 if {[%W cget -type] == "tearoff"} {
122 if {"%m" != "NotifyUngrab"} {
123 if {$tcl_platform(platform) == "unix"} {
128 tkMenuMotion %W %x %y %s
132 tkMenuLeave %W %X %Y %s
135 tkMenuMotion %W %x %y %s
137 bind Menu <ButtonPress> {
140 bind Menu <ButtonRelease> {
164 bind Menu <KeyPress> {
165 tkTraverseWithinMenu %W %A
168 # The following bindings apply to all windows, and are used to
169 # implement keyboard menu traversal.
171 if {$tcl_platform(platform) == "unix"} {
172 bind all <Alt-KeyPress> {
173 tkTraverseToMenu %W %A
180 bind Menubutton <Alt-KeyPress> {
181 tkTraverseToMenu %W %A
184 bind Menubutton <F10> {
190 # This procedure is invoked when the mouse enters a menubutton
191 # widget. It activates the widget unless it is disabled. Note:
192 # this procedure is only invoked when mouse button 1 is *not* down.
193 # The procedure tkMbB1Enter is invoked if the button is down.
196 # w - The name of the widget.
201 if {$tkPriv(inMenubutton) != ""} {
202 tkMbLeave $tkPriv(inMenubutton)
204 set tkPriv(inMenubutton) $w
205 if {[$w cget -state] != "disabled"} {
206 $w configure -state active
211 # This procedure is invoked when the mouse leaves a menubutton widget.
212 # It de-activates the widget, if the widget still exists.
215 # w - The name of the widget.
220 set tkPriv(inMenubutton) {}
221 if ![winfo exists $w] {
224 if {[$w cget -state] == "active"} {
225 $w configure -state normal
230 # Given a menubutton, this procedure does all the work of posting
231 # its associated menu and unposting any other menu that is currently
235 # w - The name of the menubutton widget whose menu
237 # x, y - Root coordinates of cursor, used for positioning
238 # option menus. If not specified, then the center
239 # of the menubutton is used for an option menu.
241 proc tkMbPost {w {x {}} {y {}}} {
242 global tkPriv errorInfo
245 if {([$w cget -state] == "disabled") || ($w == $tkPriv(postedMb))} {
248 set menu [$w cget -menu]
252 set tearoff [expr {($tcl_platform(platform) == "unix") \
253 || ([$menu cget -type] == "tearoff")}]
254 if {[string first $w $menu] != 0} {
255 error "can't post $menu: it isn't a descendant of $w (this is a new requirement in Tk versions 3.0 and later)"
257 set cur $tkPriv(postedMb)
261 set tkPriv(cursor) [$w cget -cursor]
262 set tkPriv(relief) [$w cget -relief]
263 $w configure -cursor arrow
264 $w configure -relief raised
266 set tkPriv(postedMb) $w
267 set tkPriv(focus) [focus]
269 tkGenerateMenuSelect $menu
271 # If this looks like an option menubutton then post the menu so
272 # that the current entry is on top of the mouse. Otherwise post
273 # the menu just below the menubutton, as for a pull-down.
277 switch [$w cget -direction] {
279 set x [winfo rootx $w]
280 set y [expr [winfo rooty $w] - [winfo reqheight $menu]]
284 set x [winfo rootx $w]
285 set y [expr [winfo rooty $w] + [winfo height $w]]
289 set x [expr [winfo rootx $w] - [winfo reqwidth $menu]]
290 set y [expr (2 * [winfo rooty $w] + [winfo height $w]) / 2]
291 set entry [tkMenuFindName $menu [$w cget -text]]
292 if [$w cget -indicatoron] {
293 if {$entry == [$menu index last]} {
294 incr y [expr -([$menu yposition $entry] \
295 + [winfo reqheight $menu])/2]
297 incr y [expr -([$menu yposition $entry] \
298 + [$menu yposition [expr $entry+1]])/2]
302 if {($entry != {}) && ([$menu entrycget $entry -state] != "disabled")} {
303 $menu activate $entry
304 tkGenerateMenuSelect $menu
308 set x [expr [winfo rootx $w] + [winfo width $w]]
309 set y [expr (2 * [winfo rooty $w] + [winfo height $w]) / 2]
310 set entry [tkMenuFindName $menu [$w cget -text]]
311 if [$w cget -indicatoron] {
312 if {$entry == [$menu index last]} {
313 incr y [expr -([$menu yposition $entry] \
314 + [winfo reqheight $menu])/2]
316 incr y [expr -([$menu yposition $entry] \
317 + [$menu yposition [expr $entry+1]])/2]
321 if {($entry != {}) && ([$menu entrycget $entry -state] != "disabled")} {
322 $menu activate $entry
323 tkGenerateMenuSelect $menu
327 if [$w cget -indicatoron] {
329 set x [expr [winfo rootx $w] + [winfo width $w]/2]
330 set y [expr [winfo rooty $w] + [winfo height $w]/2]
332 tkPostOverPoint $menu $x $y [tkMenuFindName $menu [$w cget -text]]
334 $menu post [winfo rootx $w] [expr [winfo rooty $w]+[winfo height $w]]
339 # Error posting menu (e.g. bogus -postcommand). Unpost it and
342 set savedInfo $errorInfo
344 error $msg $savedInfo
348 set tkPriv(tearoff) $tearoff
357 # This procedure unposts a given menu, plus all of its ancestors up
358 # to (and including) a menubutton, if any. It also restores various
359 # values to what they were before the menu was posted, and releases
360 # a grab if there's a menubutton involved. Special notes:
361 # 1. It's important to unpost all menus before releasing the grab, so
362 # that any Enter-Leave events (e.g. from menu back to main
363 # application) have mode NotifyGrab.
364 # 2. Be sure to enclose various groups of commands in "catch" so that
365 # the procedure will complete even if the menubutton or the menu
366 # or the grab window has been deleted.
369 # menu - Name of a menu to unpost. Ignored if there
370 # is a posted menubutton.
372 proc tkMenuUnpost menu {
375 set mb $tkPriv(postedMb)
377 # Restore focus right away (otherwise X will take focus away when
378 # the menu is unmapped and under some window managers (e.g. olvwm)
379 # we'll lose the focus completely).
381 catch {focus $tkPriv(focus)}
384 # Unpost menu(s) and restore some stuff that's dependent on
389 set menu [$mb cget -menu]
391 set tkPriv(postedMb) {}
392 $mb configure -cursor $tkPriv(cursor)
393 $mb configure -relief $tkPriv(relief)
394 } elseif {$tkPriv(popup) != ""} {
395 $tkPriv(popup) unpost
397 } elseif {(!([$menu cget -type] == "menubar")
398 && !([$menu cget -type] == "tearoff"))} {
399 # We're in a cascaded sub-menu from a torn-off menu or popup.
400 # Unpost all the menus up to the toplevel one (but not
401 # including the top-level torn-off one) and deactivate the
402 # top-level torn off menu if there is one.
405 set parent [winfo parent $menu]
406 if {([winfo class $parent] != "Menu")
407 || ![winfo ismapped $parent]} {
410 $parent activate none
411 $parent postcascade none
412 tkGenerateMenuSelect $parent
413 set type [$parent cget -type]
414 if {($type == "menubar")|| ($type == "tearoff")} {
419 if {[$menu cget -type] != "menubar"} {
425 if {($tkPriv(tearoff) != 0) || ($tkPriv(menuBar) != "")} {
426 # Release grab, if any, and restore the previous grab, if there
430 set grab [grab current $menu]
436 if {$tkPriv(menuBar) != ""} {
437 $tkPriv(menuBar) configure -cursor $tkPriv(cursor)
438 set tkPriv(menuBar) {}
440 if {$tcl_platform(platform) != "unix"} {
441 set tkPriv(tearoff) 0
447 # This procedure handles mouse motion events inside menubuttons, and
448 # also outside menubuttons when a menubutton has a grab (e.g. when a
449 # menu selection operation is in progress).
452 # w - The name of the menubutton widget.
453 # upDown - "down" means button 1 is pressed, "up" means
455 # rootx, rooty - Coordinates of mouse, in (virtual?) root window.
457 proc tkMbMotion {w upDown rootx rooty} {
460 if {$tkPriv(inMenubutton) == $w} {
463 set new [winfo containing $rootx $rooty]
464 if {($new != $tkPriv(inMenubutton)) && (($new == "")
465 || ([winfo toplevel $new] == [winfo toplevel $w]))} {
466 if {$tkPriv(inMenubutton) != ""} {
467 tkMbLeave $tkPriv(inMenubutton)
469 if {($new != "") && ([winfo class $new] == "Menubutton")
470 && ([$new cget -indicatoron] == 0)
471 && ([$w cget -indicatoron] == 0)} {
472 if {$upDown == "down"} {
473 tkMbPost $new $rootx $rooty
482 # This procedure is invoked to handle button 1 releases for menubuttons.
483 # If the release happens inside the menubutton then leave its menu
484 # posted with element 0 activated. Otherwise, unpost the menu.
487 # w - The name of the menubutton widget.
489 proc tkMbButtonUp w {
493 set tearoff [expr {($tcl_platform(platform) == "unix") \
494 || ([[$w cget -menu] cget -type] == "tearoff")}]
495 if {($tearoff != 0) && ($tkPriv(postedMb) == $w)
496 && ($tkPriv(inMenubutton) == $w)} {
497 tkMenuFirstEntry [$tkPriv(postedMb) cget -menu]
504 # This procedure is called to handle mouse motion events for menus.
505 # It does two things. First, it resets the active element in the
506 # menu, if the mouse is over the menu. Second, if a mouse button
507 # is down, it posts and unposts cascade entries to match the mouse
511 # menu - The menu window.
512 # x - The x position of the mouse.
513 # y - The y position of the mouse.
514 # state - Modifier state (tells whether buttons are down).
516 proc tkMenuMotion {menu x y state} {
518 if {$menu == $tkPriv(window)} {
519 if {[$menu cget -type] == "menubar"} {
520 if {[info exists tkPriv(focus)] && \
521 ([string compare $menu $tkPriv(focus)] != 0)} {
522 $menu activate @$x,$y
523 tkGenerateMenuSelect $menu
526 $menu activate @$x,$y
527 tkGenerateMenuSelect $menu
530 if {($state & 0x1f00) != 0} {
531 $menu postcascade active
535 # tkMenuButtonDown --
536 # Handles button presses in menus. There are a couple of tricky things
538 # 1. Change the posted cascade entry (if any) to match the mouse position.
539 # 2. If there is a posted menubutton, must grab to the menubutton; this
540 # overrrides the implicit grab on button press, so that the menu
541 # button can track mouse motions over other menubuttons and change
543 # 3. If there's no posted menubutton (e.g. because we're a torn-off menu
544 # or one of its descendants) must grab to the top-level menu so that
545 # we can track mouse motions across the entire menu hierarchy.
548 # menu - The menu window.
550 proc tkMenuButtonDown menu {
553 $menu postcascade active
554 if {$tkPriv(postedMb) != ""} {
555 grab -global $tkPriv(postedMb)
557 while {([$menu cget -type] == "normal")
558 && ([winfo class [winfo parent $menu]] == "Menu")
559 && [winfo ismapped [winfo parent $menu]]} {
560 set menu [winfo parent $menu]
563 if {$tkPriv(menuBar) == {}} {
564 set tkPriv(menuBar) $menu
565 set tkPriv(cursor) [$menu cget -cursor]
566 $menu configure -cursor arrow
569 # Don't update grab information if the grab window isn't changing.
570 # Otherwise, we'll get an error when we unpost the menus and
571 # restore the grab, since the old grab window will not be viewable
574 if {$menu != [grab current $menu]} {
578 # Must re-grab even if the grab window hasn't changed, in order
579 # to release the implicit grab from the button press.
581 if {$tcl_platform(platform) == "unix"} {
588 # This procedure is invoked to handle Leave events for a menu. It
589 # deactivates everything unless the active element is a cascade element
590 # and the mouse is now over the submenu.
593 # menu - The menu window.
594 # rootx, rooty - Root coordinates of mouse.
595 # state - Modifier state.
597 proc tkMenuLeave {menu rootx rooty state} {
599 set tkPriv(window) {}
600 if {[$menu index active] == "none"} {
603 if {([$menu type active] == "cascade")
604 && ([winfo containing $rootx $rooty]
605 == [$menu entrycget active -menu])} {
609 tkGenerateMenuSelect $menu
613 # This procedure is invoked when button 1 is released over a menu.
614 # It invokes the appropriate menu action and unposts the menu if
615 # it came from a menubutton.
618 # w - Name of the menu widget.
619 # buttonRelease - 1 means this procedure is called because of
620 # a button release; 0 means because of keystroke.
622 proc tkMenuInvoke {w buttonRelease} {
625 if {$buttonRelease && ($tkPriv(window) == "")} {
626 # Mouse was pressed over a menu without a menu button, then
627 # dragged off the menu (possibly with a cascade posted) and
628 # released. Unpost everything and quit.
632 event generate $w <<MenuSelect>>
636 if {[$w type active] == "cascade"} {
637 $w postcascade active
638 set menu [$w entrycget active -menu]
639 tkMenuFirstEntry $menu
640 } elseif {[$w type active] == "tearoff"} {
643 } elseif {[$w cget -type] == "menubar"} {
646 event generate $w <<MenuSelect>>
650 uplevel #0 [list $w invoke active]
655 # This procedure is invoked for the Cancel (or Escape) key. It unposts
656 # the given menu and, if it is the top-level menu for a menu button,
657 # unposts the menu button as well.
660 # menu - Name of the menu window.
662 proc tkMenuEscape menu {
663 set parent [winfo parent $menu]
664 if {([winfo class $parent] != "Menu")} {
666 } elseif {([$parent cget -type] == "menubar")} {
670 tkMenuNextMenu $menu left
674 # The following routines handle arrow keys. Arrow keys behave
675 # differently depending on whether the menu is a menu bar or not.
677 proc tkMenuUpArrow {menu} {
678 if {[$menu cget -type] == "menubar"} {
679 tkMenuNextMenu $menu left
681 tkMenuNextEntry $menu -1
685 proc tkMenuDownArrow {menu} {
686 if {[$menu cget -type] == "menubar"} {
687 tkMenuNextMenu $menu right
689 tkMenuNextEntry $menu 1
693 proc tkMenuLeftArrow {menu} {
694 if {[$menu cget -type] == "menubar"} {
695 tkMenuNextEntry $menu -1
697 tkMenuNextMenu $menu left
701 proc tkMenuRightArrow {menu} {
702 if {[$menu cget -type] == "menubar"} {
703 tkMenuNextEntry $menu 1
705 tkMenuNextMenu $menu right
710 # This procedure is invoked to handle "left" and "right" traversal
711 # motions in menus. It traverses to the next menu in a menu bar,
712 # or into or out of a cascaded menu.
715 # menu - The menu that received the keyboard
717 # direction - Direction in which to move: "left" or "right"
719 proc tkMenuNextMenu {menu direction} {
722 # First handle traversals into and out of cascaded menus.
724 if {$direction == "right"} {
726 set parent [winfo parent $menu]
727 set class [winfo class $parent]
728 if {[$menu type active] == "cascade"} {
729 $menu postcascade active
730 set m2 [$menu entrycget active -menu]
736 set parent [winfo parent $menu]
737 while {($parent != ".")} {
738 if {([winfo class $parent] == "Menu")
739 && ([$parent cget -type] == "menubar")} {
740 tk_menuSetFocus $parent
741 tkMenuNextEntry $parent 1
744 set parent [winfo parent $parent]
749 set m2 [winfo parent $menu]
750 if {[winfo class $m2] == "Menu"} {
751 if {[$m2 cget -type] != "menubar"} {
753 tkGenerateMenuSelect $menu
756 # This code unposts any posted submenu in the parent.
758 set tmp [$m2 index active]
766 # Can't traverse into or out of a cascaded menu. Go to the next
767 # or previous menubutton, if that makes sense.
769 set m2 [winfo parent $menu]
770 if {[winfo class $m2] == "Menu"} {
771 if {[$m2 cget -type] == "menubar"} {
773 tkMenuNextEntry $m2 -1
778 set w $tkPriv(postedMb)
782 set buttons [winfo children [winfo parent $w]]
783 set length [llength $buttons]
784 set i [expr [lsearch -exact $buttons $w] + $count]
789 while {$i >= $length} {
792 set mb [lindex $buttons $i]
793 if {([winfo class $mb] == "Menubutton")
794 && ([$mb cget -state] != "disabled")
795 && ([$mb cget -menu] != "")
796 && ([[$mb cget -menu] index last] != "none")} {
805 tkMenuFirstEntry [$mb cget -menu]
809 # Activate the next higher or lower entry in the posted menu,
810 # wrapping around at the ends. Disabled entries are skipped.
813 # menu - Menu window that received the keystroke.
814 # count - 1 means go to the next lower entry,
815 # -1 means go to the next higher entry.
817 proc tkMenuNextEntry {menu count} {
820 if {[$menu index last] == "none"} {
823 set length [expr [$menu index last]+1]
824 set quitAfter $length
825 set active [$menu index active]
826 if {$active == "none"} {
829 set i [expr $active + $count]
832 if {$quitAfter <= 0} {
833 # We've tried every entry in the menu. Either there are
834 # none, or they're all disabled. Just give up.
841 while {$i >= $length} {
844 if {[catch {$menu entrycget $i -state} state] == 0} {
845 if {$state != "disabled"} {
856 tkGenerateMenuSelect $menu
857 if {[$menu type $i] == "cascade"} {
858 set cascade [$menu entrycget $i -menu]
859 if {[string compare $cascade ""] != 0} {
861 tkMenuFirstEntry $cascade
867 # This procedure searches the entire window hierarchy under w for
868 # a menubutton that isn't disabled and whose underlined character
869 # is "char" or an entry in a menubar that isn't disabled and whose
870 # underlined character is "char".
871 # It returns the name of that window, if found, or an
872 # empty string if no matching window was found. If "char" is an
873 # empty string then the procedure returns the name of the first
874 # menubutton found that isn't disabled.
877 # w - Name of window where key was typed.
878 # char - Underlined character to search for;
879 # may be either upper or lower case, and
880 # will match either upper or lower case.
882 proc tkMenuFind {w char} {
884 set char [string tolower $char]
885 set windowlist [winfo child $w]
887 foreach child $windowlist {
888 switch [winfo class $child] {
890 if {[$child cget -type] == "menubar"} {
894 set last [$child index last]
895 for {set i [$child cget -tearoff]} {$i <= $last} {incr i} {
896 if {[$child type $i] == "separator"} {
899 set char2 [string index [$child entrycget $i -label] \
900 [$child entrycget $i -underline]]
901 if {([string compare $char [string tolower $char2]] \
902 == 0) || ($char == "")} {
903 if {[$child entrycget $i -state] != "disabled"} {
913 foreach child $windowlist {
914 switch [winfo class $child] {
916 set char2 [string index [$child cget -text] \
917 [$child cget -underline]]
918 if {([string compare $char [string tolower $char2]] == 0)
920 if {[$child cget -state] != "disabled"} {
927 set match [tkMenuFind $child $char]
937 # tkTraverseToMenu --
938 # This procedure implements keyboard traversal of menus. Given an
939 # ASCII character "char", it looks for a menubutton with that character
940 # underlined. If one is found, it posts the menubutton's menu
943 # w - Window in which the key was typed (selects
944 # a toplevel window).
945 # char - Character that selects a menu. The case
946 # is ignored. If an empty string, nothing
949 proc tkTraverseToMenu {w char} {
954 while {[winfo class $w] == "Menu"} {
955 if {([$w cget -type] != "menubar") && ($tkPriv(postedMb) == "")} {
958 if {[$w cget -type] == "menubar"} {
961 set w [winfo parent $w]
963 set w [tkMenuFind [winfo toplevel $w] $char]
965 if {[winfo class $w] == "Menu"} {
967 set tkPriv(window) $w
970 tkTraverseWithinMenu $w $char
973 tkMenuFirstEntry [$w cget -menu]
979 # This procedure traverses to the first menubutton in the toplevel
980 # for a given window, and posts that menubutton's menu.
983 # w - Name of a window. Selects which toplevel
984 # to search for menubuttons.
987 set w [tkMenuFind [winfo toplevel $w] ""]
989 if {[winfo class $w] == "Menu"} {
991 set tkPriv(window) $w
997 tkMenuFirstEntry [$w cget -menu]
1002 # tkTraverseWithinMenu
1003 # This procedure implements keyboard traversal within a menu. It
1004 # searches for an entry in the menu that has "char" underlined. If
1005 # such an entry is found, it is invoked and the menu is unposted.
1008 # w - The name of the menu widget.
1009 # char - The character to look for; case is
1010 # ignored. If the string is empty then
1013 proc tkTraverseWithinMenu {w char} {
1017 set char [string tolower $char]
1018 set last [$w index last]
1019 if {$last == "none"} {
1022 for {set i 0} {$i <= $last} {incr i} {
1023 if [catch {set char2 [string index \
1024 [$w entrycget $i -label] \
1025 [$w entrycget $i -underline]]}] {
1028 if {[string compare $char [string tolower $char2]] == 0} {
1029 if {[$w type $i] == "cascade"} {
1031 $w postcascade active
1032 event generate $w <<MenuSelect>>
1033 set m2 [$w entrycget $i -menu]
1035 tkMenuFirstEntry $m2
1039 uplevel #0 [list $w invoke $i]
1046 # tkMenuFirstEntry --
1047 # Given a menu, this procedure finds the first entry that isn't
1048 # disabled or a tear-off or separator, and activates that entry.
1049 # However, if there is already an active entry in the menu (e.g.,
1050 # because of a previous call to tkPostOverPoint) then the active
1051 # entry isn't changed. This procedure also sets the input focus
1055 # menu - Name of the menu window (possibly empty).
1057 proc tkMenuFirstEntry menu {
1061 tk_menuSetFocus $menu
1062 if {[$menu index active] != "none"} {
1065 set last [$menu index last]
1066 if {$last == "none"} {
1069 for {set i 0} {$i <= $last} {incr i} {
1070 if {([catch {set state [$menu entrycget $i -state]}] == 0)
1071 && ($state != "disabled") && ([$menu type $i] != "tearoff")} {
1073 tkGenerateMenuSelect $menu
1074 if {[$menu type $i] == "cascade"} {
1075 set cascade [$menu entrycget $i -menu]
1076 if {[string compare $cascade ""] != 0} {
1077 $menu postcascade $i
1078 tkMenuFirstEntry $cascade
1087 # Given a menu and a text string, return the index of the menu entry
1088 # that displays the string as its label. If there is no such entry,
1089 # return an empty string. This procedure is tricky because some names
1090 # like "active" have a special meaning in menu commands, so we can't
1091 # always use the "index" widget command.
1094 # menu - Name of the menu widget.
1095 # s - String to look for.
1097 proc tkMenuFindName {menu s} {
1099 if {![regexp {^active$|^last$|^none$|^[0-9]|^@} $s]} {
1100 catch {set i [$menu index $s]}
1103 set last [$menu index last]
1104 if {$last == "none"} {
1107 for {set i 0} {$i <= $last} {incr i} {
1108 if ![catch {$menu entrycget $i -label} label] {
1117 # tkPostOverPoint --
1118 # This procedure posts a given menu such that a given entry in the
1119 # menu is centered over a given point in the root window. It also
1120 # activates the given entry.
1123 # menu - Menu to post.
1124 # x, y - Root coordinates of point.
1125 # entry - Index of entry within menu to center over (x,y).
1126 # If omitted or specified as {}, then the menu's
1127 # upper-left corner goes at (x,y).
1129 proc tkPostOverPoint {menu x y {entry {}}} {
1133 if {$entry == [$menu index last]} {
1134 incr y [expr -([$menu yposition $entry] \
1135 + [winfo reqheight $menu])/2]
1137 incr y [expr -([$menu yposition $entry] \
1138 + [$menu yposition [expr $entry+1]])/2]
1140 incr x [expr -[winfo reqwidth $menu]/2]
1143 if {($entry != {}) && ([$menu entrycget $entry -state] != "disabled")} {
1144 $menu activate $entry
1145 tkGenerateMenuSelect $menu
1150 # Sets the variables tkPriv(oldGrab) and tkPriv(grabStatus) to record
1151 # the state of any existing grab on the w's display.
1154 # w - Name of a window; used to select the display
1155 # whose grab information is to be recorded.
1157 proc tkSaveGrabInfo w {
1159 set tkPriv(oldGrab) [grab current $w]
1160 if {$tkPriv(oldGrab) != ""} {
1161 set tkPriv(grabStatus) [grab status $tkPriv(oldGrab)]
1165 # tkRestoreOldGrab --
1166 # Restores the grab to what it was before TkSaveGrabInfo was called.
1169 proc tkRestoreOldGrab {} {
1172 if {$tkPriv(oldGrab) != ""} {
1174 # Be careful restoring the old grab, since it's window may not
1175 # be visible anymore.
1178 if {$tkPriv(grabStatus) == "global"} {
1179 grab set -global $tkPriv(oldGrab)
1181 grab set $tkPriv(oldGrab)
1184 set tkPriv(oldGrab) ""
1188 proc tk_menuSetFocus {menu} {
1190 if {![info exists tkPriv(focus)] || [string length $tkPriv(focus)] == 0} {
1191 set tkPriv(focus) [focus]
1196 proc tkGenerateMenuSelect {menu} {
1199 if {([string compare $tkPriv(activeMenu) $menu] == 0) \
1200 && ([string compare $tkPriv(activeItem) [$menu index active]] \
1205 set tkPriv(activeMenu) $menu
1206 set tkPriv(activeItem) [$menu index active]
1207 event generate $menu <<MenuSelect>>
1211 # This procedure pops up a menu and sets things up for traversing
1212 # the menu and its submenus.
1215 # menu - Name of the menu to be popped up.
1216 # x, y - Root coordinates at which to pop up the
1218 # entry - Index of a menu entry to center over (x,y).
1219 # If omitted or specified as {}, then menu's
1220 # upper-left corner goes at (x,y).
1222 proc tk_popup {menu x y {entry {}}} {
1225 if {($tkPriv(popup) != "") || ($tkPriv(postedMb) != "")} {
1228 tkPostOverPoint $menu $x $y $entry
1229 if {$tcl_platform(platform) == "unix"} {
1230 tkSaveGrabInfo $menu
1232 set tkPriv(popup) $menu
1233 tk_menuSetFocus $menu