# sound.tcl --
#
#	The Sound Window(s) and related commands.
#

namespace eval NSSound {

variable Priv

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

proc InitModule {} {

	variable Priv

	InitImageIfNeeded Image_Checked checked.gif
	InitImageIfNeeded Image_Unchecked unchecked.gif

	lappend Priv(hook) event Events
	lappend Priv(hook) monster_attack "Monster Attacks"
	lappend Priv(hook) monster_spell "Monster Spells"
	lappend Priv(hook) martial_art "Martial Arts"

	NSObject::New NSSound
}

# NSSound::NSSound --
#
#	Description.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc NSSound oop {

	global Windows

	InitWindow $oop

	set winMain $Windows(main,win)
	set win [Info $oop win]
	NSToplevel::NaturalSize $win ""

	Info $oop sound,ignoreSel 0
	Info $oop group,current -1
	Info $oop group,keyword ""
	Info $oop assign,current -1

	NSWindowManager::RegisterWindow sound $win \
		"NSSound::GeometryCmd $oop" "" "NSSound::DisplayCmd $oop"

	#
	# Global list of application windows
	#

	set Windows(sound,win) [Info $oop win]
	set Windows(sound,class) NSSound
	set Windows(sound,oop) $oop
}

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

proc ~NSSound oop {

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

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

proc Info {oop info args} {

	global NSSound

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

	# Get info
	} else {
		switch -- $info {
			default {
				return $NSSound($oop,$info)
			}
		}
	}
}

# NSSound::InitWindow --
#
#	Description.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc InitWindow oop {

	global NSCanvist
	global NSSound
	global Windows
	variable Priv

	set win .sound$oop
	toplevel $win
	wm title $win Sound

	wm transient $win $Windows(main,win)

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

	# This window plays sound
	SoundWindow $win

	# Start out withdrawn (hidden)
	wm withdraw $win

	Info $oop win $win

	InitMenus $oop

	#
	# Divider
	#

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

	set font [Global font,fixed,normal]
	set cw [font measure $font "W"]
	set width [expr $cw * 30]

	#
	# Tabs!
	#

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

	#
	# Group List
	#

	frame $win.frameGroup \
		-borderwidth 1 -relief sunken
	set canvistId [NSObject::New NSCanvist $win.frameGroup 20 $width $width \
		"NSSound::NewItemCmd $oop" "NSSound::HighlightItemCmd $oop"]
	set canvas [NSCanvist::Info $canvistId canvas]
	$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 $canvas <Configure> "
		NSSound::Configure $oop $canvas
	"

	NSCanvist::Info $canvistId selectionCmd \
		"NSSound::SelectionChanged_Group $oop"

	Info $oop group,canvistId $canvistId

	# 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

	#
	# Sound List
	#

	frame $win.frameSound \
		-borderwidth 1 -relief sunken
	set canvistId [NSObject::New NSCanvist $win.frameSound 20 $width $width \
		"NSSound::NewItemCmd $oop" "NSSound::HighlightItemCmd $oop"]
	set canvas [NSCanvist::Info $canvistId canvas]
	$canvas configure -background [NSColorPreferences::Get listBG]
	$canvas configure -yscrollcommand "$win.frameSound.scroll set"
	scrollbar $win.frameSound.scroll \
		-borderwidth 0 -command "$canvas yview" -orient vert

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

	NSCanvist::Info $canvistId selectionCmd \
		"NSSound::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 $win.frameSound.scroll -side right -fill y
	pack $canvas -side left -expand yes -fill both

	Info $oop sound,canvistId $canvistId

	#
	# Divider
	#

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

	#
	# Assign List
	#

	set frame $win.frameAssign
	frame $frame \
		-borderwidth 1 -relief sunken
	set canvistId [NSObject::New NSCanvist $frame 20 $width $width \
		"NSSound::NewItemCmd $oop" "NSSound::HighlightItemCmd $oop"]
	set canvas [NSCanvist::Info $canvistId canvas]
	$canvas configure -background [NSColorPreferences::Get listBG]
	$canvas configure -yscrollcommand "$frame.yscroll set"
	$canvas configure -height [expr 20 * 4]
	scrollbar $frame.yscroll \
		-borderwidth 0 -command "$canvas yview" -orient vert

	NSCanvist::Info $canvistId selectionCmd \
		"NSSound::SelectionChanged_Assign $oop"

	pack $frame.yscroll -side right -fill y
	pack $canvas -side left -expand yes -fill both

	Info $oop assign,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 0 -minsize 0
	grid rowconfig $win 5 -weight 0 -minsize 0
	grid columnconfig $win 0 -weight 1 -minsize 0
	grid columnconfig $win 1 -weight 1 -minsize 0
 
	grid $win.divider1 -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 3 -columnspan 1 -sticky news
	grid $win.frameSound -in $win \
		-row 2 -column 1 -rowspan 1 -columnspan 1 -sticky news
	grid $win.divider2 -in $win \
		-row 3 -column 1 -rowspan 1 -columnspan 1 -sticky ew -pady 2
	grid $win.frameAssign -in $win \
		-row 4 -column 1 -rowspan 1 -columnspan 1 -sticky news
	grid $win.statusBar -in $win \
		-row 5 -column 0 -rowspan 1 -columnspan 2 -sticky ew

	#
	# KeyPress bindings
	#

	bind $win <KeyPress-Escape> "NSSound::Close $oop"
	bind $win <Control-KeyPress-w> "NSSound::Close $oop"
	bind $win <KeyPress-plus> "NSSound::InsertSound $oop"
	bind $win <KeyPress-minus> "NSSound::DeleteSound $oop"

	#
	# Synch the scrollbars when window is shown.
	#

#	bind $win.frameGroup.scroll <Map> "NSSound::SynchScrollBars $oop"
}

# NSSound::InitMenus --
#
#	Description.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc InitMenus oop {

	global NSMainWindow
	global NSSound
	global Windows
	variable Priv

	# Default accelerator modifier
	set mod "Ctrl"

	set win [Info $oop win]

	#
	# Menu bar
	#

	set menuDef "-tearoff 0 -postcommand \"NSSound::SetupMenus $oop\" -identifier MENUBAR"
	set NSSound($oop,mbar) [NSObject::New NSMenu $win $menuDef]
	set mbar $NSSound($oop,mbar)

	#
	# Sound Menu
	#

	NSObject::New NSMenu $mbar {-tearoff 0 -identifier MENU_SOUND }
	NSMenu::MenuInsertEntry $mbar -end MENUBAR {-type cascade \
		-menu MENU_SOUND -label "Sound" -underline 0 -identifier M_SOUND}

	lappend entries "-type command -label \"Insert Sound\" \
		-command \"NSSound::InsertSound $oop\" -underline 0 \
		-accelerator plus -identifier E_INSERT"
	lappend entries "-type command -label \"Delete Sound\" \
		-command \"NSSound::DeleteSound $oop\" -underline 0 \
		-accelerator minus -identifier E_DELETE"
	lappend entries "-type separator"
	set i 1
	foreach {hook label} $Priv(hook) {
		lappend entries "-type radiobutton -label \"$label\" \
			-variable NSSound($oop,radio,hook) -value $hook
			-command \"NSSound::SetList_Group $oop $hook\" \
			-accelerator $i -identifier E_HOOK_$i"
		bind $win <KeyPress-$i> "NSSound::SetList_Group $oop $hook"
		incr i
	}
	lappend entries "-type separator"
	lappend entries "-type checkbutton -label \"Use Sound\" \
		-command \"NSMainWindow::ToggleSound $Windows(main,oop)\" \
		-variable NSMainWindow($Windows(main,oop),useSound) \
		-identifier E_USE_SOUND"
	lappend entries "-type separator"
	lappend entries "-type command -label \"Set Directory...\" \
		-command \"NSSound::SoundDirectory $oop\" -underline 0 \
		-identifier E_DIRECTORY"
	lappend entries "-type command -label \"Update Sound List\" \
		-command \"NSSound::SetList_Sound $oop\" -underline 0 \
		-identifier E_UPDATE"
	lappend entries "-type separator"
	lappend entries "-type command -label \"Close\" \
		-command \"NSSound::Close $oop\" -underline 0 \
		-accelerator $mod+W -identifier E_CLOSE"

	NSMenu::MenuInsertEntries $mbar -end MENU_SOUND $entries
}

# NSSound::SetupMenus --
#
#	Description
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetupMenus {oop mbarId} {

	variable Priv

	if {[Info $oop group,current] != -1} {
		lappend identList E_INSERT
		if {[Info $oop assign,current] != -1} {
			lappend identList E_DELETE
		}
	}

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

	lappend identList E_USE_SOUND E_DIRECTORY E_UPDATE E_CLOSE

	NSMenu::MenuEnable $mbarId $identList
}

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

proc DisplayCmd {oop message first} {

	switch -- $message {
		preDisplay {
		}
		postDisplay {
			if $first {
				SetList_Group $oop event
				SetList_Sound $oop
			}
		}
	}
}

# NSSound::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 [NSToplevel::ContentWidth $win [expr [NSToplevel::EdgeRight $winMain] - $x]]
	set height [NSToplevel::ContentHeight $win [expr [NSToplevel::EdgeBottom $winMain] - $y]]
	return ${width}x$height+$x+$y
}

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

proc Close oop {

	NSWindowManager::Undisplay sound
}

# NSSound::SelectionChanged_Group --
#
#	Called when the group selection changes.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SelectionChanged_Group {oop canvistId select deselect} {

	set win [Info $oop win]
	set selection [NSCanvist::Selection $canvistId]
	set count [llength $selection]

	# Nothing is selected. Clear the assign list.
	if !$count {
		Info $oop group,current -1
		Info $oop group,keyword ""
		if {[NSCanvist::Info [Info $oop assign,canvistId] count]} {
			NSCanvist::Delete [Info $oop assign,canvistId] 0 end
		}
		$win.statusBar.label2 configure -text ""
		return
	}

	set group [Info $oop group]
	set match [Info $oop group,match]

	# Only one keyword is selected. Display assigned sounds in the
	# assign list.
	if {$count == 1} {
		set row [lindex $selection 0]
		Info $oop group,current $row
	
		set index [lindex $match $row]
		Info $oop group,keyword [angband sound keyword $group $index]
	
		SetList_Assign $oop
		set canvistId [Info $oop assign,canvistId]
		if {[NSCanvist::Info $canvistId count]} {
			NSCanvist::UpdateSelection $canvistId 0 ""
		}

		$win.statusBar.label2 configure -text [Info $oop group,keyword]

	# More than one item is now selected. The sounds assigned to the
	# group,keyword are assigned to the newly selected keywords.
	# This allows the user to assign the same sounds to a number of
	# keywords just by selecting the new keywords.
	} else {

		set soundList [angband sound assign $group [Info $oop group,keyword]]
		foreach row $select {
			set index [lindex $match $row]
			set keyword [angband sound keyword $group $index]
			while {[angband sound count $group $keyword]} {
				angband sound delete $group $keyword 0
			}
			foreach sound $soundList {
				angband sound insert $group $keyword 1000 $sound
			}
			UpdateList_Group $oop $row
		}

		$win.statusBar.label2 configure -text "$count selected"
	}
}

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

proc SelectionChanged_Sound {oop canvistId select deselect} {

	if {![llength $select]} {
		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 ""
	}

	# Don't assign the sound, just select and play it
	if {[Info $oop sound,ignoreSel]} return

	set row [Info $oop assign,current]
	if {$row == -1} return

	set group [Info $oop group]
	set match [Info $oop group,match]
	set keyword [Info $oop group,keyword]

	# All selected keywords are updated to use the new sound.
	foreach row2 [NSCanvist::Selection [Info $oop group,canvistId]] {
		set index [lindex $match $row2]
		set keyword [angband sound keyword $group $index]
		angband sound assign $group $keyword $row $sound
	}

	UpdateList_Assign $oop $row
}

# NSSound::SelectionChanged_Assign --
#
#	Called when the assign selection changes.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SelectionChanged_Assign {oop canvistId select deselect} {

	if {![llength $select]} {
		Info $oop assign,current -1
		if {![llength [NSCanvist::Selection $canvistId]]} {
			NSCanvist::RemoveSelection [Info $oop sound,canvistId]
		}
		return
	}

	set row [lindex $select 0]
	Info $oop assign,current $row

	set canvistId [Info $oop sound,canvistId]
	set group [Info $oop group]
	set keyword [Info $oop group,keyword]

	Info $oop sound,ignoreSel 1

	set sound [angband sound assign $group $keyword $row]
	set row [lsearch -exact [Info $oop sound,match] $sound]
	incr row

	# Always play the sound, even if selection doesn't change
	if {[NSCanvist::IsRowSelected $canvistId $row]} {
		angband sound play $sound
	}

	NSCanvist::UpdateSelection $canvistId $row all
	NSCanvist::See $canvistId $row

	Info $oop sound,ignoreSel 0
}

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

proc SetList_Group {oop group} {

	variable Priv

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

	Info $oop group $group

	Progress $oop open

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

	# Map rows to sound indexes
	set match {}

	set max [angband sound count $group]
	for {set i 0} {$i < $max} {incr i} {

		if {![angband sound exists $group $i]} continue

		set keyword [angband sound keyword $group $i]
		set desc [angband sound desc $group $keyword]

		if {[angband sound count $group $keyword]} {
			set image Image_Checked
		} else {
			set image Image_Unchecked
		}

		NSCanvist::Insert $canvistId end $image $desc

		# Map rows to sound indexes
		lappend match $i

		Progress $oop update $i $max
	}

	# Map rows to sound indexes
	Info $oop group,match $match

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

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

	Progress $oop close
}

# NSSound::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]

	Progress $oop open

	# 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

	set max [llength $files]

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

	set i 0
	foreach soundFile $files {
		NSCanvist::Insert $canvistId end "" $soundFile
		Progress $oop update $i $max
		incr i
	}

	Info $oop sound,match $files

	Progress $oop close
}

# NSSound::SetList_Assign --
#
#	Display the sounds assigned to the selected keyword(s).
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetList_Assign oop {

	global Angband

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

	set group [Info $oop group]
	set keyword [Info $oop group,keyword]

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

	foreach sound [angband sound assign $group $keyword] {
		if {$sound == ""} {set sound None}
		NSCanvist::Insert $canvistId end "" $sound
	}
}

# NSSound::SynchScrollBars --
#
#	There is a bug (my bug or in Tk?) which prevents the scroll bars
#	from synchronizing when the window is not mapped. So I bind to
#	the <Map> event and synch the scroll bars here.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SynchScrollBars oop {

	set win [Info $oop win]
	set canvistId [Info $oop group,canvistId]
	set canvas [NSCanvist::Info $canvistId canvas]
	eval $win.frameGroup.scroll set [$canvas yview]
	$canvas yview moveto 0.0

	set canvistId [Info $oop sound,canvistId]
	set canvas [NSCanvist::Info $canvistId canvas]
	eval $win.frameSound.scroll set [$canvas yview]
	$canvas yview moveto 0.0
}

# NSSound::StatusBar --
#
#	.
#
# 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
	}
}

# NSSound::UpdateList_Group --
#
#	Configure the Widget canvas item on the row of a list.
#	When a sound is assigned/unassigned, the event list must
#	be updated.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc UpdateList_Group {oop row} {

	set group [Info $oop group]
	set keyword [Info $oop group,keyword]

	if {[angband sound count $group $keyword]} {
		set image Image_Checked
	} else {
		set image Image_Unchecked
	}

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

# NSSound::UpdateList_Assign --
#
#	Configure the Widget canvas item on the row of a list.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc UpdateList_Assign {oop row} {

	set group [Info $oop group]
	set keyword [Info $oop group,keyword]

	set sound [angband sound assign $group $keyword $row]
	if {$sound == ""} {set sound None}

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

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

proc NewItemCmd {oop canvistId y image text} {

	global NSCanvist

	set c $NSCanvist($canvistId,canvas)
	set lineHeight $NSCanvist($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]

	set textLeft 3
	if {$image != ""} {
		set iw [image width $image]
		set ih [image height $image]
		set idiff [expr int([expr (20 - $iw) / 2])]
		set ydiff [expr int([expr ($lineHeight - $ih) / 2])]
		lappend itemIdList [$c create image $idiff [expr $y + $ydiff] \
			-image $image -anchor nw -tags enabled]
		set textLeft 21
	}

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

	return $itemIdList
}

# NSSound::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} {

	global NSCanvist

	set canvas $NSCanvist($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 {}
	}
}

# NSSound::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]
	SetList_Group $oop [lindex $Priv(hook) [expr $index * 2]]
}

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
	}
}

proc InsertSound oop {

	if {[Info $oop group,current] == -1} return
	set group [Info $oop group]
	set keyword [Info $oop group,keyword]

	set index [Info $oop assign,current]
	if {$index == -1} {
		set index [angband sound count $group $keyword]
	}
	angband sound insert $group $keyword $index ""

	SetList_Assign $oop

	set canvistId [Info $oop assign,canvistId]
	NSCanvist::UpdateSelection $canvistId $index ""
	NSCanvist::See $canvistId $index

	if {[angband sound count $group $keyword] == 1} {
		UpdateList_Group $oop [Info $oop group,current]
	}
}

proc DeleteSound oop {

	if {[Info $oop group,current] == -1} return
	set group [Info $oop group]
	set keyword [Info $oop group,keyword]

	set index [Info $oop assign,current]
	if {$index == -1} return

	angband sound delete $group $keyword $index

	SetList_Assign $oop

	set count [angband sound count $group $keyword]
	if $count {
		if {($index > 0) && ($index >= $count - 1)} {
			incr index -1
		}
		set canvistId [Info $oop assign,canvistId]
		NSCanvist::UpdateSelection $canvistId $index ""
		NSCanvist::See $canvistId $index

	} else {
		UpdateList_Group $oop [Info $oop group,current]
	}
}

# NSSound::Progress --
#
#	Show, update, and hide the progress bar.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc Progress {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 ""
		}
	}
}

# NSSound::SoundDirectory --
#
#	Puts up a dialog to allow the user to change the current
#	sound directory.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SoundDirectory oop {

	set win .setsounddir
	toplevel $win
	wm title $win "Set Sound Directory"
	
	frame $win.frameSrc \
		-borderwidth 0

	set frame $win.frameSrc
	message $frame.labelSrc \
		-text "The current sound directory is displayed below. You can change\
		the directory by clicking \"Browse...\" and selecting a file in\
		another directory, or by typing in the name of another directory." \
		-width 350
	entry $frame.entrySrc \
		-width 60
	button $frame.buttonSrc \
		-text "Browse..." -command "NSSound::SoundDirectory_ChooseFile $oop $win $frame.entrySrc"

	set frame $win.frameSrc
	pack $frame \
		-side top -padx 5 -pady 5
	pack $frame.labelSrc \
		-in $frame -side top -padx 5 -anchor w
	pack $frame.entrySrc \
		-in $frame -side left -padx 5
	pack $frame.buttonSrc \
		-in $frame -side left -padx 5

	frame $win.divider \
		-borderwidth 1 -height 2 -relief sunken
	pack $win.divider \
		-side top -fill x -padx 10 -pady 5

	set frame $win.frameButtons
	frame $frame \
		-borderwidth 0
	button $frame.cancel \
		-text Cancel -width 9 -command "destroy $win"
	button $frame.begin \
		-text OK -width 9 -default active \
		-command "NSSound::SoundDirectory_Set $oop $win $win.frameSrc.entrySrc"

	pack $frame \
		-side top -padx 5 -pady 0 -anchor e
	pack $frame.cancel \
		-side right -padx 5 -pady 5
	pack $frame.begin \
		-side right -padx 5 -pady 5

	bind $win <KeyPress-Return> \
		"tkButtonInvoke $frame.begin"
	bind $win <KeyPress-Escape> \
		"tkButtonInvoke $frame.cancel"
	
	# Default
	set path [angband game directory ANGBAND_DIR_XTRA_SOUND]
	catch {set path [cxrl_getLongPathName $path]}
	$win.frameSrc.entrySrc insert 0 [file nativename $path]

	$win.frameSrc.entrySrc selection range 0 end
	focus $win.frameSrc.entrySrc

	WindowPosition $win 2 3
}

proc SoundDirectory_ChooseFile {oop parent entry} {

	set types {
		{"All files"	*}
	}

	set path [$entry get]
	if {![file exists $path] || ![file isdirectory $path]} {
		set path [angband game directory ANGBAND_DIR_XTRA_SOUND]
	}
	
	set filePath [tk_getOpenFile -filetypes $types -parent $parent \
		-initialdir $path]
	if {$filePath != {}} {
		$entry delete 0 end
		$entry insert 0 [file nativename [file dirname $filePath]]
	}
}

proc SoundDirectory_Set {oop parent entry} {

	set path [$entry get]

	# Is it a real directory?
	if {![file exists $path] || ![file isdirectory $path]} {
		tk_messageBox -parent $parent -title "Bad Directory" \
		-message "No such directory\n\n    \"$path\"\n\n\
		Try entering a real directory, and see if that helps."
		return
	}

	# Update the binary with the new directory
	if {[catch {angband game directory ANGBAND_DIR_XTRA_SOUND $path} err]} {
		tk_messageBox -parent $parent -title "Oops" -message $err
		return
	}

	destroy $parent
	update
	
	# The value-manager saves/restores the value for us
	Value sound,directory [file split $path]
	
	# Display list of sounds in the new directory
	SetList_Sound $oop
}

# namespace eval NSSound
}
