# nick_colors.tcl - Copyright (C) 2004 Pat Thoyts <patthoyts@users.sourceforge.net>
#
# Do full text coloring based upon nicks. Includes a color editor and
# persistence for modified color selection.
#
# $Id$

package require sum

namespace eval nickcolors {

    custom::defvar options(use_colored_nicks) 0 \
	[::msgcat::mc "Use colored nicks in chat windows."] \
	-group Chat -type boolean \
	-command [namespace current]::change_options

    custom::defvar options(use_colored_roster_nicks) 0 \
	[::msgcat::mc "Use colored nicks in groupchat rosters."] \
	-group Chat -type boolean \
	-command [namespace current]::change_options

    custom::defvar options(use_colored_messages) 0 \
	[::msgcat::mc "Color message bodies in chat windows."] \
	-group Chat -type boolean \
	-command [namespace current]::change_options

    hook::add open_chat_post_hook [namespace current]::chat_add_nick_colors
    hook::add close_chat_post_hook [namespace current]::chat_delete_nick_colors
    hook::add quit_hook           [namespace current]::save_nick_colors
    hook::add draw_message_hook   [namespace current]::check_nick 60
    hook::add finload_hook        [namespace current]::init_nick_colors
    hook::add chat_win_popup_menu_hook [namespace current]::add_chat_win_popup_menu 10
    hook::add roster_create_groupchat_user_menu_hook \
        [namespace current]::add_groupchat_user_menu_items
    
    variable NickColorPool
    if {![info exists NickColorPool]} {
        set NickColorPool [list blue4 green4 red brown4 orange3 purple3 \
                               tomato chocolate pink3]
    }

    variable NickColors
    if {![info exists NickColors]} {
        array set NickColors {}
    }
}

proc nickcolors::init_nick_colors {} {
    load_nick_colors
    add_nick_colors_menu
}

proc nickcolors::add_nick_colors_menu {} {
    set m [.mainframe getmenu chats]
    
    $m insert end checkbutton \
        -label [::msgcat::mc "Use colored nicks"] \
	-variable [namespace current]::options(use_colored_nicks) \
        -command [namespace current]::change_options
    $m insert end checkbutton \
        -label [::msgcat::mc "Use colored roster nicks"] \
	-variable [namespace current]::options(use_colored_roster_nicks) \
        -command [namespace current]::change_options
    $m insert end checkbutton \
        -label [::msgcat::mc "Use colored messages"] \
	-variable [namespace current]::options(use_colored_messages) \
        -command [namespace current]::change_options
    $m insert end command \
        -label [::msgcat::mc "Edit nick colors..."] \
        -command [namespace current]::edit_nick_colors
}

# Called upon startup, this will merge the user's stored set of nick-colors
# into the current array. New chat windows will pick these up.
#
proc nickcolors::load_nick_colors {} {
    variable NickColors
    set filename [file join $::configdir nickcolors.tcl]
    if {[file exists $filename]} {
        set f [open $filename r]
	fconfigure $f -encoding utf-8
        while {![eof $f]} {
            set line [string trim [gets $f]]
            if {[string length $line] > 0
                && ![string match \#* $line]} {
                catch {
                    set NickColors([lindex $line 0]) [lindex $line 1]
                }
            }
        }
        close $f
    }
}

# Called at shutdown to save the current set of nick-colors to file.
proc nickcolors::save_nick_colors {} {
    variable NickColors
    set filename [file join $::configdir nickcolors.tcl]
    set f [open $filename w]
    fconfigure $f -encoding utf-8
    puts $f "# This is an automatically generated file. Do not edit."
    foreach {nick clr} [array get NickColors] {
        puts $f [list $nick $clr]
    }
    close $f
}

proc nickcolors::get_color {nick} {
    variable NickColors
    variable NickColorPool

    if {[info exists NickColors($nick)]} {
	return $NickColors($nick)
    } else {
	set index [expr {[crc::sum -- $nick] % [llength $NickColorPool]}]
	return [lindex $NickColorPool $index]
    }
}

proc nickcolors::set_color {chatid nick color} {
    variable options

    if {[catch {set w [chat::chat_win $chatid]}] || \
	    ![winfo exists $w]} {
        return
    }

    if {$options(use_colored_nicks)} {
	$w tag configure NICK-$nick -foreground $color
	$w tag configure NICKMSG-$nick -foreground $color
    }
    if {$options(use_colored_messages)} {
	$w tag configure MSG-$nick -foreground $color
	$w tag lower MSG-$nick
    }
}

# Called upon opening a new chat window. This added all the currently defined
# nick-colors as tags into the text widget.
#
proc nickcolors::chat_add_nick_colors {chatid type} {
    variable NicksInChat

    debugmsg chat "on_open_chat $chatid $type"
    set NicksInChat($chatid) {}
}

proc nickcolors::chat_delete_nick_colors {chatid} {
    variable NicksInChat

    debugmsg chat "on_close_chat $chatid"
    catch {unset NicksInChat($chatid)}
}

# draw_message hook used to check that the nick exists as a color and tag.
proc nickcolors::check_nick {chatid from type body x} {
    variable NicksInChat

    set xlib [chat::get_xlib $chatid]
    set nick [chat::get_nick $xlib $from $type]
    if {[lsearch -exact $NicksInChat($chatid) $nick] < 0} {
	lappend NicksInChat($chatid) $nick
	set_color $chatid $nick [get_color $nick]
    }
}

proc nickcolors::edit_nick_colors {} {
    variable NickColors
    variable NickColorEdits

    array set NickColorEdits [array get NickColors]

    set w .edit_nicks

    Dialog $w -title [::msgcat::mc "Edit chat user colors"] \
	-modal none -separator 1 -anchor e \
	-default 0 -cancel 1

    $w add -text [::msgcat::mc "OK"] \
	-command [list [namespace current]::end_dialog $w ok]
    $w add -text [::msgcat::mc "Cancel"] \
	-command [list [namespace current]::end_dialog $w cancel]

    set f [$w getframe]

    bind $f <Destroy> [list [namespace current]::end_dialog [double% $w] cancel]

    set tools [frame $f.tools]
    pack $tools -side bottom -fill x
    
    set sw [ScrolledWindow $w.sw]
    
    set lf [text $w.nicks -width 32 -height 14 -cursor left_ptr]
    pack $sw -side top -expand yes -fill both -in $f -pady 1m -padx 1m
    $sw setwidget $lf

    foreach nick [lsort -dictionary [array names NickColors]] {
	set clr $NickColors($nick)
        $lf tag configure NICK-$nick -foreground $clr
        $lf tag bind NICK-$nick <Enter> \
            [double% [list [namespace current]::on_nick_hover $lf $nick Enter]]
        $lf tag bind NICK-$nick <Leave> \
            [double% [list [namespace current]::on_nick_hover $lf $nick Leave]]
        $lf tag bind NICK-$nick <ButtonPress-1> \
            [double% [list [namespace current]::on_nick_click $lf $nick]]
        $lf insert end $nick [list NICK-$nick]
	$lf insert end "\n"
    }

    $lf configure -state disabled

    $w draw
}

proc nickcolors::end_dialog {w res} {
    variable options
    variable NickColors
    variable NickColorEdits

    bind [$w getframe] <Destroy> { }
    destroy $w

    if {$res == "ok"} {
        array set NickColors [array get NickColorEdits]
	change_options
    }
    catch {unset NickColorEdits}
}

proc nickcolors::on_nick_hover {w nick event} {
    if {$event == "Enter"} {
        $w tag configure NICK-$nick -underline 1
        $w configure -cursor hand2
    } else {
        $w tag configure NICK-$nick -underline 0
        $w configure -cursor left_ptr
    }
}

proc nickcolors::on_nick_click {w nick} {
    variable NickColorEdits

    if {[info exists NickColorEdits($nick)]} {
        set clr $NickColorEdits($nick)
    } else { 
        set clr black
    }
    set new [tk_chooseColor -initialcolor $clr \
		 -title [::msgcat::mc "Edit %s color" $nick]]
    if {$new != ""} {
	$w tag configure NICK-$nick -foreground $new
	set NickColorEdits($nick) $new
	[namespace current]::save_nick_colors
    }
}

proc nickcolors::change_options {args} {
    variable options
    variable NicksInChat

    foreach chatid [chat::opened] {
	set wn [chat::chat_win $chatid]
        if {[winfo exists $wn]} {
	    if {[chat::is_groupchat $chatid]} {
		chat::redraw_roster_after_idle $chatid
	    }
            foreach nick $NicksInChat($chatid) {
		set clr [get_color $nick]
                $wn tag configure NICK-$nick \
		    -foreground [expr {$options(use_colored_nicks) ? $clr : ""}]
                $wn tag configure NICKMSG-$nick \
		    -foreground [expr {$options(use_colored_nicks) ? $clr : ""}]
                $wn tag configure MSG-$nick \
		    -foreground [expr {$options(use_colored_messages) ? $clr : ""}]
            }
        }
    }
}

proc nickcolors::add_chat_win_popup_menu {m chatwin X Y x y} {
    variable options

    set tags [$chatwin tag names "@$x,$y"]
    set nick ""
    if {$options(use_colored_messages)} {
	if {[set idx [lsearch -glob $tags MSG-*]] >= 0} {
	    set nick [string range [lindex $tags $idx] 4 end]
	}
    }
    if {$options(use_colored_nicks)} {
	if {[set idx [lsearch -glob $tags NICK-*]] >= 0} {
	    set nick [string range [lindex $tags $idx] 5 end]
	}
	if {[set idx [lsearch -glob $tags NICKMSG-*]] >= 0} {
	    set nick [string range [lindex $tags $idx] 5 end]
	}
    }

    if {$nick == ""} return

    $m add command -label [::msgcat::mc "Edit nick color..."] \
        -command [list [namespace current]::edit_nick_color $chatwin $nick]
}

proc nickcolors::add_groupchat_user_menu_items {m xlib jid} {
    variable options

    if {$options(use_colored_roster_nicks)} {
	set chatid [chat::chatid $xlib [::xmpp::jid::stripResource $jid]]
	set chatwin [chat::chat_win $chatid]
	set nick [chat::get_nick $xlib $jid groupchat]
	$m add command -label [::msgcat::mc "Edit nick color..."] \
	    -command [list [namespace current]::edit_nick_color $chatwin $nick]
    }
}

proc nickcolors::edit_nick_color {chatwin nick} {
    variable NickColors

    set new [tk_chooseColor -initialcolor [get_color $nick] \
		 -title [::msgcat::mc "Edit %s color" $nick]]
    if {$new == ""} return

    if {$new != [get_color $nick]} {
	set NickColors($nick) $new
	change_options
	[namespace current]::save_nick_colors
    }
}

