stk/Lib/menu.stk

802 lines
28 KiB
Plaintext

;;;;
;;;; Copyright © 1993-1996 Erick Gallesio - I3S-CNRS/ESSI <eg@unice.fr>
;;;;
;;;; Permission to use, copy, and/or distribute this software and its
;;;; documentation for any purpose and without fee is hereby granted, provided
;;;; that both the above copyright notice and this permission notice appear in
;;;; all copies and derived works. Fees for distribution or use of this
;;;; software or derived works may only be charged with express written
;;;; permission of the copyright holder.
;;;; This software is provided ``as is'' without express or implied warranty.
;;;;
;;;; This software is a derivative work of other copyrighted softwares; the
;;;; copyright notices of these softwares are placed in the file COPYRIGHTS
;;;;
;;;;
;;;; Author: Erick Gallesio [eg@unice.fr]
;;;; Creation date: 17-May-1993 12:35
;;;; Last file update: 2-Jul-1996 19:30
;;;;
;; This file is loaded for the first menu or menub-button. Avoid to load it twice
(unless (or (tk-command? Tk:menu) (tk-command? Tk:menubutton))
(let ()
(define tk::in-menu-button #f)
(define tk::posted-mb #f)
(define tk::popup #f)
(define tk::grab-status #f)
(define tk::old-grab #f)
;;-------------------------------------------------------------------------
;; Globals that are used in this file:
;;
;; cursor - Saves the -cursor option for the posted menubutton.
;; focus - Saves the focus during a menu selection operation.
;; Focus gets restored here when the menu is unposted.
;; grab-status - Used in conjunction with Tk::old-grab: if Tk:old-grab
;; is not false, then Tk:grab-status contains either an
;; empty string or "-global" to indicate whether the old
;; grab was a local one or a global one.
;; in-menu-button - The name of the menubutton widget containing
;; the mouse, or an empty string if the mouse is
;; not over any menubutton.
;; old-grab - Window that had the grab before a menu was posted.
;; Used to restore the grab state after the menu
;; is unposted. Empty string means there was no
;; grab previously set.
;; popup - If a menu has been popped up via tk_popup, this
;; gives the name of the menu. Otherwise this
;; value is empty.
;; posted-mb - Name of the menubutton whose menu is currently
;; posted, or an empty string if nothing is posted
;; A grab is set on this widget.
;; relief - Used to save the original relief of the current
;; menubutton.
;; window - When the mouse is over a menu, this holds the
;; name of the menu; it's cleared when the mouse
;; leaves the menu.
;;-------------------------------------------------------------------------
;;-------------------------------------------------------------------------
;; Overall note:
;; This file is tricky because there are four different ways that menus
;; can be used:
;;
;; 1. As a pulldown from a menubutton. This is the most common usage.
;; In this style, the variable tk::posted-mb identifies the posted
;; menubutton.
;; 2. As a torn-off menu copied from some other menu. In this style
;; tk::posted-Mb is empty, and the top-level menu is no
;; override-redirect.
;; 3. As an option menu, triggered from an option menubutton. In thi
;; style tk::posted-Mb identifies the posted menubutton.
;; 4. As a popup menu. In this style tk::posted-mb is empty and
;; the top-level menu is override-redirect.
;;
;; The various binding procedures use the state described above to
;; distinguish the various cases and take different actions in each
;; case.
;;-------------------------------------------------------------------------
;;-------------------------------------------------------------------------
;; The code below creates the default class bindings for menus
;; and menubuttons.
;;-------------------------------------------------------------------------
(define-binding "Menubutton" "<FocusIn>" ()
'())
(define-binding "Menubutton" "<Enter>" (|W|)
(Tk:menu-button-enter |W|))
(define-binding "Menubutton" "<Leave>" (|W|)
(Tk:menu-button-leave |W|))
(define-binding "Menubutton" "<1>" (|W| |X| |Y|)
(when tk::in-menu-button
(Tk:menu-button-post tk::in-menu-button |X| |Y|)))
(define-binding "Menubutton" "<Motion>" (|W| |X| |Y|)
(Tk:menu-button-motion |W| 'up |X| |Y|))
(define-binding "Menubutton" "<B1-Motion>" (|W| |X| |Y|)
(Tk:menu-button-motion |W| 'down |X| |Y|))
(define-binding "Menubutton" "<ButtonRelease-1>" (|W|)
(Tk:menu-button-button-up |W|))
(define-binding "Menubutton" "<space>" (|W|)
(Tk:menu-button-post |W|)
(Tk:menu-first-entry (tk-get |W| :menu)))
;; Must set focus when mouse enters a menu, in order to allow
;; mixed-mode processing using both the mouse and the keyboard.
;; Don't set the focus if the event comes from a grab release,
;; though: such an event can happen after as part of unposting
;; a cascaded chain of menus, after the focus has already been
;; restored to wherever it was before menu selection started.
(define-binding "Menu" "<FocusIn>" ()
'())
(define-binding "Menu" "<Enter>" (|W| m)
(set! tk::window |W|)
(unless (string=? m "NotifyUngrab")
(focus |W|)))
(define-binding "Menu" "<Leave>" (|W| |X| |Y|)
(Tk:menu-leave |W| |X| |Y|))
(define-binding "Menu" "<Motion>" (|W| y s)
(Tk:menu-Motion |W| y s))
(define-binding "Menu" "<ButtonPress>" (|W|)
(Tk:menu-button-down |W|))
(define-binding "Menu" "<ButtonRelease>" (|W|)
(Tk:menu-invoke |W| #t))
(define-binding "Menu" "<space>" (|W|)
(Tk:menu-invoke |W| #f))
(define-binding "Menu" "<Return>" (|W|)
(Tk:menu-invoke |W| #f))
(define-binding "Menu" "<Escape>" (|W|)
(Tk:menu-escape |W|))
(define-binding "Menu" "<Left>" (|W|)
(Tk:menu-left-right |W| 'left))
(define-binding "Menu" "<Right>" (|W|)
(Tk:menu-left-right |W| 'right))
(define-binding "Menu" "<Up>" (|W|)
(Tk:menu-next-entry |W| -1))
(define-binding "Menu" "<Down>" (|W|)
(Tk:menu-next-entry |W| +1))
(define-binding "Menu" "<KeyPress>" (|W| |A|)
(Tk:traverse-within-menu |W| |A|))
;; The following bindings apply to all windows, and are used to
;; implement keyboard menu traversal.
(define-binding "all" "<Alt-KeyPress>" (|W| |A|)
(Tk:traverse-to-menu |W| |A|))
(define-binding "all" "<Meta-KeyPress>" (|W| |A|)
(Tk:traverse-to-menu |W| |A|))
(define-binding "all" "<F10>" (|W|)
(Tk:first-menu |W|))
;; Tk:menu-button-enter --
;; This procedure is invoked when the mouse enters a menubutton
;; widget. It activates the widget unless it is disabled. Note:
;; this procedure is only invoked when mouse button 1 is *not* down.
;; The procedure Tk:menu-button-B1-enter is invoked if the button is down.
;;
(define (Tk:menu-button-enter w)
(when tk::in-menu-button
(Tk:menu-button-leave tk::in-menu-button))
(set! tk::in-menu-button w)
(unless (equal? (tk-get w :state) "disabled")
(tk-set! w :state "active")))
;; Tk:menu-button-leave --
;; This procedure is invoked when the mouse leaves a menubutton widget.
;; It de-activates the widget, if the widget still exists.
;;
(define (Tk:menu-button-leave w)
(set! tk::in-menu-button #f)
(when (and (winfo 'exists w) (equal? (tk-get w :state) "active"))
(tk-set! w :state "normal")))
;; Tk:menu-button-Post --
;; Given a menubutton, this procedure does all the work of posting
;; its associated menu and unposting any other menu that is currently
;; posted.
;;
;; w - The name of the menubutton widget whose menu
;; is to be posted.
;; x, y - Root coordinates of cursor, used for positioning
;; option menus. If not specified, then the center
;; of the menubutton is used for an option menu.
(define (Tk:menu-button-post w . coords)
(unless (or (equal? (tk-get w :state) "disabled") (equal? w tk::posted-mb))
(let ((menu (tk-get w :menu)))
(when menu
(unless (string-find? (string-append (widget->string w) ".")
(widget->string menu))
;; This is weak, but should be sufficient
(error "can't post ~S: it isn't a descendant of ~S" menu w))
(let ((cur tk::posted-mb))
(when tk::posted-mb (Tk:menu-unpost #f))
(set! tk::cursor (tk-get w :cursor))
(set! tk::relief (tk-get w :relief))
(tk-set! w :cursor "arrow")
(tk-set! w :relief "raised")
(set! tk::posted-mb w)
(set! tk::focus (focus))
(menu 'activate 'none)
;; 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.
(if (tk-get w :indicatoron)
(let ((x (if (null? coords)
(+ (winfo 'rootx w) (/ (winfo 'width w) 2))
(car coords)))
(y (if (null? coords)
(+ (winfo 'rooty w) (/ (winfo 'height w) 2))
(cadr coords))))
(Tk:post-over-point menu x y
(Tk:menu-find-name menu (tk-get w :text))))
(menu 'post (winfo 'rootx w)
(+ (winfo 'rooty w) (winfo 'height w))))
(focus menu)
(Tk:save-grab-info w)
(grab :global w))))))
;; Tk:menu-unpost --
;; This procedure unposts a given menu, plus all of its ancestors up
;; to (and including) a menubutton, if any. It also restores various
;; values to what they were before the menu was posted, and releases
;; a grab if there's a menubutton involved. Special notes:
;; 1. It's important to unpost all menus before releasing the grab, so
;; that any Enter-Leave events (e.g. from menu back to main
;; application) have mode NotifyGrab.
;; 2. Be sure to enclose various groups of commands in "catch" so that
;; the procedure will complete even if the menubutton or the menu
;; or the grab window has been deleted.
;;
;; menu - Name of a menu to unpost. Ignored if there
;; is a posted menubutton.
(define (Tk:menu-unpost menu)
(let ((mb tk::posted-mb))
;; Restore focus right away (otherwise X will take focus away when
;; the menu is unmapped and under some window managers (e.g. olvwm)
;; we'll lose the focus completely).
(catch (focus tk::focus))
(set! tk::focus #f)
;; Unpost menu(s) and restore some stuff that's dependent on
;; what was posted.
(BEGIN ;catch
(if mb
(begin
(set! menu (tk-get mb :menu))
(menu 'unpost)
(set! tk::posted-mb #f)
(tk-set! mb :cursor tk::cursor :relief tk::relief))
(if tk::popup
(begin
(tk::popup 'unpost)
(set! tk::popup #f))
(when (and menu (wm 'overrideredirect menu))
;; We're in a cascaded sub-menu from a torn-off menu or popup.
;; Unpost all the menus up to the toplevel one (but not
;; including the top-level torn-off one) and deactivate the
;; top-level torn off menu if there is one.
(let loop ((parent (winfo 'parent menu)))
(when (and (equal? (winfo 'class parent) "Menu")
(winfo 'ismapped parent))
(parent 'activate "none")
(parent 'postcascade "none")
(if (wm 'overrideredirect parent)
(loop (winfo 'parent parent)))))
(menu 'unpost)))))
;; Release grab, if any, and restore the previous grab, if there was one.
(if menu
(let ((g (grab 'current menu)))
(and g (grab 'release g))))
(when tk::old-grab
;; Be careful restoring the old grab, since it's window may not
;; be visible anymore.
(catch
(if (equal? tk::grab-status "global")
(grab 'set :global tk::old-grab)
(grab 'set tk::old-grab)))
(set! tk::old-grab #f))))
;; Tk:menu-button-motion --
;; This procedure handles mouse motion events inside menubuttons, and
;; also outside menubuttons when a menubutton has a grab (e.g. when a
;; menu selection operation is in progress).
;;
;; w - The name of the menubutton widget.
;; upDown - "down" means button 1 is pressed, "up" means
;; it isn't.
;; rootx, rooty - Coordinates of mouse, in (virtual?) root window.
(define (Tk:menu-button-motion w upDown rootx rooty)
(unless (equal? tk::in-menu-button w)
(let ((new (winfo 'containing rootx rooty)))
(when (and (not (equal? new tk::in-menu-button))
(or (not new)
(equal? (winfo 'toplevel new) (winfo 'toplevel w))))
(if tk::in-menu-button
(Tk:menu-button-leave tk::in-menu-button))
(when (and new
(equal? (winfo 'class new) "Menubutton")
(not (tk-get new :indicatoron))
(not (tk-get w :indicatoron)))
(if (eq? updown 'down)
(Tk:menu-button-post new rootx rooty)
(Tk:menu-button-enter new)))))))
;; Tk:menu-button-button-up --
;; This procedure is invoked to handle button 1 releases for menubuttons.
;; If the release happens inside the menubutton then leave its menu
;; posted with element 0 activated. Otherwise, unpost the menu.
;;
(define (Tk:menu-button-button-up w)
(if (and (equal? tk::posted-mb w) (equal? tk::in-menu-button w))
(Tk:menu-first-entry (tk-get tk::posted-mb :menu))
(Tk:menu-unpost #f)))
;; Tk:menu-motion --
;; This procedure is called to handle mouse motion events for menus.
;; It does two things. First, it resets the active element in the
;; menu, if the mouse is over the menu. Second, if a mouse button
;; is down, it posts and unposts cascade entries to match the mouse
;; position.
;;
;; Arguments:
;; menu - The menu window.
;; y - The y position of the mouse.
;; state - Modifier state (tells whether buttons are down).
(define (Tk:menu-motion menu y state)
(if (equal? menu tk::window)
(menu 'activate (format #f "@~A" y)))
(if (= (* (modulo state 128) (modulo state 512) (modulo state 1024)) 0)
(menu 'postcascade 'active)))
;; Tk:menu-button-down --
;; Handles button presses in menus. There are a couple of tricky things
;; here:
;; 1. Change the posted cascade entry (if any) to match the mouse position.
;; 2. If there is a posted menubutton, must grab to the menubutton; this
;; overrrides the implicit grab on button press, so that the menu
;; button can track mouse motions over other menubuttons and change
;; the posted menu.
;; 3. If there's no posted menubutton (e.g. because we're a torn-off menu
;; or one of its descendants) must grab to the top-level menu so that
;; we can track mouse motions across the entire menu hierarchy.
;;
(define (Tk:menu-button-down menu)
(menu 'postcascade 'active)
(if tk::posted-mb
(grab :global tk::posted-mb)
(let loop ((menu menu)
(parent (winfo 'parent menu)))
(if (and (wm 'overrideredirect menu)
(equal? (winfo 'class parent) "Menu")
(winfo 'ismapped parent))
(loop parent (winfo 'parent parent))
(begin
; Don't update grab information if the grab window isn't changing.
; Otherwise, we'll get an error when we unpost the menus and
; restore the grab, since the old grab window will not be viewable
; anymore.
(unless (equal? menu (grab 'current menu))
(Tk:save-grab-info menu))
; Must re-grab even if the grab window hasn't changed, in order
; to release the implicit grab from the button press.
(grab :global menu))))))
;; Tk:menu-leave --
;; This procedure is invoked to handle Leave events for a menu. It
;; deactivates everything unless the active element is a cascade element
;; and the mouse is now over the submenu.
;;
;; menu - The menu window.
;; rootx, rooty - Root coordinates of mouse.
;; state - Modifier state.
(define (Tk:menu-leave menu rootx rooty)
(set! tk::window #f)
(unless (equal? (menu 'index 'active) "none")
(unless (and (equal? (menu 'type "active") "cascade")
(equal? (winfo 'containing rootx rooty)
(menu 'entrycget 'active :menu)))
(menu 'activate "none"))))
;; Tk:menu-invoke --
;; This procedure is invoked when button 1 is released over a menu.
;; It invokes the appropriate menu action and unposts the menu if
;; it came from a menubutton.
;;
;; w - menu widget.
;; button-release - #t means this procedure is called because of
;; a button release; #f means because of keystroke.
;;
(define (Tk:menu-invoke w button-release)
(cond
((and button-release (not tk::window))
;; Mouse was pressed over a menu without a menu button,
;; then dragged off the menu (possibly with a cascade
;; posted) and released. Unpost everything
(w 'postcascade "none")
(w 'activate "none")
(Tk:menu-unpost w))
((equal? (w 'type "active") "cascade")
(w 'postcascade "active")
(Tk:menu-first-entry (w 'entrycget "active" :menu)))
((equal? (w 'type "active") "tearoff")
(Tk:menu-unpost w)
(Tk:tear-off-menu w))
(ELSE (Tk:menu-unpost w)
(w 'invoke "active"))))
;; Tk:menuEscape --
;; This procedure is invoked for the Cancel (or Escape) key. It unposts
;; the given menu and, if it is the top-level menu for a menu button,
;; unposts the menu button as well.
;;
(define (Tk:menu-escape menu)
(if (equal? (winfo 'class (winfo 'parent menu)) "Menu")
(Tk:menu-left-right menu -1)
(Tk:menu-unpost menu)))
;; Tk:menu-left-right --
;; This procedure is invoked to handle "left" and "right" traversal
;; motions in menus. It traverses to the next menu in a menu bar,
;; or into or out of a cascaded menu.
;;
;; menu - The menu that received the keyboard
;; event.
;; direction - Direction in which to move: "left" or "right"
(define (Tk:menu-left-right menu direction)
(let ((count +1)
(continue #t))
;; First handle traversals into and out of cascaded menus.
(if (eq? direction 'right)
(when (equal? (menu 'type "active") "cascade")
(menu 'postcascade "active")
(let ((m2 (menu 'entrycget 'active :menu)))
(and m2 (Tk:menu-first-entry m2))
(set! continue #f)))
;; Direction is 'left
(let ((m2 (winfo 'parent menu)))
(set! count -1)
(when (equal? (winfo 'class m2) "Menu")
(menu 'activate "none")
(focus m2)
;; This code unposts any posted submenu in the parent.
(let ((tmp (m2 'index "active")))
(m2 'activate "none")
(m2 'activate tmp)
(set! continue #f)))))
(when (and continue tk::posted-mb)
;; Can't traverse into or out of a cascaded menu. Go to the next
;; or previous menubutton, if that makes sense.
(let* ((buttons (winfo 'children [winfo 'parent tk::posted-mb]))
(len (length buttons)))
(let loop ((i (- count (length (member tk::posted-mb buttons)))))
(while (< i 0) (set! i (+ i len)))
(while (>= i len) (set! i (- i len)))
(let ((mb (list-ref buttons i)))
(when (and (equal? [winfo 'class mb] "Menubutton")
(not (equal? [tk-get mb :state] "disabled"))
(tk-get mb :menu)
(not (equal? ((tk-get mb :menu) 'index "last") "none")))
(Tk:menu-button-post mb)
(Tk:menu-first-entry (tk-get mb :menu)))
(unless (eq? mb tk::posted-mb)
(loop (+ i count)))))))))
;; Tk:menu-next-entry --
;; Activate the next higher or lower entry in the posted menu,
;; wrapping around at the ends. Disabled entries are skipped.
;;
;; Arguments:
;; menu - Menu window that received the keystroke.
;; count - 1 means go to the next lower entry,
;; -1 means go to the next higher entry.
(define (Tk:menu-next-entry menu count)
(unless (equal? (menu 'index "last") "none")
(let* ((length (+ (menu 'index "last") 1))
(quit-after length)
(active (menu 'index "active"))
(i 0)
(break #f))
(unless (equal? active "none")
(set! i (+ active count)))
(let loop ((i i))
(when (> quit-after 0)
;; We've not already tried every entry in the menu
(set! quit-after (- quit-after 1))
(while (< i 0) (set! i (+ i length)))
(while (>= i length) (set! i (- i length)))
(catch (set! break (not (equal? (menu 'entrycget i :state) "disabled"))))
(if break
(begin
(menu 'activate i)
(menu 'postcascade i))
(unless (= i active)
(loop (+ i count)))))))))
;; Tk:menu-find --
;; This procedure searches the entire window hierarchy under w for
;; a menubutton that isn't disabled and whose underlined character
;; is "char". It returns the name of that window, if found, or #f
;; if no matching window was found. If "char" is an
;; empty string then the procedure returns the name of the first
;; menubutton found that isn't disabled.
;;
;; w - Name of window where key was typed.
;; char - Underlined character to search for;
;; may be either upper or lower case, and
;; will match either upper or lower case.
(define (Tk:menu-find w char)
(let ((char (string-lower char)))
(let loop ((children (winfo 'children w)))
(if (null? children)
#f
(let* ((child (car children))
(C (winfo 'class child)))
(cond
((string=? C "Menubutton")
(let* ((index (tk-get child :underline))
(txt (tk-get child :text))
(char2 (if (= index -1)
""
(string (string-ref txt index)))))
(if (and (or (string=? char (string-lower char2))
(string=? char ""))
(not (equal? (tk-get child :state) "disabled")))
child
(loop (cdr children)))))
((string=? C "Frame")
(or (Tk:menu-find child char)
(loop (cdr children))))
(ELSE (loop (cdr children)))))))))
;; Tk:traverse-to-menu --
;; This procedure implements keyboard traversal of menus. Given an
;; ASCII character "char", it looks for a menubutton with that character
;; underlined. If one is found, it posts the menubutton's menu
;;
;; Arguments:
;; w - Window in which the key was typed (selects
;; a toplevel window).
;; char - Character that selects a menu. The case
;; is ignored. If an empty string, nothing
;; happens.
(define (Tk:traverse-to-menu w char)
(let ((continue #t))
(unless (string=? char "")
(while (and continue (equal? (winfo 'class w) "Menu"))
(if tk::posted-mb
(set! w (winfo 'parent w))
(set! continue #f)))
(when continue
(let ((w (Tk:menu-find (winfo 'toplevel w) char)))
(when w
(Tk:menu-button-post w)
(Tk:menu-first-entry (tk-get w :menu))))))))
;; Tk:first-menu --
;; This procedure traverses to the first menubutton in the toplevel
;; for a given window, and posts that menubutton's menu.
;;
;; w - Name of a window. Selects which toplevel
;; to search for menubuttons.
(define (Tk:first-menu w)
(let ((w (Tk:menu-find (winfo 'toplevel w) "")))
(when w
(Tk:menu-button-post w)
(Tk:menu-first-entry (tk-get w :menu)))))
;; Tk:traverse-within-menu
;; This procedure implements keyboard traversal within a menu. It
;; searches for an entry in the menu that has "char" underlined. If
;; such an entry is found, it is invoked and the menu is unposted.
;;
;; Arguments:
;; w - The name of the menu widget.
;; char - The character to look for; case is
;; ignored. If the string is empty then
;; nothing happens.
(define (Tk:traverse-within-menu w char)
(unless (equal? char "")
(let* ((char (string-lower char))
(last (w 'index "last")))
(unless (equal? last "none")
(let loop ((i 0))
(when (<= i last)
(let ((char2 #f)
(index -1)
(label ""))
(catch
(set! index (w 'entrycget i :underline))
(set! label (w 'entrycget i :label))
(set! char2 (string-lower (string (string-ref label index)))))
(if (and char2 (string=? char char2))
(if (equal? (w 'type i) "cascade")
(begin
(w 'postcascade i)
(w 'activate i)
(let ((m2 (w 'entrycget i :menu)))
(and m2 (Tk:menu-first-entry m2))))
(begin
(Tk:menu-unpost w)
(w 'invoke i)))
(loop (+ i 1))))))))))
;; Tk:menu-first-entry --
;; Given a menu, this procedure finds the first entry that isn't
;; disabled or a tear-off or separator, and activates that entry.
;; However, if there is already an active entry in the menu (e.g.,
;; because of a previous call to tkPostOverPoint) then the active
;; entry isn't changed. This procedure also sets the input focus
;; to the menu.
;;
(define (Tk:menu-first-entry menu)
(when menu
(focus menu)
(when (equal? (menu 'index "active") "none")
(let ((last (menu 'index "last")))
(unless (equal? last "none")
(let loop ((i 0))
(when (<= i last)
(let ((state #f))
(catch (set! state (menu 'entrycget i :state)))
(if (or (not state)
(equal? state "disabled")
(equal? (menu 'type i) "tearoff"))
(loop (+ i 1))
(menu 'activate i))))))))))
;; Tk:menu-find-name --
;; Given a menu and a text string, return the index of the menu entry
;; that displays the string as its label. If there is no such entry,
;; return an empty string. This procedure is tricky because some names
;; like "active" have a special meaning in menu commands, so we can't
;; always use the "index" widget command.
;;
;; menu - Name of the menu widget.
;; s - String to look for.
(define (Tk:menu-find-name menu s)
(let ((last (menu 'index "last")))
(unless (equal? last "none")
(let loop ((i 0))
(if (<= i last)
(let ((label #f))
(catch (set! label (menu 'entrycget i :label)))
(if (equal? label s)
i
(loop (+ i 1))))
#f)))))
;; Tk:post-over-point --
;; 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.
;;
;; 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).
(define (Tk:post-over-point menu x y entry)
(when entry
(if (= entry (menu 'index "last"))
(set! y (- y (/ (+ (menu 'yposition entry) (winfo 'reqheight menu)) 2)))
(set! y (- y (/ (+ (menu 'yposition entry)
(menu 'yposition (+ entry 1))) 2))))
(set! x (- x (/ (winfo 'reqwidth menu) 2))))
(menu 'post (inexact->exact x) (inexact->exact y))
(if (and entry (not (equal? (menu 'entrycget entry :state) "disabled")))
(menu 'activate entry)))
;; Tk:save-grab-info
;; Sets the variables tkPriv(oldGrab) and tkPriv(grabStatus) to record
;; the state of any existing grab on the w's display.
;;
;; w - Name of a window; used to select the display
;; whose grab information is to be recorded.
(define (Tk:save-grab-info w)
(set! tk::old-grab (grab 'current w))
(if tk::old-grab
(set! tk::grab-status [grab 'status tk::old-grab])))
(load "tearoff")
))
;; Tk:popup --
;; This procedure pops up a menu and sets things up for traversing
;; the menu and its submenus.
;;
;; menu - Name of the menu to be popped up.
;; x, y - Root coordinates at which to pop up the
;; menu.
;; entry - Index of a menu entry to center over (x,y).
;; If omitted or specified as {}, then menu's
;; upper-left corner goes at (x,y).
(define (Tk:popup menu x y entry)
(if (or tk::popup tk::posted-mb)
(Tk:menu-unpost #f))
(Tk:post-over-point menu x y entry)
(Tk:save-grab-info menu)
(grab :global menu)
(set! tk::popup menu)
(set! tk::focus (focus))
(focus menu))
;; Tk:option-menu --
;; This procedure creates an option button named and an associated
;; menu. Together they provide the functionality of Motif option menus:
;; they can be used to select one of many values, and the current value
;; appears in the global variable var-name, as well as in the text of
;; the option menubutton. The menu is returned as the
;; procedure's result, so that the caller can use it to change configuration
;; options on the menu or otherwise manipulate it.
;;
;; w - The name to use for the menubutton.
;; var-name - Global variable to hold the currently selected value.
;; first - first (mandatory) legal value for option
;; l legal values for option
(define (Tk:option-menu w var-name first . l)
;; define var-name before (if necessary) otherwise :textvar will define it to ""
(unless (symbol-bound? var-name (global-environment))
(eval `(define ,var-name ',first) (global-environment)))
(let* ((menu-name (format #f "~A.menu" w))
(mb (menubutton w :textvariable var-name
:indicatoron #t
:menu menu-name
:relief "raised"
:borderwidth 2
:highlightthickness 2
:anchor "c"))
(m (menu menu-name :tearoff #f))
(env (global-environment)))
(for-each (lambda (x)
(m 'add 'command :label x :command (lambda()
(eval `(set! ,var-name ',x)
env))))
(cons first l))
m))