#
# ::iwidgets::Disjointlistbox
# ----------------------------------------------------------------------
# Implements a widget which maintains a disjoint relationship between
# the items displayed by two listboxes.  The disjointlistbox is composed
# of 2 Scrolledlistboxes,  2 Pushbuttons, and 2 labels.
#
# The disjoint behavior of this widget exists between the two Listboxes,
# That is, a given instance of a ::iwidgets::Disjointlistbox will never
# exist which has Listbox widgets with items in common.
#
# Users may transfer items between the two Listbox widgets using the
# the two Pushbuttons.
#
# The options include the ability to configure the "items" displayed by
# either of the two Listboxes and to control the placement of the insertion
# and removal buttons.
#
# The following depicts the allowable "-buttonplacement" option values
# and their associated layout:
#
#   "-buttonplacement" => center
#
#   --------------------------
#   |listbox|        |listbox|
#   |       |________|       |
#   | (LHS) | button | (RHS) |
#   |       |========|       |
#   |       | button |       |
#   |_______|--------|_______|
#   | count |        | count |
#   --------------------------
#
#   "-buttonplacement" => bottom
#
#   ---------------------
#   | listbox | listbox |
#   |  (LHS)  |  (RHS)  |
#   |_________|_________|
#   | button  | button  |
#   |---------|---------|
#   | count   | count   |
#   ---------------------
#
# ----------------------------------------------------------------------
#  AUTHOR: John A. Tucker               EMAIL: jatucker@spd.dsccc.com
#
# ======================================================================

#
# Default resources.
#
option add *Disjointlistbox.lhsLabelText    Available   widgetDefault
option add *Disjointlistbox.rhsLabelText    Current     widgetDefault
option add *Disjointlistbox.lhsButtonLabel  {Insert >>} widgetDefault
option add *Disjointlistbox.rhsButtonLabel  {<< Remove} widgetDefault
option add *Disjointlistbox.vscrollMode     static      widgetDefault
option add *Disjointlistbox.hscrollMode     static      widgetDefault
option add *Disjointlistbox.selectMode      multiple    widgetDefault
option add *Disjointlistbox.labelPos        nw          widgetDefault
option add *Disjointlistbox.buttonPlacement bottom      widgetDefault
option add *Disjointlistbox.lhsSortOption   increasing  widgetDefault
option add *Disjointlistbox.rhsSortOption   increasing  widgetDefault


#
# Usual options.
#
itk::usual Disjointlistbox {
  keep -background -textbackground -cursor \
       -foreground -textfont -labelfont
}


# ----------------------------------------------------------------------
# ::iwidgets::Disjointlistbox 
# ----------------------------------------------------------------------
itcl::class ::iwidgets::Disjointlistbox {

  inherit itk::Widget

  #
  # options
  #
  itk_option define -buttonplacement buttonPlacement ButtonPlacement bottom
  itk_option define -lhsbuttonlabel  lhsButtonLabel  LabelText       {Insert >>}
  itk_option define -rhsbuttonlabel  rhsButtonLabel  LabelText       {<< Remove}
  itk_option define -lhssortoption   lhsSortOption   LhsSortOption   increasing
  itk_option define -rhssortoption   rhsSortOption   RhsSortOption   increasing

  constructor {args} {}

  #
  # PUBLIC
  #
  public {
    method clear {}
    method getlhs {{first 0} {last end}}
    method getrhs {{first 0} {last end}}
    method lhs {args}
    method insertlhs {items}
    method insertrhs {items}
    method setlhs {items}
    method setrhs {items}
    method rhs {args}
  }

  #
  # PROTECTED
  #
  protected {
    method insert {theListbox items}
    method listboxClick {clickSide otherSide}
    method listboxDblClick {clickSide otherSide}
    method remove {theListbox items}
    method showCount {}
    method transfer {}

    variable sourceListbox {}
    variable destinationListbox {}
  }
}

#
# Provide a lowercased access method for the ::iwidgets::Disjointlistbox class.
# 
proc ::iwidgets::disjointlistbox {pathName args} {
    uplevel ::iwidgets::Disjointlistbox $pathName $args
}

# ------------------------------------------------------------------
#
# Method: Constructor
#
# Purpose:   
#
itcl::body ::iwidgets::Disjointlistbox::constructor {args} {
    #
    # Create the left-most Listbox
    #
    itk_component add lhs {
        iwidgets::Scrolledlistbox $itk_interior.lhs \
                -selectioncommand [itcl::code $this listboxClick lhs rhs] \
                -dblclickcommand [itcl::code $this listboxDblClick lhs rhs]
    } {
        usual
        keep -selectmode -vscrollmode -hscrollmode
        rename -labeltext -lhslabeltext lhsLabelText LabelText
    }

    #
    # Create the right-most Listbox
    #
    itk_component add rhs {
        iwidgets::Scrolledlistbox $itk_interior.rhs \
                -selectioncommand [itcl::code $this listboxClick rhs lhs] \
                -dblclickcommand [itcl::code $this listboxDblClick rhs lhs]
    } {
        usual
        keep -selectmode -vscrollmode -hscrollmode
        rename -labeltext -rhslabeltext rhsLabelText LabelText
    }

    #
    # Create the left-most item count Label
    #
    itk_component add lhsCount {
        label $itk_interior.lhscount
    } {
        usual
        rename -font -labelfont labelFont Font
    }

    #
    # Create the right-most item count Label
    #
    itk_component add rhsCount {
        label $itk_interior.rhscount
    } {
        usual
        rename -font -labelfont labelFont Font
    }

    set sourceListbox $itk_component(lhs)
    set destinationListbox $itk_component(rhs)

    #
    # Bind the "showCount" method to the Map event of one of the labels
    # to keep the diplayed item count current.
    #
    bind $itk_component(lhsCount) <Map> [itcl::code $this showCount]

    grid $itk_component(lhs) -row 0 -column 0 -sticky nsew
    grid $itk_component(rhs) -row 0 -column 2 -sticky nsew

    grid rowconfigure    $itk_interior 0 -weight 1
    grid columnconfigure $itk_interior 0 -weight 1
    grid columnconfigure $itk_interior 2 -weight 1

    eval itk_initialize $args
}

# ------------------------------------------------------------------
# Method:  listboxClick
#
# Purpose: Evaluate a single click make in the specified Listbox.
#
itcl::body ::iwidgets::Disjointlistbox::listboxClick {clickSide otherSide} {
    set button "button"
    $itk_component($clickSide$button) configure -state active
    $itk_component($otherSide$button) configure -state disabled
    set sourceListbox      $clickSide
    set destinationListbox $otherSide
}

# ------------------------------------------------------------------
# Method:  listboxDblClick
#
# Purpose: Evaluate a double click in the specified Listbox.
#
itcl::body ::iwidgets::Disjointlistbox::listboxDblClick {clickSide otherSide} {
    listboxClick $clickSide $otherSide
    transfer
}

# ------------------------------------------------------------------
# Method:  transfer
#
# Purpose: Transfer source Listbox items to destination Listbox
#
itcl::body ::iwidgets::Disjointlistbox::transfer {} {

    if {[$sourceListbox selecteditemcount] == 0} {
        return
    }
    set selectedindices [lsort -integer -decreasing [$sourceListbox curselection]]
    set selecteditems [$sourceListbox getcurselection]

    foreach index $selectedindices {
        $sourceListbox delete $index
    }

    foreach item $selecteditems {
        $destinationListbox insert end $item
    }

    if {![string equal $itk_option(-${destinationListbox}sortoption) "none"]} {
        $destinationListbox sort $itk_option(-${destinationListbox}sortoption)
    }

    showCount
}

# ------------------------------------------------------------------
# Method: getlhs
#
# Purpose: Retrieve the items of the left Listbox widget
#
itcl::body ::iwidgets::Disjointlistbox::getlhs {{first 0} {last end}} {
    return [lhs get $first $last]
}

# ------------------------------------------------------------------
# Method: getrhs
#
# Purpose: Retrieve the items of the right Listbox widget
#
itcl::body ::iwidgets::Disjointlistbox::getrhs {{first 0} {last end}} {
    return [rhs get $first $last]
}

# ------------------------------------------------------------------
# Method: insertrhs
#
# Purpose: Insert items into the right Listbox widget
#
itcl::body ::iwidgets::Disjointlistbox::insertrhs {items} {
    remove $itk_component(lhs) $items
    insert rhs $items
}

# ------------------------------------------------------------------
# Method: insertlhs
#
# Purpose: Insert items into the left Listbox widget
#
itcl::body ::iwidgets::Disjointlistbox::insertlhs {items} {
    remove $itk_component(rhs) $items
    insert lhs $items
}

# ------------------------------------------------------------------
# Method:  clear
#
# Purpose: Remove the items from the Listbox widgets and set the item count
#          Labels text to 0
#
itcl::body ::iwidgets::Disjointlistbox::clear {} {
    lhs clear
    rhs clear
    showCount
}

# ------------------------------------------------------------------
# Method: insert
#
# Purpose: Insert the input items into the input Listbox widget while
#          maintaining the disjoint property between them.
#
itcl::body ::iwidgets::Disjointlistbox::insert {theListbox items} {

    set curritems [$theListbox get 0 end]

    foreach item $items {
        #
        # if the item is not already present in the Listbox then insert it
        #
        if {[lsearch -exact $curritems $item] == -1} {
            $theListbox insert end $item
        }
    }

    if {![string equal $itk_option(-${theListbox}sortoption) "none"]} {
        $theListbox sort $itk_option(-${theListbox}sortoption)
    }

    showCount
}

# ------------------------------------------------------------------
# Method: remove
#
# Purpose: Remove the input items from the input Listbox widget while
#          maintaining the disjoint property between them.
#
itcl::body ::iwidgets::Disjointlistbox::remove {theListbox items} {

    set indexes {}
    set curritems [$theListbox get 0 end]

    foreach item $items {
        #
        # if the item is in the listbox then add its index to the index list
        # 
        if {[set index [lsearch -exact $curritems $item]] != -1} {
            lappend indexes $index
        }
    }

    foreach index [lsort -integer -decreasing $indexes] {
        $theListbox delete $index
    }
    showCount
}

# ------------------------------------------------------------------
# Method: showCount
#
# Purpose: Set the text of the item count Labels.
#
itcl::body ::iwidgets::Disjointlistbox::showCount {} {
    $itk_component(lhsCount) config -text "item count: [lhs size]"
    $itk_component(rhsCount) config -text "item count: [rhs size]"
}

# ------------------------------------------------------------------
# METHOD: setlhs
#
# Set the items of the left-most Listbox with the input list
# option.  Remove all (if any) items from the right-most Listbox
# which exist in the input list option to maintain the disjoint
# property between the two
#
itcl::body ::iwidgets::Disjointlistbox::setlhs {items} {
    lhs clear
    insertlhs $items
}

# ------------------------------------------------------------------
# METHOD: setrhs
#
# Set the items of the right-most Listbox with the input list
# option.  Remove all (if any) items from the left-most Listbox
# which exist in the input list option to maintain the disjoint
# property between the two
#
itcl::body ::iwidgets::Disjointlistbox::setrhs {items} {
    rhs clear
    insertrhs $items
}

# ------------------------------------------------------------------
# Method:  lhs
#
# Purpose: Evaluates the specified arguments against the lhs Listbox
#
itcl::body ::iwidgets::Disjointlistbox::lhs {args} {
    return [eval $itk_component(lhs) $args]
}

# ------------------------------------------------------------------
# Method:  rhs
#
# Purpose: Evaluates the specified arguments against the rhs Listbox
#
itcl::body ::iwidgets::Disjointlistbox::rhs {args} {
    return [eval $itk_component(rhs) $args]
}

# ------------------------------------------------------------------
# OPTION: buttonplacement
#
# Configure the placement of the buttons to be either between or below
# the two list boxes.
#
itcl::configbody ::iwidgets::Disjointlistbox::buttonplacement {
    if {$itk_option(-buttonplacement) != ""} {

        if { [lsearch [component] lhsbutton] != -1 } {
            eval destroy $itk_component(rhsbutton) $itk_component(lhsbutton)
        }

        if { [lsearch [component] bbox] != -1 } {
            destroy $itk_component(bbox)
        }

        set where $itk_option(-buttonplacement)

        switch $where {

            center {
                #
                # Create the button box frame
                #
                itk_component add bbox {
                    frame $itk_interior.bbox
                }
    
                itk_component add lhsbutton {
                    button $itk_component(bbox).lhsbutton -command [itcl::code \
                            $this transfer]
                } {
                    usual
                    rename -text -lhsbuttonlabel lhsButtonLabel LabelText
                    rename -font -labelfont labelFont Font
                }
    
                itk_component add rhsbutton {
                    button $itk_component(bbox).rhsbutton -command [itcl::code \
                            $this transfer]
                } {
                    usual
                    rename -text -rhsbuttonlabel rhsButtonLabel LabelText
                    rename -font -labelfont labelFont Font
                }
    
                grid configure $itk_component(lhsCount) -row 1 -column 0 \
                        -sticky ew
                grid configure $itk_component(rhsCount) -row 1 -column 2 \
                        -sticky ew
     
                grid configure $itk_component(bbox) \
                        -in $itk_interior -row 0 -column 1 -columnspan 1 \
                                -sticky nsew
    
                grid configure $itk_component(rhsbutton) \
                        -in $itk_component(bbox) -row 0 -column 0 -sticky ew
                grid configure $itk_component(lhsbutton) \
                        -in $itk_component(bbox) -row 1 -column 0 -sticky ew
                }

            bottom {
    
                itk_component add lhsbutton {
                    button $itk_interior.lhsbutton -command [itcl::code $this \
                            transfer]
                } {
                    usual
                    rename -text -lhsbuttonlabel lhsButtonLabel LabelText
                    rename -font -labelfont labelFont Font
                }

                itk_component add rhsbutton {
                    button $itk_interior.rhsbutton -command [itcl::code $this \
                            transfer]
                } {
                    usual
                    rename -text -rhsbuttonlabel rhsButtonLabel LabelText
                    rename -font -labelfont labelFont Font
                }

                grid $itk_component(lhsCount)  -row 2 -column 0 -sticky ew
                grid $itk_component(rhsCount)  -row 2 -column 2 -sticky ew
                grid $itk_component(lhsbutton) -row 1 -column 0 -sticky ew
                grid $itk_component(rhsbutton) -row 1 -column 2 -sticky ew
            }

            default {
                error "bad buttonplacement option\"$where\": should be center\
                        or bottom"
            }
        }
    }
}

# ------------------------------------------------------------------
# OPTION: lhssortoption
#
# Configure the sort option to use for the left side
#
itcl::configbody ::iwidgets::Disjointlistbox::lhssortoption {

    if {![string equal $itk_option(-lhssortoption) "none"]} {
        $itk_component(lhs) sort $itk_option(-lhssortoption)
    }
}


# ------------------------------------------------------------------
# OPTION: rhssortoption
#
# Configure the sort option to use for the right side
#
itcl::configbody ::iwidgets::Disjointlistbox::rhssortoption {

    if {![string equal $itk_option(-rhssortoption) "none"]} {
        $itk_component(rhs) sort $itk_option(-rhssortoption)
    }
}