Toolbutton with optional menu

Difference between version 0 and 0 - Previous - Next

**Toolbutton with optional menu**

Sometimes it's useful in a toolbar, to have a button which an optional popdown menu. For example, in an IDE you could have a "Compile" button but occasionally the user wants to "Clean & Compile" or "Compile docs". "optionbuttion" is a crossover between a regular pushbutton and a menubutton. When clicking, it executes the command as a pushbutton. When holding the button down for longer than a 0.5 s (adjustable delay), a menu appears. The menu entries execute the callback with an index corresponding to the selected entry.
The menu can also be displayed with a right button click, like context menus. 

Code:
======
# Copyright (c) 2021 Christian Gollwitzer
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
 
package require Tk
package require snit

snit::widgetadaptor optionbutton {
        # a toolbutton that opens a menu upon 
        # longer click
        delegate option * to hull except -command
        delegate method * to hull
        
        # delay before opening the options menu
        option -delay -default 500
        # values for display
        option -values -default {} -configuremethod setvalues
        option -headline -default {}
        
        # callback before menu opens
        option -optcallback -default {}
        
        # callback after press / select
        option -command -default {} 

        component optmenu

        variable afterid {}

        constructor {args} {
                installhull using ttk::menubutton -style Toolbutton
                install optmenu using menu $win.m
                $hull configure -menu $optmenu

                $self configurelist $args
                
                # replace bindings
                bind $self <Button-1> [mymethod LeftBPress]
                bind $self <ButtonRelease-1> [mymethod LeftBRelease]
                bind $self <Button-3> [mymethod RightBPress]
        }

        destructor {
                if {$afterid ne {}} {
                        catch [after cancel $afterid]
                }
        }


        method LeftBPress {} {
                $hull state pressed
                set afterid [after $options(-delay) [mymethod openmenu]]
                return -code break
        }

        method LeftBRelease {} {
                if {$afterid ne {}} {
                        # the delay for the popdown menu is not passes
                        after cancel $afterid
                        set afterid {}
                        uplevel #0 $options(-command)
                }
                $hull state !pressed
                return -code break
        }

        method RightBPress {} {
                $self openmenu
                return -code break
        }

        method openmenu {} {
                set afterid {}
                uplevel #0 $options(-optcallback)
                $hull state !pressed
                ttk::menubutton::Popdown $win
        }

        method selected {i} {
                uplevel #0 [linsert $options(-command) end $i]
        }

        method setvalues {opt val} {
                set options($opt) $val
                $optmenu delete 0 end

                if {$options(-headline) ne {}} {
                        $optmenu add command -label $options(-headline) -state disabled
                        $optmenu add separator
                }
                set i 0
                foreach lbl $val {
                        $optmenu add command -label $lbl -command [mymethod selected $i]
                        incr i
                }
        }
}
======

Test:

======
proc echo args {
    puts $args
}

pack [optionbutton .b -text "Compile" -values {"Compile" "Clean & Compile" "Compile docs"} -command {echo called}]

# fill the values just before the menu appears
pack [optionbutton .c -text "I'm dynamic!" -values {1 2 3} -command {echo called} -optcallback {shuffle .c} -headline "Select number"]

proc shuffle {w} {
        $w configure -values [list [expr rand()] [clock scan now] [clock format [clock scan now]]]
}

======