ToolBar -An Alternative to the BWidget ButtonBox

WJG (06/06/06) Ah, 666, the number of the beast or the beastly BWidget ButtonBox? This stalwart member of the BWidget set is useful for making clusters of Buttons but what if you want other items such as everyone's favourite -ComboBox? Mmm, not really. So, here'a an alternative.

RH (07/06/06) There is already a nice Toolbar from George Petasis. It doesn't rely on any widget extension.

WJG (09/06/06) Here's revised version of the earlier code (now deleted). The proc arguments remain the same but now toolbars can be detached into floating palettes. Close the palette and the toolbar returns to its orginal ordering in its container. I did look at GP's ToolBar, nice work. GP's package allows for the side and bottom packing of toolbars which I haven't allowed for. It's not particularly a feature that I would personally use, but seeing as it's what's expected perhaps I'll have a go at it next.

 #---------------
 # ToolBar.tcl
 #---------------
 # William J Giddings, 2006
 #---------------
 # Provide a ToolBar megawidget using the BWidget package.
 #
 # This is a hack of the code found at https://wiki.tcl-lang.org/1916.
 #---------------
 # Notes:
 # -----
 # BWidget has the ButtonBox widget but, this only adds buttons.
 # This method allows ** any ** other sort of widget to be added,
 # typically a ComboBox or spinbox.
 # To detach a toolbar, Button-1 click on the blue rule and a floating palette will appear.
 # Closing the palette will result in the toolbar re-appearing.
 #----------------

 package require BWidget

 #---------------
 # define the widget
 #---------------
 namespace eval ToolBar {
    Widget::define ToolBar ToolBar
    # include other widgets
    Widget::tkinclude ToolBar frame .f
    Widget::declare ToolBar { }
 }

 #---------------
 # create the thing..
 #---------------
 proc ToolBar::create { path args } {

  # define maps
  array set maps [list ToolBar {} :cmd {} .f {}]
  array set maps [Widget::parseArgs ToolBar $args]

  # map the various args
  Widget::initFromODB ToolBar "$path" $maps(ToolBar)

  # add the container
  set frame [eval frame $path [Widget::subcget $path :cmd] \
   -class ToolBar -relief groove -bd 1 -highlightthickness 0]

  # include other widgets here..
  set fr [eval frame $frame.f $maps(.f)]
  pack $fr -fill both -expand true

  # create the palette
  set p .pal_[string trimleft $path .]
  toplevel $p
  wm withdraw $p    
  wm attributes $p -tool 1
  wm title $p {}
  wm transient $p .
  wm resizable $p 0 0

  # create containers -palette
  pack [frame $p.fr -relief groove -borderwidth 0]  -side left -anchor nw

  # add ruler
  pack [frame $fr.rule -bg #9999ff -relief raised -borderwidth 1 -width 3] -side left -anchor nw -fill y

  # detach palette
  bind $fr.rule <Button-1>  [ list ToolBar::rule_cmd $path $p %X %Y ]

  return [Widget::create ToolBar $path]
 }

 #---------------
 # called when the palette closes
 #---------------
 proc ToolBar::palette_cmd {a b c} {
  set opts "-side left -anchor nw"
  # obtain a list of currently packed items
  set slave_list [pack slaves $c] ;
  # sort the list by ending index
  set slave_list [lsort [lappend slave_list $a]]
  # repack everything on the list
  eval pack $slave_list -in $c $opts
  # hide the assocated palette
  wm withdraw $b
 }

 #---------------
 # called when the toolbar rule is clicked
 #---------------
 proc ToolBar::rule_cmd {b p x y} {
  wm geometry $p =+$x+$y
  wm deiconify $p
  set c [lindex [pack info $b] 1]
  wm protocol $p WM_DELETE_WINDOW [list ToolBar::palette_cmd $b $p $c]
  pack forget $b
 }

 #---------------
 # change settings..
 #---------------
 proc ToolBar::configure { path args } {
  set res [Widget::configure $path $args]
  return $res
 }

 #---------------
 # inquire about currents settings..
 #---------------
 proc ToolBar::cget { path option } {
  return [Widget::cget $path $option]
 }

 #---------------
 # add a new widget to the toolbar
 #---------------
 proc ToolBar::add { path class indx args} {
  # name the palette
  set p .pal_[string trimleft $path .]
  # add rule
  if {[string tolower $class] == "rule"} {
    # one for the toolbar
    frame $path.f.r$indx -width 2 -borderwidth 1 -relief groove
    pack $path.f.r$indx -side left -anchor nw -fill y -padx 2
    # one for the palette
    frame $p.r$indx -width 2 -borderwidth 1 -relief groove
    pack $p.r$indx -side left -anchor nw -fill y -padx 2
    return
  }
  # add other types of widget
  # create some storage
  set args1 {}  ;# core widget options
  set args2 {}  ;# anything else, add switches below
  set argsp1 {} ;# same for the palette
  set argsp2 {}
  foreach {arg val} $args {
      switch -- $arg {
        -help {
          append args1 "DynamicHelp::add $path.f.$indx -help ballon -text \{$val\}"
          append argsp1 "DynamicHelp::add $p.$indx -help ballon -text \{$val\}"
        }
        default {
          append args2 " $arg \{$val\} "
          append argsp2 " $arg \{$val\} "
        }
      }
  }
  # create child widget
  eval "$class $path.f.$indx $args2"  ;# toolbar
  eval "$class $p.$indx $argsp2"       ;# palette
  # do the extra stuff -toolbar
  eval $args1
  pack $path.f.$indx -side left -padx 2
  # do the extra stuff -palette
  eval $argsp1
  pack $p.$indx -side left -padx 2
  return $path.f.$indx
 }

 #---------------
 # the ubiquitous demo
 #---------------
 proc ToolBar::demo {} {
 
  catch {console show}
 
  BWidget::place . 500 500 center
  
  # create toolbar holder and sample text
  set base [frame .fr -relief groove -bd 2]
  pack $base -side top -anchor nw -fill x 
  pack [text .txt] -side top -anchor nw -expand 1 -fill both

  # populate the toolbar holder  
  pack [ToolBar .tb1 ] -in $base -side left  -padx 0 -anchor nw
  foreach {i img hlp} {
    1 New "New Document"
    2 Open "Open New File"
    3 Save "Save Work"
  } {
      .tb1 add button b$i \
        -image [Bitmap::get $img] \
        -relief flat -overrelief raised \
        -help abc \
        -command cmd$img
  }

  # create toolbar 2 -EDIT
  pack [ToolBar .tb2 ] -in $base -side left -padx 0 -anchor nw

  foreach {i img hlp} {
    1 Cut "Cut selection to clipboard"
    2 Copy "Copy selection to clipboard"
    3 Paste "Paste clipboard into selection"
  } {
    .tb2 add button b$i \
      -image [Bitmap::get $img] \
      -relief flat -overrelief raised \
      -help abc \
      -command cmd$img
  }

  # and some BWidget buttons
  pack [ToolBar .tb3 ] -in $base -side left -padx 0 -anchor nw

  foreach {i img hlp} {
    1 Bold "Embolden Selection"
    2 Overstrike "Overstrike Selection"
    3 Underline "Underline Selection"
  } {
    .tb3 add Button b$i \
      -image [Bitmap::get $img] \
      -relief link \
      -help abc \
      -command cmd$img
  }

  # and, a combox
  .tb3 add Rule 2
  .tb3 add ComboBox cmb1 -values {How Now Brown Cow}
  .tb3 add Rule 3
  .tb3 add checkbutton cb1 -text test
  
 }

 ToolBar::demo