1# nick_colors.tcl - Copyright (C) 2004 Pat Thoyts <patthoyts@users.sourceforge.net>
2#
3# Do full text coloring based upon nicks. Includes a color editor and
4# persistence for modified color selection.
5#
6# $Id$
7
8package require sum
9
10namespace eval nickcolors {
11
12    custom::defvar options(use_colored_nicks) 0 \
13	[::msgcat::mc "Use colored nicks in chat windows."] \
14	-group Chat -type boolean \
15	-command [namespace current]::change_options
16
17    custom::defvar options(use_colored_roster_nicks) 0 \
18	[::msgcat::mc "Use colored nicks in groupchat rosters."] \
19	-group Chat -type boolean \
20	-command [namespace current]::change_options
21
22    custom::defvar options(use_colored_messages) 0 \
23	[::msgcat::mc "Color message bodies in chat windows."] \
24	-group Chat -type boolean \
25	-command [namespace current]::change_options
26
27    hook::add open_chat_post_hook [namespace current]::chat_add_nick_colors
28    hook::add close_chat_post_hook [namespace current]::chat_delete_nick_colors
29    hook::add quit_hook           [namespace current]::save_nick_colors
30    hook::add draw_message_hook   [namespace current]::check_nick 60
31    hook::add finload_hook        [namespace current]::init_nick_colors
32    hook::add chat_win_popup_menu_hook [namespace current]::add_chat_win_popup_menu 10
33    hook::add roster_create_groupchat_user_menu_hook \
34        [namespace current]::add_groupchat_user_menu_items
35
36    variable NickColorPool
37    if {![info exists NickColorPool]} {
38        set NickColorPool [list blue4 green4 red brown4 orange3 purple3 \
39                               tomato chocolate pink3]
40    }
41
42    variable NickColors
43    if {![info exists NickColors]} {
44        array set NickColors {}
45    }
46}
47
48proc nickcolors::init_nick_colors {} {
49    load_nick_colors
50    add_nick_colors_menu
51}
52
53proc nickcolors::add_nick_colors_menu {} {
54    set m [.mainframe getmenu chats]
55
56    $m insert end checkbutton \
57        -label [::msgcat::mc "Use colored nicks"] \
58	-variable [namespace current]::options(use_colored_nicks) \
59        -command [namespace current]::change_options
60    $m insert end checkbutton \
61        -label [::msgcat::mc "Use colored roster nicks"] \
62	-variable [namespace current]::options(use_colored_roster_nicks) \
63        -command [namespace current]::change_options
64    $m insert end checkbutton \
65        -label [::msgcat::mc "Use colored messages"] \
66	-variable [namespace current]::options(use_colored_messages) \
67        -command [namespace current]::change_options
68    $m insert end command \
69        -label [::msgcat::mc "Edit nick colors..."] \
70        -command [namespace current]::edit_nick_colors
71}
72
73# Called upon startup, this will merge the user's stored set of nick-colors
74# into the current array. New chat windows will pick these up.
75#
76proc nickcolors::load_nick_colors {} {
77    variable NickColors
78    set filename [file join $::configdir nickcolors.tcl]
79    if {[file exists $filename]} {
80        set f [open $filename r]
81	fconfigure $f -encoding utf-8
82        while {![eof $f]} {
83            set line [string trim [gets $f]]
84            if {[string length $line] > 0
85                && ![string match \#* $line]} {
86                catch {
87                    set NickColors([lindex $line 0]) [lindex $line 1]
88                }
89            }
90        }
91        close $f
92    }
93}
94
95# Called at shutdown to save the current set of nick-colors to file.
96proc nickcolors::save_nick_colors {} {
97    variable NickColors
98    set filename [file join $::configdir nickcolors.tcl]
99    set f [open $filename w]
100    fconfigure $f -encoding utf-8
101    puts $f "# This is an automatically generated file. Do not edit."
102    foreach {nick clr} [array get NickColors] {
103        puts $f [list $nick $clr]
104    }
105    close $f
106}
107
108proc nickcolors::get_color {nick} {
109    variable NickColors
110    variable NickColorPool
111
112    if {[info exists NickColors($nick)]} {
113	return $NickColors($nick)
114    } else {
115	set index [expr {[crc::sum -- $nick] % [llength $NickColorPool]}]
116	return [lindex $NickColorPool $index]
117    }
118}
119
120proc nickcolors::set_color {chatid nick color} {
121    variable options
122
123    if {[catch {set w [chat::chat_win $chatid]}] || \
124	    ![winfo exists $w]} {
125        return
126    }
127
128    if {$options(use_colored_nicks)} {
129	$w tag configure NICK-$nick -foreground $color
130	$w tag configure NICKMSG-$nick -foreground $color
131    }
132    if {$options(use_colored_messages)} {
133	$w tag configure MSG-$nick -foreground $color
134	$w tag lower MSG-$nick
135    }
136}
137
138# Called upon opening a new chat window. This added all the currently defined
139# nick-colors as tags into the text widget.
140#
141proc nickcolors::chat_add_nick_colors {chatid type} {
142    variable NicksInChat
143
144    debugmsg chat "on_open_chat $chatid $type"
145    set NicksInChat($chatid) {}
146}
147
148proc nickcolors::chat_delete_nick_colors {chatid} {
149    variable NicksInChat
150
151    debugmsg chat "on_close_chat $chatid"
152    catch {unset NicksInChat($chatid)}
153}
154
155# draw_message hook used to check that the nick exists as a color and tag.
156proc nickcolors::check_nick {chatid from type body x} {
157    variable NicksInChat
158
159    set xlib [chat::get_xlib $chatid]
160    set nick [chat::get_nick $xlib $from $type]
161    if {[lsearch -exact $NicksInChat($chatid) $nick] < 0} {
162	lappend NicksInChat($chatid) $nick
163	set_color $chatid $nick [get_color $nick]
164    }
165}
166
167proc nickcolors::edit_nick_colors {} {
168    variable NickColors
169    variable NickColorEdits
170
171    array set NickColorEdits [array get NickColors]
172
173    set w .edit_nicks
174
175    Dialog $w -title [::msgcat::mc "Edit chat user colors"] \
176	-modal none -separator 1 -anchor e \
177	-default 0 -cancel 1
178
179    $w add -text [::msgcat::mc "OK"] \
180	-command [list [namespace current]::end_dialog $w ok]
181    $w add -text [::msgcat::mc "Cancel"] \
182	-command [list [namespace current]::end_dialog $w cancel]
183
184    set f [$w getframe]
185
186    bind $f <Destroy> [list [namespace current]::end_dialog [double% $w] cancel]
187
188    set tools [frame $f.tools]
189    pack $tools -side bottom -fill x
190
191    set sw [ScrolledWindow $w.sw]
192
193    set lf [text $w.nicks -width 32 -height 14 -cursor left_ptr]
194    pack $sw -side top -expand yes -fill both -in $f -pady 1m -padx 1m
195    $sw setwidget $lf
196
197    foreach nick [lsort -dictionary [array names NickColors]] {
198	set clr $NickColors($nick)
199        $lf tag configure NICK-$nick -foreground $clr
200        $lf tag bind NICK-$nick <Enter> \
201            [double% [list [namespace current]::on_nick_hover $lf $nick Enter]]
202        $lf tag bind NICK-$nick <Leave> \
203            [double% [list [namespace current]::on_nick_hover $lf $nick Leave]]
204        $lf tag bind NICK-$nick <ButtonPress-1> \
205            [double% [list [namespace current]::on_nick_click $lf $nick]]
206        $lf insert end $nick [list NICK-$nick]
207	$lf insert end "\n"
208    }
209
210    $lf configure -state disabled
211
212    $w draw
213}
214
215proc nickcolors::end_dialog {w res} {
216    variable options
217    variable NickColors
218    variable NickColorEdits
219
220    bind [$w getframe] <Destroy> { }
221    destroy $w
222
223    if {$res == "ok"} {
224        array set NickColors [array get NickColorEdits]
225	change_options
226    }
227    catch {unset NickColorEdits}
228}
229
230proc nickcolors::on_nick_hover {w nick event} {
231    if {$event == "Enter"} {
232        $w tag configure NICK-$nick -underline 1
233        $w configure -cursor hand2
234    } else {
235        $w tag configure NICK-$nick -underline 0
236        $w configure -cursor left_ptr
237    }
238}
239
240proc nickcolors::on_nick_click {w nick} {
241    variable NickColorEdits
242
243    if {[info exists NickColorEdits($nick)]} {
244        set clr $NickColorEdits($nick)
245    } else {
246        set clr black
247    }
248    set new [tk_chooseColor -initialcolor $clr \
249		 -title [::msgcat::mc "Edit %s color" $nick]]
250    if {$new != ""} {
251	$w tag configure NICK-$nick -foreground $new
252	set NickColorEdits($nick) $new
253	[namespace current]::save_nick_colors
254    }
255}
256
257proc nickcolors::change_options {args} {
258    variable options
259    variable NicksInChat
260
261    foreach chatid [chat::opened] {
262	set wn [chat::chat_win $chatid]
263        if {[winfo exists $wn]} {
264	    if {[chat::is_groupchat $chatid]} {
265		chat::redraw_roster_after_idle $chatid
266	    }
267            foreach nick $NicksInChat($chatid) {
268		set clr [get_color $nick]
269                $wn tag configure NICK-$nick \
270		    -foreground [expr {$options(use_colored_nicks) ? $clr : ""}]
271                $wn tag configure NICKMSG-$nick \
272		    -foreground [expr {$options(use_colored_nicks) ? $clr : ""}]
273                $wn tag configure MSG-$nick \
274		    -foreground [expr {$options(use_colored_messages) ? $clr : ""}]
275            }
276        }
277    }
278}
279
280proc nickcolors::add_chat_win_popup_menu {m chatwin X Y x y} {
281    variable options
282
283    set tags [$chatwin tag names "@$x,$y"]
284    set nick ""
285    if {$options(use_colored_messages)} {
286	if {[set idx [lsearch -glob $tags MSG-*]] >= 0} {
287	    set nick [string range [lindex $tags $idx] 4 end]
288	}
289    }
290    if {$options(use_colored_nicks)} {
291	if {[set idx [lsearch -glob $tags NICK-*]] >= 0} {
292	    set nick [string range [lindex $tags $idx] 5 end]
293	}
294	if {[set idx [lsearch -glob $tags NICKMSG-*]] >= 0} {
295	    set nick [string range [lindex $tags $idx] 5 end]
296	}
297    }
298
299    if {$nick == ""} return
300
301    $m add command -label [::msgcat::mc "Edit nick color..."] \
302        -command [list [namespace current]::edit_nick_color $chatwin $nick]
303}
304
305proc nickcolors::add_groupchat_user_menu_items {m xlib jid} {
306    variable options
307
308    if {$options(use_colored_roster_nicks)} {
309	set chatid [chat::chatid $xlib [::xmpp::jid::stripResource $jid]]
310	set chatwin [chat::chat_win $chatid]
311	set nick [chat::get_nick $xlib $jid groupchat]
312	$m add command -label [::msgcat::mc "Edit nick color..."] \
313	    -command [list [namespace current]::edit_nick_color $chatwin $nick]
314    }
315}
316
317proc nickcolors::edit_nick_color {chatwin nick} {
318    variable NickColors
319
320    set new [tk_chooseColor -initialcolor [get_color $nick] \
321		 -title [::msgcat::mc "Edit %s color" $nick]]
322    if {$new == ""} return
323
324    if {$new != [get_color $nick]} {
325	set NickColors($nick) $new
326	change_options
327	[namespace current]::save_nick_colors
328    }
329}
330
331