# macros.tcl --
#
#	The macros window(s) and related commands.
#

namespace eval NSMacros {

variable Priv

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

proc InitModule {} {

	global Windows
	variable Priv

	InitImageIfNeeded Image_Open open.gif
	InitImageIfNeeded Image_Save save.gif

	set Priv(oop) [NSObject::New NSMacros]
	set Priv(selectedMacro) -1
}

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

proc NSMacros oop {

	global Windows

	InitWindow $oop

	NSWindowManager::RegisterWindow macros [Info $oop win] \
		"NSMacros::GeometryCmd $oop" "" "NSMacros::DisplayCmd $oop"

	#
	# Global list of application windows
	#

	set Windows(macros,win) [Info $oop win]
	set Windows(macros,class) NSMacros
	set Windows(macros,oop) $oop
}

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

proc Info {oop info args} {

	global NSMacros

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

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

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

proc InitWindow oop {

	global NSCanvist
	global NSToolbar
	global Windows

	set win .macros$oop
	toplevel $win
	wm title $win Macros

	wm transient $win $Windows(main,win)

	# Start out withdrawn (hidden)
	wm withdraw $win

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

	Info $oop win $win

	InitMenus $oop

	#
	# Toolbar
	#

	set toolId [NSObject::New NSToolbar 20 $win]
	NSToolbar::AddTool $toolId Image_Open "NSMacros::MacroLoad $oop"
	NSToolbar::AddTool $toolId Image_Save "NSMacros::MacroDump $oop"

	NSStatusText::StatusText [NSToolbar::GetTool $toolId 1] \
		$win.statusBar.label \
		"Read settings from an existing preferences file."
	NSStatusText::StatusText [NSToolbar::GetTool $toolId 2] \
		$win.statusBar.label \
		"Append macros to a new or existing preferences file."

	#
	# Divider
	#

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

	#
	# List
	#

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

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

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

	Info $oop canvistId $canvistId

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

	set NSCanvist($canvistId,selectionCmd) "NSMacros::SelectionChanged $oop"

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

	# Trigger and Action fields
	set frame $win.frameFields
	frame $frame \
		-borderwidth 0
	label $frame.labelTrigger \
		-text "Trigger:"
	label $frame.entryTrigger \
		-width 30 -borderwidth 2 -relief sunken -takefocus 1
	label $frame.labelAction \
		-text "Action:"
	entry $frame.entryAction \
		-width 30
	pack $frame.labelTrigger \
		-side left -padx 2 -pady 5
	pack $frame.entryTrigger \
		-side left -padx 2 -pady 5
	pack $frame.labelAction \
		-side left -padx 2 -pady 5
	pack $frame.entryAction \
		-side left -padx 2 -pady 5

	# When the Trigger field has the focus, typing a key sequence
	# changes the trigger for the selected macro.
	set entry $frame.entryTrigger
	bind $entry <Any-Enter> {focus %W}
	bind $entry <Any-Leave> "focus $canvas"
	bind $entry <FocusIn> {%W configure -background White}
	bind $entry <FocusOut> {%W configure -background [[winfo toplevel %W] cget -background]}
	bindtags $entry [list $entry MacroTrigger_BindTag]

	set entry $frame.entryAction
	bind $entry <KeyPress-Return> "NSMacros::SetMacroAction $oop"

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

	#
	# 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 columnconfig $win 0 -weight 1 -minsize 0
 
	pack forget $NSToolbar($toolId,frame)
	grid $NSToolbar($toolId,frame) -in $win \
		-row 0 -column 0 -rowspan 1 -columnspan 1 -sticky ew
	grid $win.divider2 -in $win \
		-row 1 -column 0 -rowspan 1 -columnspan 1 -sticky ew
	grid $win.frame -in $win \
		-row 2 -column 0 -rowspan 1 -columnspan 1 -sticky news
	grid $win.frameFields -in $win \
		-row 3 -column 0 -rowspan 1 -columnspan 1 -sticky ew
	grid $win.statusBar -in $win \
		-row 4 -column 0 -rowspan 1 -columnspan 1 -sticky ew

	#
	# Context Menu
	#
	
	set m $win.context
	menu $m -tearoff 0
	bind $canvas <Button-3> "::NSMacros::ContextMenu $oop $m %X %Y"

	#
	# KeyPress bindings
	#

	bind $win <KeyPress-Escape> "NSMacros::Close $oop"
	bind $win <Control-KeyPress-w> "NSMacros::Close $oop"
	bind $win <KeyPress-n> "NSMacros::CreateMacro $oop"

	#
	# Synch the scrollbars when window is shown.
	#

	bind $win.frame.scroll <Map> "NSMacros::SynchScrollBars $oop"

	NSStatusText::StatusText $win.frameFields.entryTrigger $win.statusBar.label \
		"Type a new trigger for the selected macro."
	NSStatusText::StatusText $win.frameFields.entryAction $win.statusBar.label \
		"Enter an encoded action and hit Enter."
}

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

proc InitMenus oop {

	# Default accelerator modifier
	set mod "Ctrl"

	set win [Info $oop win]

	#
	# Menu bar
	#

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

	#
	# Macro Menu
	#

	NSObject::New NSMenu $mbar {-tearoff 0 -identifier MENU_MACRO }
	NSMenu::MenuInsertEntry $mbar -end MENUBAR {-type cascade -menu MENU_MACRO -label "Macro" -underline 0 -identifier M_MACRO}

	set entries {}
	lappend entries "-type command -label \"New Empty Macro\" -command \"NSMacros::CreateMacro $oop\" -underline 0 -accelerator n -identifier E_MACRO_NEW"
	lappend entries "-type command -label \"Dump Macros\" -command \"NSMacros::MacroDump $oop\" -underline 0 -identifier E_MACRO_DUMP"
	lappend entries "-type command -label \"Load Pref File\" -command \"NSMacros::MacroLoad $oop\" -underline 0 -identifier E_MACRO_LOAD"
	lappend entries "-type separator"
	lappend entries "-type command -label \"Close\" \
		-command \"NSMacros::Close $oop\" -underline 0 \
		-accelerator $mod+W -identifier E_CLOSE"

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

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

proc SetupMenus {oop mbarId} {

	set canvistId [Info $oop canvistId]

	lappend identList E_MACRO_NEW E_MACRO_DUMP E_MACRO_LOAD E_CLOSE

	if {[llength [NSCanvist::Selection $canvistId]]} {
		lappend identList E_MACRO_ACTION
	}

	NSMenu::MenuEnable $mbarId $identList
}

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

proc DisplayCmd {oop message first} {

	set canvistId [Info $oop canvistId]
	set canvas [NSCanvist::Info $canvistId canvas]

	switch -- $message {
		preDisplay {
			SetList $oop
		}
		postDisplay {
			focus $canvas
		}
	}
}

# NSMacros::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
}

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

proc Close oop {

	angband keypress \033
}

# NSMacros::ContextMenu --
#
#	When an macros item is right-clicked, pop up a context
#	menu of actions.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc ContextMenu {oop menu x y} {

	set canvistId [Info $oop canvistId]
	set canvas [NSCanvist::Info $canvistId canvas]

	set x1 [expr $x - [winfo rootx $canvas]]
	set y1 [expr $y - [winfo rooty $canvas]]
	set row [NSCanvist::PointToRow $canvistId $x1 $y1]
	if {$row == -1} return

	$menu delete 0 end
	$menu add command -label "Entry One" -command ""
	$menu add separator
	$menu add command -label "Entry Two" -command ""

	tk_popup $menu $x $y
}

# NSMacros::SetList --
#
#	Add all the defined macros to the list.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetList oop {

	variable Priv

	set win [Info $oop win]
	set canvistId [Info $oop canvistId]
	set canvas [NSCanvist::Info $canvistId canvas]
	set index $Priv(selectedMacro)

	# Clear the list
	NSCanvist::Delete $canvistId 0 end
	
	# Get number of macros
	set max [angband macro max]

	# Iterate over macros
	for {set i 0} {$i < $max} {incr i} {

		# Get the keypress
		set keypress [angband macro keypress $i]

		# Strip leading "_^" and trailing "\r" from keypress
		set keypress [string trim $keypress "^_\\r"]

		# Get the action
		set action [angband macro action $i]

		# Hack -- Remove weird formatting

		# Append to the list
		NSCanvist::Insert $canvistId end $keypress $action
	}

	# Display total number of macros
	$win.statusBar.label2 configure -text "$max macros"

	if {$index != -1} {
		NSCanvist::UpdateSelection $canvistId $index ""
	}
}

# NSMacros::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 canvistId]
	set canvas [NSCanvist::Info $canvistId canvas]

	eval $win.frame.scroll set [$canvas yview]
	$canvas yview moveto 0.0
}

# NSMacros::CreateMacro --
#
#	Create a new macro.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc CreateMacro oop {

	variable Priv

	# Create a new empty macro.
	set index [angband macro create ^_NEW\\r]
	set Priv(selectedMacro) $index

	# Update the list
	SetList $oop

	# See the new macro
	NSCanvist::See [Info $oop canvistId] $index
}

# NSMacros::SetMacroAction --
#
#	Update the action for the selected macro. The new action is taken
#	from the Action Entry.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetMacroAction oop {

	global NSCanvist
	variable Priv

	if {$Priv(selectedMacro) == -1} return
	set index $Priv(selectedMacro)

	set win [Info $oop win]
	set canvistId [Info $oop canvistId]
	set entry $win.frameFields.entryAction

	# Set the action for the selected macro
	angband macro action $index [$entry get]

	# Update the list
	SetList $oop

	# See the updated macro
	NSCanvist::See $canvistId $index

	focus $NSCanvist($canvistId,canvas)
}

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

proc NewItemCmd {oop canvistId y keypress action} {

	global NSCanvist

	set c $NSCanvist($canvistId,canvas)
	set lineHeight $NSCanvist($canvistId,rowHgt)
	set font [Global font,fixed,normal]

	set text $keypress

	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]

	#
	# Keypress
	#

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

	#
	# Action
	#

	lappend itemIdList [$c create text [expr $cw * 30] [expr $y + $diff] \
		-text $action -anchor nw -font $font -fill White]

	return $itemIdList
}

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


# The following bindings are used to get a macro trigger keypress
# from the user. They should be almost identical to those found in
# keyboard.tcl. The differences are that (1) the keypress passed
# a local command, not as an argument to "angband keypress"; and (2)
# the "wrapper" chars \037 and \015 are not written.

bind MacroTrigger_BindTag <Control-Shift-KeyPress> {

	NSMacros::SetMacroTrigger Control-Shift-%K
}

bind MacroTrigger_BindTag <Control-Shift_L> {
}

bind MacroTrigger_BindTag <Shift-Control_L> {
}

bind MacroTrigger_BindTag <Control-KeyPress> {

	# Special Control-KeyPress (ex Control-F1)
	if {![string compare %A ""]} {
		NSMacros::SetMacroTrigger Control-%K

	# Ascii Control-KeyPress
	} else {
		NSMacros::SetMacroTrigger %A
	}
}

bind MacroTrigger_BindTag <Control_L> {
}

bind MacroTrigger_BindTag <Shift-KeyPress> {

	# Special Shift-KeyPress (ex Shift-F1)
	if {![string compare %A ""]} {
		NSMacros::SetMacroTrigger Shift-%K

	# Ascii Shift-KeyPress
	} else {
		NSMacros::SetMacroTrigger %A
	}
}

bind MacroTrigger_BindTag <Shift_L> {
}

bind MacroTrigger_BindTag <Alt-KeyPress> {

	NSMacros::SetMacroTrigger Alt-%K
}

bind MacroTrigger_BindTag <Alt_L> {
}

bind MacroTrigger_BindTag <KeyPress> {

	# Special KeyPress (ex F1)
	if {![string compare %A ""]} {
		NSMacros::SetMacroTrigger %K

	# Normal keys with no modifiers
	} else {
		NSMacros::SetMacroTrigger %A
	}
}

bind MacroTrigger_BindTag <Escape> {

	bell
}

bind MacroTrigger_BindTag <Return> {

	bell
}

bind MacroTrigger_BindTag <Tab> {

	bell
}

bind MacroTrigger_BindTag <BackSpace> {

	bell
}

bind MacroTrigger_BindTag <Delete> {

	bell
}

# NSMacros::MacroDump --
#
#	Get a filename from the user then append all macros to the given
#	file (or create a new file). The file goes inside the lib/user
#	directory.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc MacroDump oop {

	global Angband

	set parent [Info $oop win]
	set filename [tk_getSaveFile -initialfile [angband player base_name].prf \
		-initialdir [file join $Angband(dir) lib user] -parent $parent]
	if {![string compare $filename ""]} return

	if {![IsUserFile $filename]} {
		tk_messageBox -title "Pref File Error" -icon info -message \
			"Pref files must be saved in the lib/user directory."
		return
	}

	set filename [file tail $filename]
	if {[catch {angband game macro_dump $filename} result]} {
		tk_messageBox -title "Pref File Error" -icon error -message $result
	}
}

# NSMacros::MacroLoad --
#
#	Get a filename from the user then read in the given pref file.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc MacroLoad oop {

	# Let the user choose a file, and read it
	if {[ProcessPrefFile [Info $oop win]]} return

	# Update the list
	SetList $oop
}

# NSMacros::SetMacroTrigger --
#
#	When the Trigger Entry has the focus, and the user types a
#	key sequence, the trigger keypress for the selected macro
#	is updated.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetMacroTrigger trigger {

	variable Priv

	if {$Priv(selectedMacro) == -1} return

	set oop $Priv(oop)
	set win [Info $oop win]
	set canvistId [Info $oop canvistId]
	set index $Priv(selectedMacro)

	# Attempt to change the trigger for the selected macro. If another
	# macro is already assigned the same trigger, just tell the user
	# about it and return.
	set index2 [angband macro keypress $index ^_$trigger\\r]
	if {$index2 != $index} {
		bell
		$win.statusBar.label configure \
			-text "That trigger is being used by macro #$index2."
		return
	}

	# Update the list
	SetList $oop

	# See the updated macro
	NSCanvist::See $canvistId $index
}

# NSMacros::SelectionChanged --
#
#	Called when the list selection changes.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SelectionChanged {oop canvistId select deselect} {

	variable Priv

	set win [Info $oop win]
	set entryTrigger $win.frameFields.entryTrigger
	set entryAction $win.frameFields.entryAction

	if {[llength $select]} {
		set row [lindex $select 0]

		# Strip leading "_^" and trailing "\r" from keypress
		set keypress [angband macro keypress $row]
		set keypress [string trim $keypress "^_\\r"]
		$entryTrigger configure -text $keypress

		$entryAction delete 0 end
		$entryAction insert 0 [angband macro action $row]

		set Priv(selectedMacro) $row

	} else {
		$entryTrigger configure -text ""
		$entryAction delete 0 end

		set Priv(selectedMacro) -1
	}
}

# NSMacros::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
	}
}

# namespace eval NSMacros
}
