# assign.tcl --
#
#	The Assign Window and related commands.
#

namespace eval NSAssign {

variable Priv

# NSAssign::InitModule --
#
#	One-time-only-ever initialization.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc InitModule {} {

	variable Priv

	NSModule::LoadIfNeeded NSIconBrowser

	set Priv(hook) {}
	lappend Priv(hook) assign_hook_character Character
	lappend Priv(hook) assign_hook_effect Effect
	lappend Priv(hook) assign_hook_feature Feature
	lappend Priv(hook) assign_hook_monster Monsters
	lappend Priv(hook) assign_hook_object Objects

	set Priv(find,string) ""

	set Priv(didAssign) 0
	qebind ASSIGN <Assign> {
		set NSAssign::Priv(didAssign) 1
	}

	# Create the Assign Window
	NSObject::New NSAssign
}

# NSAssign::NSAssign --
#
#	Object constructor called by NSObject::New().
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc NSAssign oop {

	global Windows

	InitWindow $oop

	Info $oop hook assign_hook_monster
	Info $oop group,current -1
	Info $oop member,current -1
	Info $oop group,current2 0
	Info $oop member,current2 -1
	Info $oop display icon
	Info $oop display,ignoreSel 0
	Info $oop radio,display icon

	set win [Info $oop win]

	NSWindowManager::RegisterWindow assign $win \
		"NSAssign::GeometryCmd $oop" "NSAssign::SetupCmd $oop" \
		"NSAssign::DisplayCmd $oop"		

	bind $win <KeyPress-Escape> "NSAssign::Close $oop"
	bind $win <Control-KeyPress-w> "NSAssign::Close $oop"
	bind $win <KeyPress-f> "NSAssign::Find $oop 0"
	bind $win <KeyPress-g> "NSAssign::Find $oop 1"

	#
	# Global list of application windows
	#

	set Windows(assign,win) [Info $oop win]
	set Windows(assign,class) NSAssign
	set Windows(assign,oop) $oop
}

# NSAssign::~NSAssign --
#
#	Object destructor called by NSObject::Delete().
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc ~NSAssign oop {

	NSValueManager::RemoveClient listBG [Info $oop group,clientId]
	NSValueManager::RemoveClient listBG [Info $oop member,clientId]
	NSValueManager::RemoveClient listBG [Info $oop sound,clientId]
	NSValueManager::RemoveClient listBG [Info $oop sprite,clientId]
}

# NSAssign::Info --
#
#	Query and modify info.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc Info {oop info args} {

	global NSAssign

	# Set info
	if {[llength $args]} {
		switch -- $info {
			default {
				set NSAssign($oop,$info) [lindex $args 0]
			}
		}

	# Get info
	} else {
		switch -- $info {
			groupCanvas {
				set canvistId [Info $oop group,canvistId]
				return [NSCanvist::Info $canvistId canvas]
			}
			memberCanvas {
				set canvistId [Info $oop member,canvistId]
				return [NSCanvist::Info $canvistId canvas]
			}
			default {
				return $NSAssign($oop,$info)
			}
		}
	}
}

# NSAssign::InitWindow --
#
#	Create the window.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc InitWindow oop {

	global NSAssign
	global Windows
	variable Priv

	set win .assign$oop
	toplevel $win
	wm title $win Assign

	wm transient $win $Windows(main,win)

	# Start out withdrawn (hidden)
	wm withdraw $win

	# Do stuff when window closes
	wm protocol $win WM_DELETE_WINDOW "NSAssign::Close $oop"

	# This window plays sound
	SoundWindow $win

	Info $oop win $win

	InitMenus $oop

	#
	# Divider
	#

	frame $win.divider2 \
		-borderwidth 1 -height 2 -relief groove

	#
	# Tabs!
	#

	set tabsId [NSObject::New NSTabs $win]
	foreach {hook label} $Priv(hook) {
		NSTabs::Add $tabsId $label
	}
	NSTabs::Info $tabsId invokeCmd "NSAssign::InvokeTab $oop"
	NSTabs::Info $tabsId active 1
	Info $oop tabsId $tabsId

	#
	# Group List
	#

	set font [Global font,fixed,normal]
	set cw [font measure $font "W"]
	set width [expr $cw * 26]
	set iconSize [expr [icon size] + 8]

	frame $win.frameGroup \
		-borderwidth 1 -relief sunken
	set canvistId [NSObject::New NSCanvist $win.frameGroup $iconSize $width 300 \
		"NSAssign::NewItemCmd $oop" "NSAssign::HighlightItemCmd $oop"]
	set canvas [NSCanvist::Info $canvistId canvas]
	$canvas configure -height [expr 40 * 5]
	$canvas configure -background [NSColorPreferences::Get listBG]
	$canvas configure -yscrollcommand "$win.frameGroup.scroll set"
	scrollbar $win.frameGroup.scroll \
		-borderwidth 0 -command "$canvas yview" -orient vert
	bind $win.frameGroup.scroll <Map> "eval %W set \[$canvas yview]"

	# Do something when a group item is selected
	NSCanvist::Info $canvistId selectionCmd \
		"NSAssign::SelectionChanged_Group $oop"

	# This call updates the list background color whenever the
	# global list background color changes
	Info $oop group,clientId \
		[NSValueManager::AddClient listBG "ListBackgroundChanged $canvas"]

	pack $win.frameGroup.scroll -side right -fill y
	pack $canvas -side left -expand yes -fill both -anchor nw

	Info $oop group,canvistId $canvistId

	#
	# Member List
	#

	set width [expr $iconSize + $cw * 40]

	frame $win.frameMember \
		-borderwidth 1 -relief sunken
	set canvistId [NSObject::New NSCanvist $win.frameMember $iconSize $width 300 \
		"NSAssign::NewItemCmd $oop" "NSAssign::HighlightItemCmd $oop"]
	set canvas [NSCanvist::Info $canvistId canvas]
	$canvas configure -height [expr 40 * 5]
	$canvas configure -background [NSColorPreferences::Get listBG]
	$canvas configure -yscrollcommand "$win.frameMember.scroll set"
	scrollbar $win.frameMember.scroll \
		-borderwidth 0 -command "$canvas yview" -orient vert
	bind $win.frameMember.scroll <Map> "eval %W set \[$canvas yview]"

	# This call updates the list background color whenever the
	# global list background color changes
	Info $oop member,clientId \
		[NSValueManager::AddClient listBG "ListBackgroundChanged $canvas"]

	# Do something when a member item is selected.
	NSCanvist::Info $canvistId selectionCmd \
		"NSAssign::SelectionChanged_Member $oop"

	bind $canvas <Configure> "
		NSAssign::Configure $oop $canvas
	"

	Info $oop member,canvistId $canvistId

	pack $win.frameMember.scroll -side right -fill y
	pack $canvas -side left -expand yes -fill both -anchor nw

	frame $win.divider1 \
		-borderwidth 1 -relief sunken -height 2	

	#
	# An NSIconBrowser lets the user examine all icon types
	#

	set browserId [NSObject::New NSIconBrowser $win]
	set canvistId [NSIconBrowser::Info $browserId member,canvistId]
	NSCanvist2::Info $canvistId selectionCmd \
		"NSAssign::SelectionChanged_Icon $oop"
	set canvas [NSIconBrowser::Info $browserId groupCanvas]
	$canvas configure -height [expr 40 * 3]
	set canvas [NSIconBrowser::Info $browserId memberCanvas]
	$canvas configure -height [expr 40 * 3]

	# Display progress while listing an icon type
	NSIconBrowser::Info $browserId clientCmd \
		"NSAssign::BrowserCmd $oop"

	Info $oop browserId $browserId

	#
	# Sound List
	#

	frame $win.frameSound \
		-borderwidth 1 -relief sunken
	set canvistId [NSObject::New NSCanvist $win.frameSound 20 100 100 \
		"NSAssign::NewItemCmd_Sound $oop" "NSAssign::HighlightItemCmd $oop"]
	set canvas [NSCanvist::Info $canvistId canvas]
	$canvas configure -background [NSColorPreferences::Get listBG]
	$canvas configure -yscrollcommand "$win.frameSound.yscroll set"
	scrollbar $win.frameSound.yscroll \
		-borderwidth 0 -command "$canvas yview" -orient vert
	$canvas configure -height [expr 40 * 3]

	bind $win.frameSound.yscroll <Map> "eval %W set \[$canvas yview]"

	NSCanvist::Info $canvistId selectionCmd \
		"NSAssign::SelectionChanged_Sound $oop"

	# This call updates the list background color whenever the
	# global list background color changes
	Info $oop sound,clientId \
		[NSValueManager::AddClient listBG "ListBackgroundChanged $canvas"]

	pack $canvas \
		-side left -expand yes -fill both -anchor nw
	pack $win.frameSound.yscroll \
		-side left -fill y

	Info $oop sound,canvistId $canvistId

	#
	# The list of sprites
	#

	set frameSprite $win.frameSprite
	frame $frameSprite \
		-borderwidth 1 -relief sunken
	set canvistId [NSObject::New NSCanvist2 $frameSprite $iconSize $iconSize [expr 40 * 10] [expr 40 * 3]]
	NSCanvist2::Info $canvistId newItemCmd \
		"NSIconBrowser::NewItemCmd $oop"
	NSCanvist2::Info $canvistId highlightCmd \
		"NSIconBrowser::HighlightItemCmd $oop"
	NSCanvist2::Info $canvistId columns 10
	set canvas [NSCanvist2::Info $canvistId canvas]
	scrollbar $frameSprite.yscroll \
		-borderwidth 0 -command "$canvas yview" -orient vertical
	$canvas configure -background [NSColorPreferences::Get listBG]
	$canvas configure -yscrollcommand "$frameSprite.yscroll set"
	NSCanvist2::Info $canvistId selectionCmd \
		"NSAssign::SelectionChanged_Sprite $oop"

	# This call updates the list background color whenever the
	# global list background color changes
	Info $oop sprite,clientId \
		[NSValueManager::AddClient listBG "ListBackgroundChanged $canvas"]

	pack $canvas \
		-side left -expand yes -fill both -anchor nw
	pack $frameSprite.yscroll \
		-side left -fill y

	Info $oop sprite,canvistId $canvistId



	#
	# Statusbar
	#

	frame $win.statusBar -relief flat -borderwidth 0
	label $win.statusBar.label -anchor w -relief sunken -padx 2
	label $win.statusBar.label2 -anchor w -relief sunken -padx 2 -width 20
	pack $win.statusBar.label -side left -expand yes -fill both
	pack $win.statusBar.label2 -side right -expand no

	# Progress bar used to display progress of listing icons
	set progId [NSObject::New NSProgress2 $win.statusBar.label 225 10 {} {}]
	[NSProgress2::Info $progId frame] configure -borderwidth 0
	Info $oop progId $progId

	#
	# Geometry
	#

	grid rowconfig $win 0 -weight 0 -minsize 0
	grid rowconfig $win 1 -weight 0 -minsize 0
	grid rowconfig $win 2 -weight 1 -minsize 0
	grid rowconfig $win 3 -weight 0 -minsize 0
	grid rowconfig $win 4 -weight 1 -minsize 0
	grid rowconfig $win 5 -weight 0 -minsize 0
	grid columnconfig $win 0 -weight 0 -minsize 0
	grid columnconfig $win 1 -weight 1 -minsize 0
 
	grid $win.divider2 -in $win \
		-row 0 -column 0 -rowspan 1 -columnspan 2 -sticky ew \
		-pady 2
	grid [NSTabs::Info $tabsId canvas] \
		-row 1 -column 0 -rowspan 1 -columnspan 2 -sticky ew
	grid $win.frameGroup -in $win \
		-row 2 -column 0 -rowspan 1 -columnspan 1 -sticky ns
	grid $win.frameMember -in $win \
		-row 2 -column 1 -rowspan 1 -columnspan 1 -sticky news
	grid $win.divider1 -in $win \
		-row 3 -column 0 -rowspan 1 -columnspan 2 -sticky ew \
		-pady 2
	grid [NSIconBrowser::Info $browserId frame] \
		-row 4 -column 0 -rowspan 1 -columnspan 2 -sticky news
	grid $win.statusBar -in $win \
		-row 5 -column 0 -rowspan 1 -columnspan 2 -sticky ew
}

# NSAssign::InitMenus --
#
#	Create the menus.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc InitMenus oop {

	global NSMenu
	variable Priv

	set win [Info $oop win]
	set mod "Ctrl"

	#
	# Menu bar
	#

	set menuDef "-tearoff 0 -postcommand \"NSAssign::SetupMenus $oop\" \
		-identifier MENUBAR"
	set mbar [NSObject::New NSMenu $win $menuDef]
	Info $oop mbar $mbar

	#
	# Assign To
	#

	NSObject::New NSMenu $mbar {-tearoff 0 -identifier MENU_ASSIGN_TO}
	NSMenu::MenuInsertEntry $mbar -end MENUBAR {-type cascade \
		-menu MENU_ASSIGN_TO -label "Assign To" -underline 7 \
		-identifier M_ASSIGN_TO}

	set entries {}
	set i 1
	foreach {hook label} $Priv(hook) {
		lappend entries "-type radiobutton -label \"$label\" \
			-variable NSAssign($oop,radio,hook) -value $hook
			-command \"NSAssign::SetHook $oop $hook\" \
			-accelerator $i -identifier E_HOOK_$i"
		bind $win <KeyPress-$i> "NSAssign::SetHook $oop $hook"
		incr i
	}
	lappend entries "-type separator"
	lappend entries "-type command -label \"Find...\" \
		-command \"NSAssign::Find $oop 0\" \
		-accelerator f -underline 0 -identifier E_ASSIGN_FIND"
	lappend entries "-type command -label \"Find Again\" \
		-command \"NSAssign::Find $oop 1\" \
		-accelerator g -underline 6 -identifier E_ASSIGN_FIND_AGAIN"
	lappend entries "-type separator"
	lappend entries "-type command -label \"Close\" \
		-command \"NSAssign::Close $oop\" -underline 0 \
		-accelerator $mod+W -identifier E_CLOSE"
	
	NSMenu::MenuInsertEntries $mbar -end MENU_ASSIGN_TO $entries

	#
	# Assign What Menu
	#

	NSObject::New NSMenu $mbar {-tearoff 0 -identifier MENU_ASSIGN_WHAT}
	NSMenu::MenuInsertEntry $mbar -end MENUBAR {-type cascade \
		-menu MENU_ASSIGN_WHAT -label "Assign What" -underline 7 \
		-identifier M_ASSIGN_WHAT}

	set entries {}
	lappend entries "-type radiobutton -label \"Alternate\" \
		-variable NSAssign($oop,radio,display) -value alternate
		-command \"NSAssign::SetDisplay $oop alternate\" \
		-identifier E_ASSIGN_ALTERNATE"
	lappend entries "-type radiobutton -label \"Icon\" \
		-variable NSAssign($oop,radio,display) -value icon
		-command \"NSAssign::SetDisplay $oop icon\" \
		-identifier E_ASSIGN_ICONS"
	lappend entries "-type radiobutton -label \"Sound\" \
		-variable NSAssign($oop,radio,display) -value sound
		-command \"NSAssign::SetDisplay $oop sound\" \
		-identifier E_ASSIGN_SOUND"
	lappend entries "-type radiobutton -label \"Sprite\" \
		-variable NSAssign($oop,radio,display) -value sprite
		-command \"NSAssign::SetDisplay $oop sprite\" \
		-identifier E_ASSIGN_SPRITES"
	
	NSMenu::MenuInsertEntries $mbar -end MENU_ASSIGN_WHAT $entries

	#
	# Hook Menu
	#

	set m $NSMenu($mbar,menu).hook
	menu $m -tearoff 0
	Info $oop hookMenu,menu $m
	Info $oop hookMenu,inserted 0
}

# NSAssign::SetupMenus --
#
#	Prepare to post the menus.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetupMenus {oop mbarID} {

	variable Priv

	lappend identList E_ASSIGN_FIND E_ASSIGN_FIND_AGAIN E_CLOSE
		
	set i 0
	foreach {hook label} $Priv(hook) {
		lappend identList E_HOOK_[incr i]
	}

	lappend identList E_ASSIGN_ALTERNATE E_ASSIGN_ICONS E_ASSIGN_SPRITES \
		E_ASSIGN_SOUND

	if {[Info $oop hookMenu,inserted]} {
		set menu [Info $oop hookMenu,menu]
		set last [$menu index end]
		for {set i 0} {$i <= $last} {incr i} {
			if {[$menu type $i] == "separator"} continue
			$menu entryconfigure $i -state disabled
		}
		CallHook $oop menu_setup
	}

	NSMenu::MenuEnable $mbarID $identList
}

# NSAssign::DisplayCmd --
#
#	Called by NSWindowManager::Display().
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc DisplayCmd {oop message first} {

	switch -- $message {
		preDisplay {
			Map $oop
		}
		postDisplay {
			if $first {
				set browserId [Info $oop browserId]
				set canvistId [NSIconBrowser::Info $browserId group,canvistId]
				NSIconBrowser::SetList_Group $browserId
				NSCanvist2::UpdateSelection $canvistId 0 ""
			}
		}
		postWithdraw {
			Unmap $oop
		}
	}
}

# NSAssign::GeometryCmd --
#
#	Called by NSWindowManager::Setup(). Returns the desired (default)
#	geometry for the window.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc GeometryCmd oop {

	global Windows

	set win [Info $oop win]
	set winMain $Windows(main,win)
	set x [winfo x $winMain]
	set y [winfo y $winMain]
	set width [winfo width $win]
	set height [winfo height $win]
	return ${width}x$height+$x+$y
}

# NSAssign::SetupCmd --
#
#	Called by NSWindowManager::Setup().
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetupCmd oop {

	set win [Info $oop win]

	grid $win.frameSound \
		-row 4 -column 0 -rowspan 1 -columnspan 2 -sticky news
	update idletasks
	grid forget $win.frameSound

	grid $win.frameSprite \
		-row 4 -column 0 -rowspan 1 -columnspan 2 -sticky news
	update idletasks
	grid forget $win.frameSprite
}

# NSAssign::Close --
#
#	Description.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc Close oop {

	global DEBUG
	variable Priv

	if $DEBUG {
		if $Priv(didAssign) {
			set answer [tk_messageBox -icon question -type yesno \
				-parent [Info $oop win] -message "WriteAssignFile Now?"]
			if {$answer == "yes"} {
				WriteAssignFile
			}
			set Priv(didAssign) 0
		}
	}

	NSWindowManager::Undisplay assign
}

# NSAssign::Map --
#
#	Do something the toplevel is mapped.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc Map oop {

	SetHook $oop [Info $oop hook]
#	SetList_Group $oop

	if {[Info $oop display] == "sprite"} {
		SetList_Other $oop sprite
	}

	# Restore the selected group
	set current [Info $oop group,current2]
	set canvistId [Info $oop group,canvistId]
	if {$current != -1} {
		NSCanvist::UpdateSelection $canvistId $current {}
		NSCanvist::See $canvistId $current
	}

	# Restore the selected member
	set current [Info $oop member,current2]
	set canvistId [Info $oop member,canvistId]
	if {$current != -1} {
		NSCanvist::UpdateSelection $canvistId $current {}
		NSCanvist::See $canvistId $current
	}
}

# NSAssign::Unmap --
#
#	Do something when unmapping the window.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc Unmap oop {

	if !$NSWindowManager::Priv(assign,setup) return

	# Because we are clearing the lists here, and don't want to
	# upset the user, I save the selected group/member so it can
	# be restored in Map() below.
	Info $oop group,current2 [Info $oop group,current]
	Info $oop member,current2 [Info $oop member,current]

	NSCanvist::Delete [Info $oop group,canvistId] 0 end
	NSCanvist::Delete [Info $oop member,canvistId] 0 end
	NSCanvist2::DeleteAll [Info $oop sprite,canvistId]
}

# NSAssign::CallHook --
#
#	Call the hook.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc CallHook {oop message args} {

	uplevel #0 eval NSAssign::[Info $oop hook] $oop $message [list $args]
}

# NSAssign::SetHook --
#
#	Set the hook. The hook controls what is displayed and what is
#	done when icons/sprites are selected.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetHook {oop hook} {

	global NSMenu
	variable Priv

	# Remember the hook
	Info $oop hook $hook

	# Clear the hook-specific menu
	set hookMenu [Info $oop hookMenu,menu]
	$hookMenu delete 0 end
	eval destroy [winfo children $hookMenu]

	# Set the group list
	SetList_Group $oop

	# If the hook-menu is empty, remove it, otherwise insert it
	set mbarId [Info $oop mbar]
	if {[$hookMenu index end] == "none"} {
		if {[Info $oop hookMenu,inserted]} {
			$NSMenu($mbarId,menu) delete 2
			Info $oop hookMenu,inserted 0
		}
	} else {
		set index [lsearch -exact $Priv(hook) $hook]
		if {![Info $oop hookMenu,inserted]} {
			$NSMenu($mbarId,menu) add cascade -menu $hookMenu
			Info $oop hookMenu,inserted 1
		}
		$NSMenu($mbarId,menu) entryconfigure end \
			-label [lindex $Priv(hook) [incr index]]
	}

	# Radiobutton menu entries
	Info $oop radio,hook $hook

	set tabsId [Info $oop tabsId]
	set index [expr [lsearch -exact $Priv(hook) $hook] / 2]
	if {$index != [NSTabs::Info $tabsId current]} {
		NSTabs::Smaller $tabsId [NSTabs::Info $tabsId current]
		NSTabs::Bigger $tabsId $index
		NSTabs::Info $tabsId current $index
	}
}

# NSAssign::AssignStuff --
#
#	Call the hook to assign stuff, and display the number of members
#	the thing was assigned to.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc AssignStuff {oop what args} {

	set count [eval CallHook $oop assign $what $args]
	if $count {
		StatusBar $oop "Assigned $what to $count things." 1
	}
}

# NSAssign::SelectionChanged_Group --
#
#	When a "group" list item is selected, display members in the
#	"member" list.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SelectionChanged_Group {oop canvistId select deselect} {

	global NSAssign

	# If nothing was selected, clear the member list
	if {![llength $select]} {
		set canvistId [Info $oop member,canvistId]
		NSCanvist::Delete $canvistId 0 end
		Info $oop group,current -1
		return
	}

	# Get the (first) row
	set row [lindex $select 0]
	Info $oop group,current $row

	# Display members in that group
	SetList_Member $oop $row
}

# NSAssign::SelectionChanged_Member --
#
#	Do something when a "member" list item is selected.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SelectionChanged_Member {oop canvistId select deselect} {

	set win [Info $oop win]

	# Do nothing if no new row was selected
	if {![llength $select]} {
		$win.statusBar.label2 configure -text ""
		Info $oop member,current -1
		return
	}

	# Get the (first) row
	set row [lindex $select 0]
	Info $oop member,current $row

	# Get the icon assigned to the selected member
	set icon [CallHook $oop get_icon $row]

	# Display it in the status line
	set member [lindex [CallHook $oop member_list [Info $oop group,current]] $row]
	$win.statusBar.label2 configure -text "#$member: $icon"

	# Play the sound associated with this member
	set sound [CallHook $oop get_sound $row]
	angband sound play $sound

	Info $oop display,ignoreSel 1

	switch -- [Info $oop display] {

		alternate {
			if {[lindex $icon 0] == "alternate"} {
				set canvistId [Info $oop sprite,canvistId]
				set index [lindex $icon 1]
				NSCanvist2::See $canvistId $index
				NSCanvist2::UpdateSelection $canvistId $index all
			}
		}

		icon {
			# If the NSIconBrowser is displaying the type of icon assigned
			# to the selected member, then select the icon and scroll it into
			# view.
			set browserId [Info $oop browserId]
			set iconType [NSIconBrowser::Info $browserId iconType]
			if {![string compare $iconType [lindex $icon 0]]} {
				NSIconBrowser::SeeIcon $browserId $iconType [lindex $icon 1]
			}
		}

		sound {
			if {[string length sound]} {
				set row2 [lsearch -exact [Info $oop sound,match] $sound]
				incr row2
			} else {
				set row2 0
			}
			set canvistId [Info $oop sound,canvistId]
			NSCanvist::UpdateSelection $canvistId $row2 all
			NSCanvist::See $canvistId $row2
		}

		sprite {
			if {[lindex $icon 0] == "sprite"} {
				set canvistId [Info $oop sprite,canvistId]
				set index [lindex $icon 1]
				NSCanvist2::See $canvistId $index
				NSCanvist2::UpdateSelection $canvistId $index all
			}
		}
	} 

	Info $oop display,ignoreSel 0

	# Call the hook to do other stuff (recall, etc)
	CallHook $oop select_member $row
}

# NSAssign::SelectionChanged_Icon --
#
#	Do something when an icon is selected in the NSIconBrowser.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SelectionChanged_Icon {oop canvistId select deselect} {

	# Don't assign stuff during feedback
	if {[Info $oop display,ignoreSel]} return

	# Do nothing if no new row was selected
	if {![llength $select]} return

	# Do nothing if no member is selected
	set selection [NSCanvist::Selection [Info $oop member,canvistId]]
	if {![llength $selection]} return

	# Get the (first) index
	set index [lindex $select 0]

	# Get the type of icon
	set browserId [Info $oop browserId]
	set type [NSIconBrowser::Info $browserId iconType]

	# Call hook to assign icons to selected members
	AssignStuff $oop icon $type $index

	# Update the assigned icon in the status bar
	set row [Info $oop member,current]
	set member [lindex [CallHook $oop member_list [Info $oop group,current]] $row]
	[Info $oop win].statusBar.label2 configure -text "#$member: $type $index"
}

# NSAssign::SelectionChanged_Sound --
#
#	Called when the sound selection changes.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SelectionChanged_Sound {oop canvistId select deselect} {

	# Do nothing if no new row was selected
	if {![llength $select]} return

	# Don't assign stuff (or play the sound) during feedback
	if {[Info $oop display,ignoreSel]} return

	set row [lindex $select 0]
	if $row {
		incr row -1
		set sound [lindex [Info $oop sound,match] $row]
		angband sound play $sound
	} else {
		set sound ""
	}

	# Do nothing if no member is selected
	set selection [NSCanvist::Selection [Info $oop member,canvistId]]
	if {![llength $selection]} return

	# Call hook to assign sound to selected members
#	CallHook $oop assign sound $sound
	AssignStuff $oop sound $sound
}

# NSAssign::SelectionChanged_Sprite --
#
#	Do something when an icon is selected in the sprite list.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SelectionChanged_Sprite {oop canvistId select deselect} {

	# Don't assign stuff during feedback
	if {[Info $oop display,ignoreSel]} return

	# Do nothing if no new row was selected
	if {![llength $select]} return

	# Do nothing if no member is selected
	set selection [NSCanvist::Selection [Info $oop member,canvistId]]
	if {![llength $selection]} return

	# Get the (first) index
	set index [lindex $select 0]

	# Call hook to assign sprite to selected members
	set other [Info $oop display]
#	CallHook $oop assign $other $other $index
	AssignStuff $oop $other $other $index
}

# NSAssign::SetList_Group --
#
#	.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetList_Group oop {

	set win [Info $oop win]
	set canvistId [Info $oop group,canvistId]

	# Feedback
	StatusBar $oop "Displaying..." 0
	update idletasks

	# Clear the list
	NSCanvist::Delete $canvistId 0 end

	# Call hook to set the group list
	CallHook $oop set_list_group

	Info $oop group,current -1

	# Hack -- Clear the "member" list
	NSCanvist::Delete [Info $oop member,canvistId] 0 end

	# Feedback
	StatusBar $oop "Done." 1
}

# NSAssign::SetList_Member --
#
#	.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetList_Member {oop group} {

	set canvistId [Info $oop member,canvistId]

	# Clear the list
	NSCanvist::Delete $canvistId 0 end

	Info $oop member,current -1

	# Call hook to set the member list
	CallHook $oop set_list_member $group
}

# NSAssign::SetList_Other --
#
#	Set the other list.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetList_Other {oop other} {

	set canvistId [Info $oop sprite,canvistId]
	set canvas [NSCanvist2::Info $canvistId canvas]

	# Clear the list
	NSCanvist2::DeleteAll $canvistId

# Is there a <Configure> event or something?
set width [winfo width $canvas]
set iconSize [expr [icon size] + 8]
set columns [expr $width / $iconSize]
if {$columns == 0} {set columns 1}
NSCanvist2::Info $canvistId columns $columns
	
	# Get the number of sprites */
	set max [$other count]

	# Add each sprite to the list
	for {set i 0} {$i < $max} {incr i} {

		# Append match to the list
		NSCanvist2::Append $canvistId $other $i
	}
}

# NSAssign::SetList_Sound --
#
#	.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetList_Sound oop {

	global Angband

	set win [Info $oop win]
	set canvistId [Info $oop sound,canvistId]
	set canvas [NSCanvist::Info $canvistId canvas]

	# Clear the list
	NSCanvist::Delete $canvistId 0 end

	# Grab a list of sound files in lib/xtra/sound
	set wd [pwd]
	cd [file join $Angband(dir) lib xtra sound]
	set files [lsort -dictionary [glob -nocomplain *.wav]]
	cd $wd

	# Row zero is "None", for "no sound assigned"	
	NSCanvist::Insert $canvistId end None

	foreach soundFile $files {
		NSCanvist::Insert $canvistId end $soundFile
	}

	Info $oop sound,match $files
}

# NSAssign::SetDisplay --
#
#	Displays the list of what is assigned (icons or sprites).
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetDisplay {oop display} {

	set win [Info $oop win]
	set browserId [Info $oop browserId]

	switch -- $display {
		alternate {
			set frame $win.frameSprite
			SetList_Other $oop alternate
		}
		icon {
			set frame [NSIconBrowser::Info $browserId frame]
		}
		sound {
			set frame $win.frameSound
			SetList_Sound $oop
		}
		sprite {
			set frame $win.frameSprite
			SetList_Other $oop sprite
		}
	}

	grid forget [grid slaves $win -row 4]
	grid $frame \
		-row 4 -column 0 -rowspan 1 -columnspan 2 -sticky news

	Info $oop display $display

	# Radiobutton menu entries
	Info $oop radio,display $display
}

# NSAssign::UpdateList --
#
#	Configure the Widget canvas item on the row of a list.
#	When the user assigns an icon to a member, we want to update
#	the member list, and sometimes the group list too.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc UpdateList {oop list row type index} {

	set canvistId [Info $oop $list,canvistId]
	set canvas [Info $oop ${list}Canvas]
	set rowTag [lindex [NSCanvist::Info $canvistId rowTags] $row]
	foreach itemId [$canvas find withtag $rowTag] {
		if {[$canvas type $itemId] == "widget"} break
	}
	$canvas itemconfigure $itemId -type $type -index $index
}

# NSAssign::Find --
#
#	Simple search routine to look for a member by name.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc Find {oop again} {

	variable Priv

	# Repeat the last search
	if {$again && [string length $Priv(find,string)]} {
		set string $Priv(find,string)
		incr Priv(find,member)

	# Enter a string to find, start from the beginning
	} else {

		# Ask the user for a name
		set string [NSUtils::StringBox "Find" $Priv(find,string) \
			Name "Cancel Find" [Info $oop win]]
		if {$string == ""} return

		# Clean up after the dialog, give message
		StatusBar $oop "Searching..." 1
		update

		# Reset search parameters
		set Priv(find,string) $string
		set Priv(find,group) 0
		set Priv(find,member) 0
	}

	set group $Priv(find,group)
	set member $Priv(find,member)
	set max [NSCanvist::Info [Info $oop group,canvistId] count]

	# Compare lowercase
	set string [string tolower $string]

	for {set i $group} {$i < $max} {incr i} {

		set match [lrange [CallHook $oop member_list $i] $member end]

		foreach index $match {
	
			# Get the member name
			set name2 [CallHook $oop member_name $index]

			# Compare lowercase
			set name2 [string tolower $name2]

			# Found a match
			if {[string first $string $name2] != -1} {
	
				set canvistId [Info $oop group,canvistId]
	
				# The new group is not displayed
				if {![NSCanvist::IsRowSelected $canvistId $i]} {

					# Select the new group. As a side effect, the
					# SetList_Member() is called to display the monsters in
					# the group.
					NSCanvist::RemoveSelection $canvistId
					NSCanvist::UpdateSelection $canvistId $i {}
					NSCanvist::See $canvistId $i

				# The new group is already selected
				} else {

					# The found monster is in the currently-displayed
					# group, so remove the current selection
					NSCanvist::RemoveSelection [Info $oop member,canvistId]
				}
	
				set canvistId [Info $oop member,canvistId]
	
				# Select the matching member
				NSCanvist::UpdateSelection $canvistId $member {}
				NSCanvist::See $canvistId $member
	
				set Priv(find,group) $i
				set Priv(find,member) $member

				# Clear "Searching..." message
				StatusBar $oop "" 0

				# Done
				return
			}
			incr member
		}
		set member 0
	}

	set Priv(find,group) 0
	set Priv(find,member) 0

	# If we aren't searching from the start, then wrap around if
	# a match isn't found
	if {$group || $member} {
		Find $oop 1
		return
	}

	StatusBar $oop "No match for \"$string\"." 1
}

# NSAssign::StatusBar --
#
#	Display text in the status bar, perhaps clearing it later.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc StatusBar {oop text zap} {

	set win [Info $oop win]
	set label $win.statusBar.label
	$label configure -text $text
	if $zap {
		NSUtils::ZapLabel $label
	}
}

# NSAssign::NewItemCmd --
#
#	Called by NSCanvist::InsertItem() to create a list row.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc NewItemCmd {oop canvistId y icon text} {

	set c [NSCanvist::Info $canvistId canvas]
	set lineHeight [NSCanvist::Info $canvistId rowHgt]
	set font [Global font,fixed,normal]

	set cw [font measure $font "W"]
	set fw [font measure $font $text]
	set fh [font metrics $font -linespace]
	set diff [expr int([expr ($lineHeight - $fh) / 2])]

	# Selection rectangle around everything
	lappend itemIdList [$c create rectangle 2 [expr $y + 2] \
		[expr [winfo width $c] - 3] [expr $y + $lineHeight - 2] \
		-fill {} -outline {} -tags {enabled selrect} -width 2.0]

	# Image
	set iw [icon size]
	set ih [icon size]
	set wid [expr [icon size] + 8]
	set xdiff [expr int([expr ($wid - $iw) / 2])]
	set ydiff [expr int([expr ($lineHeight - $ih) / 2])]
	lappend itemIdList [$c create widget $xdiff [expr $y + $ydiff] \
		-type [lindex $icon 0] -index [lindex $icon 1] -tags enabled]

	# Text
	set fill White
	lappend itemIdList [$c create text [expr $wid + 1] [expr $y + $diff] \
		-text $text -anchor nw -font $font -fill $fill -tags enabled]

	return $itemIdList
}

# NSAssign::NewItemCmd_Sound --
#
#	Called by NSCanvist::InsertItem() to create a list row.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc NewItemCmd_Sound {oop canvistId y text} {

	set c [NSCanvist::Info $canvistId canvas]
	set lineHeight [NSCanvist::Info $canvistId rowHgt]
	set font [Global font,fixed,normal]

	set cw [font measure $font "W"]
	set fw [font measure $font $text]
	set fh [font metrics $font -linespace]
	set diff [expr int([expr ($lineHeight - $fh) / 2])]

	# Selection rectangle inside row
	lappend itemIdList [$c create rectangle 2 [expr $y + 2] \
		[expr [winfo width $c] - 3] [expr $y + $lineHeight - 2] \
		-fill "" -outline "" -tags {enabled selrect} -width 2.0]

	lappend itemIdList [$c create text 3 [expr $y + $diff] \
		-text $text -anchor nw -font $font -fill White -tags enabled]

	return $itemIdList
}

# NSAssign::HighlightItemCmd --
#
#	Called by NSCanvist::Select() to highlight a row.
#
# Arguments:
#	oop					OOP ID. See above.
#	canvistId					OOP ID of NSCanvist object.
#	state					1 or 0 highlight state.
#	args					List of canvas item ids
#
# Results:
#	What happened.

proc HighlightItemCmd {oop canvistId state args} {

	set canvas [NSCanvist::Info $canvistId canvas]
	set itemIdList $args

	set idRect [lindex $itemIdList 0]

	if {[NSUtils::HasFocus $canvas]} {
		set fill [NSColorPreferences::Get listHilite]
	} else {
		set fill [NSColorPreferences::Get listInactive]
	}

	if $state {
		$canvas itemconfigure $idRect -outline $fill

	} else {
		$canvas itemconfigure $idRect -outline {}
	}
}

# NSAssign::Configure --
#
#	Called as a <Configure> event script. Resizes the selection rectangles
#	so they fit properly.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc Configure {oop canvas} {

	foreach itemId [$canvas find withtag selrect] {
		set coords [$canvas coords $itemId]
		set right [expr [winfo width $canvas] -3]
		set coords [lreplace $coords 2 2 $right]
		eval $canvas coords $itemId $coords
	}
}

# NSAssign::InvokeTab --
#
#	Called when a tab is clicked.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc InvokeTab {oop tabsId tabId} {

	variable Priv
	set index [lsearch -exact [NSTabs::Info $tabsId id] $tabId]
	SetHook $oop [lindex $Priv(hook) [expr $index * 2]]
}

# NSAssign::BrowserCmd --
#
#	Called by NSIconBrowser when displaying an icon type. Display
#	the progress of listing the icons. Note that this can
#	actually slow down listing the icons.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc BrowserCmd {oop action args} {

	set win [Info $oop win]
	set progId [Info $oop progId]
	
	switch -- $action {
		open {
			NSProgress2::Zero $progId
			pack [NSProgress2::Info $progId frame] -pady 1 -fill x
			update idletasks
		}
		update {
			set cur [lindex $args 0]
			set max [lindex $args 1]
			set bump [expr (($max / 20) > 40) ? ($max / 20) : 40]
			if {$cur && ($cur % $bump) == 0} {
				NSProgress2::SetDoneRatio $progId [expr $cur / $max.0]
				$win.statusBar.label2 configure -text "$cur/$max"
				update idletasks
			}
		}
		close {
			NSProgress2::SetDoneRatio $progId 1.0
			update idletasks
			pack forget [NSProgress2::Info $progId frame]
			$win.statusBar.label2 configure -text ""
		}
	}
}

proc assign_hook_character {oop message args} {

	switch -- $message {

		set_list_group {

			set canvistId [Info $oop group,canvistId]
			set icon [angband player char_attr]
			NSCanvist::Insert $canvistId end $icon [angband player name]

			set hookMenu [Info $oop hookMenu,menu]
			$hookMenu add command -label "Auto Assign..." \
				-command "NSAssign::CallHook $oop menu_cmd auto_assign"
		}

		set_list_member {
			set canvistId [Info $oop member,canvistId]
			set icon [angband player char_attr]
			NSCanvist::Insert $canvistId end $icon [angband player name]
			Info $oop member,match 0
		}

		assign {
			set count 0
			set what [lindex $args 0]
			switch -- $what {
				alternate {
					tk_messageBox -title "Assign" -icon info \
						-message "You can only assign alternates to\
						objects" -parent [Info $oop win]
				}
				icon -
				sprite {
					if {[Global autoAssign]} {
						set answer [tk_messageBox -title "Character Icon" \
							-message "Override automatic icon assignment?" \
							-type yesno -parent [Info $oop win]]
						if {$answer == "no"} {return 0}
						Global autoAssign 0
					}
					set iconType [lindex $args 1]
					set iconIndex [lindex $args 2]
					icon assign -to character -toindex 0 \
						-type $iconType -index $iconIndex
					UpdateList $oop member 0 $iconType $iconIndex
					UpdateList $oop group 0 $iconType $iconIndex
					set count 1
				}
			}
			return $count
		}

		get_icon {
			return [angband player char_attr]
		}

		get_sound {
			return ""
		}

		select_member {
		}

		member_name {
			return [angband player name]
		}

		member_list {
			return 0
		}

		menu_setup {
			set hookMenu [Info $oop hookMenu,menu]
			if {![Global autoAssign] && \
				[llength [info commands ::AutoAssignCharacterIcon]]} {
				$hookMenu entryconfigure 0 -state normal
			}
		}

		menu_cmd {
			switch -- [lindex $args 0] {
				auto_assign {
					set answer [tk_messageBox -title "Character Icon" \
						-message "Automatically assign an icon to the\
						character from now on?" -type yesno -parent [Info $oop win]]
					if {$answer == "no"} return
					AutoAssignCharacterIcon
					scan [angband player char_attr] "%s %d" iconType iconIndex
					if {[NSCanvist::Info [Info $oop member,canvistId] count]} {
						UpdateList $oop member 0 $iconType $iconIndex
					}
					UpdateList $oop group 0 $iconType $iconIndex
				}
			}
		}
	}
}

proc assign_hook_effect {oop message args} {

	switch -- $message {

		set_list_group {

			set canvistId [Info $oop group,canvistId]
			set icon [effect assign ball fire]
			NSCanvist::Insert $canvistId end $icon Ball
			set icon [effect assign bolt fire]
			NSCanvist::Insert $canvistId end $icon Bolt
		}

		set_list_member {
			set group [lindex $args 0]
			if $group {
				set effectType bolt
			} else {
				set effectType ball
			}
			set canvistId [Info $oop member,canvistId]
			foreach effect [effect names] {
				set icon [effect assign $effectType $effect]
				NSCanvist::Insert $canvistId end $icon $effect
				lappend match $effect
			}

			# Keep a list of matching effect names
			Info $oop member,match $match
		}

		assign {
			set count 0
			set what [lindex $args 0]
			set group [Info $oop group,current]
			if $group {
				set effectType bolt
			} else {
				set effectType ball
			}
			switch -- $what {
				alternate {
					tk_messageBox -title "Assign" -icon info \
						-message "You can only assign alternates to\
						objects" -parent [Info $oop win]
				}
				icon -
				sprite {
					set canvistId [Info $oop member,canvistId]
					set iconType [lindex $args 1]
					set iconIndex [lindex $args 2]
					foreach row [NSCanvist::Selection $canvistId] {
						set effectName [lindex [Info $oop member,match] $row]
						effect assign $effectType $effectName \
							-type $iconType -index $iconIndex
						UpdateList $oop member $row $iconType $iconIndex
						incr count
					}
					if {$row == [expr [NSCanvist::Info $canvistId count] - 1]} {
						set canvistId [Info $oop group,canvistId]
						set row [NSCanvist::Selection $canvistId]
						UpdateList $oop group $row $iconType $iconIndex
					}
				}
			}
			return $count
		}

		get_icon {
			set row [lindex $args 0]
			set effectName [lindex [Info $oop member,match] $row]
			set group [Info $oop group,current]
			if $group {
				set effectType bolt
			} else {
				set effectType ball
			}
			return [effect assign $effectType $effectName]
		}

		get_sound {
			return ""
		}

		select_member {
		}

		member_name {
			return [lindex [effect names] [lindex $args 0]]
		}

		member_list {
			set index -1
			foreach effect [effect names] {
				lappend match [incr index]
			}
			return $match
		}
	}
}

proc assign_hook_feature {oop message args} {

	global NSAssign

	set feat_name {"nothing" "open floor" "invisible trap" "glyph of warding"
		"open door" "broken door" "up staircase" "down staircase"
		"quest entrance" "quest exit" "quest down level" "quest up level"
		"town exit" "" "" "" "trap door" "open pit"
		"spiked pit" "poisoned pit" "strange rune (summon)"
		"strange rune (teleport)" "discolored spot (fire)"
		"discolored spot (acid)" "dart trap (slow)" "dart trap (strength)"
		"dart trap (dexterity)" "dart trap (constitution)"
		"gas trap (blind)" "gas trap (confuse)" "gas trap (poison)"
		"gas trap (sleep)" "locked door" "locked door" "locked door"
		"locked door" "locked door" "locked door" "locked door"
		"locked door" "jammed door" "jammed door" "jammed door"
		"jammed door" "jammed door" "jammed door" "jammed door"
		"jammed door" "secret door" "pile of rubble" "magma vein"
		"quartz vien" "magma vein" "quartz vien"
		"magma vein + treasure" "quartz vein + treasure"
		"granite wall (basic)" "granite wall (inner)"
		"granite wall (outer)" "granite wall (solid)"
		"permanent wall (basic)" "permanent wall (inner)"
		"permanent wall (outer)" "permanent wall (solid)"
		"explosive rune" "Pattern startpoint"
		"section of the Pattern" "section of the Pattern"
		"section of the Pattern" "section of the Pattern"
		"section of the Pattern" "section of the Pattern (discharged)"
		"Pattern exit" "corrupted section of the Pattern"
		"General Store" "Armoury" "Weapon Smiths" "Temple" "Alchemy Shop"
		"Magic Shop" "Black Market" "Home" "Bookstore"
		"a pool of deep water" "stream of shallow water"
		"a pool of deep lava" "a stream of shallow lava"
		"dark pit" "dirt" "patch of grass"
		"" "" "" "" "" ""
		"trees" "a mountain chain"
		"" "" "" "" "" "" "" "" "" "" "" "" "" ""
		"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
		Building Building Building Building Building
		Building Building Building Building Building
		Building Building Building Building Building
		Building Building Building Building Building
		Building Building Building Building Building
		Building Building Building Building Building
		Building Building
	}

	switch -- $message {

		set_list_group {

			set canvistId [Info $oop group,canvistId]
			set icon [icon assign -to feature -toindex 7]
			NSCanvist::Insert $canvistId end $icon Feature

			set hookMenu [Info $oop hookMenu,menu]
			set menu $hookMenu.light
			menu $menu -tearoff 0
			$hookMenu add cascade -menu $menu -label Lighting
			$menu add radiobutton -label Icon \
				-variable NSAssign($oop,hookMenu,radio) -value icon \
				-command "NSAssign::CallHook $oop menu_cmd light"
			$menu add radiobutton -label None \
				-variable NSAssign($oop,hookMenu,radio) -value none \
				-command "NSAssign::CallHook $oop menu_cmd light"
			$menu add radiobutton -label Tint \
				-variable NSAssign($oop,hookMenu,radio) -value tint \
				-command "NSAssign::CallHook $oop menu_cmd light"
			$hookMenu add command -label Background... \
				-command "NSAssign::CallHook $oop menu_cmd background"
		}

		set_list_member {
			set canvistId [Info $oop member,canvistId]
			set f_idx 0
			foreach name $feat_name {
				if {[string length $name]} {
					set icon [icon assign -to feature -toindex $f_idx]
					if {$::DEBUG} {
						angband f_info info $f_idx attrib
						append name " \[$attrib(name)\]"
					}
					NSCanvist::Insert $canvistId end $icon $name
					lappend match $f_idx
				}
				incr f_idx
			}

			# Keep a list of matching indexes
			Info $oop member,match $match
		}

		assign {
			set count 0
			set what [lindex $args 0]
			switch -- $what {
				alternate -
				icon -
				sprite {
					set canvistId [Info $oop member,canvistId]
					set match [Info $oop member,match]
					set iconType [lindex $args 1]
					set iconIndex [lindex $args 2]
					foreach row [NSCanvist::Selection $canvistId] {
						set f_idx [lindex $match $row]
						icon assign -to feature -toindex $f_idx \
							-type $iconType -index $iconIndex
						UpdateList $oop member $row $iconType $iconIndex
						incr count
					}
					if {$row == 15} {
						set canvistId [Info $oop group,canvistId]
						set row [NSCanvist::Selection $canvistId]
						UpdateList $oop group $row $iconType $iconIndex
					}
				}
			}
			return $count
		}

		get_icon {
			set row [lindex $args 0]
			set f_idx [lindex [Info $oop member,match] $row]
			return [icon assign -to feature -toindex $f_idx]
		}

		get_sound {
			return ""
		}

		select_member {
			set row [lindex $args 0]
			set f_idx [lindex [Info $oop member,match] $row]
			Info $oop hookMenu,radio [feature configure $f_idx -light]
		}

		member_name {
			set row [lindex $args 0]
			set f_idx [lindex [Info $oop member,match] $row]
			return [lindex $feat_name $f_idx]
		}

		member_list {
			set f_idx 0
			foreach name $feat_name {
				if {[string length $name]} {
					lappend match $f_idx
				}
				incr f_idx
			}
			return $match
		}

		menu_setup {
			set hookMenu [Info $oop hookMenu,menu]
			if {[Info $oop member,current] != -1} {
				$hookMenu entryconfigure 0 -state normal
				$hookMenu entryconfigure 1 -state normal
			}
		}

		menu_cmd {
			set canvistId [Info $oop member,canvistId]
			switch -- [lindex $args 0] {
				background {
					set row [Info $oop member,current]
					set f_idx [lindex [Info $oop member,match] $row]
					set initial [feature configure $f_idx -background]
					set bg [NSUtils::StringBox "Set Feature Background" \
						$initial "Feature Index" "Cancel OK" [Info $oop win]]
					if {![string length $bg]} return
					foreach row [NSCanvist::Selection $canvistId] {
						set f_idx [lindex [Info $oop member,match] $row]
						feature configure $f_idx -background $bg
					}
					StatusBar $oop "Set feature background to \"$bg\"." 1
				}
				light {
					set light [Info $oop hookMenu,radio]
					foreach row [NSCanvist::Selection $canvistId] {
						set f_idx [lindex [Info $oop member,match] $row]
						feature configure $f_idx -light $light
					}
					StatusBar $oop "Set feature lighting to \"$light\"." 1
				}
			}
		}
	}
}

proc assign_hook_monster {oop message args} {

	global Windows

	switch -- $message {

		set_list_group {

			set canvistId [Info $oop group,canvistId]

			foreach {title findSpec} [Global groups,r_info] {

				# Find the last monster in the group
				set match [eval angband r_info find -unique no -limit 1 \
					-backwards $findSpec]

				# Get the icon
				angband r_info info [lindex $match 0] attrib
				set icon $attrib(icon)
		
				# Add this group to the list
				NSCanvist::Insert $canvistId end $icon $title
			}
		}

		set_list_member {

			set canvistId [Info $oop member,canvistId]

			set group [lindex $args 0]
			set findSpec [lindex [Global groups,r_info] [expr $group * 2 + 1]]

			# Get a list of monsters in the group
			set match [eval angband r_info find -unique no $findSpec]

			# Add each match to the list
			foreach index $match {
		
				# Get the icon and name
				angband r_info info $index attrib
		
				# Append match to the list
				NSCanvist::Insert $canvistId end $attrib(icon) $attrib(name)
		
				# Hack -- Keep a list of matching indexes
				lappend KnowledgePriv(index) $index
			}

			# Keep a list of matching indexes
			Info $oop member,match $match
		}

		assign {
			set count 0
			set canvistId [Info $oop member,canvistId]
			set what [lindex $args 0]
			switch -- $what {
				alternate {
					tk_messageBox -title "Assign" -icon info \
						-message "You can only assign alternates to\
						objects" -parent [Info $oop win]
				}
				icon -
				sprite {
					set match [Info $oop member,match]
					set iconType [lindex $args 1]
					set iconIndex [lindex $args 2]
					foreach row [NSCanvist::Selection $canvistId] {
						set index [lindex $match $row]
						icon assign -to monster -toindex $index \
							-type $iconType -index $iconIndex
						UpdateList $oop member $row $iconType $iconIndex
						incr count
					}
					if {$row == [expr [NSCanvist::Info $canvistId count] - 1]} {
						set canvistId [Info $oop group,canvistId]
						set row [NSCanvist::Selection $canvistId]
						UpdateList $oop group $row $iconType $iconIndex
					}
				}
				sound {
					set match [Info $oop member,match]
					set sound [lindex $args 1]
					foreach row [NSCanvist::Selection $canvistId] {
						set index [lindex $match $row]
						while {[angband sound count monster mon$index]} {
							angband sound delete monster mon$index 0
						}
						angband sound insert monster mon$index 0 $sound
						incr count
					}
				}
			}
			return $count
		}

		get_icon {
			set row [lindex $args 0]
			set index [lindex [Info $oop member,match] $row]
			angband r_info info $index attrib
			return $attrib(icon)
		}

		get_sound {
			set row [lindex $args 0]
			set index [lindex [Info $oop member,match] $row]
			set soundList [angband sound assign monster mon$index]
			if {[llength $soundList]} {
				return [lindex $soundList 0]
			}
			return ""
		}

		select_member {
			set row [lindex $args 0]
			set index [lindex [Info $oop member,match] $row]

			# Display memory for the selected monster
			NSRecall::Recall $Windows(recall,oop) r_info $index
		}

		member_name {
			angband r_info info [lindex $args 0] attrib
			return $attrib(name)
		}

		member_list {
			set group [lindex $args 0]
			set findSpec [lindex [Global groups,r_info] [expr $group * 2 + 1]]
			return [eval angband r_info find -unique no $findSpec]
		}
	}	
}

proc assign_hook_object {oop message args} {

	switch -- $message {

		set_list_group {

			set canvistId [Info $oop group,canvistId]

			foreach {title findSpec} [Global groups,k_info] {

				# Find the last monster in the group
				set match [eval angband k_info find -limit 1 \
					-backwards $findSpec]

				# Get the icon
				angband k_info info [lindex $match 0] attrib
				set icon $attrib(icon)
		
				# Add this group to the list
				NSCanvist::Insert $canvistId end $icon $title
			}
		}

		set_list_member {

			set canvistId [Info $oop member,canvistId]

			set group [lindex $args 0]
			set findSpec [lindex [Global groups,k_info] [expr $group * 2 + 1]]

			# Get a list of monsters in the group
			set match [eval angband k_info find $findSpec]

			# Add each match to the list
			foreach index $match {
		
				# Get the icon and name
				angband k_info info $index attrib
		
				# Append match to the list
				NSCanvist::Insert $canvistId end $attrib(icon) $attrib(name)
		
				# Hack -- Keep a list of matching indexes
				lappend KnowledgePriv(index) $index
			}

			# Keep a list of matching indexes
			Info $oop member,match $match
		}

		assign {
			set count 0
			set canvistId [Info $oop member,canvistId]
			set what [lindex $args 0]
			switch -- $what {
				alternate -
				icon -
				sprite {
					set match [Info $oop member,match]
					foreach row [NSCanvist::Selection $canvistId] {
						set iconType [lindex $args 1]
						set iconIndex [lindex $args 2]
						set index [lindex $match $row]
						icon assign -to object -toindex $index \
							-type $iconType -index $iconIndex
						UpdateList $oop member $row $iconType $iconIndex
						incr count
					}
					if {$row == [expr [NSCanvist::Info $canvistId count] - 1]} {
						set canvistId [Info $oop group,canvistId]
						set row [NSCanvist::Selection $canvistId]
						UpdateList $oop group $row $iconType $iconIndex
					}
				}
			}
			return $count
		}

		get_icon {
			set row [lindex $args 0]
			set index [lindex [Info $oop member,match] $row]
			angband k_info info $index attrib
			return $attrib(icon)
		}

		get_sound {
			return ""
		}

		select_member {
		}

		member_name {
			angband k_info info [lindex $args 0] attrib
			return $attrib(name)
		}

		member_list {
			set group [lindex $args 0]
			set findSpec [lindex [Global groups,k_info] [expr $group * 2 + 1]]
			return [eval angband k_info find $findSpec]
		}
	}
}

proc assign_hook_XXX {oop message args} {

	switch -- $message {

		set_list_group {
		}

		set_list_member {
		}

		assign {
		}

		get_icon {
		}

		get_sound {
		}

		select_member {
		}

		member_name {
		}

		member_list {
		}
	}
}

# namespace eval NSAssign
}

