flexmenu

Difference between version 42 and 43 - Previous - Next
Last update: 2019-9-10 version 1.26

Sourceforge: https://sourceforge.net/projects/tcl-flexmenu/

[bll] 2018-9-20: An alternative menu system.

2018-9-21: There seems to be a little interest in this.
I wrote it for a couple of rather minor reasons (one of which I'm not sure is valid). 
Though I do really like having real
checkbuttons and radiobuttons in the menu, 
and I like the -keepopen option.  I have not tried using it in my
production code as yet.
[flexmenu-ex1][flexmenu-ex2][flexmenu-ex3][flexmenu-ex4]

<<discussion>>Version History
======none
   1.26 2019-9-10
       Internal cleanup.
       Fix sizing issues.
   1.25
       Change color defaults for Mac OS.
   1.24
       Mac OS dark mode colors.
   1.23
       Fixed cascade unpost.
   1.22
       Handle change of item selection on insert/add/delete.
   1.21 2018-12-03
       Fix grab handling issue.
       Fix close check handling.
   1.20 2018-12-03
       Resolve issues with menu display and configuration.
   1.19 2018-12-02
       Added -bindaccelerators option.
       Added -stickycascade option.
       Fixed display when moving off of active item in certain cases.
       Fixed initial display of menu.
   1.18 2018-12-02
       Mac OS X: Force -mode frame, as toplevels cannot be supported
         at this time.
   1.17 2018-11-29
       Change manual invoke processing to work when the menu is inactive.
         This matches the original menu function.  May create some odd
         states if run on a sub-cascade.
       Fixed overlapping menu handling.
   1.16 2018-11-29
       Allow vertical separators in menubar.
       Add new -gap option for items.
   1.15 2018-10-25
       Fix mac os x: enter/leave processing does not work properly.
   1.14 2018-10-22
       Fix various bugs with user 'invoke' method.
   1.13 2018-10-22
       Turn off mouse activation of menus in the wait state so that
         overlapping menus work (changed in 1.12).
       -xalign and -yalign can have tag[+-]offset
       Fix menutop/menuleft display (call postprocess).
   1.12 2018-10-21
       Initialize styles once only.
       Fix x and y offsets.
       Fix borderwidth and relief on menubar/menuleft menus.
       Fix placement of active frame in scrolling window.
       Fix enter/leave handling.
       Fix accelerator keys.
       Fix F10.
       Fix left arrow item positioning.
   1.11 2018-10-1
       Fix mistake.
   1.10 2018-10-1
       Rewrite styling and active state handling (pass 1).
       Some option cleanup.
   1.9 2018-10-1
       Fix incorrect default for -borderwidth.
       Fix bad copy/paste in enterLeaveHandler.
       Fix column spans with -hidemargin.
       Fix non-global grab.
       Fix enter/leave handling.
       Fix press handler/menu.enter/grab.
       Fix column weighting with -hidemargin.
   1.8 2018-10-1
       Fix crash when window closed.
   1.7 2018-10-1
       Remove tailcall.
       Rewrite grab handling.
       Rewrite enter/leave handling.
       Fix invoke of item when -keepopen is false.
   1.6 2018-9-30
       Set menu colors from the current theme.
       Fix -font handling.
       Fix menus not closing on checkbutton select.
       Button press will activate the menu and item.
   1.5 2018-9-30
       Fixed -borderwidth and -relief options for items.
       Added -mode {toplevel|frame} option.
       Cleaned up some stacking order issues
         (fixes the activeframe covering menu items).
   1.4 2018-9-29
       Added -precommand for cascades.
       Added -yalign for cascades.
       Added -xalign for cascades.
       Fixed bug in entrycget.
       Fixed 'delete' operation to match 'menu'.
       Fixed bug where the width was lost on a redisplay.
       Fixed display when initial number of items did not exceed -maxheight.
       Fixed various bugs with empty menus.
   1.3 2018-9-22
       Fixed initialization to only execute on mac os x.
       Fixed incorrect state for menus after invoking an item.
       Brought state table documentation up to date.
   1.2 2018-9-21  (broken for non mac os x)
       Fixed -padx/-pady so they will work for the main menubar
       Set the -acceleratorprefix to \u2318 for Mac OS X.
         This is converted to 'Meta' (hope that's right).
       Changed default -acceleratorprefix to 'Alt-' (due to mac os x changes).
       Fixed the colors on mac os x.  Dark mode and graphite themes
         are supported.
       Documentation
   1.1 2018-9-20
       Fixed active highlight for scrolled menus
   1.0 2018-9-20
       initial release
======
<<enddiscussion>>

****Disadvantages:****

Does not work with `.toplevel configure -menu .mymenu`.  Tk uses the internal menu API to attach menus.
flexmenu cannot work with the `-menu` option.  

Must use pack or grid (or place) to attach the menu.  Converting an existing program to use flexmenu 
could be quite painful.

The main menu must be created with either `-type menubar` or `-type menuleft`.

It has not been tested much.

****Features:****

   * Checkbuttons and Radiobuttons are ttk widgets.
   * Supports left side menus (`-type menuleft`).
   * Scrolling menus (`-maxheight`).
   * Configure `-columnbreak <value>` at the menu level to automatically break every <value> items.
   * `-keepopen` option will leave the menu open after invoking an item.
   * `-acceleratorfont`, `-acceleratorforeground` and `-acceleratoractiveforeground` options.
   * `-activerelief` option.
   * `-hidearrows` option.
   * `-hideaccelerators` option.
   * `-acceleratorprefix` option to set the default accelerator prefix.
   * `-padx`, `-pady` options to change the padding for menu items.
   * `-bindaccelerators` option.
   * `-stickycascade` option.
   * `-mode` option to select the use of frames or toplevels.  Mac OS X is forced to use `-mode frame`.
   * Is a little more dynamic than the standard `menu`.  Many things can be reconfigured and the changes will be picked up.

****Item Features:****

   * Any widget can be put into the menu (`.mymenu add widget -widget .mymenu.mycombobox`).
   * Margin images (`-marginimage`).  It is quite common nowadays to use small icons on the left margin of the menu as an aid for the user.
   * Accelerator labels are automatically generated based on either an `&` prefix in the label, the `-underline` option, or the `-accelerator` option.
   * Accelerator bindings are automatically generated.
   * -activerelief option.
   * -gap option.
   * Cascades: -yalign, -xalign options to change cascade alignment.
   * Cascades: -precommand option.  Useful for dynamic menu generation.

****Notes:****

Ignored: `-bitmap`, `-tearoff`, `-tearoffcommand`, `-title`, `-selectcolor`.

`-hidemargin` works properly on a per-entry basis. `menu` seems to treat it as a menu option even though it is specified per entry.
I think `-hidemargin` would be better off as a menu option rather than an item option, but backwards compatibility is an issue. 

Mac OS X: -mode toplevel is not supported.  Mac OS X is forced into -mode frame.

****Problems:****

May be overeager in generating accelerator labels.  The user may not want accelerator labels displayed for every item.

Has not been tested much.

At this time, flexmenu does not check to see if the entire menu is visible, and does not do any relocation of the menu.

Could use better documentation.

****Known Issues:****

'''Fixed in 1.18''': Mac OS X is not working at this time (2018-10-22).  Mac OS X works, but only with
-mode frame.  -mode frame is forced for Mac OS X (2018-11-02).

'''Fixed in 1.1''': Active item highlighting for scrolling menus is not working right.

'''Fixed in 1.2''': The accelerator key prefix is set to `Alt`.  
This needs to be set correctly for Mac OS X, but I do not recall
which meta key the Mac sends.

The `clone` command has only a very basic implementation.

Flicker with `-selectimage`.  Currently, the menu layout is reapplied when a `-image` is changed to a `-selectimage`.  This is
just in case the `-selectimage` is a different size.   If the assumption can be made that the image sizes are identical, this
redraw could be removed.

****Examples:****

<<discussion>>Example 1
======
package require Tk
source flexmenu.tcl

# standard checkbutton widgets
# -keepopen set
flexmenu .mcb -keepopen true
.mcb add checkbutton -variable ::x -text Check -onvalue 0 -offvalue 1 \
    -accelerator Ctrl-4
.mcb add checkbutton -variable ::x -text Check -onvalue 0 -offvalue 1 \
    -accelerator Ctrl-5 -indicatoron false
.mcb add checkbutton -variable ::x -text Check -onvalue 0 -offvalue 1 \
    -state disabled

# standard radiobutton widgets
flexmenu .mrb -keepopen true
.mrb add radiobutton -variable ::x -value 0 -text Radio-0 \
    -accelerator Ctrl-2
.mrb add radiobutton -variable ::x -value 1 -text Radio-1 \
    -accelerator Ctrl-3
.mrb add radiobutton -variable ::x -value 2 -text Radio-2 \
    -accelerator Ctrl-4 -state disabled

# automatic column break every fifth item.
# frame widgets in the menu
set clist {#000000 #ff0000 #00ff00 #0000ff #ff8000 #ff0080 #80ff00
    #00ff80 #8000ff #0080ff #ffff00 #ff00ff #00ffff #ffff80 #ff80ff
    #80ffff #ffffff #ff4040 #40ff40 #4040ff #404040 #808080 #ffff40
    #ff40ff #40ffff}
set col [flexmenu .col -columnbreak 5]
set count 0
foreach {c} $clist {
  set tf [frame .col.x$c \
      -background $c -relief raised -borderwidth 2 \
      -width 20 -height 20 ]
  .col add widget -widget $tf -hidemargin 1
  incr count
}

# scrolling menu
# frame widgets in the menu
set clist {#000000 #ff0000 #00ff00 #0000ff #ff8000 #ff0080 #80ff00
    #00ff80 #8000ff #0080ff #ffff00 #ff00ff #00ffff #ffff80 #ff80ff
    #80ffff #ffffff #ff4040 #40ff40 #4040ff #404040 #808080 #ffff40
    #ff40ff #40ffff}
set colsc [flexmenu .colsc -keepopen true -maxheight 5]
foreach {c} $clist {
  set tf [frame .colsc.bbb$c \
      -background $c -relief raised -borderwidth 2 \
      -width 20 -height 20 ]
  .colsc add widget -widget $tf -hidemargin 1
}

# combobox widget
flexmenu .mw 
ttk::combobox .mw.cb -values {aa bb cc dd ee ff gg} \
    -textvariable ::y -state readonly -width 5
.mw add widget -widget .mw.cb

# main cascade
flexmenu .m
.m add cascade -menu .col -label {Colors}
.m add cascade -menu .colsc -label {Scrolling Colors}
.m add cascade -menu .mcb -label {Check Buttons}
.m add cascade -menu .mrb -label {Radio Buttons}
.m add cascade -menu .mw -label {Widgets}
.m add command -label E&xit -command exit

flexmenu .mtop -type menubar
.mtop add cascade -label Test -menu .m
.mtop add command -label E&xit -command exit
pack .mtop -side top -fill x -expand 1 -anchor nw
frame .f -width 200 -height 200 
pack .f
======
<<enddiscussion>>

<<discussion>>Example 2 : -type menuleft
======
#!/usr/bin/tclsh

# This has not been tested on windows.

package require Tk
source flexmenu.tcl

set ::lsortcmd [list ::lsort]
try {
  package require collate
  set ::lsortcmd [list ::lsort -command collate]
} on error {err res} {
}

variable vars

proc attachmenu { pm dir } {
  if { [catch {$pm entrycget active -menu}] } {
    set m [mkmenu $dir]
    $pm entryconfigure active -menu $m
  } else {
    set m [$pm entrycget active -menu]
    refreshmenu $m $dir
  }
}

proc refreshmenu { nm dir } {
  variable vars

  set flist {}
  set tm [clock milliseconds]
  if { ! [info exists vars(cache.$dir)] ||
      ($tm - $vars(cache.ts.$dir)) > 60000 } {
    try {
      set flist [{*}$::lsortcmd [glob -directory $dir -tails *]]
    } on error {err res} {
    }
    set vars(cache.$dir) $flist
    set vars(cache.ts.$dir) [clock milliseconds]

    $nm delete 0 end

    set count 0
    foreach {f} $flist {
      set ff [file join $dir $f]
      if { [file isdirectory $ff] } {
        $nm add cascade \
            -label $f \
            -yalign menutop \
            -precommand [list ::attachmenu $nm $ff]
      } else {
        $nm add command -label $f
      }
      incr count
      if { $count > 20 } {
        break
      }
    }
  }
}

proc mkmenu { dir args } {
  variable vars

  set nm [flexmenu .m$vars(menu.counter) \
      -keepopen true \
      -padx 4p \
      -pady 4p \
      -maxheight 10 \
      {*}$args]
  refreshmenu $nm $dir
  set vars(dir.$nm) $dir
  incr vars(menu.counter)
  return $nm
}

proc main { } {
  variable vars

  set dir [lindex $::argv 0]
  if { $dir eq {} } {
    set dir /
  }
  set vars(menu.counter) 1

  set mm [mkmenu $dir -type menuleft]
  $mm configure
  grid $mm -sticky news
  grid rowconfigure . 0 -weight 1
  grid columnconfigure . 0 -weight 1

  ttk::frame .f -width 400 -height 300
  grid .f -sticky se -row 0 -column 1
  lower .f
  . configure -background [ttk::style lookup . -background]
  after 100 update
}

main
======
<<enddiscussion>>

<<discussion>>Example 3 : menubar with clock
======
package require Tk
source ../code/flexmenu.tcl

set clock {}

proc doclock { } {
  global clock
  set clock [clock format [clock seconds] \
      -format "%Y-%M-%d %r" -locale system]
  after 500 ::doclock
}

flexmenu .mtop -type menubar
.mtop add command -label E&xit -command exit
.mtop add separator
.mtop add command -label Exit -underline 0 -command exit
.mtop add command -label Help -underline 0 -command exit -gap true
doclock
ttk::style map Clock.TLabel -foreground [list disabled darkblue]
ttk::label .clock -textvariable clock -style Clock.TLabel
.mtop add widget -widget .clock -hidemargin true -state disabled
pack .mtop -side top -fill x -expand 1 -anchor nw

ttk::frame .f -width 400 -height 300
pack .f
lower .f
. configure -background [ttk::style lookup . -background]
======
<<enddiscussion>>