blob: 064d2c58d8853ca018163fe24a0face4974e81f0 [file] [log] [blame]
#
# Optionmenu
# ----------------------------------------------------------------------
# Implements an option menu widget with options to manage it.
# An option menu displays a frame containing a label and a button.
# A pop-up menu will allow for the value of the button to change.
#
# ----------------------------------------------------------------------
# AUTHOR: Alfredo Jahn Phone: (214) 519-3545
# Email: ajahn@spd.dsccc.com
# alfredo@wn.com
#
# @(#) $Id: optionmenu.itk,v 1.9 2001/10/26 15:28:22 smithc Exp $
# ----------------------------------------------------------------------
# Copyright (c) 1995 DSC Technologies Corporation
# ======================================================================
# Permission to use, copy, modify, distribute and license this software
# and its documentation for any purpose, and without fee or written
# agreement with DSC, is hereby granted, provided that the above copyright
# notice appears in all copies and that both the copyright notice and
# warranty disclaimer below appear in supporting documentation, and that
# the names of DSC Technologies Corporation or DSC Communications
# Corporation not be used in advertising or publicity pertaining to the
# software without specific, written prior permission.
#
# DSC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, AND NON-
# INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND THE
# AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. IN NO EVENT SHALL
# DSC BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTUOUS ACTION,
# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
# SOFTWARE.
# ======================================================================
#
# Default resources.
#
option add *Optionmenu.highlightThickness 1 widgetDefault
option add *Optionmenu.borderWidth 2 widgetDefault
option add *Optionmenu.labelPos w widgetDefault
option add *Optionmenu.labelMargin 2 widgetDefault
option add *Optionmenu.popupCursor arrow widgetDefault
#
# Usual options.
#
itk::usual Optionmenu {
keep -activebackground -activeborderwidth -activeforeground \
-background -borderwidth -cursor -disabledforeground -font \
-foreground -highlightcolor -highlightthickness -labelfont \
-popupcursor
}
# ------------------------------------------------------------------
# OPTONMENU
# ------------------------------------------------------------------
itcl::class iwidgets::Optionmenu {
inherit iwidgets::Labeledwidget
constructor {args} {}
destructor {}
itk_option define -clicktime clickTime ClickTime 150
itk_option define -command command Command {}
itk_option define -cyclicon cyclicOn CyclicOn true
itk_option define -width width Width 0
itk_option define -font font Font -Adobe-Helvetica-Bold-R-Normal--*-120-*
itk_option define -borderwidth borderWidth BorderWidth 2
itk_option define -highlightthickness highlightThickness HighlightThickness 1
itk_option define -state state State normal
public {
method index {index}
method delete {first {last {}}}
method disable {index}
method enable {args}
method get {{first "current"} {last ""}}
method insert {index string args}
method popupMenu {args}
method select {index}
method sort {{mode "increasing"}}
}
protected {
variable _calcSize "" ;# non-null => _calcSize pending
}
private {
method _buttonRelease {time}
method _getNextItem {index}
method _next {}
method _postMenu {time}
method _previous {}
method _setItem {item}
method _setSize {{when later}}
method _setitems {items} ;# Set the list of menu entries
variable _postTime 0
variable _items {} ;# List of popup menu entries
variable _numitems 0 ;# List of popup menu entries
variable _currentItem "" ;# Active menu selection
}
}
#
# Provide a lowercased access method for the Optionmenu class.
#
proc ::iwidgets::optionmenu {pathName args} {
uplevel ::iwidgets::Optionmenu $pathName $args
}
# ------------------------------------------------------------------
# CONSTRUCTOR
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::constructor {args} {
global tcl_platform
component hull configure -highlightthickness 0
itk_component add menuBtn {
menubutton $itk_interior.menuBtn -relief raised -indicatoron on \
-textvariable [itcl::scope _currentItem] -takefocus 1 \
-menu $itk_interior.menuBtn.menu
} {
usual
keep -borderwidth
if {$tcl_platform(platform) != "unix"} {
ignore -activebackground -activeforeground
}
}
pack $itk_interior.menuBtn -fill x
pack propagate $itk_interior no
itk_component add popupMenu {
menu $itk_interior.menuBtn.menu -tearoff no
} {
usual
ignore -tearoff
keep -activeborderwidth -borderwidth
rename -cursor -popupcursor popupCursor Cursor
}
#
# Bind to button release for all components.
#
bind $itk_component(menuBtn) <ButtonPress-1> \
"[itcl::code $this _postMenu %t]; break"
bind $itk_component(menuBtn) <KeyPress-space> \
"[itcl::code $this _postMenu %t]; break"
bind $itk_component(popupMenu) <ButtonRelease-1> \
[itcl::code $this _buttonRelease %t]
#
# Initialize the widget based on the command line options.
#
eval itk_initialize $args
}
# ------------------------------------------------------------------
# DESTRUCTOR
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::destructor {} {
if {$_calcSize != ""} {after cancel $_calcSize}
}
# ------------------------------------------------------------------
# OPTIONS
# ------------------------------------------------------------------
# ------------------------------------------------------------------
# OPTION -clicktime
#
# Interval time (in msec) used to determine that a single mouse
# click has occurred. Used to post menu on a quick mouse click.
# **WARNING** changing this value may cause the sigle-click
# functionality to not work properly!
# ------------------------------------------------------------------
itcl::configbody iwidgets::Optionmenu::clicktime {}
# ------------------------------------------------------------------
# OPTION -command
#
# Specifies a command to be evaluated upon change in option menu.
# ------------------------------------------------------------------
itcl::configbody iwidgets::Optionmenu::command {}
# ------------------------------------------------------------------
# OPTION -cyclicon
#
# Turns on/off the 3rd mouse button capability. This feature
# allows the right mouse button to cycle through the popup
# menu list without poping it up. <shift>M3 cycles through
# the menu in reverse order.
# ------------------------------------------------------------------
itcl::configbody iwidgets::Optionmenu::cyclicon {
if {$itk_option(-cyclicon)} {
bind $itk_component(menuBtn) <3> [itcl::code $this _next]
bind $itk_component(menuBtn) <Shift-3> [itcl::code $this _previous]
bind $itk_component(menuBtn) <KeyPress-Down> [itcl::code $this _next]
bind $itk_component(menuBtn) <KeyPress-Up> [itcl::code $this _previous]
} else {
bind $itk_component(menuBtn) <3> break
bind $itk_component(menuBtn) <Shift-3> break
bind $itk_component(menuBtn) <KeyPress-Down> break
bind $itk_component(menuBtn) <KeyPress-Up> break
}
}
# ------------------------------------------------------------------
# OPTION -width
#
# Allows the menu label width to be set to a fixed size
# ------------------------------------------------------------------
itcl::configbody iwidgets::Optionmenu::width {
_setSize
}
# ------------------------------------------------------------------
# OPTION -font
#
# Change all fonts for this widget. Also re-calculate height based
# on font size (used to line up menu items over menu button label).
# ------------------------------------------------------------------
itcl::configbody iwidgets::Optionmenu::font {
_setSize
}
# ------------------------------------------------------------------
# OPTION -borderwidth
#
# Change borderwidth for this widget. Also re-calculate height based
# on font size (used to line up menu items over menu button label).
# ------------------------------------------------------------------
itcl::configbody iwidgets::Optionmenu::borderwidth {
_setSize
}
# ------------------------------------------------------------------
# OPTION -highlightthickness
#
# Change highlightthickness for this widget. Also re-calculate
# height based on font size (used to line up menu items over
# menu button label).
# ------------------------------------------------------------------
itcl::configbody iwidgets::Optionmenu::highlightthickness {
_setSize
}
# ------------------------------------------------------------------
# OPTION -state
#
# Specified one of two states for the Optionmenu: normal, or
# disabled. If the Optionmenu is disabled, then option menu
# selection is ignored.
# ------------------------------------------------------------------
itcl::configbody iwidgets::Optionmenu::state {
switch $itk_option(-state) {
normal {
$itk_component(menuBtn) config -state normal
$itk_component(label) config -fg $itk_option(-foreground)
}
disabled {
$itk_component(menuBtn) config -state disabled
$itk_component(label) config -fg $itk_option(-disabledforeground)
}
default {
error "bad state option \"$itk_option(-state)\":\
should be disabled or normal"
}
}
}
# ------------------------------------------------------------------
# METHODS
# ------------------------------------------------------------------
# ------------------------------------------------------------------
# METHOD: index index
#
# Return the numerical index corresponding to index.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::index {index} {
if {[regexp {(^[0-9]+$)} $index]} {
set idx [$itk_component(popupMenu) index $index]
if {$idx == "none"} {
return 0
}
return [expr {$index > $idx ? $_numitems : $idx}]
} elseif {$index == "end"} {
return [expr {$_numitems - 1}]
} elseif {$index == "select"} {
return [lsearch $_items $_currentItem]
}
set numValue [lsearch -glob $_items $index]
if {$numValue == -1} {
error "bad Optionmenu index \"$index\""
}
return $numValue
}
# ------------------------------------------------------------------
# METHOD: delete first ?last?
#
# Remove an item (or range of items) from the popup menu.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::delete {first {last {}}} {
set first [index $first]
set last [expr {$last != {} ? [index $last] : $first}]
set nextAvail $_currentItem
#
# If current item is in delete range point to next available.
#
if {$_numitems > 1 &&
([lsearch -exact [lrange $_items $first $last] [get]] != -1)} {
set nextAvail [_getNextItem $last]
}
_setitems [lreplace $_items $first $last]
#
# Make sure "nextAvail" is still in the list.
#
set index [lsearch -exact $_items $nextAvail]
_setItem [expr {$index != -1 ? $nextAvail : ""}]
}
# ------------------------------------------------------------------
# METHOD: disable index
#
# Disable a menu item in the option menu. This will prevent the user
# from being able to select this item from the menu. This only effects
# the state of the item in the menu, in other words, should the item
# be the currently selected item, the user is responsible for
# determining this condition and taking appropriate action.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::disable {index} {
set index [index $index]
$itk_component(popupMenu) entryconfigure $index -state disabled
}
# ------------------------------------------------------------------
# METHOD: enable index
#
# Enable a menu item in the option menu. This will allow the user
# to select this item from the menu.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::enable {index} {
set index [index $index]
$itk_component(popupMenu) entryconfigure $index -state normal
}
# ------------------------------------------------------------------
# METHOD: get
#
# Returns the current menu item.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::get {{first "current"} {last ""}} {
if {"current" == $first} {
return $_currentItem
}
set first [index $first]
if {"" == $last} {
return [$itk_component(popupMenu) entrycget $first -label]
}
if {"end" == $last} {
set last [$itk_component(popupMenu) index end]
} else {
set last [index $last]
}
set rval ""
while {$first <= $last} {
lappend rval [$itk_component(popupMenu) entrycget $first -label]
incr first
}
return $rval
}
# ------------------------------------------------------------------
# METHOD: insert index string ?string?
#
# Insert an item in the popup menu.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::insert {index string args} {
if {$index == "end"} {
set index $_numitems
} else {
set index [index $index]
}
set args [linsert $args 0 $string]
_setitems [eval linsert {$_items} $index $args]
return ""
}
# ------------------------------------------------------------------
# METHOD: select index
#
# Select an item from the popup menu to display on the menu label
# button.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::select {index} {
set index [index $index]
if {$index > ($_numitems - 1)} {
incr index -1
}
_setItem [lindex $_items $index]
}
# ------------------------------------------------------------------
# METHOD: popupMenu
#
# Evaluates the specified args against the popup menu component
# and returns the result.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::popupMenu {args} {
return [eval $itk_component(popupMenu) $args]
}
# ------------------------------------------------------------------
# METHOD: sort mode
#
# Sort the current menu in either "ascending" or "descending" order.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::sort {{mode "increasing"}} {
switch $mode {
ascending -
increasing {
_setitems [lsort -increasing $_items]
}
descending -
decreasing {
_setitems [lsort -decreasing $_items]
}
default {
error "bad sort argument \"$mode\": should be ascending,\
descending, increasing, or decreasing"
}
}
}
# ------------------------------------------------------------------
# PRIVATE METHOD: _buttonRelease
#
# Display the popup menu. Menu position is calculated.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::_buttonRelease {time} {
if {(abs([expr $_postTime - $time])) <= $itk_option(-clicktime)} {
return -code break
}
}
# ------------------------------------------------------------------
# PRIVATE METHOD: _getNextItem index
#
# Allows either a string or index number to be passed in, and returns
# the next item in the list in string format. Wrap around is automatic.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::_getNextItem {index} {
if {[incr index] >= $_numitems} {
set index 0 ;# wrap around
}
return [lindex $_items $index]
}
# ------------------------------------------------------------------
# PRIVATE METHOD: _next
#
# Sets the current option label to next item in list if that item is
# not disbaled.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::_next {} {
if {$itk_option(-state) != "normal"} {
return
}
set i [lsearch -exact $_items $_currentItem]
for {set cnt 0} {$cnt < $_numitems} {incr cnt} {
if {[incr i] >= $_numitems} {
set i 0
}
if {[$itk_component(popupMenu) entrycget $i -state] != "disabled"} {
_setItem [lindex $_items $i]
break
}
}
}
# ------------------------------------------------------------------
# PRIVATE METHOD: _previous
#
# Sets the current option label to previous item in list if that
# item is not disbaled.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::_previous {} {
if {$itk_option(-state) != "normal"} {
return
}
set i [lsearch -exact $_items $_currentItem]
for {set cnt 0} {$cnt < $_numitems} {incr cnt} {
set i [expr {$i - 1}]
if {$i < 0} {
set i [expr {$_numitems - 1}]
}
if {[$itk_component(popupMenu) entrycget $i -state] != "disabled"} {
_setItem [lindex $_items $i]
break
}
}
}
# ------------------------------------------------------------------
# PRIVATE METHOD: _postMenu time
#
# Display the popup menu. Menu position is calculated.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::_postMenu {time} {
#
# Don't bother to post if menu is empty.
#
if {[llength $_items] > 0 && $itk_option(-state) == "normal"} {
set _postTime $time
set itemIndex [lsearch -exact $_items $_currentItem]
set margin [expr {$itk_option(-borderwidth) \
+ $itk_option(-highlightthickness)}]
set x [expr {[winfo rootx $itk_component(menuBtn)] + $margin}]
set y [expr {[winfo rooty $itk_component(menuBtn)] \
- [$itk_component(popupMenu) yposition $itemIndex] + $margin}]
tk_popup $itk_component(popupMenu) $x $y
}
}
# ------------------------------------------------------------------
# PRIVATE METHOD: _setItem
#
# Set the menu button label to item, then dismiss the popup menu.
# Also check if item has been changed. If so, also call user-supplied
# command.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::_setItem {item} {
if {$_currentItem != $item} {
set _currentItem $item
if {[winfo ismapped $itk_component(hull)]} {
uplevel #0 $itk_option(-command)
}
}
}
# ------------------------------------------------------------------
# PRIVATE METHOD: _setitems items
#
# Create a list of items available on the menu. Used to create the
# popup menu.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::_setitems {items_} {
#
# Delete the old menu entries, and set the new list of
# menu entries to those specified in "items_".
#
$itk_component(popupMenu) delete 0 last
set _items ""
set _numitems [llength $items_]
#
# Clear the menu button label.
#
if {$_numitems == 0} {
_setItem ""
return
}
set savedCurrentItem $_currentItem
foreach opt $items_ {
lappend _items $opt
$itk_component(popupMenu) add command -label $opt \
-command [itcl::code $this _setItem $opt]
}
set first [lindex $_items 0]
#
# Make sure "savedCurrentItem" is still in the list.
#
if {$first != ""} {
set i [lsearch -exact $_items $savedCurrentItem]
#-------------------------------------------------------------
# BEGIN BUG FIX: csmith (Chad Smith: csmith@adc.com), 11/18/99
#-------------------------------------------------------------
# The previous code fragment:
# <select [expr {$i != -1 ? $savedCurrentItem : $first}]>
# is faulty because of exponential numbers. For example,
# 2e-4 is numerically equal to 2e-04, but the string representation
# is of course different. As a result, the select invocation
# fails, and an error message is printed.
#-------------------------------------------------------------
if {$i != -1} {
select $savedCurrentItem
} else {
select $first
}
#-------------------------------------------------------------
# END BUG FIX
#-------------------------------------------------------------
} else {
_setItem ""
}
_setSize
}
# ------------------------------------------------------------------
# PRIVATE METHOD: _setSize ?when?
#
# Set the size of the option menu. If "when" is "now", the change
# is applied immediately. If it is "later" or it is not specified,
# then the change is applied later, when the application is idle.
# ------------------------------------------------------------------
itcl::body iwidgets::Optionmenu::_setSize {{when later}} {
if {$when == "later"} {
if {$_calcSize == ""} {
set _calcSize [after idle [itcl::code $this _setSize now]]
}
return
}
set margin [expr {2*($itk_option(-borderwidth) \
+ $itk_option(-highlightthickness))}]
if {"0" != $itk_option(-width)} {
set width $itk_option(-width)
} else {
set width [expr {[winfo reqwidth $itk_component(popupMenu)]+$margin+20}]
}
set height [winfo reqheight $itk_component(menuBtn)]
$itk_component(lwchildsite) configure -width $width -height $height
set _calcSize ""
}