#-------------------------------------------------------------------------------
# Extbutton
#-------------------------------------------------------------------------------
# This [incr Widget] is pretty simple - it just extends the behavior of
# the Tk button by allowing the user to add a bitmap or an image, which
# can be placed at various locations relative to the text via the -imagepos
# configuration option.
#
#-------------------------------------------------------------------------------
# IMPORTANT NOTE: This [incr Widget] will only work with Tk 8.4 or later.
#
#-------------------------------------------------------------------------------
# AUTHOR:  Chad Smith               E-mail: csmith@adc.com, itclguy@yahoo.com
#-------------------------------------------------------------------------------
# Permission to use, copy, modify, distribute, and license this software
# and its documentation for any purpose is hereby granted as long as this
# comment block remains intact.
#-------------------------------------------------------------------------------

#
# Default resources
#
option add *Extbutton.borderwidth 2 widgetDefault
option add *Extbutton.relief raised widgetDefault

#
# Usual options
#
itk::usual Extbutton {
  keep -cursor -font
}

itcl::class iwidgets::Extbutton {
  inherit itk::Widget

  constructor {args} {}

  itk_option define -activebackground activeBackground Foreground #ececec
  itk_option define -bd borderwidth BorderWidth 2
  itk_option define -bitmap bitmap Bitmap {}
  itk_option define -command command Command {}
  itk_option define -defaultring defaultring DefaultRing 0
  itk_option define -defaultringpad defaultringpad Pad 4
  itk_option define -image image Image {}
  itk_option define -imagepos imagePos Position w
  itk_option define -relief relief Relief raised
  itk_option define -state state State normal
  itk_option define -text text Text {}

  public method invoke {} {eval $itk_option(-command)}
  public method flash {}

  private method changeColor {event_}
  private method sink {}
  private method raise {} {configure -relief $_oldValues(-relief)}

  private variable _oldValues
}


#
# Provide the usual lowercase access command.
#
proc iwidgets::extbutton {path_ args} {
  uplevel iwidgets::Extbutton $path_ $args
}


#-------------------------------------------------------------------------------
# OPTION: -bd
#
# DESCRIPTION: This isn't a new option.  Similar to -image, we just need to
#   repack the frame when the borderwidth changes.  This option is kept by
#   the private reliefframe component.
#-------------------------------------------------------------------------------
itcl::configbody iwidgets::Extbutton::bd {
  pack $itk_component(frame) -padx 4 -pady 4
}


#-------------------------------------------------------------------------------
# OPTION: -bitmap
#
# DESCRIPTION: This isn't a new option - we just need to reset the -image option
#   so that the user can toggle back and forth between images and bitmaps.
#   Otherwise, the image will take precedence and the user will be unable to
#   change to a bitmap without manually setting the label component's -image to
#   an empty string.  This option is kept by the image component.
#-------------------------------------------------------------------------------
itcl::configbody iwidgets::Extbutton::bitmap {
  if {$itk_option(-bitmap) == ""} {
    return
  }
  if {$itk_option(-image) != ""} {
    configure -image {}
  }
  pack $itk_component(frame) -padx 4 -pady 4
}


#-------------------------------------------------------------------------------
# OPTION: -command
#
# DESCRIPTION: Invoke the given command to simulate the Tk button's -command
#   option.  The command is invoked on <ButtonRelease-1> events only or by
#   direct calls to the public invoke() method.
#-------------------------------------------------------------------------------
itcl::configbody iwidgets::Extbutton::command {
  if {$itk_option(-command) == ""} {
    return
  }

  # Only create the tag binding if the button is operable.
  if {$itk_option(-state) == "normal"} {
    bind $this-commandtag <ButtonRelease-1> [itcl::code $this invoke]
  }

  # Associate the tag with each component if it's not already done.
  if {[lsearch [bindtags $itk_interior] $this-commandtag] == -1} {
    foreach component [component] {
      bindtags [component $component] \
        [linsert [bindtags [component $component]] end $this-commandtag]
    }
  }
}


#-------------------------------------------------------------------------------
# OPTION: -defaultring
#
# DESCRIPTION: Controls display of the sunken frame surrounding the button.
#   This option simulates the pushbutton iwidget -defaultring option.
#-------------------------------------------------------------------------------
itcl::configbody iwidgets::Extbutton::defaultring {
  switch -- $itk_option(-defaultring) {
    1 {set ring 1}
    0 {set ring 0}
    default {
      error "Invalid option for -defaultring: \"$itk_option(-defaultring)\".  \
             Should be 1 or 0."
    }
  }

  if ($ring) {
    $itk_component(ring) configure -borderwidth 2
    pack $itk_component(reliefframe) -padx $itk_option(-defaultringpad) \
      -pady $itk_option(-defaultringpad)
  } else {
    $itk_component(ring) configure -borderwidth 0
    pack $itk_component(reliefframe) -padx 0 -pady 0
  }
}


#-------------------------------------------------------------------------------
# OPTION: -defaultringpad
#
# DESCRIPTION: The pad distance between the ring and the button.
#-------------------------------------------------------------------------------
itcl::configbody iwidgets::Extbutton::defaultringpad {
  # Must be an integer.
  if ![string is integer $itk_option(-defaultringpad)] {
    error "Invalid value specified for -defaultringpad:\
	   \"$itk_option(-defaultringpad)\".  Must be an integer."
  }

  # Let's go ahead and make the maximum padding 20 pixels.  Surely no one
  # will want more than that.
  if {$itk_option(-defaultringpad) < 0 || $itk_option(-defaultringpad) > 20} {
    error "Value for -defaultringpad must be between 0 and 20."
  }

  # If the ring is displayed, repack it according to the new padding amount.
  if {$itk_option(-defaultring)} {
    pack $itk_component(reliefframe) -padx $itk_option(-defaultringpad) \
      -pady $itk_option(-defaultringpad)
  }
}


#-------------------------------------------------------------------------------
# OPTION: -image
#
# DESCRIPTION: This isn't a new option - we just need to repack the frame after
#   the image is changed in case the size is different than the previous one.
#   This option is kept by the image component.
#-------------------------------------------------------------------------------
itcl::configbody iwidgets::Extbutton::image {
  pack $itk_component(frame) -padx 4 -pady 4
}


#-------------------------------------------------------------------------------
# OPTION: -imagepos
#
# DESCRIPTION: Allows the user to move the image to different locations areound
#   the text.  Valid options are n, nw, ne, s, sw, se e, en, es, w, wn or ws.
#-------------------------------------------------------------------------------
itcl::configbody iwidgets::Extbutton::imagepos {
  switch -- $itk_option(-imagepos) {
    n  {set side top;    set anchor center}
    ne {set side top;    set anchor e}
    nw {set side top;    set anchor w}

    s  {set side bottom; set anchor center}
    se {set side bottom; set anchor e}
    sw {set side bottom; set anchor w}

    w  {set side left;   set anchor center}
    wn {set side left;   set anchor n}
    ws {set side left;   set anchor s}

    e  {set side right;  set anchor center}
    en {set side right;  set anchor n}
    es {set side right;  set anchor s}

    default {
      error "Invalid option: \"$itk_option(-imagepos)\". \
             Must be n, nw, ne, s, sw, se e, en, es, w, wn or ws."
    }
  }

  pack $itk_component(image) -side $side -anchor $anchor
  pack $itk_component(frame) -padx 4 -pady 4
}


#-------------------------------------------------------------------------------
# OPTION: -relief
#
# DESCRIPTION: Move the frame component according to the relief to simulate
#   the text in a Tk button when its relief is changed.
#-------------------------------------------------------------------------------
itcl::configbody iwidgets::Extbutton::relief {
  update idletasks
  switch -- $itk_option(-relief) {
    flat - ridge - groove {
      place $itk_component(frame) -x 5 -y 5
    }

    raised {
      place $itk_component(frame) -x 4 -y 4
    }

    sunken {
      place $itk_component(frame) -x 6 -y 6
    }

    default {
      error "Invalid option: \"$itk_option(-relief)\".  \
             Must be flat, ridge, groove, raised, or sunken."
    }
  }
}


#-------------------------------------------------------------------------------
# OPTION: -state
#
# DESCRIPTION: Simulate the button's -state option.
#-------------------------------------------------------------------------------
itcl::configbody iwidgets::Extbutton::state {
  switch -- $itk_option(-state) {
    disabled {
      bind $itk_interior <Enter> { }
      bind $itk_interior <Leave> { }
      bind $this-sunkentag <1> { }
      bind $this-raisedtag <ButtonRelease-1> { }
      bind $this-commandtag <ButtonRelease-1> { }
      set _oldValues(-fg) [cget -foreground]
      set _oldValues(-cursor) [cget -cursor]
      configure -foreground $itk_option(-disabledforeground)
      configure -cursor "X_cursor red black"
    }

    normal {
      bind $itk_interior <Enter> [itcl::code $this changeColor enter]
      bind $itk_interior <Leave> [itcl::code $this changeColor leave]
      bind $this-sunkentag <1> [itcl::code $this sink]
      bind $this-raisedtag <ButtonRelease-1> [itcl::code $this raise]
      bind $this-commandtag <ButtonRelease-1> [itcl::code $this invoke]
      configure -foreground $_oldValues(-fg)
      configure -cursor $_oldValues(-cursor)
    }

    default {
      error "Bad option for -state: \"$itk_option(-state)\".  Should be\
	normal or disabled."
    }
  }
}


#-------------------------------------------------------------------------------
# OPTION: -text
#
# DESCRIPTION: This isn't a new option.  Similar to -image, we just need to
#   repack the frame when the text changes.   
#-------------------------------------------------------------------------------
itcl::configbody iwidgets::Extbutton::text {
  pack $itk_component(frame) -padx 4 -pady 4
}



#-------------------------------------------------------------------------------
#                                CONSTRUCTOR
#-------------------------------------------------------------------------------
itcl::body iwidgets::Extbutton::constructor {args} {
  # Extbutton will not work with versions of Tk less than 8.4 (the
  # -activeforeground option was added to the Tk label widget in 8.4, for
  # example).  So disallow its use unless the right wish is being used.
  if {$::tk_version < 8.4} {
    error "The extbutton \[incr Widget\] can only be used with versions of\
      Tk greater than 8.3.\nYou're currently using version $::tk_version."
  }

  # This frame is optionally displayed as a "default ring" around the button.
  itk_component add ring {
    frame $itk_interior.ring -relief sunken
  } {
    rename -background -ringbackground ringBackground Background
  }

  # Add an outer frame for the widget's relief.  Ideally we could just keep
  # the hull's -relief, but it's too tricky to handle relief changes.
  itk_component add -private reliefframe {
    frame $itk_component(ring).f
  } {
    rename -borderwidth -bd borderwidth BorderWidth
    keep -relief
    usual
  }

  # This frame contains the image and text.  It will be moved slightly to
  # simulate the text in a Tk button when the button is depressed/raised.
  itk_component add frame {
    frame $itk_component(reliefframe).f -borderwidth 0
  }

  itk_component add image {
    label $itk_component(frame).img -borderwidth 0
  } {
    keep -bitmap -background -image
    rename -foreground -bitmapforeground foreground Foreground
  }

  itk_component add label {
    label $itk_component(frame).txt -borderwidth 0
  } {
    keep -activeforeground -background -disabledforeground
    keep -font -foreground -justify -text
  }

  pack $itk_component(image) $itk_component(label) -side left -padx 6 -pady 4
  pack $itk_component(frame) -padx 4 -pady 4
  pack $itk_component(reliefframe) -fill both
  pack $itk_component(ring) -fill both

  # Create a couple of binding tags for handling relief changes.  Then
  # add these tags to each component.
  foreach component [component] {
    bindtags [component $component] \
      [linsert [bindtags [component $component]] end $this-sunkentag]
    bindtags [component $component] \
      [linsert [bindtags [component $component]] end $this-raisedtag]
  }

  set _oldValues(-fg) [cget -foreground]
  set _oldValues(-cursor) [cget -cursor]

  eval itk_initialize $args
}


#-------------------------------------------------------------------------------
# METHOD: flash
#
# ACCESS: public
#
# DESCRIPTION: Simulate the Tk button flash command.
#
# ARGUMENTS: none
#-------------------------------------------------------------------------------
itcl::body iwidgets::Extbutton::flash {} {
  set oldbg [cget -background]
  config -background $itk_option(-activebackground)
  update idletasks

  after 50; config -background $oldbg; update idletasks
  after 50; config -background $itk_option(-activebackground); update idletasks
  after 50; config -background $oldbg
}


#-------------------------------------------------------------------------------
# METHOD: changeColor
#
# ACCESS: private
#
# DESCRIPTION: This method is invoked by <Enter> and <Leave> events to change
#   the background and foreground colors of the widget.
#
# ARGUMENTS: event_ --> either "enter" or "leave"
#-------------------------------------------------------------------------------
itcl::body iwidgets::Extbutton::changeColor {event_} {
  switch -- $event_ {
    enter {
      set _oldValues(-bg) [cget -background]
      set _oldValues(-fg) [cget -foreground]
      configure -background $itk_option(-activebackground)
      configure -foreground $itk_option(-activeforeground)
    }
    leave {
      configure -background $_oldValues(-bg)
      configure -foreground $_oldValues(-fg)
    }
  }
}


#-------------------------------------------------------------------------------
# METHOD: sink
#
# ACCESS: private
#
# DESCRIPTION: This method is invoked on <1> mouse events.  It saves the
#   current relief for later restoral and configures the relief to sunken if
#   it isn't already sunken.
#
# ARGUMENTS: none
#-------------------------------------------------------------------------------
itcl::body iwidgets::Extbutton::sink {} {
  set _oldValues(-relief) [cget -relief]
  if {$_oldValues(-relief) == "sunken"} {
    return
  }
  configure -relief sunken
}
