[Tkabber-dev] r1915 - in trunk/tkabber: . examples/configs ifacetk plugins/chat plugins/general plugins/roster

tkabber-svn at jabber.ru tkabber-svn at jabber.ru
Sat Feb 6 20:13:29 MSK 2010


Author: sergei
Date: 2010-02-06 20:13:28 +0300 (Sat, 06 Feb 2010)
New Revision: 1915

Added:
   trunk/tkabber/ifacetk/login.tcl
   trunk/tkabber/ifacetk/muc.tcl
   trunk/tkabber/ifacetk/roster.tcl
   trunk/tkabber/plugins/chat/muc_commands.tcl
Removed:
   trunk/tkabber/ifacetk/ilogin.tcl
   trunk/tkabber/ifacetk/iroster.tcl
   trunk/tkabber/joingrdialog.tcl
Modified:
   trunk/tkabber/ChangeLog
   trunk/tkabber/chats.tcl
   trunk/tkabber/examples/configs/badlop-config.tcl
   trunk/tkabber/examples/configs/mtr-config.tcl
   trunk/tkabber/ifacetk/iface.tcl
   trunk/tkabber/login.tcl
   trunk/tkabber/muc.tcl
   trunk/tkabber/plugins/chat/histool.tcl
   trunk/tkabber/plugins/chat/irc_commands.tcl
   trunk/tkabber/plugins/chat/logger.tcl
   trunk/tkabber/plugins/general/remote.tcl
   trunk/tkabber/plugins/roster/conferences.tcl
   trunk/tkabber/tkabber.tcl
   trunk/tkabber/utils.tcl
Log:
	* login.tcl: Removed assigning global gr_nick, gr_server and
	  gra_server variables. They don't make sense in multiuser
	  environment.

	* utils.tcl, plugins/chat/histool.tcl, plugins/chat/irc_commands.tcl,
	  plugins/chat/logger.tcl, plugins/roster/conferences.tcl: Rewtitten
	  get_group_nick procedure not to use fallback which was often the
	  removed global variable gr_nick.

	* muc.tcl, ifacetk/muc.tcl: Moved Tk specific MUC code to a separate
	  file.

	* muc.tcl, plugins/chat/muc_commands.tcl: Moved MUC commands to a
	  separate plugin.

	* joingrdialog.tcl: Removed because all its code has been moved to
	  ifacetk/muc.tcl.

	* tkabber.tcl: Don't source joingrdialog.tcl.

	* plugins/general/remote.tcl: Don't use variable from ::chat namespace
	  directly.

	* ifacetk/iface.tcl: Source ifacetk/muc.tcl file. Added a check for
	  window existence before setting its title because of after idle call.

	* ifacetk/ilogin.tcl, ifacetk/iroster.tcl: renamed to ifacetk/login.tcl
	  and ifacetk/roster.tcl.

	* chats.tcl: Reset MUC tokens on disconnect instead of leaving groups.

	* examples/configs/badlop-config.tcl, examples/configs/mtr-config.tcl:
	  Replaced join_group calls by muc::join_group.


Modified: trunk/tkabber/ChangeLog
===================================================================
--- trunk/tkabber/ChangeLog	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/ChangeLog	2010-02-06 17:13:28 UTC (rev 1915)
@@ -1,3 +1,39 @@
+2010-02-06  Sergei Golovan  <sgolovan at nes.ru>
+
+	* login.tcl: Removed assigning global gr_nick, gr_server and
+	  gra_server variables. They don't make sense in multiuser
+	  environment.
+
+	* utils.tcl, plugins/chat/histool.tcl, plugins/chat/irc_commands.tcl,
+	  plugins/chat/logger.tcl, plugins/roster/conferences.tcl: Rewtitten
+	  get_group_nick procedure not to use fallback which was often the
+	  removed global variable gr_nick.
+
+	* muc.tcl, ifacetk/muc.tcl: Moved Tk specific MUC code to a separate
+	  file.
+
+	* muc.tcl, plugins/chat/muc_commands.tcl: Moved MUC commands to a
+	  separate plugin.
+
+	* joingrdialog.tcl: Removed because all its code has been moved to
+	  ifacetk/muc.tcl.
+
+	* tkabber.tcl: Don't source joingrdialog.tcl.
+
+	* plugins/general/remote.tcl: Don't use variable from ::chat namespace
+	  directly.
+
+	* ifacetk/iface.tcl: Source ifacetk/muc.tcl file. Added a check for
+	  window existence before setting its title because of after idle call.
+
+	* ifacetk/ilogin.tcl, ifacetk/iroster.tcl: renamed to ifacetk/login.tcl
+	  and ifacetk/roster.tcl.
+
+	* chats.tcl: Reset MUC tokens on disconnect instead of leaving groups.
+
+	* examples/configs/badlop-config.tcl, examples/configs/mtr-config.tcl:
+	  Replaced join_group calls by muc::join_group.
+
 2010-02-05  Sergei Golovan  <sgolovan at nes.ru>
 
 	* si.tcl: Moved customization variables definitions from finload_hook

Modified: trunk/tkabber/chats.tcl
===================================================================
--- trunk/tkabber/chats.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/chats.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -327,7 +327,6 @@
 	    } else {
 		set password ""
 	    }
-	    muc::leave_group $chatid ""
 	    muc::join_group $xlib \
 			    [get_jid $chatid] \
 			    [get_our_groupchat_nick $chatid] \
@@ -347,7 +346,13 @@
 
 	set jid [get_jid $chatid]
 
-	muc::leave_group $chatid $chats(exit_status,$chatid)
+	if {[is_groupchat $chatid]} {
+	    muc::reset_group $chatid
+	    client:presence $xlib $jid unavailable "" {}
+	    foreach jid [get_jids_of_user $xlib $jid] {
+		client:presence $xlib $jid unavailable "" {}
+	    }
+	}
 	add_message $chatid $jid error [::msgcat::mc "Disconnected"] {}
 
 	set cw [winid $chatid]

Modified: trunk/tkabber/examples/configs/badlop-config.tcl
===================================================================
--- trunk/tkabber/examples/configs/badlop-config.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/examples/configs/badlop-config.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -123,9 +123,9 @@
 	# show SSL warnings
 #	set ::tls_warnings 0
 
-# Join groupchats automatically at the start (other available option: -nick <yournick>)
+# Join groupchats automatically at the start
 #proc connload {connid} {
-#	join_group $connid jabber at conference.jabber.org }
+#	muc::join_group $connid jabber at conference.jabber.org badlop }
 #hook::add connected_hook connload 1000
 
 

Modified: trunk/tkabber/examples/configs/mtr-config.tcl
===================================================================
--- trunk/tkabber/examples/configs/mtr-config.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/examples/configs/mtr-config.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -395,9 +395,9 @@
     return
 
     foreach g [list xmpp] {
-        join_group $connid ${g}@ietf.xmpp.org -nick mrose
+        muc::join_group $connid ${g}@ietf.xmpp.org mrose
     }
-    join_group $connid wgchairs at conference.psg.com -nick mrose
+    muc::join_group $connid wgchairs at conference.psg.com mrose
 }
 
 hook::add connected_hook config_connload 1000

Modified: trunk/tkabber/ifacetk/iface.tcl
===================================================================
--- trunk/tkabber/ifacetk/iface.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/ifacetk/iface.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -109,9 +109,10 @@
 	source [file join $dir $file]
     }
 
-    isource ilogin.tcl
-    isource iroster.tcl
+    isource login.tcl
+    isource roster.tcl
     isource systray.tcl
+    isource muc.tcl
 
     variable after_focused_id ""
 
@@ -1204,6 +1205,8 @@
     variable title_after_id
     variable title_after_value
 
+    if {![winfo exists $w]} return
+
     wm title $w $title_after_value($w)
     wm iconname $w $title_after_value($w)
     unset title_after_id($w)

Deleted: trunk/tkabber/ifacetk/ilogin.tcl
===================================================================
--- trunk/tkabber/ifacetk/ilogin.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/ifacetk/ilogin.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -1,463 +0,0 @@
-# $Id$
-
-proc update_login_entries {l {i 0}} {
-    global ltmp
-
-    if {$i} {
-        array set ltmp [array get ::loginconf$i]
-	array set loginconf [array get ltmp]
-    }
-    foreach ent {username server port password resource priority \
-            altserver proxyhost proxyport proxyusername proxypassword \
-            sslcertfile pollurl} {
-        if {[winfo exists $l.$ent]} {
-            catch { $l.$ent icursor end }
-        }
-    }
-    foreach {check enable disable} { \
-            usehttppoll {lpollurl pollurl usepollkeys} \
-			{dontusessl usecompress legacyssl encrypted \
-			 sslcertfile lsslcertfile bsslcertfile} \
-            usealtserver {altserver laltserver port lport} {} \
-	    usesasl {allowgoogletoken} {} \
-	    } {
-        if {![info exists ltmp($check)] || ![winfo exists $l.$check]} {
-            continue
-        }
-
-        if {$ltmp($check) && ![cequal [$l.$check cget -state] disabled]} {
-            set state1 normal
-            set state2 disabled
-        } else {
-            set state1 disabled
-            set state2 normal
-        }
-        foreach ent $enable {
-            if {[winfo exists $l.$ent]} {
-                $l.$ent configure -state $state1
-                if {[cequal [focus] $l.$ent] && [cequal $state1 "disabled"]} {
-                    focus [Widget::focusPrev $l.$ent]
-                }
-            }
-        }
-        foreach ent $disable {
-            if {[winfo exists $l.$ent]} {
-                $l.$ent configure -state $state2
-                if {[cequal [focus] $l.$ent] && [cequal $state2 "disabled"]} {
-                    focus [Widget::focusPrev $l.$ent]
-                }
-	    }
-	}
-    }
-    catch {
-	if {[cequal $ltmp(proxy) none]} {
-	    foreach ent {proxyhost proxyport proxyusername proxypassword \
-			 lproxyhost lproxyport lproxyusername lproxypassword} {
-		$l.$ent configure -state disabled
-                if {[cequal [focus] $l.$ent]} {
-                    focus [Widget::focusPrev $l.$ent]
-                }
-	    }
-	} else {
-	    foreach ent {proxyhost proxyport proxyusername proxypassword \
-			 lproxyhost lproxyport lproxyusername lproxypassword} {
-		$l.$ent configure -state normal
-	    }
-	}
-    }
-    catch {
-	if {![cequal [$l.dontusessl cget -state] disabled] && \
-		($ltmp(stream_options) == "ssl" || \
-		 $ltmp(stream_options) == "encrypted")} {
-	    $l.sslcertfile configure -state normal
-	    $l.lsslcertfile configure -state normal
-	    $l.bsslcertfile configure -state normal
-	} else {
-	    $l.sslcertfile configure -state disabled
-	    $l.lsslcertfile configure -state disabled
-	    $l.bsslcertfile configure -state disabled
-	    if {[cequal [focus] $l.sslcertfile] || \
-		    [cequal [focus] $l.bsslcertfile]} {
-		focus [Widget::focusPrev $l.sslcertfile]
-	    }
-	}
-    }
-}
-
-proc login_dialog {} {
-    global loginconf
-    global ltmp
-    global use_tls have_compress have_sasl have_http_poll have_proxy
-
-    if {[winfo exists .login]} {
-	focus -force .login
-	return
-    }
-
-    array set ltmp [array get loginconf]
-
-    Dialog .login -title [::msgcat::mc "Login"] \
-	-separator 1 -anchor e -default 0 -cancel 1
-
-    wm resizable .login 0 0
-
-    set l [.login getframe]
-
-    set n 1
-    while {[info exists ::loginconf$n]} {incr n}
-    incr n -1
-
-    if {$n} {
-	menubutton $l.profiles -text [::msgcat::mc "Profiles"] \
-	    -relief $::tk_relief -menu $l.profiles.menu
-	set m [menu $l.profiles.menu -tearoff 0]
-	for {set i 1} {$i <= $n} {incr i} {
-	    if {[info exists ::loginconf${i}(profile)]} {
-		set lab [set ::loginconf${i}(profile)]
-	    } else {
-		set lab "[::msgcat::mc Profile] $i"
-	    }
-	    if {$i <= 10} {
-		set j [expr {$i % 10}]
-		$m add command -label $lab -accelerator "$::tk_modify-$j" \
-		    -command [list [namespace current]::update_login_entries $l $i]
-		bind .login <Control-Key-$j> \
-		    [list [namespace current]::update_login_entries [double% $l] $i]
-	    } else {
-		$m add command -label $lab \
-		    -command [list [namespace current]::update_login_entries $l $i]
-	    }
-	}
-
-	grid $l.profiles -row 0 -column 0 -sticky e
-    }
-
-    set nb [NoteBook $l.nb]
-
-    set account_page [$nb insert end account_page -text [::msgcat::mc "Account"]]
-
-    label $l.lusername -text [::msgcat::mc "Username:"]
-    entry $l.username -textvariable ltmp(user)
-    label $l.lserver -text [::msgcat::mc "Server:"]
-    entry $l.server -textvariable ltmp(server)
-    label $l.lpassword -text [::msgcat::mc "Password:"]
-    entry $l.password -show * -textvariable ltmp(password)
-    label $l.lresource -text [::msgcat::mc "Resource:"]
-    entry $l.resource -textvariable ltmp(resource)
-    label $l.lpriority -text [::msgcat::mc "Priority:"]
-    Spinbox $l.priority -1000 1000 1 ltmp(priority)
-
-    grid $l.lusername -row 0 -column 0 -sticky e -in $account_page
-    grid $l.username  -row 0 -column 1 -sticky ew -in $account_page
-    grid $l.lserver   -row 0 -column 2 -sticky e -in $account_page
-    grid $l.server    -row 0 -column 3 -sticky ew -in $account_page
-    grid $l.lpassword -row 1 -column 0 -sticky e -in $account_page
-    grid $l.password  -row 1 -column 1 -sticky ew -in $account_page
-    grid $l.lresource -row 2 -column 0 -sticky e -in $account_page
-    grid $l.resource  -row 2 -column 1 -sticky ew -in $account_page
-    grid $l.lpriority -row 2 -column 2 -sticky e -in $account_page
-    grid $l.priority  -row 2 -column 3 -sticky ew -in $account_page
-
-    grid columnconfigure $account_page 1 -weight 3
-    grid columnconfigure $account_page 2 -weight 1
-    grid columnconfigure $account_page 3 -weight 3
-
-    set connection_page [$nb insert end connection_page -text [::msgcat::mc "Connection"]]
-
-    checkbutton $l.usealtserver -text [::msgcat::mc "Explicitly specify host and port to connect"] \
-	-variable ltmp(usealtserver) \
-	-command [list [namespace current]::update_login_entries $l]
-    label $l.laltserver -text [::msgcat::mc "Host:"]
-    entry $l.altserver -textvariable ltmp(altserver)
-    label $l.lport -text [::msgcat::mc "Port:"]
-    Spinbox $l.port 0 65535 1 ltmp(altport)
-
-    grid $l.usealtserver -row 0 -column 0 -sticky w -columnspan 4 -in $connection_page
-    grid $l.laltserver -row 1 -column 0 -sticky e -in $connection_page
-    grid $l.altserver -row 1 -column 1 -sticky ew -in $connection_page
-    grid $l.lport     -row 1 -column 2 -sticky e -in $connection_page
-    grid $l.port      -row 1 -column 3 -sticky we -in $connection_page
-
-    checkbutton $l.replace -text [::msgcat::mc "Replace opened connections"] \
-	-variable ltmp(replace_opened)
-    grid $l.replace   -row 3 -column 0 -sticky w -columnspan 3 -in $connection_page
-
-    grid columnconfigure $connection_page 1 -weight 6
-    grid columnconfigure $connection_page 2 -weight 1
-
-
-    set auth_page [$nb insert end auth_page -text [::msgcat::mc "Authentication"]]
-
-    checkbutton $l.allowauthplain \
-	-text [::msgcat::mc "Allow plaintext authentication mechanisms"] \
-	-variable ltmp(allowauthplain) \
-	-command [list [namespace current]::update_login_entries $l]
-
-    grid $l.allowauthplain -row 0 -column 0 -sticky w -in $auth_page
-
-    if {$have_sasl} {
-	checkbutton $l.usesasl \
-	    -text [::msgcat::mc "Use SASL authentication"] \
-	    -variable ltmp(usesasl) \
-	    -command [list [namespace current]::update_login_entries $l]
-
-	grid $l.usesasl -row 1 -column 0 -sticky w -in $auth_page
-
-	checkbutton $l.allowgoogletoken \
-	    -text [::msgcat::mc "Allow X-GOOGLE-TOKEN SASL mechanism"] \
-	    -variable ltmp(allowgoogletoken) \
-	    -command [list [namespace current]::update_login_entries $l]
-
-	grid $l.allowgoogletoken -row 2 -column 0 -sticky w -in $auth_page
-    }
-
-    grid columnconfigure $auth_page 0 -weight 1
-
-    if {$use_tls || $have_compress} {
-	if {$use_tls && $have_compress} {
-		set page_label [::msgcat::mc "SSL & Compression"]
-	} elseif {$have_compress} {
-	    set page_label [::msgcat::mc "Compression"]
-	} else {
-	    set page_label [::msgcat::mc "SSL"]
-	}
-
-	set ssl_page [$nb insert end ssl_page -text $page_label]
-
-	radiobutton $l.dontusessl -text [::msgcat::mc "Plaintext"] \
-	    -variable ltmp(stream_options) -value plaintext \
-	    -command [list [namespace current]::update_login_entries $l]
-	if {$have_compress} {
-	    radiobutton $l.usecompress -text [::msgcat::mc "Compression"] \
-		-variable ltmp(stream_options) -value compressed \
-		-command [list [namespace current]::update_login_entries $l]
-	}
-	if {$use_tls} {
-	    radiobutton $l.encrypted -text [::msgcat::mc "Encryption (STARTTLS)"] \
-		-variable ltmp(stream_options) -value encrypted \
-		-command [list [namespace current]::update_login_entries $l]
-	    radiobutton $l.legacyssl -text [::msgcat::mc "Encryption (legacy SSL)"] \
-		-variable ltmp(stream_options) -value ssl \
-		-command [list [namespace current]::update_login_entries $l]
-	    label $l.lsslcertfile -text [::msgcat::mc "SSL certificate:"]
-	    entry $l.sslcertfile -textvariable ltmp(sslcertfile)
-	    button $l.bsslcertfile -text [::msgcat::mc "Browse..."] \
-		-command [list eval set ltmp(sslcertfile) {[tk_getOpenFile]}]
-	}
-
-	set column 0
-	grid $l.dontusessl -row 0 -column $column -sticky w -in $ssl_page
-	
-	if {$have_compress} {
-	    grid $l.usecompress -row 0 -column [incr column] -sticky w -in $ssl_page
-	}
-	if {$use_tls} {
-	    grid $l.encrypted     -row 0 -column [incr column] -sticky w -in $ssl_page
-	    grid $l.legacyssl    -row 0 -column [incr column] -sticky w -in $ssl_page
-
-	    grid $l.lsslcertfile -row 1 -column 0 -sticky e -in $ssl_page
-	    grid $l.sslcertfile  -row 1 -column 1 -sticky ew -columnspan 2 -in $ssl_page
-	    grid $l.bsslcertfile -row 1 -column 3 -sticky w -in $ssl_page
-	}
-
-	grid columnconfigure $ssl_page 1 -weight 1
-	grid columnconfigure $ssl_page 2 -weight 1
-    }
-
-    if {$have_http_poll} {
-	set httppoll_page [$nb insert end httpoll_page -text [::msgcat::mc "HTTP Poll"]]
-
-	checkbutton $l.usehttppoll -text [::msgcat::mc "Connect via HTTP polling"] \
-	    -variable ltmp(usehttppoll) \
-	    -command [list [namespace current]::update_login_entries $l]
-	label $l.lpollurl -text [::msgcat::mc "URL to poll:"]
-	entry $l.pollurl -textvariable ltmp(pollurl)
-	checkbutton $l.usepollkeys -text [::msgcat::mc "Use client security keys"] \
-	    -state disabled \
-	    -variable ltmp(usepollkeys) \
-	    -command [list [namespace current]::update_login_entries $l]
-    
-	grid $l.usehttppoll -row 0 -column 0 -sticky w -columnspan 3 -in $httppoll_page
-	grid $l.lpollurl -row 1 -column 0 -sticky e -in $httppoll_page
-	grid $l.pollurl -row 1 -column 1 -sticky ew -in $httppoll_page
-	grid $l.usepollkeys -row 2 -column 0 -sticky w -columnspan 3 -in $httppoll_page
-
-	grid columnconfigure $httppoll_page 1 -weight 1
-    }
-    
-    if {0 && $have_proxy} {
-	set proxy_page [$nb insert end proxy_page -text [::msgcat::mc "Proxy"]]
-
-	label $l.lproxy -text [::msgcat::mc "Proxy type:"]
-	grid $l.lproxy -row 0 -column 0 -sticky e -in $proxy_page
-	frame $l.proxy
-	grid $l.proxy -row 0 -column 1 -columnspan 3 -sticky w -in $proxy_page
-
-	set col 0
-
-	radiobutton $l.proxy.none -text [::msgcat::mc "None"] \
-		    -variable ltmp(proxy) -value none \
-		    -command [list [namespace current]::update_login_entries $l]
-	grid $l.proxy.none -row 0 -column [incr col] -sticky w
-
-	if {![catch {package present pconnect::https}]} {
-	    radiobutton $l.proxy.https -text [::msgcat::mc "HTTPS"] \
-			-variable ltmp(proxy) -value https \
-			-command [list [namespace current]::update_login_entries $l]
-	    grid $l.proxy.https -row 0 -column [incr col] -sticky w
-	}
-	if {![catch {package present pconnect::socks4}]} {
-	    radiobutton $l.proxy.socks4 -text [::msgcat::mc "SOCKS4a"] \
-			-variable ltmp(proxy) -value socks4 \
-			-command [list [namespace current]::update_login_entries $l]
-	    grid $l.proxy.socks4 -row 0 -column [incr col] -sticky w
-	}
-	if {![catch {package present pconnect::socks5}]} {
-	    radiobutton $l.proxy.socks5 -text [::msgcat::mc "SOCKS5"] \
-			-variable ltmp(proxy) -value socks5 \
-			-command [list [namespace current]::update_login_entries $l]
-	    grid $l.proxy.socks5 -row 0 -column [incr col] -sticky w
-	}
-
-	label $l.lproxyhost -text [::msgcat::mc "Proxy server:"]
-	entry $l.proxyhost -textvariable ltmp(proxyhost)
-	label $l.lproxyport -text [::msgcat::mc "Proxy port:"]
-	Spinbox $l.proxyport 0 65535 1 ltmp(proxyport)
-
-	grid $l.lproxyhost     -row 1 -column 0 -sticky e -in $proxy_page
-	grid $l.proxyhost      -row 1 -column 1 -sticky ew -in $proxy_page
-	grid $l.lproxyport -row 1 -column 2 -sticky e -in $proxy_page
-	grid $l.proxyport  -row 1 -column 3 -sticky ew -in $proxy_page
-
-	label $l.lproxyusername -text [::msgcat::mc "Proxy username:"]
-	ecursor_entry [entry $l.proxyusername -textvariable ltmp(proxyusername)]
-	label $l.lproxypassword -text [::msgcat::mc "Proxy password:"]
-	ecursor_entry [entry $l.proxypassword -show * -textvariable ltmp(proxypassword)]
-
-	grid $l.lproxyusername    -row 2 -column 0 -sticky e -in $proxy_page
-	grid $l.proxyusername     -row 2 -column 1 -sticky ew -in $proxy_page
-	grid $l.lproxypassword -row 2 -column 2 -sticky e -in $proxy_page
-	grid $l.proxypassword  -row 2 -column 3 -sticky ew -in $proxy_page
-
-	grid columnconfigure $proxy_page 1 -weight 3
-	grid columnconfigure $proxy_page 2 -weight 1
-	grid columnconfigure $proxy_page 3 -weight 3
-    }
-
-
-    $nb compute_size
-    $nb raise account_page
-    bind .login <Control-Prior> [list [namespace current]::tab_move [double% $nb] -1]
-    bind .login <Control-Next> [list [namespace current]::tab_move [double% $nb] 1]
-    grid $nb -row 1 -column 0
-
-    .login add -text [::msgcat::mc "Log in"] -command {
-	array set loginconf [array get ltmp]
-	destroy .login
-	if {$loginconf(replace_opened)} {
-	    logout
-	}
-	update 
-	login [array get loginconf]
-    }
-    .login add -text [::msgcat::mc "Cancel"] -command {destroy .login}
-
-    update_login_entries $l
-
-    if {[cequal $ltmp(user) ""]} {
-	.login draw $l.username
-    } elseif {[cequal $ltmp(password) ""]} {
-	.login draw $l.password
-    } else {
-	.login draw $l.resource
-    }
-}
-
-hook::add finload_hook {
-    if {(![info exist ::autologin]) || ($::autologin == 0)} {
-        ifacetk::login_dialog
-    } elseif {$::autologin > 0} {
-	logout
-	update
-	login [array get ::loginconf]
-    }
-} 9999
-
-proc logout_dialog {} {
-    global logout_conn
-    
-    set w .logout
-    if {[winfo exists $w]} {
-	destroy $w
-    }
-
-    switch -- [llength [connections]] {
-	0 -
-	1 {
-	    logout
-	    return
-	}
-    }
-
-    set lnames {}
-    foreach xlib [connections] {
-	lappend lnames $xlib [connection_jid $xlib]
-    }
-
-    if {[CbDialog $w [::msgcat::mc "Logout"] \
-	    [list [::msgcat::mc "Log out"] [list $w enddialog 0] \
-		  [::msgcat::mc "Cancel"] [list $w enddialog 1]] \
-	    logout_conn $lnames {} -modal local] != 0} {
-	return
-    }
-
-    foreach xlib [array names logout_conn] {
-	if {[lcontain [connections] $xlib] && $logout_conn($xlib)} {
-	    logout $xlib
-	}
-    }
-}
-
-# TODO
-proc change_password_dialog {} {
-    global oldpassword newpassword password
-
-    set oldpassword ""
-    set newpassword ""
-    set password ""
-
-    if {[winfo exists .passwordchange]} {
-	destroy .passwordchange
-    }
-    
-    Dialog .passwordchange -title [::msgcat::mc "Change password"] \
-	-separator 1 -anchor e -default 0 -cancel 1
-
-    .passwordchange add -text [::msgcat::mc "OK"] -command {
-	destroy .passwordchange
-	send_change_password
-    }
-    .passwordchange add -text [::msgcat::mc "Cancel"] -command [list destroy .passwordchange]
-
-
-    set p [.passwordchange getframe]
-    
-    label $p.loldpass -text [::msgcat::mc "Old password:"]
-    ecursor_entry [entry $p.oldpass -show * -textvariable oldpassword]
-    label $p.lnewpass -text [::msgcat::mc "New password:"]
-    ecursor_entry [entry $p.newpass -show * -textvariable newpassword]
-    label $p.lpassword -text [::msgcat::mc "Repeat new password:"]
-    ecursor_entry [entry $p.password -show * -textvariable password]
-
-    grid $p.loldpass  -row 0 -column 0 -sticky e
-    grid $p.oldpass   -row 0 -column 1 -sticky ew
-    grid $p.lnewpass  -row 1 -column 0 -sticky e
-    grid $p.newpass   -row 1 -column 1 -sticky ew
-    grid $p.lpassword -row 2 -column 0 -sticky e
-    grid $p.password  -row 2 -column 1 -sticky ew
-
-    focus $p.oldpass
-    .passwordchange draw
-
-}
-

Deleted: trunk/tkabber/ifacetk/iroster.tcl
===================================================================
--- trunk/tkabber/ifacetk/iroster.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/ifacetk/iroster.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -1,2225 +0,0 @@
-# $Id$
-
-Tree .faketree
-foreach {k v} [list background       White \
-		    foreground       Black] {
-    if {[string equal [set t$k [option get .faketree $k Tree]] ""]} {
-	set t$k $v
-    }
-}
-destroy .faketree
-
-Button .fakebutton
-foreach {k v} [list background       Gray \
-		    activeBackground LightGray] {
-    if {[string equal [set b$k [option get .fakebutton $k Button]] ""]} {
-	set b$k $v
-    }
-}
-destroy .fakebutton
-
-option add *Roster.cbackground            $tbackground       widgetDefault
-option add *Roster.groupindent            22                 widgetDefault
-option add *Roster.jidindent              24                 widgetDefault
-option add *Roster.jidmultindent          40                 widgetDefault
-option add *Roster.subjidindent           34                 widgetDefault
-option add *Roster.groupiconindent         2                 widgetDefault
-option add *Roster.subgroupiconindent     22                 widgetDefault
-option add *Roster.iconindent              3                 widgetDefault
-option add *Roster.subitemtype             1                 widgetDefault
-option add *Roster.subiconindent          13                 widgetDefault
-option add *Roster.textuppad               1                 widgetDefault
-option add *Roster.textdownpad             1                 widgetDefault
-option add *Roster.linepad                 2                 widgetDefault
-option add *Roster.foreground             $tforeground       widgetDefault
-option add *Roster.jidfill                $tbackground       widgetDefault
-option add *Roster.jidhlfill              $bactiveBackground widgetDefault
-option add *Roster.jidborder              $tbackground       widgetDefault
-option add *Roster.metajidfill            $bbackground       widgetDefault
-option add *Roster.metajidhlfill          $bactiveBackground widgetDefault
-option add *Roster.metajidborder          $bbackground       widgetDefault
-option add *Roster.groupfill              $bbackground       widgetDefault
-option add *Roster.groupcfill             $bbackground       widgetDefault
-option add *Roster.grouphlfill            $bactiveBackground widgetDefault
-option add *Roster.groupborder            $tforeground       widgetDefault
-option add *Roster.connectionfill         $tbackground       widgetDefault
-option add *Roster.connectioncfill        $tbackground       widgetDefault
-option add *Roster.connectionhlfill       $bactiveBackground widgetDefault
-option add *Roster.connectionborder       $tforeground       widgetDefault
-option add *Roster.unsubscribedforeground #663333            widgetDefault
-option add *Roster.unavailableforeground  #666666            widgetDefault
-option add *Roster.dndforeground          #666633            widgetDefault
-option add *Roster.xaforeground           #004d80            widgetDefault
-option add *Roster.awayforeground         #004d80            widgetDefault
-option add *Roster.availableforeground    #0066cc            widgetDefault
-option add *Roster.chatforeground         #0099cc            widgetDefault
-
-unset tbackground tforeground bbackground bactiveBackground
-
-namespace eval roster {
-    custom::defgroup Roster [::msgcat::mc "Roster options."] -group Tkabber
-    custom::defvar show_only_online 0 \
-	[::msgcat::mc "Show only online users in roster."] \
-	-type boolean -group Roster \
-	-command [namespace current]::redraw_after_idle
-    custom::defvar show_transport_icons 0 \
-	[::msgcat::mc "Show native icons for transports/services in roster."] \
-	-type boolean -group Roster \
-	-command [namespace current]::redraw_after_idle
-    custom::defvar show_transport_user_icons 0 \
-	[::msgcat::mc "Show native icons for contacts, connected to transports/services in roster."] \
-	-type boolean -group Roster \
-	-command [namespace current]::redraw_after_idle
-    custom::defvar options(nested) 0 \
-	[::msgcat::mc "Enable nested roster groups."] \
-	-type boolean -group Roster \
-	-command [namespace current]::redraw_after_idle
-    custom::defvar options(nested_delimiter) "::" \
-	[::msgcat::mc "Default nested roster group delimiter."] \
-	-type string -group Roster \
-	-command [namespace current]::redraw_after_idle
-    custom::defvar options(show_own_resources) 0 \
-	[::msgcat::mc "Show my own resources in the roster."] \
-	-type boolean -group Roster \
-	-command [namespace current]::redraw_after_idle
-    custom::defvar options(chats_group) 0 \
-	[::msgcat::mc "Add chats group in roster."] \
-	-type boolean -group Roster \
-	-command [namespace current]::redraw_after_idle
-    custom::defvar options(use_filter) 0 \
-	[::msgcat::mc "Use roster filter."] \
-	-type boolean -group Roster \
-	-command [namespace current]::pack_filter_entry
-    custom::defvar options(match_jids) 0 \
-	[::msgcat::mc "Match contact JIDs in addition to nicknames in roster filter."] \
-	-type boolean -group Roster \
-	-command [namespace current]::redraw_after_idle
-    custom::defvar options(enable_metacontacts) 1 \
-	[::msgcat::mc "Enable grouping contacts into a single metacontact in roster."] \
-	-type boolean -group Roster \
-	-command [namespace current]::redraw_after_idle
-    custom::defvar options(enable_metacontact_labels) 1 \
-	[::msgcat::mc "Always use main JID label for metacontact."] \
-	-type boolean -group Roster \
-	-command [namespace current]::redraw_after_idle
-    custom::defvar options(free_drop) 1 \
-	[::msgcat::mc "Roster item may be dropped not only over group name\
-but also over any item in group."] \
-	-type boolean -group Roster
-    custom::defvar options(show_subscription) 0 \
-	[::msgcat::mc "Show subscription type in roster item tooltips."] \
-	-type boolean -group Roster
-    custom::defvar options(show_conference_user_info) 0 \
-	[::msgcat::mc "Show detailed info on conference room members in roster item tooltips."] \
-	-type boolean -group Roster
-    custom::defvar options(chat_on_doubleclick) 1 \
-	[::msgcat::mc "If set then open chat window/tab when user doubleclicks roster item.\
-		       Otherwise open normal message window."] \
-	-type boolean -group Roster
-
-#   {jid1 {group1 1 group2 0} jid2 {group3 0 group4 0}}
-    custom::defvar collapsed_group_list {} \
-	[::msgcat::mc "Stored collapsed roster groups."] \
-	-type string -group Hidden
-#   {jid1 {group1 1 group2 0} jid2 {group3 0 group4 0}}
-    custom::defvar show_offline_group_list {} \
-	[::msgcat::mc "Stored show offline roster groups."] \
-	-type string -group Hidden
-    custom::defvar options(filter) "" \
-	[::msgcat::mc "Roster filter."] \
-	-type string -group Hidden \
-	-command [namespace current]::redraw_after_idle
-
-    variable menu_item_idx 0
-
-    variable undef_group_name $::roster::undef_group_name
-    variable chats_group_name $::roster::chats_group_name
-    variable own_resources_group_name $roster::own_resources_group_name
-}
-
-proc roster::get_group_lists {xlib} {
-    variable collapsed_group_list
-    variable show_offline_group_list
-    variable options
-    variable roster
-
-    set jid [::xmpp::jid::normalize [connection_bare_jid $xlib]]
-    array set c $collapsed_group_list
-    array set s $show_offline_group_list
-
-    if {[info exists c($jid)]} {
-	foreach {group val} $c($jid) {
-	    if {$options(nested)} {
-		set gid [list $xlib [msplit $group $options(nested_delimiter)]]
-	    } else {
-		set gid [list $xlib [list $group]]
-	    }
-	    set roster(collapsed,$gid) $val
-	}
-    }
-
-    if {[info exists s($jid)]} {
-	foreach {group val} $s($jid) {
-	    if {$options(nested)} {
-		set gid [list $xlib [msplit $group $options(nested_delimiter)]]
-	    } else {
-		set gid [list $xlib [list $group]]
-	    }
-	    set roster(show_offline,$gid) $val
-	}
-    }
-}
-
-hook::add connected_hook [namespace current]::roster::get_group_lists 1
-
-proc roster::set_group_lists {xlib} {
-    variable collapsed_group_list
-    variable show_offline_group_list
-    variable options
-    variable roster
-    variable undef_group_name
-    variable chats_group_name
-
-    set jid [::xmpp::jid::normalize [connection_bare_jid $xlib]]
-    array set c $collapsed_group_list
-    array set s $show_offline_group_list
-
-    set groups [roster::get_groups $xlib -raw 1]
-    lappend groups $undef_group_name $chats_group_name
-
-    if {$options(nested)} {
-	set tmp {}
-	foreach group $groups {
-	    set tmp1 [msplit $group $options(nested_delimiter)]
-	    for {set i 0} {$i < [llength $tmp1]} {incr i} {
-		lappend tmp [lrange $tmp1 0 $i]
-	    }
-	}
-	set groups [lsort -unique $tmp]
-    }
-
-    set c($jid) {}
-    set s($jid) {}
-    foreach group $groups {
-	if {$options(nested)} {
-	    set grname [join $group $options(nested_delimiter)]
-	    set gid [list $xlib $group]
-	} else {
-	    set grname $group
-	    set gid [list $xlib [list $group]]
-	}
-	if {[info exists roster(collapsed,$gid)] && $roster(collapsed,$gid)} {
-	    lappend c($jid) $grname $roster(collapsed,$gid)
-	}
-	if {[info exists roster(show_offline,$gid)] && $roster(show_offline,$gid)} {
-	    lappend s($jid) $grname $roster(show_offline,$gid)
-	}
-    }
-
-    if {[llength $c($jid)] == 0} {
-	unset c($jid)
-    }
-    if {[llength $s($jid)] == 0} {
-	unset s($jid)
-    }
-
-    set collapsed_group_list [array get c]
-    set show_offline_group_list [array get s]
-}
-
-hook::add disconnected_hook [namespace current]::roster::set_group_lists 40
-
-proc roster::process_item {xlib jid name groups subsc ask} {
-    after cancel [namespace parent]::update_chat_titles
-    after idle [namespace parent]::update_chat_titles
-}
-
-hook::add roster_push_hook [namespace current]::roster::process_item 90
-
-proc roster::create_filter_entry {} {
-    entry .roster_filter -textvariable [namespace current]::options(filter) \
-			 -width 1
-    bind .roster_filter <Escape> \
-	 [list set [namespace current]::options(filter) ""]
-    pack_filter_entry
-}
-
-hook::add finload_hook [namespace current]::roster::create_filter_entry
-
-proc roster::pack_filter_entry {args} {
-    global usetabbar
-    variable options
-
-    if {$options(use_filter)} {
-	if {$usetabbar} {
-	    pack .roster_filter -before .roster \
-				-anchor w \
-				-side bottom \
-				-fill x \
-				-expand no \
-				-in $::ifacetk::rw
-	} else {
-	    grid .roster_filter -row 2 \
-				-column 1 \
-				-sticky we \
-				-in [$::ifacetk::mf getframe]
-	}
-	focus .roster_filter
-    } else {
-	if {$usetabbar} {
-	    pack forget .roster_filter
-	} else {
-	    grid forget .roster_filter
-	}
-    }
-    redraw_after_idle
-}
-
-proc roster::filter_match {xlib jid} {
-    variable options
-
-    if {$options(use_filter) && $options(filter) != ""} {
-	if {[string first [string tolower $options(filter)] \
-			  [string tolower [::roster::get_label $xlib $jid]]] < 0} {
-	    if {!$options(match_jids) || \
-		    [string first [string tolower $options(filter)] \
-				  [string tolower $jid]] < 0} {
-		return 0
-	    }
-	}
-    }
-
-    return 1
-}
-
-proc roster::redraw {} {
-    variable roster
-    variable options
-    variable config
-    variable show_only_online
-    variable show_transport_user_icons
-    variable undef_group_name
-    variable chats_group_name
-    variable own_resources_group_name
-
-    clear .roster 0
-
-    set connections [connections]
-    switch -- [llength $connections] {
-	0 {
-	    update_scrollregion .roster
-	    return
-	}
-	1 {
-	    set draw_connection 0
-	}
-	default {
-	    set draw_connection 1
-	}
-    }
-
-    foreach xlib $connections {
-
-	# TODO: Move to a plugin
-	# Preparing metacontacts.
-	array unset metajids
-	array unset metagroups
-	array unset groups_from_meta
-	set metacontacted {}
-	if {$options(enable_metacontacts) && \
-		[llength [info procs ::plugins::metacontacts::*]] > 0} {
-	    foreach tag [::plugins::metacontacts::get_all_tags $xlib] {
-		set jids [::plugins::metacontacts::get_jids $xlib $tag]
-		set metagroups($tag) {}
-
-		set cgroups {}
-		foreach jid $jids {
-		    # Skip JID if it doesn't match filter pattern or if it
-		    # isn't a JID of use
-		    set isuser [::roster::itemconfig $xlib $jid -isuser]
-		    if {$isuser == ""} {
-			set isuser 1
-		    }
-		    if {$isuser} {
-			set cgroups [concat $cgroups [::roster::itemconfig $xlib $jid -group]]
-		    }
-		    if {$isuser && [filter_match $xlib $jid]} {
-			lappend metagroups($tag) $jid
-			lappend metacontacted $jid
-		    }
-		}
-
-		if {[llength $metagroups($tag)] == 0} continue
-
-		set mjid [lindex $metagroups($tag) 0]
-		set pjid [get_jid_of_user $xlib $mjid]
-		set priority [get_jid_presence_info priority $xlib $pjid]
-		set status [get_jid_status $xlib $pjid]
-
-		foreach rjid [lrange $metagroups($tag) 1 end] {
-		    set jid  [get_jid_of_user $xlib $rjid]
-		    set stat [get_jid_status $xlib $jid]
-
-		    if {[string equal $stat unavailable]} continue
-
-		    set prio [get_jid_presence_info priority $xlib $jid]
-		    if {$prio == ""} {
-			set prio 0
-		    }
-
-		    if {$prio > $priority || \
-			    ($prio == $priority && [compare_status $stat $status] > 0)} {
-			set mjid $rjid
-			set pjid $jid
-			set status $stat
-			set priority $prio
-		    }
-		}
-
-		lappend metajids($mjid) $tag
-		if {![info exists groups_from_meta($mjid)]} {
-		    set groups_from_meta($mjid) $cgroups
-		} else {
-		    set groups_from_meta($mjid) [concat $groups_from_meta($mjid) $cgroups]
-		}
-	    }
-
-	    foreach jid [array names metajids] {
-		set metajids($jid) [lsort -unique $metajids($jid)]
-	    }
-	    set metacontacted [lsort -unique $metacontacted]
-	}
-
-	# Draw connection group
-	if {$draw_connection} {
-	    if {![info exists roster(collapsed,[list xlib $xlib])]} {
-		set roster(collapsed,[list xlib $xlib]) 0
-	    }
-	    addline .roster connection \
-			    [connection_jid $xlib] \
-			    [list xlib $xlib] \
-			    [list xlib $xlib] \
-			    {} 0
-
-	    if {$roster(collapsed,[list xlib $xlib])} {
-		continue
-	    }
-	}
-
-	# Don't draw roster if it doesn't contain items
-	if {[llength [::roster::get_jids $xlib]] == 0} {
-	    continue
-	}
-
-	# List of pairs {"group string for sorting", "group split on delimiter"}
-	set groups {}
-
-	# Array of lists of JIDs in group
-	array unset jidsingroup
-
-	# Array of lists of JIDs in group descendants
-	array unset jidsundergroup
-
-	# Array of lists of subgroups
-	array unset groupsundergroup
-
-	array unset jstat
-	array unset useronline
-
-	foreach jid [::roster::get_jids $xlib] {
-	    # Skip JID if it doesn't match filter pattern
-	    if {![filter_match $xlib $jid]} continue
-
-	    # TODO: Move to a plugin
-	    # Skip JID if it should be ignored because of metacontacts
-	    if {[lsearch -exact $metacontacted $jid] >= 0 && \
-		    ![info exists metajids($jid)]} continue
-
-	    # Add JID groups to a groups list
-	    set jid_groups [::roster::itemconfig $xlib $jid -group]
-
-	    if {[info exists groups_from_meta($jid)]} {
-		set jid_groups [concat $jid_groups $groups_from_meta($jid)]
-	    }
-
-	    if {[llength $jid_groups] > 0} {
-		foreach group $jid_groups {
-		    if {$options(nested)} {
-			set sgroup [msplit $group $options(nested_delimiter)]
-		    } else {
-			set sgroup [list $group]
-		    }
-
-		    # Use the fact that -dictionary sorting puts \u0000 before
-		    # any other character, so subgroups will be placed just
-		    # after their parent group
-		    lappend groups [list [join $sgroup "\u0000"] $sgroup]
-
-		    lappend jidsingroup($sgroup) $jid
-
-		    if {![info exists jidsundergroup($sgroup)]} {
-			set jidsundergroup($sgroup) {}
-		    }
-
-		    if {![info exists groupsundergroup($sgroup)]} {
-			set groupsundergroup($sgroup) {}
-		    }
-
-		    for {set i [expr {[llength $sgroup] - 2}]} {$i >= 0} {incr i -1} {
-			# Adding all parent groups to a group list
-			set sgr [lrange $sgroup 0 $i]
-
-			lappend groups [list [join $sgr "\u0000"] $sgr]
-
-			lappend jidsundergroup($sgr) $jid
-
-			lappend groupsundergroup($sgr) $sgroup
-
-			if {![info exists jidsingroup($sgr)]} {
-			    set jidsingroup($sgr) {}
-			}
-		    }
-		}
-	    } else {
-		set sgroup [list $undef_group_name]
-
-		lappend jidsingroup($sgroup) $jid
-
-		if {![info exists jidsundergroup($sgroup)]} {
-		    set jidsundergroup($sgroup) {}
-		}
-
-		set groupsundergroup($sgroup) {}
-	    }
-	}
-
-	set groups [lsort -unique -dictionary -index 0 $groups]
-
-	# FIXME: What to do with subgroups of $undef_group_name?
-	# Putting undefined group to the and of list
-	set ugroup [list $undef_group_name]
-	if {[info exists jidsingroup($ugroup)]} {
-	    lappend groups [list [join $ugroup "\u0000"] $ugroup]
-	}
-
-	# Putting active chats group to the beginning
-	if {$options(chats_group)} {
-	    set cgroup [list $chats_group_name]
-	    foreach chatid [chat::opened $xlib] {
-		set jid [chat::get_jid $chatid]
-		lappend jidsingroup($cgroup) $jid
-		if {[string equal [roster::itemconfig $xlib $jid -isuser] ""]} {
-		    roster::itemconfig $xlib $jid \
-			    -name [chat::get_nick $xlib $jid chat]
-		    roster::itemconfig $xlib $jid -subsc none
-		}
-	    }
-	    if {[info exists jidsingroup($cgroup)]} {
-		set groups [linsert $groups 0 [list [join $cgroup "\u0000"] $cgroup]]
-	    }
-	    set groupsundergroup($cgroup) {}
-	    set jidsundergroup($cgroup) {}
-	}
-
-	# Putting own resources group to the beginning
-	if {$options(show_own_resources)} {
-	    set cgroup [list $own_resources_group_name]
-	    set jid [::xmpp::jid::normalize [connection_bare_jid $xlib]]
-	    set jidsingroup($cgroup) [list $jid]
-	    set groups [linsert $groups 0 [list [join $cgroup "\u0000"] $cgroup]]
-	    roster::itemconfig $xlib $jid -subsc both
-	    set groupsundergroup($cgroup) {}
-	    set jidsundergroup($cgroup) {}
-	}
-
-	# Info on whether to show offline users in a group is needed for
-	# subgroups too, so an extra loop
-	foreach group $groups {
-	    set group [lindex $group 1]
-	    set gid [list $xlib $group]
-	    if {![info exists roster(show_offline,$gid)]} {
-		set roster(show_offline,$gid) 0
-	    }
-	}
-
-	# Drawing groups an JIDs in them
-	foreach group $groups {
-	    set group [lindex $group 1]
-	    set gid [list $xlib $group]
-
-	    set jidsingroup($group) [lsort -unique $jidsingroup($group)]
-	    set groupsundergroup($group) [lsort -unique $groupsundergroup($group)]
-
-	    if {![info exists roster(collapsed,$gid)]} {
-		set roster(collapsed,$gid) 0
-	    }
-
-	    # How to indent the group (also, the number of its ancestors)
-	    set indent [expr {[llength $group] - 1}]
-
-	    # Whether to draw group at all
-	    set collapse 0
-
-	    # Whether to show offline users in the group
-	    set show_offline_users 0
-
-	    # Whether to show the group ???
-	    set show_offline_group 0
-
-	    foreach undergroup $groupsundergroup($group) {
-		if {$roster(show_offline,[list $xlib $undergroup])} {
-		    set show_offline_group 1
-		    break
-		}
-	    }
-	    for {set i 0} {$i < $indent} {incr i} {
-		set sgr [list $xlib [lrange $group 0 $i]]
-		if {$roster(collapsed,$sgr)} {
-		    # Whether some ancestor is collapsed
-		    set collapse 1
-		    break
-		}
-		if {$roster(show_offline,$sgr)} {
-		    # Whether showing offline users is required for some
-		    # ancestor
-		    set show_offline_users 1
-		    set show_offline_group 1
-		}
-	    }
-
-	    # If some ancestor is collapsed don't draw the group
-	    if {$collapse} continue
-
-	    set group_label [lindex $group end]
-	    set online 0
-	    set users 0
-	    set not_users 0
-	    foreach jid [concat $jidsingroup($group) $jidsundergroup($group)] {
-		if {[::roster::itemconfig $xlib $jid -isuser]} {
-		    incr users
-		    if {![info exists jstat($jid)]} {
-			set jstat($jid) [get_user_status $xlib $jid]
-		    }
-		    if {$jstat($jid) != "unavailable"} {
-			incr online
-			set useronline($jid) 1
-		    } else {
-			set useronline($jid) 0
-		    }
-		} else {
-		    incr not_users
-		}
-	    }
-
-	    # Draw group label
-	    if {!$show_only_online || $show_offline_group || \
-		    $roster(show_offline,$gid) || \
-		    ($options(use_filter) && $options(filter) != "") || \
-		    $online + $not_users > 0} {
-		if {$users > 0} {
-		    append group_label " ($online/$users)"
-		}
-		addline .roster group $group_label $gid $gid {} $indent
-	    }
-
-	    # Draw group contents if it isn't collapsed
-	    if {!$roster(collapsed,$gid)} {
-		set jid_labels {}
-		foreach jid $jidsingroup($group) {
-		    lappend jid_labels [list $jid [::roster::get_label $xlib $jid]]
-		}
-		set jid_labels [lsort -index 1 -dictionary $jid_labels]
-
-		foreach jid_label $jid_labels {
-		    lassign $jid_label jid label
-
-		    if {$options(chats_group)} {
-			set chatid [chat::chatid $xlib $jid]
-			if {[info exists chat::chats(messages,$chatid)] && \
-				$chat::chats(messages,$chatid) > 0} {
-			    append label " ($chat::chats(messages,$chatid))"
-			}
-		    }
-
-		    set condition [expr {!$show_only_online || $show_offline_users || \
-					 $roster(show_offline,$gid) || \
-					 ($options(use_filter) && $options(filter) != "")}]
-		    set cjid [list $xlib $jid]
-		    if {$condition || ![info exists useronline($jid)] || $useronline($jid)} {
-
-			if {[info exists metajids($jid)]} {
-
-			    set jids {}
-			    foreach tag $metajids($jid) {
-				foreach subjid $metagroups($tag) {
-				    # Metacontact members are necessarily users, so don't
-				    # check for this
-				    if {![info exists jstat($subjid)]} {
-					set jstat($subjid) [get_user_status $xlib $subjid]
-				    }
-				    if {$jstat($subjid) != "unavailable"} {
-					set useronline($subjid) 1
-				    } else {
-					set useronline($subjid) 0
-				    }
-				    if {$condition || $useronline($subjid)} {
-					lappend jids $subjid
-				    }
-				}
-			    }
-			    set jids [lsort -unique $jids]
-			    set numjids [llength $jids]
-
-			    if {$options(enable_metacontact_labels)} {
-				set tag [lindex $metajids($jid) 0]
-				set mjid [lindex [::plugins::metacontacts::get_jids $xlib $tag] 0]
-				set label [::roster::get_label $xlib $mjid]
-			    }
-
-			    if {($options(enable_metacontact_labels) && $numjids > 0) || \
-				    $numjids > 1} {
-				# Draw as a metacontact
-				if {$config(subitemtype) & 1} {
-				    append label " ($numjids)"
-				}
-				addline .roster metajid $label $cjid $gid \
-					        [list $xlib $metajids($jid)] $indent $jids \
-					        [get_jid_icon $xlib $jid] \
-					        [get_jid_foreground $xlib $jid]
-
-				if {!$roster(metacollapsed,$gid,[list $xlib $metajids($jid)])} {
-				    set subjid_labels {}
-				    foreach subjid $jids {
-					lappend subjid_labels \
-					        [list $subjid [::roster::get_label $xlib $subjid]]
-				    }
-				    set subjid_labels [lsort -index 1 -dictionary $subjid_labels]
-				    foreach subjid_label $subjid_labels {
-					lassign $subjid_label subjid label
-					draw_jid $xlib $subjid $label $gid \
-					         [list $xlib $metajids($jid)] $indent jstat
-				    }
-				}
-			    } else {
-				# Draw as an ordinary contact using a hack with indent
-				# which allows to add metajid tag. The hack depends on
-				# metajid indent equals to group indent
-				draw_jid $xlib $jid $label $gid \
-					 [list $xlib $metajids($jid)] \
-					 [expr {$indent - 1}] jstat
-			    }
-			} else {
-			    draw_jid $xlib $jid $label $gid {} $indent jstat
-			}
-		    }
-		}
-	    }
-	}
-    }
-
-    update_scrollregion .roster
-}
-
-proc roster::draw_jid {xlib jid label gid metajids indent jstatVar} {
-    variable config
-    variable roster
-    variable show_transport_user_icons
-    upvar $jstatVar jstat
-
-    set cjid [list $xlib $jid]
-    lassign [::roster::get_category_and_subtype $xlib $jid] category type
-
-    set jids [get_jids_of_user $xlib $jid]
-    set numjids [llength $jids]
-
-    if {$category == "user" && $numjids > 1 && $config(subitemtype) > 0} {
-
-	if {$config(subitemtype) & 1} {
-	    append label " ($numjids)"
-	}
-	addline .roster jid $label $cjid $gid $metajids $indent $jids \
-			[get_jid_icon $xlib $jid] \
-			[get_jid_foreground $xlib $jid]
-
-	if {!$roster(jidcollapsed,$gid,$cjid)} {
-	    foreach subjid $jids {
-		set subjid_resource [::xmpp::jid::resource $subjid]
-		if {$subjid_resource != ""} {
-		    addline .roster jid2 \
-				    $subjid_resource [list $xlib $subjid] \
-				    $gid $metajids $indent \
-				    [list $subjid] \
-				    [get_jid_icon $xlib $subjid] \
-				    [get_jid_foreground $xlib $subjid]
-		}
-	    }
-	}
-    } elseif {$category == "user" && $numjids <= 1 && !$show_transport_user_icons} {
-
-	if {[info exists jstat($jid)]} {
-	    set status $jstat($jid)
-	} else {
-	    set status [get_user_status $xlib $jid]
-	}
-
-	set subsc [::roster::itemconfig $xlib $jid -subsc]
-	if {([string equal $subsc from] || [string equal $subsc none]) && \
-		$status == "unavailable"} {
-	    set status unsubscribed
-	}
-	addline .roster jid $label $cjid $gid $metajids $indent $jids \
-			roster/user/$status \
-			$config(${status}foreground)
-    } else {
-	if {$category == "conference" && ($config(subitemtype) & 1) && $numjids > 1} {
-	    append label " ([incr numjids -1])"
-	}
-	addline .roster jid $label $cjid $gid $metajids $indent $jids \
-			[get_jid_icon $xlib $jid] \
-			[get_jid_foreground $xlib $jid]
-    }
-}
-
-proc roster::redraw_after_idle {args} {
-    variable redraw_afterid
-
-    if {[info exists redraw_afterid]} return
-
-    if {![winfo exists .roster.canvas]} return
-
-    set redraw_afterid \
-	[after idle "[namespace current]::redraw
-		     unset [namespace current]::redraw_afterid"]
-}
-
-# Callback
-proc ::redraw_roster {args} {
-    ifacetk::roster::redraw_after_idle
-}
-
-proc roster::get_jids_of_user {xlib user} {
-    # TODO: metacontacts
-    return [::get_jids_of_user $xlib $user]
-}
-
-proc roster::get_foreground {status} {
-    variable config
-
-    return $config(${status}foreground)
-}
-
-proc roster::get_jid_foreground {xlib jid} {
-    variable config
-
-    lassign [::roster::get_category_and_subtype $xlib $jid] category type
-
-    switch -- $category {
-	"" -
-	user {
-	    return [get_user_foreground $xlib $jid]
-	}
-	conference {
-	    if {[get_jid_status $xlib $jid] != "unavailable"} {
-		return $config(availableforeground)
-	    } else {
-		return $config(unavailableforeground)
-	    }
-	}
-	server  -
-	gateway -
-	service {
-	    return [get_service_foreground $xlib $jid $type]
-	}
-	default {
-	    return $config(foreground)
-	}
-    }
-}
-
-proc roster::get_service_foreground {xlib service type} {
-    variable config
-
-    switch -- $type {
-	jud {
-	    return $config(foreground)
-	}
-    }
-
-    if {![string equal [::roster::itemconfig $xlib $service -subsc] none]} {
-	set status [get_user_status $xlib $service]
-	return $config(${status}foreground)
-    } else {
-	return $config(unsubscribedforeground)
-    }
-}
-
-proc roster::get_user_foreground {xlib user} {
-    variable config
-
-    set status [get_user_status $xlib $user]
-
-    set subsc [::roster::itemconfig $xlib $user -subsc]
-    if {[string equal $subsc ""]} {
-	set subsc [::roster::itemconfig $xlib \
-		       [::roster::find_jid $xlib $user] -subsc]
-    }
-
-    if {([string equal $subsc from] || [string equal $subsc none]) && \
-	    $status == "unavailable"} {
-	return $config(unsubscribedforeground)
-    } else {
-	return $config(${status}foreground)
-    }
-}
-
-proc roster::get_jid_icon {xlib jid {status ""}} {
-    lassign [::roster::get_category_and_subtype $xlib $jid] category type
-
-    switch -- $category {
-	"" -
-	user {
-	    if {$status == ""} {
-		set status [get_user_status $xlib $jid]
-	    }
-	    return [get_user_icon $xlib $jid $status]
-	}
-	conference {
-	    if {$status == ""} {
-		set status [get_jid_status $xlib $jid]
-	    }
-	    if {$status != "unavailable"} {
-		return roster/conference/available
-	    }
-	    return roster/conference/unavailable
-	}
-	server  -
-	gateway -
-	service {
-	    if {$status == ""} {
-		set status [get_user_status $xlib $jid]
-	    }
-	    return [get_service_icon $xlib $jid $type $status]
-	}
-	default {
-	    if {$status == ""} {
-		set status [get_jid_status $xlib $jid]
-	    }
-	    return [get_user_icon $xlib $jid $status]
-	}
-    }
-}
-
-proc roster::get_service_icon {xlib service type status} {
-    variable show_transport_icons
-
-    if {$show_transport_icons} {
-	switch -- $type {
-	    jud {return services/jud}
-	    sms {return services/sms}
-	}
-	if {![string equal [::roster::itemconfig $xlib $service -subsc] none]} {
-	    if {![catch { image type services/$type/$status }]} {
-		return services/$type/$status
-	    } else {
-		return roster/user/$status
-	    }
-	} else {
-	    return roster/user/unsubscribed
-	}
-    } else {
-	if {![string equal [::roster::itemconfig $xlib $service -subsc] none]} {
-	    return roster/user/$status
-	} else {
-	    return roster/user/unsubscribed
-	}
-    }
-}
-
-proc roster::get_user_icon {xlib user status} {
-    variable show_transport_user_icons
-
-    set subsc [::roster::itemconfig $xlib $user -subsc]
-    if {[string equal $subsc ""]} {
-	set subsc [::roster::itemconfig $xlib \
-	               [::roster::find_jid $xlib $user] -subsc]
-    }
-
-    if {!([string equal $subsc from] || [string equal $subsc none]) || \
-	    $status != "unavailable"} {
-	if {$show_transport_user_icons} {
-	    set service [::xmpp::jid::server $user]
-	    lassign [::roster::get_category_and_subtype $xlib $service] category type
-	    switch -glob -- $category/$type {
-		directory/* -
-		*/jud {
-		    return services/jud
-		}
-		*/sms {
-		    return services/sms
-		}
-	    }
-	    if {![catch { image type services/$type/$status }]} {
-		return services/$type/$status
-	    } else {
-		return roster/user/$status
-	    }
-	} else {
-	    return roster/user/$status
-	}
-    } else {
-	return roster/user/unsubscribed
-    }
-}
-
-proc roster::changeicon {w jid icon} {
-    set c $w.canvas
-    set tag [jid_to_tag $jid]
-
-    $c itemconfigure jid$tag&&icon -image $icon
-}
-
-proc roster::changeforeground {w jid color} {
-    set c $w.canvas
-    set tag [jid_to_tag $jid]
-
-    $c itemconfigure jid$tag&&text -fill $color
-}
-
-proc roster::create {w args} {
-    variable iroster
-    variable config
-
-    set c $w.canvas
-
-    set width 150
-    set height 100
-    set popupproc {}
-    set grouppopupproc {}
-    set singleclickproc {}
-    set doubleclickproc {}
-    foreach {attr val} $args {
-	switch -- $attr {
-	    -width {set width $val}
-	    -height {set height $val}
-	    -popup {set popupproc $val}
-	    -grouppopup {set grouppopupproc $val}
-	    -singleclick {set singleclickproc $val}
-	    -doubleclick {set doubleclickproc $val}
-	    -draginitcmd {set draginitcmd $val}
-	    -dropovercmd {set dropovercmd $val}
-	    -dropcmd {set dropcmd $val}
-	}
-    }
-
-    frame $w -relief sunken -borderwidth $::tk_borderwidth -class Roster
-    set sw [ScrolledWindow $w.sw]
-    pack $sw -fill both -expand yes
-
-    set config(groupindent)            [option get $w groupindent            Roster]
-    set config(jidindent)              [option get $w jidindent              Roster]
-    set config(jidmultindent)          [option get $w jidmultindent          Roster]
-    set config(jid2indent)             [option get $w subjidindent           Roster]
-    set config(groupiconindent)        [option get $w groupiconindent        Roster]
-    set config(subgroupiconindent)     [option get $w subgroupiconindent     Roster]
-    set config(iconindent)             [option get $w iconindent             Roster]
-    set config(subitemtype)            [option get $w subitemtype            Roster]
-    set config(subiconindent)          [option get $w subiconindent          Roster]
-    set config(textuppad)              [option get $w textuppad              Roster]
-    set config(textdownpad)            [option get $w textdownpad            Roster]
-    set config(linepad)	               [option get $w linepad                Roster]
-    set config(background)             [option get $w cbackground            Roster]
-    set config(metajidfill)	       [option get $w metajidfill            Roster]
-    set config(metajidhlfill)          [option get $w metajidhlfill          Roster]
-    set config(metajidborder)          [option get $w metajidborder          Roster]
-    set config(jidfill)	               [option get $w jidfill                Roster]
-    set config(jidhlfill)              [option get $w jidhlfill              Roster]
-    set config(jidborder)              [option get $w jidborder              Roster]
-    set config(jid2fill)               $config(jidfill)
-    set config(jid2hlfill)             $config(jidhlfill)
-    set config(jid2border)             $config(jidborder)
-    set config(groupfill)              [option get $w groupfill              Roster]
-    set config(groupcfill)             [option get $w groupcfill             Roster]
-    set config(grouphlfill)            [option get $w grouphlfill            Roster]
-    set config(groupborder)            [option get $w groupborder            Roster]
-    set config(connectionfill)         [option get $w connectionfill         Roster]
-    set config(connectioncfill)        [option get $w connectioncfill        Roster]
-    set config(connectionhlfill)       [option get $w connectionhlfill       Roster]
-    set config(connectionborder)       [option get $w connectionborder       Roster]
-    set config(foreground)             [option get $w foreground             Roster]
-    set config(unsubscribedforeground) [option get $w unsubscribedforeground Roster]
-    set config(unavailableforeground)  [option get $w unavailableforeground  Roster]
-    set config(dndforeground)          [option get $w dndforeground          Roster]
-    set config(xaforeground)           [option get $w xaforeground           Roster]
-    set config(awayforeground)         [option get $w awayforeground         Roster]
-    set config(availableforeground)    [option get $w availableforeground    Roster]
-    set config(chatforeground)         [option get $w chatforeground         Roster]
-
-    canvas $c -bg $config(background) \
-	-highlightthickness $::tk_highlightthickness \
-	-scrollregion {0 0 0 0} \
-	-width $width -height $height
-
-    $sw setwidget $c
-
-    set iroster($w,ypos) 1
-    set iroster($w,width) 0
-    set iroster($w,popup) $popupproc
-    set iroster($w,grouppopup) $grouppopupproc
-    set iroster($w,singleclick) $singleclickproc
-    set iroster($w,doubleclick) $doubleclickproc
-
-    bindscroll $c
-
-    if {[info exists draginitcmd]} {
-	DragSite::register $c -draginitcmd $draginitcmd
-    }
-
-    set args {}
-    if {[info exists dropovercmd]} {
-	lappend args -dropovercmd $dropovercmd
-    }
-    if {[info exists dropcmd]} {
-	lappend args -dropcmd $dropcmd
-    }
-    if {[llength $args] > 0} {
-	eval [list DropSite::register $c -droptypes {JID}] $args
-    }
-}
-
-proc roster::addline {w type text jid group metajids indent {jids {}} {icon ""} {foreground ""}} {
-    variable options
-    variable roster
-    variable iroster
-    variable config
-
-    set c $w.canvas
-
-    set tag [jid_to_tag $jid]
-    set grouptag [jid_to_tag $group]
-    set metatag [jid_to_tag $metajids]
-
-    set ypad 1
-    set linespace [font metric $::RosterFont -linespace]
-    set lineheight [expr {$linespace + $ypad}]
-
-    set uy $iroster($w,ypos)
-    set ly [expr {$uy + $lineheight + $config(textuppad) + $config(textdownpad)}]
-
-    set levindent [expr $config(groupindent)*$indent]
-
-    if {[string equal $metajids {}]} {
-	set metaindent 0
-    } else {
-	set metaindent $config(groupindent)
-    }
-
-    set border $config(${type}border)
-    set hlfill $config(${type}hlfill)
-
-    if {([string equal $type group] || [string equal $type connection]) && \
-	    [info exists roster(collapsed,$jid)] && $roster(collapsed,$jid)} {
-	set rfill $config(${type}cfill)
-    } else {
-	set rfill $config(${type}fill)
-    }
-
-    if {[string equal $type connection]} {
-	set type group
-    }
-
-    $c create rectangle [expr {1 + $levindent}] $uy 10000 $ly \
-	      -fill $rfill \
-	      -outline $border \
-	      -tags [list jid$tag group$grouptag meta$metatag $type rect]
-
-    switch -- $type {
-	metajid {
-	    set isuser 1
-	    set y [expr {($uy + $ly)/2}]
-	    set x [expr {$config(iconindent) + $levindent}]
-
-	    if {$icon == ""} {
-		set icon roster/user/unavailable
-	    }
-	    $c create image $x $y -image $icon \
-				  -anchor w \
-				  -tags [list jid$tag group$grouptag meta$metatag $type icon]
-
-	    if {[llength $jids] > 0} {
-		if {[info exists roster(metacollapsed,$group,$metajids)] && \
-			!$roster(metacollapsed,$group,$metajids)} {
-		    set jid_state opened
-		} else {
-		    set roster(metacollapsed,$group,$metajids) 1
-		    set jid_state closed
-		}
-		if {$config(subitemtype) > 0 && ($config(subitemtype) & 2)} {
-		    set y [expr {($uy + $ly)/2}]
-		    set x [expr {$config(subgroupiconindent) + $levindent}]
-		    $c create image $x $y -image roster/group/$jid_state \
-					  -anchor w \
-					  -tags [list jid$tag group$grouptag meta$metatag $type group]
-		}
-	    } else {
-		set roster(metacollapsed,$group,$metajids) 1
-	    }
-	}
-	jid {
-	    lassign $jid xlib jjid
-	    set isuser [::roster::itemconfig $xlib $jjid -isuser]
-	    if {[string equal $isuser ""]} {
-		set isuser 1
-	    }
-
-	    set y [expr {($uy + $ly)/2}]
-	    set x [expr {$config(iconindent) + $levindent + $metaindent}]
-
-	    if {$icon == ""} {
-		set icon roster/user/unavailable
-	    }
-	    $c create image $x $y -image $icon \
-				  -anchor w \
-				  -tags [list jid$tag group$grouptag meta$metatag $type icon]
-
-	    if {[llength $jids] > 1} {
-		if {[info exists roster(jidcollapsed,$group,$jid)] && !$roster(jidcollapsed,$group,$jid)} {
-		    set jid_state opened
-		} else {
-		    set roster(jidcollapsed,$group,$jid) 1
-		    set jid_state closed
-		}
-		if {$config(subitemtype) > 0 && ($config(subitemtype) & 2) && $isuser} {
-		    set y [expr {($uy + $ly)/2}]
-		    set x [expr {$config(subgroupiconindent) + $levindent + $metaindent}]
-		    $c create image $x $y -image roster/group/$jid_state \
-					  -anchor w \
-					  -tags [list jid$tag group$grouptag meta$metatag $type group]
-		}
-	    } else {
-		set roster(jidcollapsed,$group,$jid) 1
-	    }
-	}
-	jid2 {
-	    set y [expr {($uy + $ly)/2}]
-	    set x [expr {$config(subiconindent) + $levindent + $metaindent}]
-
-	    if {$icon == ""} {
-		set icon roster/user/unavailable
-	    }
-	    $c create image $x $y -image $icon \
-				  -anchor w \
-				  -tags [list jid$tag group$grouptag meta$metatag $type icon]
-	}
-	group {
-	    set y [expr {($uy + $ly)/2}]
-	    set x [expr {$config(groupiconindent) + $levindent}]
-	    if {[info exists roster(collapsed,$jid)] && $roster(collapsed,$jid)} {
-		set group_state closed
-	    } else {
-		set group_state opened
-	    }
-	    $c create image $x $y -image roster/group/$group_state \
-				  -anchor w \
-				  -tags [list jid$tag group$grouptag meta$metatag $type icon]
-	}
-    }
-
-    switch -- $type {
-	metajid {
-	    if {($config(subitemtype) > 0) && ($config(subitemtype) & 2) && \
-		    (($options(enable_metacontact_labels) && [llength $jids] > 0) || \
-		    [llength $jids] > 1)} {
-		set x [expr {$config(jidmultindent) + $levindent}]
-	    } else {
-		set x [expr {$config(jidindent) + $levindent}]
-	    }
-	}
-	jid {
-	    if {($config(subitemtype) > 0) && ($config(subitemtype) & 2) && \
-		    $isuser && ([llength $jids] > 1)} {
-		set x [expr {$config(jidmultindent) + $levindent}]
-	    } else {
-		set x [expr {$config(jidindent) + $levindent}]
-	    }
-	}
-	default {
-	    set x [expr {$config(${type}indent) + $levindent}]
-	}
-    }
-    switch -- $type {
-	jid -
-	jid2 {
-	    incr x $metaindent
-	}
-    }
-
-    incr uy $config(textuppad)
-
-    if {[string equal $foreground ""]} {
-	switch -- $type {
-	    metajid -
-	    jid -
-	    jid2 {
-		set foreground $config(unavailableforeground)
-	    }
-	    default {
-		set foreground $config(foreground)
-	    }
-	}
-    }
-    $c create text $x $uy -text $text \
-			  -anchor nw \
-			  -font $::RosterFont \
-			  -fill $foreground \
-			  -tags [list jid$tag group$grouptag meta$metatag $type text]
-
-    set iroster($w,width) [max $iroster($w,width) \
-			       [expr {$x + [font measure $::RosterFont $text]}]]
-
-    $c bind jid$tag&&$type <Any-Enter> \
-	    [double% [list $c itemconfig jid$tag&&$type&&rect -fill $hlfill]]
-    $c bind jid$tag&&$type <Any-Leave> \
-	    [double% [list $c itemconfig jid$tag&&$type&&rect -fill $rfill]]
-
-    set doubledjid  [double% $jid]
-    set doubledjids [double% $jids]
-
-    set iroster($w,ypos) [expr {$ly + $config(linepad)}]
-
-    switch -- $type {
-	metajid -
-	jid -
-	jid2 {
-	    $c bind jid$tag&&$type <Button-1> \
-		    [list [namespace current]::on_singleclick \
-			  [double% $iroster($w,singleclick)] \
-			  [double% $c] %x %y $doubledjid $doubledjids]
-
-	    $c bind jid$tag&&$type <Double-Button-1> \
-		    [list [namespace current]::on_doubleclick \
-			  [double% $iroster($w,doubleclick)] $doubledjid $doubledjids]
-
-	    $c bind jid$tag&&$type <Any-Enter> \
-		    +[list eval balloon::set_text \
-			   \[[namespace current]::jids_popup_info \
-			   [list $doubledjid] [list $doubledjids]\]]
-
-	    $c bind jid$tag&&$type <Any-Motion> \
-		    [list eval balloon::on_mouse_move \
-			  \[[namespace current]::jids_popup_info \
-			  [list $doubledjid] [list $doubledjids]\] %X %Y]
-
-	    $c bind jid$tag&&$type <Any-Leave> {+ balloon::destroy}
-
-	    if {![string equal $iroster($w,popup) ""]} {
-		$c bind jid$tag&&$type <3> [list [double% $iroster($w,popup)] \
-						 $doubledjid $doubledjids]
-	    }
-	}
-	default {
-	    if {$w == ".roster"} {
-		$c bind jid$tag&&group <Button-1> \
-		        [list [namespace current]::group_click $doubledjid]
-	    }
-
-	    if {![string equal $iroster($w,grouppopup) {}]} {
-		$c bind jid$tag&&group <3> \
-		        [list [double% $iroster($w,grouppopup)] $doubledjid]
-	    }
-	}
-    }
-}
-
-proc roster::clear {w {updatescroll 1}} {
-    variable iroster
-
-    $w.canvas delete rect||icon||text||group
-
-    set iroster($w,ypos) 1
-    set iroster($w,width) 0
-    if {$updatescroll} {
-	update_scrollregion $w
-    }
-}
-
-proc roster::update_scrollregion {w} {
-    variable iroster
-
-    $w.canvas configure \
-	-scrollregion [list 0 0 $iroster($w,width) $iroster($w,ypos)]
-}
-
-###############################################################################
-
-proc roster::on_singleclick {command c x y cjid jids} {
-    variable click_afterid
-
-    if {$command == ""} return
-
-    set xc [$c canvasx $x]
-    set yc [$c canvasy $y]
-    set tags [$c gettags [lindex [$c find closest $xc $yc] 0]]
-
-    if {![info exists click_afterid]} {
-	set click_afterid \
-	    [after 300 [list [namespace current]::singleclick_run $command $tags $cjid $jids]]
-    } else {
-	after cancel $click_afterid
-	unset click_afterid
-    }
-}
-
-proc roster::singleclick_run {command tags cjid jids} {
-    variable click_afterid
-
-    if {[info exists click_afterid]} {
-	unset click_afterid
-    }
-
-    eval $command [list $tags $cjid $jids]
-}
-
-proc roster::on_doubleclick {command cjid jids} {
-    variable click_afterid
-
-    if {[info exists click_afterid]} {
-	after cancel $click_afterid
-	unset click_afterid
-    }
-
-    if {$command == ""} return
-
-    eval $command [list $cjid $jids]
-}
-
-###############################################################################
-
-proc roster::jid_doubleclick {id ids} {
-    lassign $id xlib jid
-    lassign [::roster::get_category_and_subtype $xlib $jid] category subtype
-
-    hook::run roster_jid_doubleclick $xlib $jid $category $subtype
-}
-
-###############################################################################
-
-proc roster::doubleclick_fallback {xlib jid category subtype} {
-    variable options
-
-    if {$options(chat_on_doubleclick)} {
-	chat::open_to_user $xlib $jid
-    } else {
-	message::send_dialog -to $jid
-    }
-}
-
-hook::add roster_jid_doubleclick \
-    [namespace current]::roster::doubleclick_fallback 100
-
-###############################################################################
-
-proc roster::group_click {gid} {
-    variable roster
-
-    set roster(collapsed,$gid) [expr {!$roster(collapsed,$gid)}]
-    redraw_after_idle
-}
-
-proc roster::jids_popup_info {id jids} {
-    lassign $id xlib jid
-
-    # TODO: metacontacts
-    if {$jids == {}} {
-	set jids [list $jid]
-    }
-
-    set text {}
-    set i 0
-    foreach j $jids {
-	append text "\n[[namespace current]::user_popup_info $xlib $j $i]"
-	incr i
-    }
-    set text [string trimleft $text "\n"]
-    return $text
-}
-
-proc roster::user_popup_info {xlib user i} {
-    variable options
-    variable user_popup_info
-    global statusdesc
-
-    lassign [::roster::get_category_and_subtype $xlib $user] category subtype
-    set bare_user [::roster::find_jid $xlib $user]
-    lassign [::roster::get_category_and_subtype $xlib $bare_user] \
-	category1 subtype1
-
-    set name $user
-    switch -- $category {
-	conference {
-	    set status $statusdesc([get_jid_status $xlib $user])
-	    set desc ""
-	}
-	user -
-	default {
-	    set status $statusdesc([get_user_status $xlib $user])
-	    set desc   [get_user_status_desc $xlib $user]
-	    if {[string equal $category1 conference] && $i > 0} {
-		if {$options(show_conference_user_info)} {
-		    set name "     [::xmpp::jid::resource $user]"
-		} else {
-		    set name "\t[::xmpp::jid::resource $user]"
-		}
-	    }
-	}
-    }
-
-    if {(![string equal -nocase  $status $desc]) && (![string equal $desc ""])} {
-	append status " ($desc)"
-    }
-
-    set subsc [::roster::itemconfig $xlib $bare_user -subsc]
-    if {($options(show_subscription) && ![string equal $subsc ""]) &&
-	    !([string equal $category1 conference] && [string equal $category user])} {
-	set subsc [format "\n\t[::msgcat::mc {Subscription:}] %s" $subsc]
-	set ask [::roster::itemconfig $xlib $bare_user -ask]
-	if {![string equal $ask ""]} {
-	    set ask [format "  [::msgcat::mc {Ask:}] %s" $ask]
-	}
-    } else {
-	set subsc ""
-	set ask ""
-    }
-
-    set user_popup_info "$name: $status$subsc$ask"
-
-    if {!([string equal $category1 conference] && $i > 0) || \
-	    $options(show_conference_user_info)} {
-	hook::run roster_user_popup_info_hook \
-	    [namespace which -variable user_popup_info] $xlib $user
-    }
-
-    return $user_popup_info
-}
-
-proc roster::switch_only_online {args} {
-    variable show_only_online
-
-    set show_only_online [expr {!$show_only_online}]
-}
-
-proc roster::is_online {xlib jid} {
-    if {[::roster::itemconfig $xlib $jid -isuser]} {
-	switch -- [get_user_status $xlib $jid] {
-	    unavailable {return 0}
-	    default {return 1}
-	}
-    } else {
-	return 1
-    }
-}
-
-###############################################################################
-
-proc roster::add_remove_item_menu_item {m xlib jid} {
-    set rjid [roster::find_jid $xlib $jid]
-    if {$rjid == ""} {
-	set state disabled
-    } else {
-	set state normal
-    }
-    $m add command -label [::msgcat::mc "Remove from roster..."] \
-	-command [list ifacetk::roster::remove_item_dialog $xlib $rjid] \
-	-state $state
-}
-
-hook::add chat_create_user_menu_hook \
-    [namespace current]::roster::add_remove_item_menu_item 90
-hook::add roster_conference_popup_menu_hook \
-    [namespace current]::roster::add_remove_item_menu_item 90
-hook::add roster_service_popup_menu_hook \
-    [namespace current]::roster::add_remove_item_menu_item 90
-hook::add roster_jid_popup_menu_hook \
-    [namespace current]::roster::add_remove_item_menu_item 90
-
-###############################################################################
-
-proc roster::remove_item_dialog {xlib jid} {
-    set res [MessageDlg .remove_item -aspect 50000 -icon question -type user \
-	-buttons {yes no} -default 0 -cancel 1 \
-	-message [::msgcat::mc "Are you sure to remove %s from roster?" $jid]]
-    if {$res == 0} {
-	::roster::remove_item $xlib $jid
-    }
-}
-
-proc roster::update_chat_activity {args} {
-    variable options
-
-    if {$options(chats_group)} {
-	redraw_after_idle
-    }
-}
-
-hook::add open_chat_post_hook [namespace current]::roster::redraw_after_idle
-hook::add close_chat_post_hook [namespace current]::roster::redraw_after_idle
-hook::add draw_message_hook [namespace current]::roster::update_chat_activity
-hook::add raise_chat_tab_hook [namespace current]::roster::update_chat_activity
-
-
-###############################################################################
-
-proc roster::dropcmd {target source X Y op type data} {
-    variable options
-
-    debugmsg roster "$target $source $X $Y $op $type $data"
-
-    set c .roster.canvas
-
-    set x [expr {$X-[winfo rootx $c]}]
-    set y [expr {$Y-[winfo rooty $c]}]
-    set xc [$c canvasx $x]
-    set yc [$c canvasy $y]
-
-    set tags [$c gettags [lindex [$c find closest $xc $yc] 0]]
-
-    if {$options(free_drop) && ![string equal $tags ""]} {
-	lassign [tag_to_jid [string range [lindex $tags 1] 5 end]] xlib gr
-	if {$xlib == "xlib"} {
-	    set xlib $gr
-	    set gr {}
-	}
-    } elseif {[lsearch -exact $tags group] >= 0} {
-	lassign [tag_to_jid [string range [lindex $tags 0] 3 end]] xlib gr
-	if {$xlib == "xlib"} {
-	    set xlib $gr
-	    set gr {}
-	}
-    } elseif {![string equal $tags ""]} {
-	lassign [tag_to_jid [string range [lindex $tags 1] 5 end]] xlib
-	set gr {}
-    } else {
-	set xlib [lindex [connections] 0]
-	set gr {}
-    }
-    if {$options(nested)} {
-	set gr [join $gr $options(nested_delimiter)]
-    } else {
-	set gr [lindex $gr 0]
-    }
-
-    debugmsg roster "GG: $gr; $tags"
-
-    lassign $data _xlib jid category type name version fromgid
-    set subsc ""
-
-    if {[info exists fromgid]} {
-	lassign $fromgid fromxlib fromgr
-	if {$options(nested)} {
-	    set fromgr [join $fromgr $options(nested_delimiter)]
-	} else {
-	    set fromgr [lindex $fromgr 0]
-	}
-    }
-
-    if {[lsearch -exact [::roster::get_jids $xlib] $jid] < 0} {
-	if {$gr != {}} {
-	    set groups [list $gr]
-	} else {
-	    set groups {}
-	}
-	::roster::itemconfig $xlib $jid -category $category -subtype $type \
-	    -name $name -group $groups
-
-	lassign [::roster::get_category_and_subtype $xlib $jid] ccategory ctype
-	switch -- $ccategory {
-	    conference {
-		::roster::itemconfig $xlib $jid -subsc bookmark
-	    }
-	    user {
-		::xmpp::sendPresence $xlib -to $jid -type subscribe
-	    }
-	}
-    } else {
-	set groups [::roster::itemconfig $xlib $jid -group]
-
-	if {[info exists fromgid] && ($fromxlib == $xlib)} {
-	    set idx [lsearch -exact $groups $fromgr]
-	    if {$idx >= 0} {
-		set groups [lreplace $groups $idx $idx]
-	    }
-	}
-	if {$gr != ""} {
-	    lappend groups $gr
-	    set groups [lsort -unique $groups]
-	    debugmsg roster $groups
-	}
-
-	::roster::itemconfig $xlib $jid -category $category -subtype $type \
-	    -name $name -group $groups
-    }
-
-    ::roster::send_item $xlib $jid
-}
-
-proc roster::draginitcmd {target x y top} {
-    debugmsg roster "$target $x $y $top"
-
-    balloon::destroy
-    set c .roster.canvas
-
-    set tags [$c gettags current]
-    if {[lsearch -exact $tags jid] >= 0} {
-	set grouptag [string range [lindex $tags 1] 5 end]
-	set gid [tag_to_jid $grouptag]
-	set tag [string range [lindex $tags 0] 3 end]
-	set cjid [tag_to_jid $tag]
-	lassign $cjid xlib jid
-
-	set data [list $xlib $jid \
-		      [::roster::itemconfig $xlib $jid -category] \
-		      [::roster::itemconfig $xlib $jid -subtype] \
-		      [::roster::itemconfig $xlib $jid -name] {} \
-		      $gid]
-
-	debugmsg roster $data
-	return [list JID {move} $data]
-    } else {
-	return {}
-    }
-}
-
-###############################################################################
-
-proc roster::user_singleclick {tags cjid jids} {
-    variable options
-    variable roster
-
-    lassign $cjid xlib jid
-    set type [lindex $tags 3]
-    set cgroup [tag_to_jid [string range [lindex $tags 1] 5 end]]
-    set cmeta [tag_to_jid [string range [lindex $tags 2] 4 end]]
-
-    switch -- $type {
-	metajid {
-	    if {[llength $jids] > 1 || \
-		    ($options(enable_metacontact_labels) && [llength $jids] > 0)} {
-		set roster(metacollapsed,$cgroup,$cmeta) \
-		    [expr {!$roster(metacollapsed,$cgroup,$cmeta)}]
-		redraw_after_idle
-	    }
-	}
-	default {
-	    if {[roster::itemconfig $xlib $jid -isuser] && [llength $jids] > 1} {
-		set roster(jidcollapsed,$cgroup,$cjid) \
-		    [expr {!$roster(jidcollapsed,$cgroup,$cjid)}]
-		redraw_after_idle
-	    }
-	}
-    }
-}
-
-###############################################################################
-
-proc roster::popup_menu {id jids} {
-    lassign $id xlib jid
-
-    lassign [::roster::get_category_and_subtype $xlib $jid] category subtype
-
-    switch -- $category {
-	user       {set menu [create_user_menu $xlib $jid $jids]}
-	conference {set menu [conference_popup_menu $xlib $jid]}
-	server  -
-	gateway -
-	service    {set menu [service_popup_menu $xlib $jid]}
-	default    {set menu [jid_popup_menu $xlib $jid]}
-    }
-
-    tk_popup $menu [winfo pointerx .] [winfo pointery .]
-}
-
-###############################################################################
-
-proc roster::group_popup_menu {id} {
-    variable options
-
-    lassign $id xlib name
-    if {$options(nested)} {
-	set name [join $name $options(nested_delimiter)]
-    } else {
-	set name [lindex $name 0]
-    }
-    if {$xlib != "xlib"} {
-	tk_popup [create_group_popup_menu $xlib $name] \
-	    [winfo pointerx .] [winfo pointery .]
-    }
-}
-
-###############################################################################
-
-proc roster::groupchat_popup_menu {id jids} {
-    lassign $id xlib jid
-    tk_popup [create_groupchat_user_menu $xlib $jid] \
-	[winfo pointerx .] [winfo pointery .]
-}
-
-###############################################################################
-
-proc roster::create_user_menu {xlib user jids} {
-    set m .jidpopupmenu
-    if {[winfo exists $m]} { destroy $m }
-    menu $m -tearoff 0
-
-    set jids1 {}
-    foreach jid $jids {
-	set resources [get_jids_of_user $xlib $jid]
-	if {[llength $resources] == 0} {
-	    lappend jids1 $jid
-	} else {
-	    set jids1 [concat $jids1 [get_jids_of_user $xlib $jid]]
-	}
-    }
-    set jids $jids1
-
-    switch -- [llength $jids] {
-	0 {
-	    hook::run roster_jid_popup_menu_hook $m $xlib $user
-	    return $m
-	}
-	1 {
-	    hook::run roster_jid_popup_menu_hook $m $xlib [lindex $jids 0]
-	    return $m
-	}
-	default {
-	    foreach jid $jids {
-		set m1 .jidpopupmenu[jid_to_tag $jid]
-		if {[winfo exists $m1]} { destroy $m1 }
-		menu $m1 -tearoff 0
-		hook::run roster_jid_popup_menu_hook $m1 $xlib $jid
-	    }
-
-	    add_menu_submenu $m .jidpopupmenu "" $jids
-
-	    foreach jid $jids {
-		set m1 .jidpopupmenu[jid_to_tag $jid]
-		if {[winfo exists $m1]} { destroy $m1 }
-	    }
-
-	    return $m
-	}
-    }
-}
-
-###############################################################################
-
-proc roster::add_menu_submenu {m prefix suffix jids} {
-    set m1 $prefix[jid_to_tag [lindex $jids 0]]$suffix
-
-    for {set i 0} {[$m1 index $i] == $i} {incr i} {
-	switch -- [$m1 type $i] {
-	    separator {
-		$m add separator
-	    }
-	    cascade {
-		set label [$m1 entrycget $i -label]
-		set menu [$m1 entrycget $i -menu]
-		set state [$m1 entrycget $i -state]
-		set suffix2 [join [lrange [split $menu .] 2 end] .]
-		set suffix3 [lindex [split $menu .] end]
-		set m2 [menu $m.$suffix3 -tearoff 0]
-		# TODO: Check if state is the same for all menus
-		$m add cascade -label $label -menu $m2 -state $state
-		add_menu_submenu $m2 $prefix .$suffix2 $jids
-	    }
-	    checkbutton {
-		set label [$m1 entrycget $i -label]
-		add_checkbutton_submenu $m $prefix $suffix $i $label $jids
-	    }
-	    radiobutton {
-		set label [$m1 entrycget $i -label]
-		add_radiobutton_submenu $m $prefix $suffix $i $label $jids
-	    }
-	    command {
-		set label [$m1 entrycget $i -label]
-		add_command_submenu $m $prefix $suffix $i $label $jids
-	    }
-	}
-    }
-}
-
-###############################################################################
-
-proc roster::get_popup_command_list {m prefix suffix label jids args} {
-    set command_list0 {}
-    set command_list1 {}
-    set command_list2 {}
-    foreach jid $jids {
-	set bjid [::xmpp::jid::stripResource $jid]
-	set m1 $prefix[jid_to_tag $jid]$suffix
-	if {![catch {$m1 index $label} idx] && $idx != "none"} {
-	    set command {}
-	    foreach opt $args {
-		lappend command [$m1 entrycget $idx $opt]
-	    }
-	    lappend command_list0 [list $label $command]
-	    lappend command_list1 [list $jid $command]
-	    lappend command_list2 [list $bjid $command]
-	}
-    }
-
-    set command_list0 [lsort -unique $command_list0]
-    set command_list2 [lsort -unique $command_list2]
-    set command_list3 [lsort -unique -index 0 $command_list2]
-
-    if {[llength $command_list0] == 1} {
-	return $command_list0
-    } elseif {[llength $command_list2] != [llength $command_list3]} {
-	return $command_list1
-    } else {
-	return $command_list2
-    }
-}
-
-proc roster::add_command_submenu {m prefix suffix i label jids} {
-    set command_list [get_popup_command_list $m $prefix $suffix $label $jids \
-					     -command -state]
-    if {[llength $command_list] > 1} {
-	set m2 [menu $m.$i -tearoff 0]
-	$m add cascade -label $label -menu $m2
-
-	foreach jid_command $command_list {
-	    lassign $jid_command jid command
-	    $m2 add command -label $jid \
-			    -command [lindex $command 0] \
-			    -state [lindex $command 1]
-	}
-    } else {
-	lassign [lindex [lindex $command_list 0] 1] command state
-	$m add command -label $label \
-		       -command $command \
-		       -state $state
-    }
-}
-
-proc roster::add_checkbutton_submenu {m prefix suffix i label jids} {
-    set command_list [get_popup_command_list $m $prefix $suffix $label $jids \
-					     -variable -command -state]
-
-    if {[llength $command_list] > 1} {
-	set m2 [menu $m.$i -tearoff 0]
-	$m add cascade -label $label -menu $m2
-
-	foreach jid_command $command_list {
-	    lassign $jid_command jid command
-	    $m2 add checkbutton -label $jid \
-				-variable [lindex $command 0] \
-				-command [lindex $command 1] \
-				-state [lindex $command 2]
-	}
-    } else {
-	lassign [lindex [lindex $command_list 0] 1] var command state
-	$m add checkbutton -label $label \
-			   -variable $var \
-			   -command $command \
-			   -state $state
-    }
-}
-
-proc roster::add_radiobutton_submenu {m prefix suffix i label jids} {
-    set command_list [get_popup_command_list $m $prefix $suffix $label $jids \
-					     -value -variable -command -state]
-
-    if {[llength $command_list] > 1} {
-	set m2 [menu $m.$i -tearoff 0]
-	$m add cascade -label $label -menu $m2
-
-	foreach jid_command $command_list {
-	    lassign $jid_command jid command
-	    $m2 add radiobutton -label $jid \
-				-value [lindex $command 0] \
-				-variable [lindex $command 1] \
-				-command [lindex $command 2] \
-				-state [lindex $command 3]
-	}
-    } else {
-	lassign [lindex [lindex $command_list 0] 1] value var command state
-	$m add radiobutton -label $label \
-			   -value $value \
-			   -variable $var \
-			   -command $command \
-			   -state $state
-    }
-}
-
-###############################################################################
-
-proc roster::add_separator {m xlib jid} {
-    $m add separator
-}
-
-###############################################################################
-
-proc roster::jid_popup_menu {xlib jid} {
-    if {[winfo exists [set m .jidpopupmenu]]} {
-	destroy $m
-    }
-    menu $m -tearoff 0
-
-    hook::run roster_jid_popup_menu_hook $m $xlib $jid
-
-    return $m
-}
-
-hook::add roster_jid_popup_menu_hook \
-    [namespace current]::roster::add_separator 40
-hook::add roster_jid_popup_menu_hook \
-    [namespace current]::roster::add_separator 50
-hook::add roster_jid_popup_menu_hook \
-    [namespace current]::roster::add_separator 70
-hook::add roster_jid_popup_menu_hook \
-    [namespace current]::roster::add_separator 85
-
-###############################################################################
-
-proc roster::conference_popup_menu {xlib jid} {
-    if {[winfo exists [set m .confpopupmenu]]} {
-	destroy $m
-    }
-    menu $m -tearoff 0
-
-    hook::run roster_conference_popup_menu_hook $m $xlib $jid
-
-    return $m
-}
-
-hook::add roster_conference_popup_menu_hook \
-    [namespace current]::roster::add_separator 50
-hook::add roster_conference_popup_menu_hook \
-    [namespace current]::roster::add_separator 70
-hook::add roster_conference_popup_menu_hook \
-    [namespace current]::roster::add_separator 85
-
-###############################################################################
-
-proc roster::service_popup_menu {xlib jid} {
-    if {[winfo exists [set m .servicepopupmenu]]} {
-	destroy $m
-    }
-    menu $m -tearoff 0
-
-    hook::run roster_service_popup_menu_hook $m $xlib $jid
-
-    return $m
-}
-
-hook::add roster_service_popup_menu_hook \
-    [namespace current]::roster::add_separator 50
-hook::add roster_service_popup_menu_hook \
-    [namespace current]::roster::add_separator 70
-hook::add roster_service_popup_menu_hook \
-    [namespace current]::roster::add_separator 85
-
-###############################################################################
-
-proc roster::create_groupchat_user_menu {xlib jid} {
-    if {[winfo exists [set m .groupchatpopupmenu]]} {
-	destroy $m
-    }
-    menu $m -tearoff 0
-
-    hook::run roster_create_groupchat_user_menu_hook $m $xlib $jid
-
-    return $m
-}
-
-hook::add roster_create_groupchat_user_menu_hook \
-    [namespace current]::roster::add_separator 40
-hook::add roster_create_groupchat_user_menu_hook \
-    [namespace current]::roster::add_separator 50
-
-###############################################################################
-
-proc roster::create_group_popup_menu {xlib name} {
-    variable options
-    variable chats_group_name
-
-    if {$name == $chats_group_name} {
-	set state disabled
-    } else {
-	set state normal
-    }
-
-    if {[winfo exists [set m .grouppopupmenu]]} {
-	destroy $m
-    }
-    if {$options(nested)} {
-	set oname [msplit $name $options(nested_delimiter)]
-    } else {
-	set oname $name
-    }
-    menu $m -tearoff 0
-    $m add command \
-	-label [::msgcat::mc "Send message to all users in group..."] \
-	-command [list ::message::send_dialog \
-		       -to $name -group 1 -connection $xlib]
-    $m add command \
-	-label [::msgcat::mc "Resubscribe to all users in group..."] \
-	-command [list ::roster::resubscribe_group $xlib $name]
-
-    add_group_custom_presence_menu $m $xlib $name
-
-    $m add checkbutton -label [::msgcat::mc "Show offline users"] \
-	-variable [namespace current]::roster(show_offline,[list $xlib $oname]) \
-	-command [list [namespace current]::redraw_after_idle]
-    $m add command -label [::msgcat::mc "Rename group..."] \
-	-command [list [namespace current]::rename_group_dialog $xlib $name] \
-	-state $state
-    $m add command -label [::msgcat::mc "Remove group..."] \
-	-command [list [namespace current]::remove_group_dialog $xlib $name] \
-	-state $state
-    $m add command -label [::msgcat::mc "Remove all users in group..."] \
-	-command [list [namespace current]::remove_users_group_dialog $xlib $name]
-
-    set last [$m index end]
-
-    ::hook::run roster_group_popup_menu_hook $m $xlib $name
-
-    if {[$m index end] > $last} {
-	$m insert [expr $last + 1] separator
-    }
-
-    return $m
-}
-
-###############################################################################
-
-proc roster::remove_group_dialog {xlib name} {
-    set res [MessageDlg .remove_item -aspect 50000 -icon question -type user \
-		 -buttons {yes no} -default 0 -cancel 1 \
-		 -message [::msgcat::mc "Are you sure to remove group '%s' from roster?\
-\n(Users which are in this group only, will be in undefined group.)" $name]]
-
-    if {$res == 0} {
-	roster::send_rename_group $xlib $name ""
-    }
-}
-
-proc roster::remove_users_group_dialog {xlib name} {
-    set res [MessageDlg .remove_item -aspect 50000 -icon question -type user \
-		 -buttons {yes no} -default 0 -cancel 1 \
-		 -message [::msgcat::mc "Are you sure to remove all users in group '%s' from roster?\
-\n(Users which are not in this group only, will be removed from the roster as well.)" $name]]
-
-    if {$res == 0} {
-	roster::send_remove_users_group $xlib $name
-    }
-}
-
-proc roster::rename_group_dialog {xlib name} {
-    global new_roster_group_name
-
-    set new_roster_group_name $name
-
-    set w .roster_group_rename
-    if {[winfo exists $w]} {
-	destroy $w
-    }
-
-    Dialog $w -title [::msgcat::mc "Rename roster group"] \
-	-separator 1 -anchor e -default 0 -cancel 1
-
-    $w add -text [::msgcat::mc "OK"] -command \
-	[list [namespace current]::confirm_rename_group $w $xlib $name]
-    $w add -text [::msgcat::mc "Cancel"] -command [list destroy $w]
-
-    set p [$w getframe]
-
-    label $p.lgroupname -text [::msgcat::mc "New group name:"]
-    ecursor_entry [entry $p.groupname -textvariable new_roster_group_name]
-
-    grid $p.lgroupname  -row 0 -column 0 -sticky e
-    grid $p.groupname   -row 0 -column 1 -sticky ew
-
-    focus $p.groupname
-    $w draw
-}
-
-proc roster::confirm_rename_group {w xlib name} {
-    global new_roster_group_name
-    variable roster
-
-    destroy $w
-
-    ::roster::send_rename_group $xlib $name $new_roster_group_name
-
-    set gid [list $xlib $name]
-    set newgid [list $xlib $new_roster_group_name]
-
-    if {[info exists roster(collapsed,$gid)]} {
-	set roster(collapsed,$newgid) $roster(collapsed,$gid)
-	unset roster(collapsed,$gid)
-    }
-    if {[info exists roster(show_offline,$gid)]} {
-	set roster(show_offline,$newgid) $roster(show_offline,$gid)
-	unset roster(show_offline,$gid)
-    }
-}
-
-proc roster::add_group_by_jid_regexp_dialog {} {
-    global new_roster_group_rname
-    global new_roster_group_regexp
-
-    set w .roster_group_add_by_jid_regexp
-    if {[winfo exists $w]} {
-	destroy $w
-    }
-
-    Dialog $w -title [::msgcat::mc "Add roster group by JID regexp"] \
-	-separator 1 -anchor e -default 0 -cancel 1
-
-    $w add -text [::msgcat::mc "OK"] -command "
-	destroy [list $w]
-	roster::add_group_by_jid_regexp \
-	    \$new_roster_group_rname \$new_roster_group_regexp
-    "
-    $w add -text [::msgcat::mc "Cancel"] -command [list destroy $w]
-
-    set p [$w getframe]
-
-    label $p.lgroupname -text [::msgcat::mc "New group name:"]
-    ecursor_entry [entry $p.groupname -textvariable new_roster_group_rname]
-    label $p.lregexp -text [::msgcat::mc "JID regexp:"]
-    ecursor_entry [entry $p.regexp -textvariable new_roster_group_regexp]
-
-    grid $p.lgroupname -row 0 -column 0 -sticky e
-    grid $p.groupname  -row 0 -column 1 -sticky ew
-    grid $p.lregexp    -row 1 -column 0 -sticky e
-    grid $p.regexp     -row 1 -column 1 -sticky ew
-
-    focus $p.groupname
-    $w draw
-}
-
-###############################################################################
-
-proc roster::add_group_custom_presence_menu {m xlib name} {
-    set mm [menu $m.custom_presence -tearoff 0]
-
-    $mm add command -label [::msgcat::mc "Available"] \
-	-command [list roster::send_custom_presence_group $xlib $name available]
-    $mm add command -label [::msgcat::mc "Free to chat"] \
-	-command [list roster::send_custom_presence_group $xlib $name chat]
-    $mm add command -label [::msgcat::mc "Away"] \
-	-command [list roster::send_custom_presence_group $xlib $name away]
-    $mm add command -label [::msgcat::mc "Extended away"] \
-	-command [list roster::send_custom_presence_group $xlib $name xa]
-    $mm add command -label [::msgcat::mc "Do not disturb"] \
-	-command [list roster::send_custom_presence_group $xlib $name dnd]
-    $mm add command -label [::msgcat::mc "Unavailable"] \
-	-command [list roster::send_custom_presence_group $xlib $name unavailable]
-
-    $m add cascade -label [::msgcat::mc "Send custom presence"] -menu $mm
-}
-
-###############################################################################
-
-# vim:ts=8:sw=4:sts=4:noet

Copied: trunk/tkabber/ifacetk/login.tcl (from rev 1914, trunk/tkabber/ifacetk/ilogin.tcl)
===================================================================
--- trunk/tkabber/ifacetk/login.tcl	                        (rev 0)
+++ trunk/tkabber/ifacetk/login.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -0,0 +1,463 @@
+# $Id$
+
+proc update_login_entries {l {i 0}} {
+    global ltmp
+
+    if {$i} {
+        array set ltmp [array get ::loginconf$i]
+	array set loginconf [array get ltmp]
+    }
+    foreach ent {username server port password resource priority \
+            altserver proxyhost proxyport proxyusername proxypassword \
+            sslcertfile pollurl} {
+        if {[winfo exists $l.$ent]} {
+            catch { $l.$ent icursor end }
+        }
+    }
+    foreach {check enable disable} { \
+            usehttppoll {lpollurl pollurl usepollkeys} \
+			{dontusessl usecompress legacyssl encrypted \
+			 sslcertfile lsslcertfile bsslcertfile} \
+            usealtserver {altserver laltserver port lport} {} \
+	    usesasl {allowgoogletoken} {} \
+	    } {
+        if {![info exists ltmp($check)] || ![winfo exists $l.$check]} {
+            continue
+        }
+
+        if {$ltmp($check) && ![cequal [$l.$check cget -state] disabled]} {
+            set state1 normal
+            set state2 disabled
+        } else {
+            set state1 disabled
+            set state2 normal
+        }
+        foreach ent $enable {
+            if {[winfo exists $l.$ent]} {
+                $l.$ent configure -state $state1
+                if {[cequal [focus] $l.$ent] && [cequal $state1 "disabled"]} {
+                    focus [Widget::focusPrev $l.$ent]
+                }
+            }
+        }
+        foreach ent $disable {
+            if {[winfo exists $l.$ent]} {
+                $l.$ent configure -state $state2
+                if {[cequal [focus] $l.$ent] && [cequal $state2 "disabled"]} {
+                    focus [Widget::focusPrev $l.$ent]
+                }
+	    }
+	}
+    }
+    catch {
+	if {[cequal $ltmp(proxy) none]} {
+	    foreach ent {proxyhost proxyport proxyusername proxypassword \
+			 lproxyhost lproxyport lproxyusername lproxypassword} {
+		$l.$ent configure -state disabled
+                if {[cequal [focus] $l.$ent]} {
+                    focus [Widget::focusPrev $l.$ent]
+                }
+	    }
+	} else {
+	    foreach ent {proxyhost proxyport proxyusername proxypassword \
+			 lproxyhost lproxyport lproxyusername lproxypassword} {
+		$l.$ent configure -state normal
+	    }
+	}
+    }
+    catch {
+	if {![cequal [$l.dontusessl cget -state] disabled] && \
+		($ltmp(stream_options) == "ssl" || \
+		 $ltmp(stream_options) == "encrypted")} {
+	    $l.sslcertfile configure -state normal
+	    $l.lsslcertfile configure -state normal
+	    $l.bsslcertfile configure -state normal
+	} else {
+	    $l.sslcertfile configure -state disabled
+	    $l.lsslcertfile configure -state disabled
+	    $l.bsslcertfile configure -state disabled
+	    if {[cequal [focus] $l.sslcertfile] || \
+		    [cequal [focus] $l.bsslcertfile]} {
+		focus [Widget::focusPrev $l.sslcertfile]
+	    }
+	}
+    }
+}
+
+proc login_dialog {} {
+    global loginconf
+    global ltmp
+    global use_tls have_compress have_sasl have_http_poll have_proxy
+
+    if {[winfo exists .login]} {
+	focus -force .login
+	return
+    }
+
+    array set ltmp [array get loginconf]
+
+    Dialog .login -title [::msgcat::mc "Login"] \
+	-separator 1 -anchor e -default 0 -cancel 1
+
+    wm resizable .login 0 0
+
+    set l [.login getframe]
+
+    set n 1
+    while {[info exists ::loginconf$n]} {incr n}
+    incr n -1
+
+    if {$n} {
+	menubutton $l.profiles -text [::msgcat::mc "Profiles"] \
+	    -relief $::tk_relief -menu $l.profiles.menu
+	set m [menu $l.profiles.menu -tearoff 0]
+	for {set i 1} {$i <= $n} {incr i} {
+	    if {[info exists ::loginconf${i}(profile)]} {
+		set lab [set ::loginconf${i}(profile)]
+	    } else {
+		set lab "[::msgcat::mc Profile] $i"
+	    }
+	    if {$i <= 10} {
+		set j [expr {$i % 10}]
+		$m add command -label $lab -accelerator "$::tk_modify-$j" \
+		    -command [list [namespace current]::update_login_entries $l $i]
+		bind .login <Control-Key-$j> \
+		    [list [namespace current]::update_login_entries [double% $l] $i]
+	    } else {
+		$m add command -label $lab \
+		    -command [list [namespace current]::update_login_entries $l $i]
+	    }
+	}
+
+	grid $l.profiles -row 0 -column 0 -sticky e
+    }
+
+    set nb [NoteBook $l.nb]
+
+    set account_page [$nb insert end account_page -text [::msgcat::mc "Account"]]
+
+    label $l.lusername -text [::msgcat::mc "Username:"]
+    entry $l.username -textvariable ltmp(user)
+    label $l.lserver -text [::msgcat::mc "Server:"]
+    entry $l.server -textvariable ltmp(server)
+    label $l.lpassword -text [::msgcat::mc "Password:"]
+    entry $l.password -show * -textvariable ltmp(password)
+    label $l.lresource -text [::msgcat::mc "Resource:"]
+    entry $l.resource -textvariable ltmp(resource)
+    label $l.lpriority -text [::msgcat::mc "Priority:"]
+    Spinbox $l.priority -1000 1000 1 ltmp(priority)
+
+    grid $l.lusername -row 0 -column 0 -sticky e -in $account_page
+    grid $l.username  -row 0 -column 1 -sticky ew -in $account_page
+    grid $l.lserver   -row 0 -column 2 -sticky e -in $account_page
+    grid $l.server    -row 0 -column 3 -sticky ew -in $account_page
+    grid $l.lpassword -row 1 -column 0 -sticky e -in $account_page
+    grid $l.password  -row 1 -column 1 -sticky ew -in $account_page
+    grid $l.lresource -row 2 -column 0 -sticky e -in $account_page
+    grid $l.resource  -row 2 -column 1 -sticky ew -in $account_page
+    grid $l.lpriority -row 2 -column 2 -sticky e -in $account_page
+    grid $l.priority  -row 2 -column 3 -sticky ew -in $account_page
+
+    grid columnconfigure $account_page 1 -weight 3
+    grid columnconfigure $account_page 2 -weight 1
+    grid columnconfigure $account_page 3 -weight 3
+
+    set connection_page [$nb insert end connection_page -text [::msgcat::mc "Connection"]]
+
+    checkbutton $l.usealtserver -text [::msgcat::mc "Explicitly specify host and port to connect"] \
+	-variable ltmp(usealtserver) \
+	-command [list [namespace current]::update_login_entries $l]
+    label $l.laltserver -text [::msgcat::mc "Host:"]
+    entry $l.altserver -textvariable ltmp(altserver)
+    label $l.lport -text [::msgcat::mc "Port:"]
+    Spinbox $l.port 0 65535 1 ltmp(altport)
+
+    grid $l.usealtserver -row 0 -column 0 -sticky w -columnspan 4 -in $connection_page
+    grid $l.laltserver -row 1 -column 0 -sticky e -in $connection_page
+    grid $l.altserver -row 1 -column 1 -sticky ew -in $connection_page
+    grid $l.lport     -row 1 -column 2 -sticky e -in $connection_page
+    grid $l.port      -row 1 -column 3 -sticky we -in $connection_page
+
+    checkbutton $l.replace -text [::msgcat::mc "Replace opened connections"] \
+	-variable ltmp(replace_opened)
+    grid $l.replace   -row 3 -column 0 -sticky w -columnspan 3 -in $connection_page
+
+    grid columnconfigure $connection_page 1 -weight 6
+    grid columnconfigure $connection_page 2 -weight 1
+
+
+    set auth_page [$nb insert end auth_page -text [::msgcat::mc "Authentication"]]
+
+    checkbutton $l.allowauthplain \
+	-text [::msgcat::mc "Allow plaintext authentication mechanisms"] \
+	-variable ltmp(allowauthplain) \
+	-command [list [namespace current]::update_login_entries $l]
+
+    grid $l.allowauthplain -row 0 -column 0 -sticky w -in $auth_page
+
+    if {$have_sasl} {
+	checkbutton $l.usesasl \
+	    -text [::msgcat::mc "Use SASL authentication"] \
+	    -variable ltmp(usesasl) \
+	    -command [list [namespace current]::update_login_entries $l]
+
+	grid $l.usesasl -row 1 -column 0 -sticky w -in $auth_page
+
+	checkbutton $l.allowgoogletoken \
+	    -text [::msgcat::mc "Allow X-GOOGLE-TOKEN SASL mechanism"] \
+	    -variable ltmp(allowgoogletoken) \
+	    -command [list [namespace current]::update_login_entries $l]
+
+	grid $l.allowgoogletoken -row 2 -column 0 -sticky w -in $auth_page
+    }
+
+    grid columnconfigure $auth_page 0 -weight 1
+
+    if {$use_tls || $have_compress} {
+	if {$use_tls && $have_compress} {
+		set page_label [::msgcat::mc "SSL & Compression"]
+	} elseif {$have_compress} {
+	    set page_label [::msgcat::mc "Compression"]
+	} else {
+	    set page_label [::msgcat::mc "SSL"]
+	}
+
+	set ssl_page [$nb insert end ssl_page -text $page_label]
+
+	radiobutton $l.dontusessl -text [::msgcat::mc "Plaintext"] \
+	    -variable ltmp(stream_options) -value plaintext \
+	    -command [list [namespace current]::update_login_entries $l]
+	if {$have_compress} {
+	    radiobutton $l.usecompress -text [::msgcat::mc "Compression"] \
+		-variable ltmp(stream_options) -value compressed \
+		-command [list [namespace current]::update_login_entries $l]
+	}
+	if {$use_tls} {
+	    radiobutton $l.encrypted -text [::msgcat::mc "Encryption (STARTTLS)"] \
+		-variable ltmp(stream_options) -value encrypted \
+		-command [list [namespace current]::update_login_entries $l]
+	    radiobutton $l.legacyssl -text [::msgcat::mc "Encryption (legacy SSL)"] \
+		-variable ltmp(stream_options) -value ssl \
+		-command [list [namespace current]::update_login_entries $l]
+	    label $l.lsslcertfile -text [::msgcat::mc "SSL certificate:"]
+	    entry $l.sslcertfile -textvariable ltmp(sslcertfile)
+	    button $l.bsslcertfile -text [::msgcat::mc "Browse..."] \
+		-command [list eval set ltmp(sslcertfile) {[tk_getOpenFile]}]
+	}
+
+	set column 0
+	grid $l.dontusessl -row 0 -column $column -sticky w -in $ssl_page
+	
+	if {$have_compress} {
+	    grid $l.usecompress -row 0 -column [incr column] -sticky w -in $ssl_page
+	}
+	if {$use_tls} {
+	    grid $l.encrypted     -row 0 -column [incr column] -sticky w -in $ssl_page
+	    grid $l.legacyssl    -row 0 -column [incr column] -sticky w -in $ssl_page
+
+	    grid $l.lsslcertfile -row 1 -column 0 -sticky e -in $ssl_page
+	    grid $l.sslcertfile  -row 1 -column 1 -sticky ew -columnspan 2 -in $ssl_page
+	    grid $l.bsslcertfile -row 1 -column 3 -sticky w -in $ssl_page
+	}
+
+	grid columnconfigure $ssl_page 1 -weight 1
+	grid columnconfigure $ssl_page 2 -weight 1
+    }
+
+    if {$have_http_poll} {
+	set httppoll_page [$nb insert end httpoll_page -text [::msgcat::mc "HTTP Poll"]]
+
+	checkbutton $l.usehttppoll -text [::msgcat::mc "Connect via HTTP polling"] \
+	    -variable ltmp(usehttppoll) \
+	    -command [list [namespace current]::update_login_entries $l]
+	label $l.lpollurl -text [::msgcat::mc "URL to poll:"]
+	entry $l.pollurl -textvariable ltmp(pollurl)
+	checkbutton $l.usepollkeys -text [::msgcat::mc "Use client security keys"] \
+	    -state disabled \
+	    -variable ltmp(usepollkeys) \
+	    -command [list [namespace current]::update_login_entries $l]
+    
+	grid $l.usehttppoll -row 0 -column 0 -sticky w -columnspan 3 -in $httppoll_page
+	grid $l.lpollurl -row 1 -column 0 -sticky e -in $httppoll_page
+	grid $l.pollurl -row 1 -column 1 -sticky ew -in $httppoll_page
+	grid $l.usepollkeys -row 2 -column 0 -sticky w -columnspan 3 -in $httppoll_page
+
+	grid columnconfigure $httppoll_page 1 -weight 1
+    }
+    
+    if {0 && $have_proxy} {
+	set proxy_page [$nb insert end proxy_page -text [::msgcat::mc "Proxy"]]
+
+	label $l.lproxy -text [::msgcat::mc "Proxy type:"]
+	grid $l.lproxy -row 0 -column 0 -sticky e -in $proxy_page
+	frame $l.proxy
+	grid $l.proxy -row 0 -column 1 -columnspan 3 -sticky w -in $proxy_page
+
+	set col 0
+
+	radiobutton $l.proxy.none -text [::msgcat::mc "None"] \
+		    -variable ltmp(proxy) -value none \
+		    -command [list [namespace current]::update_login_entries $l]
+	grid $l.proxy.none -row 0 -column [incr col] -sticky w
+
+	if {![catch {package present pconnect::https}]} {
+	    radiobutton $l.proxy.https -text [::msgcat::mc "HTTPS"] \
+			-variable ltmp(proxy) -value https \
+			-command [list [namespace current]::update_login_entries $l]
+	    grid $l.proxy.https -row 0 -column [incr col] -sticky w
+	}
+	if {![catch {package present pconnect::socks4}]} {
+	    radiobutton $l.proxy.socks4 -text [::msgcat::mc "SOCKS4a"] \
+			-variable ltmp(proxy) -value socks4 \
+			-command [list [namespace current]::update_login_entries $l]
+	    grid $l.proxy.socks4 -row 0 -column [incr col] -sticky w
+	}
+	if {![catch {package present pconnect::socks5}]} {
+	    radiobutton $l.proxy.socks5 -text [::msgcat::mc "SOCKS5"] \
+			-variable ltmp(proxy) -value socks5 \
+			-command [list [namespace current]::update_login_entries $l]
+	    grid $l.proxy.socks5 -row 0 -column [incr col] -sticky w
+	}
+
+	label $l.lproxyhost -text [::msgcat::mc "Proxy server:"]
+	entry $l.proxyhost -textvariable ltmp(proxyhost)
+	label $l.lproxyport -text [::msgcat::mc "Proxy port:"]
+	Spinbox $l.proxyport 0 65535 1 ltmp(proxyport)
+
+	grid $l.lproxyhost     -row 1 -column 0 -sticky e -in $proxy_page
+	grid $l.proxyhost      -row 1 -column 1 -sticky ew -in $proxy_page
+	grid $l.lproxyport -row 1 -column 2 -sticky e -in $proxy_page
+	grid $l.proxyport  -row 1 -column 3 -sticky ew -in $proxy_page
+
+	label $l.lproxyusername -text [::msgcat::mc "Proxy username:"]
+	ecursor_entry [entry $l.proxyusername -textvariable ltmp(proxyusername)]
+	label $l.lproxypassword -text [::msgcat::mc "Proxy password:"]
+	ecursor_entry [entry $l.proxypassword -show * -textvariable ltmp(proxypassword)]
+
+	grid $l.lproxyusername    -row 2 -column 0 -sticky e -in $proxy_page
+	grid $l.proxyusername     -row 2 -column 1 -sticky ew -in $proxy_page
+	grid $l.lproxypassword -row 2 -column 2 -sticky e -in $proxy_page
+	grid $l.proxypassword  -row 2 -column 3 -sticky ew -in $proxy_page
+
+	grid columnconfigure $proxy_page 1 -weight 3
+	grid columnconfigure $proxy_page 2 -weight 1
+	grid columnconfigure $proxy_page 3 -weight 3
+    }
+
+
+    $nb compute_size
+    $nb raise account_page
+    bind .login <Control-Prior> [list [namespace current]::tab_move [double% $nb] -1]
+    bind .login <Control-Next> [list [namespace current]::tab_move [double% $nb] 1]
+    grid $nb -row 1 -column 0
+
+    .login add -text [::msgcat::mc "Log in"] -command {
+	array set loginconf [array get ltmp]
+	destroy .login
+	if {$loginconf(replace_opened)} {
+	    logout
+	}
+	update 
+	login [array get loginconf]
+    }
+    .login add -text [::msgcat::mc "Cancel"] -command {destroy .login}
+
+    update_login_entries $l
+
+    if {[cequal $ltmp(user) ""]} {
+	.login draw $l.username
+    } elseif {[cequal $ltmp(password) ""]} {
+	.login draw $l.password
+    } else {
+	.login draw $l.resource
+    }
+}
+
+hook::add finload_hook {
+    if {(![info exist ::autologin]) || ($::autologin == 0)} {
+        ifacetk::login_dialog
+    } elseif {$::autologin > 0} {
+	logout
+	update
+	login [array get ::loginconf]
+    }
+} 9999
+
+proc logout_dialog {} {
+    global logout_conn
+    
+    set w .logout
+    if {[winfo exists $w]} {
+	destroy $w
+    }
+
+    switch -- [llength [connections]] {
+	0 -
+	1 {
+	    logout
+	    return
+	}
+    }
+
+    set lnames {}
+    foreach xlib [connections] {
+	lappend lnames $xlib [connection_jid $xlib]
+    }
+
+    if {[CbDialog $w [::msgcat::mc "Logout"] \
+	    [list [::msgcat::mc "Log out"] [list $w enddialog 0] \
+		  [::msgcat::mc "Cancel"] [list $w enddialog 1]] \
+	    logout_conn $lnames {} -modal local] != 0} {
+	return
+    }
+
+    foreach xlib [array names logout_conn] {
+	if {[lcontain [connections] $xlib] && $logout_conn($xlib)} {
+	    logout $xlib
+	}
+    }
+}
+
+# TODO
+proc change_password_dialog {} {
+    global oldpassword newpassword password
+
+    set oldpassword ""
+    set newpassword ""
+    set password ""
+
+    if {[winfo exists .passwordchange]} {
+	destroy .passwordchange
+    }
+    
+    Dialog .passwordchange -title [::msgcat::mc "Change password"] \
+	-separator 1 -anchor e -default 0 -cancel 1
+
+    .passwordchange add -text [::msgcat::mc "OK"] -command {
+	destroy .passwordchange
+	send_change_password
+    }
+    .passwordchange add -text [::msgcat::mc "Cancel"] -command [list destroy .passwordchange]
+
+
+    set p [.passwordchange getframe]
+    
+    label $p.loldpass -text [::msgcat::mc "Old password:"]
+    ecursor_entry [entry $p.oldpass -show * -textvariable oldpassword]
+    label $p.lnewpass -text [::msgcat::mc "New password:"]
+    ecursor_entry [entry $p.newpass -show * -textvariable newpassword]
+    label $p.lpassword -text [::msgcat::mc "Repeat new password:"]
+    ecursor_entry [entry $p.password -show * -textvariable password]
+
+    grid $p.loldpass  -row 0 -column 0 -sticky e
+    grid $p.oldpass   -row 0 -column 1 -sticky ew
+    grid $p.lnewpass  -row 1 -column 0 -sticky e
+    grid $p.newpass   -row 1 -column 1 -sticky ew
+    grid $p.lpassword -row 2 -column 0 -sticky e
+    grid $p.password  -row 2 -column 1 -sticky ew
+
+    focus $p.oldpass
+    .passwordchange draw
+
+}
+


Property changes on: trunk/tkabber/ifacetk/login.tcl
___________________________________________________________________
Added: svn:keywords
   + Author Date Id Revision
Added: svn:mergeinfo
   + 
Added: svn:eol-style
   + native

Added: trunk/tkabber/ifacetk/muc.tcl
===================================================================
--- trunk/tkabber/ifacetk/muc.tcl	                        (rev 0)
+++ trunk/tkabber/ifacetk/muc.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -0,0 +1,862 @@
+# $Id$
+
+namespace eval muc {
+    set winid 0
+
+    # MUC affiliations and roles:
+
+    array set e2l [list owner       [::msgcat::mc "owner"] \
+			admin       [::msgcat::mc "admin"] \
+			member      [::msgcat::mc "member"] \
+			outcast     [::msgcat::mc "outcast"] \
+			none        [::msgcat::mc "none"] \
+			moderator   [::msgcat::mc "moderator"] \
+			participant [::msgcat::mc "participant"] \
+			visitor     [::msgcat::mc "visitor"]]
+
+    variable maxl 0
+    foreach n [array names e2l] {
+	set l2e($e2l($n)) $n
+	set l [string length $e2l($n)]
+	if {$l + 2 > $maxl} {
+	    set maxl [expr {$l + 2}]
+	}
+    }
+
+    namespace export join_group_dialog add_muc_menu_items
+}
+
+custom::defvar ::gr_nick_list {} \
+        [::msgcat::mc "Join group dialog data (nicks)."] -group Hidden
+custom::defvar ::gr_group_list {} \
+        [::msgcat::mc "Join group dialog data (groups)."] -group Hidden
+custom::defvar ::gr_server_list {} \
+        [::msgcat::mc "Join group dialog data (servers)."] -group Hidden
+
+###############################################################################
+
+proc muc::join_group_dialog {xlib args} {
+    global gr_nick_list gr_group_list gr_server_list
+
+    if {[llength [connections]] == 0} return
+
+    if {[llength $gr_group_list] > 0} {
+	set gr_group [lindex $gr_group_list 0]
+    } else {
+	set gr_group jabber
+    }
+    if {[llength $gr_server_list] > 0} {
+	set gr_server [lindex $gr_server_list 0]
+    } else {
+	set gr_server conference.jabber.org
+    }
+    if {[string equal $xlib ""]} {
+	set xlib [lindex [connections] 0]
+    }
+    set gr_xlib [connection_jid $xlib]
+
+    set gr_nick [get_group_nick $xlib [::xmpp::jid::jid $gr_group $gr_server]]
+    set gr_passwd ""
+    foreach {opt val} $args {
+	switch -- $opt {
+	    -server     {set gr_server $val}
+	    -group      {set gr_group $val}
+	    -nick       {set gr_nick $val}
+	    -password   {set gr_passwd $val}
+	}
+    }
+
+    set gw .joingroup
+    catch { destroy $gw }
+
+    Dialog $gw -title [::msgcat::mc "Join group"] \
+	       -separator 1 \
+	       -anchor e \
+	       -default 0 \
+	       -cancel 1 \
+	       -modal none
+
+    set gf [$gw getframe]
+    grid columnconfigure $gf 1 -weight 1
+
+    label $gf.lgroup -text [::msgcat::mc "Group:"]
+    ComboBox $gf.group -text $gr_group -values $gr_group_list
+    label $gf.lserver -text [::msgcat::mc "Server:"]
+    ComboBox $gf.server -text $gr_server -values $gr_server_list
+    label $gf.lnick -text [::msgcat::mc "Nick:"]
+    ComboBox $gf.nick -text $gr_nick -values $gr_nick_list
+
+    label $gf.lpasswd -text [::msgcat::mc "Password:"]
+    Entry $gf.passwd -text $gr_passwd -width 30 -show *
+
+    grid $gf.lgroup  -row 0 -column 0 -sticky e
+    grid $gf.group   -row 0 -column 1 -sticky ew
+    grid $gf.lserver -row 1 -column 0 -sticky e
+    grid $gf.server  -row 1 -column 1 -sticky ew
+    grid $gf.lnick   -row 2 -column 0 -sticky e
+    grid $gf.nick    -row 2 -column 1 -sticky ew
+    grid $gf.lpasswd -row 3 -column 0 -sticky e
+    grid $gf.passwd  -row 3 -column 1 -sticky ew
+
+    if {[llength [connections]] > 1} {
+	foreach c [connections] {
+	    lappend connections [connection_jid $c]
+	}
+	label $gf.lxlib -text [::msgcat::mc "Connection:"]
+	ComboBox $gf.xlib -text $gr_xlib -values $connections
+
+	grid $gf.lxlib -row 4 -column 0 -sticky e
+	grid $gf.xlib  -row 4 -column 1 -sticky ew
+    }
+
+    $gw add -text [::msgcat::mc "Join"] \
+	    -command [namespace code [list join_group_dialog_continue $gw $gf]]
+    $gw add -text [::msgcat::mc "Cancel"] -command [list destroy $gw]
+    $gw draw $gf.gr_group
+}
+
+proc muc::join_group_dialog_continue {gw gf} {
+    global gr_nick_list gr_group_list gr_server_list
+
+    set gr_group  [$gf.group  get]
+    set gr_server [$gf.server get]
+    set gr_nick   [$gf.nick   get]
+    set gr_passwd [$gf.passwd get]
+    catch {set gr_xlib [$gf.xlib get]}
+
+    destroy $gw
+
+    set gr_group_list  [update_combo_list $gr_group_list  $gr_group  20]
+    set gr_server_list [update_combo_list $gr_server_list $gr_server 10]
+    set gr_nick_list   [update_combo_list $gr_nick_list   $gr_nick   10]
+
+    if {[info exists gr_xlib]} {
+	foreach c [connections] {
+	    if {[connection_jid $c] == $gr_xlib} {
+		set xlib $c
+	    }
+	}
+    }
+
+    if {![info exists xlib]} {
+	set xlib [lindex [connections] 0]
+    }
+
+    muc::join_group_raise $xlib \
+			  [::xmpp::jid::jid $gr_group $gr_server] \
+			  $gr_nick \
+			  $gr_passwd
+}
+
+###############################################################################
+
+proc muc::add_groupchat_user_menu_items {type m xlib jid} {
+    set group [::xmpp::jid::stripResource $jid]
+
+    if {![muc::is_compatible $group]} return
+
+    set chatid [chat::chatid $xlib $group]
+
+    if {![chat::is_groupchat $chatid]} {
+	return
+    }
+
+    switch -- $type {
+	chat { set reschatid [chat::chatid $xlib $jid] }
+	roster { set reschatid $chatid }
+    }
+
+    set mm [menu $m.muc -tearoff 0]
+    $mm add command -label [::msgcat::mc "Whois"] \
+	-command [list muc::whois $xlib $jid $reschatid]
+    $mm add command -label [::msgcat::mc "Kick"] \
+	-command [list muc::change_item_attr \
+		      $xlib $jid role none down "" $reschatid]
+    $mm add command -label [::msgcat::mc "Ban"] \
+	-command [list muc::change_item_attr \
+		      $xlib $jid affiliation outcast down "" $reschatid]
+    $mm add command -label [::msgcat::mc "Grant Voice"] \
+	-command [list muc::change_item_attr \
+		      $xlib $jid role participant up "" $reschatid]
+    $mm add command -label [::msgcat::mc "Revoke Voice"] \
+	-command [list muc::change_item_attr \
+		      $xlib $jid role visitor down "" $reschatid]
+    $mm add command -label [::msgcat::mc "Grant Membership"] \
+	-command [list muc::change_item_attr \
+		      $xlib $jid affiliation member up "" $reschatid]
+    $mm add command -label [::msgcat::mc "Revoke Membership"] \
+	-command [list muc::change_item_attr \
+		      $xlib $jid affiliation none down "" $reschatid]
+    $mm add command -label [::msgcat::mc "Grant Moderator Privileges"] \
+	-command [list muc::change_item_attr \
+		      $xlib $jid role moderator up "" $reschatid]
+    $mm add command -label [::msgcat::mc "Revoke Moderator Privileges"] \
+	-command [list muc::change_item_attr \
+		      $xlib $jid role participant down "" $reschatid]
+    $mm add command -label [::msgcat::mc "Grant Admin Privileges"] \
+	-command [list muc::change_item_attr \
+		      $xlib $jid affiliation admin up "" $reschatid]
+    $mm add command -label [::msgcat::mc "Revoke Admin Privileges"] \
+	-command [list muc::change_item_attr \
+		      $xlib $jid affiliation member down "" $reschatid]
+    #$mm add command -label [::msgcat::mc "Grant Owner Privileges"] \
+    #    -command [list muc::change_item_attr \
+    #    	      $xlib $jid affiliation owner up "" $reschatid]
+    #$mm add command -label [::msgcat::mc "Revoke Owner Privileges"] \
+    #    -command [list muc::change_item_attr \
+    #		      $xlib $jid affiliation admin down "" $reschatid]
+
+    $m add cascade -label [::msgcat::mc "MUC"] -menu $mm
+}
+
+hook::add chat_create_user_menu_hook \
+	  [list [namespace current]::muc::add_groupchat_user_menu_items chat] 43
+hook::add roster_create_groupchat_user_menu_hook \
+	  [list [namespace current]::muc::add_groupchat_user_menu_items roster] 39
+
+###############################################################################
+
+proc muc::add_muc_menu_items {m xlib group {state disabled}} {
+    if {[muc::is_compatible $group]} {
+	set state normal
+    }
+
+    set chatid [chat::chatid $xlib $group]
+
+    set mm [menu $m.muc -tearoff 0]
+
+    $mm add command -label [::msgcat::mc "Configure room"] \
+	-command [list muc::request_config $chatid]
+    $mm add command -label [::msgcat::mc "Edit voice list"] \
+	-command [namespace code [list request_roles_list participant $chatid]]
+    $mm add command -label [::msgcat::mc "Edit ban list"] \
+	-command [namespace code [list request_affiliations_list outcast $chatid]]
+    $mm add command -label [::msgcat::mc "Edit member list"] \
+	-command [namespace code [list request_affiliations_list member $chatid]]
+    $mm add command -label [::msgcat::mc "Edit moderator list"] \
+	-command [namespace code [list request_roles_list moderator $chatid]]
+    $mm add command -label [::msgcat::mc "Edit admin list"] \
+	-command [namespace code [list request_affiliations_list admin $chatid]]
+    $mm add command -label [::msgcat::mc "Edit owner list"] \
+	-command [namespace code [list request_affiliations_list owner $chatid]]
+    $mm add separator
+    $mm add command -label [::msgcat::mc "Destroy room"] \
+	-command [namespace code [list request_destruction_dialog $chatid "" ""]]
+
+    $m add cascade -label [::msgcat::mc "MUC"] -menu $mm -state $state
+}
+
+hook::add chat_create_conference_menu_hook \
+	  [namespace current]::muc::add_muc_menu_items 37
+
+proc muc::disco_node_menu_setup {m bw tnode data parentdata} {
+    lassign $data type xlib jid node
+    switch -- $type {
+	item -
+	item2 {
+	    set identities [disco::browser::get_identities $bw $tnode]
+
+	    if {[lempty $identities]} {
+		set identities [disco::browser::get_parent_identities $bw $tnode]
+	    }
+
+	    set features [disco::browser::get_features $bw $tnode]
+
+	    if {[lempty $features]} {
+		set features [disco::browser::get_parent_features $bw $tnode]
+	    }
+
+	    # JID with resource is not a room JID
+	    if {[::xmpp::jid::stripResource $jid] != $jid} return
+
+	    # A room must have non-empty node
+	    if {[::xmpp::jid::node $jid] == ""} return
+
+	    foreach id $identities {
+		if {[::xmpp::xml::getAttr $id category] == "conference"} {
+		    if {[lsearch -exact $f http://jabber.org/protocol/muc] >= 0} {
+			add_muc_menu_items $m $xlib $jid normal
+		    }
+		}
+	    }
+	}
+    }
+}
+
+hook::add disco_node_menu_hook \
+	  [namespace current]::muc::disco_node_menu_setup 60
+
+###############################################################################
+
+proc muc::request_destruction_dialog {chatid alt reason} {
+    set xlib [chat::get_xlib $chatid]
+    set group [chat::get_jid $chatid]
+
+    set w .muc_request_destruction
+    if {[info exists $w]} {
+	destroy $w
+    }
+
+    set res [MessageDlg $w \
+			-aspect 50000 \
+			-icon warning \
+			-type user \
+			-buttons {yes no} \
+			-default 1 \
+			-cancel 1 \
+			-message [::msgcat::mc "Conference room %s will be\
+					destroyed permanently.\n\nProceed?" \
+					$group]]
+    if {!$res} {
+	set args \
+	    [list -command [list muc::test_error_res "destroy" $xlib $group $chatid]]
+	if {![string equal $alt ""]} {
+	    lappend args -jid $alt
+	}
+	if {![string equal $reason ""]} {
+	    lappend args -reason $reason
+	}
+	eval [list ::xmpp::muc::destroy $xlib $group] $args
+    }
+}
+
+###############################################################################
+
+proc muc::request_affiliations_list {val chatid} {
+    set xlib [chat::get_xlib $chatid]
+    set group [chat::get_jid $chatid]
+
+    ::xmpp::muc::requestAffiliations $xlib $group $val \
+	    -command [namespace code [list receive_list affiliation $val $chatid]]
+}
+
+proc muc::request_roles_list {val chatid} {
+    set xlib [chat::get_xlib $chatid]
+    set group [chat::get_jid $chatid]
+
+    ::xmpp::muc::requestRoles $xlib $group $val \
+	    -command [namespace code [list receive_list role $val $chatid]]
+}
+
+proc muc::receive_list {attr val chatid res items} {
+    variable e2l
+    variable maxl
+
+    set xlib [chat::get_xlib $chatid]
+    set group [chat::get_jid $chatid]
+    if {![string equal $res ok]} {
+	chat::add_message $chatid $group error \
+	    "$attr $val list: [error_to_string $items]" {}
+	return
+    }
+
+    variable winid
+
+    set w .muc_list$winid
+    incr winid
+
+    if {[winfo exists $w]} {
+	destroy $w
+    }
+
+    # [format] is intentional here. After %s substitution, it becomes
+    # one of the following:
+    # [::msgcat::mc "Edit owner list"]
+    # [::msgcat::mc "Edit admin list"]
+    # [::msgcat::mc "Edit member list"]
+    # [::msgcat::mc "Edit outcast list"]
+    # [::msgcat::mc "Edit moderator list"]
+    # [::msgcat::mc "Edit participant list"]
+    # [::msgcat::mc "Edit visitor list"]
+    Dialog $w -title [::msgcat::mc [format "Edit %s list" $val]] \
+	      -modal none \
+	      -separator 1 \
+	      -anchor e \
+	      -default 0 \
+	      -cancel 1
+
+    set wf [$w getframe]
+
+    set sw [ScrolledWindow $wf.sw]
+
+    set lb [::mclistbox::mclistbox $sw.listbox \
+		    -exportselection 0 \
+		    -resizeonecolumn 1 \
+		    -labelanchor w \
+		    -width 90 \
+		    -height 16]
+
+    bind $lb <Destroy> [namespace code [list list_cleanup %W]]
+
+    bind $lb <<ListboxSelect>> [namespace code [list update_fields %W [double% $val] [double% $w.fr1]]]
+
+    $sw setwidget $lb
+    fill_list $lb $items $attr $val
+
+    $w add -text [::msgcat::mc "Send"] \
+	-command [namespace code [list send_list $chatid $attr $val $w $lb]]
+    $w add -text [::msgcat::mc "Cancel"] -command [list destroy $w]
+
+
+    frame $w.fr1
+    pack $w.fr1 -side bottom -in $wf -fill x -pady 1m
+
+    grid columnconfigure $w.fr1 3 -weight 1
+
+    label $w.fr1.lnick -text [::msgcat::mc "Nick"]
+    label $w.fr1.ljid -text [::msgcat::mc "JID"]
+    switch -- $attr {
+	role {
+	    label $w.fr1.lattr -text [::msgcat::mc "Role"]
+	}
+	affiliation {
+	    label $w.fr1.lattr -text [::msgcat::mc "Affiliation"]
+	}
+    }
+    label $w.fr1.lreason -text [::msgcat::mc "Reason"]
+
+    grid $w.fr1.lnick   -row 0 -column 0 -sticky w
+    grid $w.fr1.ljid    -row 0 -column 1 -sticky w
+    grid $w.fr1.lattr   -row 0 -column 2 -sticky w
+    grid $w.fr1.lreason -row 0 -column 3 -sticky w
+
+    entry $w.fr1.nick1 -takefocus 0 -highlightthickness 0
+    if {[catch {$w.fr1.nick1 configure -state readonly}]} {
+	$w.fr1.nick1 configure -state disabled
+    }
+    entry $w.fr1.jid1 -takefocus 0 -highlightthickness 0
+    if {[catch {$w.fr1.jid1 configure -state readonly}]} {
+	$w.fr1.jid1 configure -state disabled
+    }
+
+    switch -- $attr {
+	role {
+	    ComboBox $w.fr1.attr1 -text $e2l($val) \
+		-values [list $e2l(moderator) $e2l(participant) $e2l(visitor) $e2l(none)] \
+		-editable no \
+		-width $maxl
+	}
+	affiliation {
+	    ComboBox $w.fr1.attr1 -text $e2l($val) \
+		-values [list $e2l(owner) $e2l(admin) $e2l(member) $e2l(none) $e2l(outcast)] \
+		-editable no \
+		-width $maxl
+	}
+    }
+
+    entry $w.fr1.reason1
+
+    button $w.fr1.update -text [::msgcat::mc "Update item"] \
+			 -command [namespace code [list list_update_item $lb $attr $w.fr1]]
+
+    grid $w.fr1.nick1   -row 1 -column 0 -sticky ew
+    grid $w.fr1.jid1    -row 1 -column 1 -sticky ew
+    grid $w.fr1.attr1   -row 1 -column 2 -sticky ew
+    grid $w.fr1.reason1 -row 1 -column 3 -sticky ew
+    grid $w.fr1.update  -row 1 -column 4 -sticky ew
+
+    entry $w.fr1.nick2
+    entry $w.fr1.jid2
+
+    switch -- $attr {
+	role {
+	    ComboBox $w.fr1.attr2 -text $e2l($val) \
+		-values [list $e2l(moderator) $e2l(participant) $e2l(visitor) $e2l(none)] \
+		-editable no \
+		-width $maxl
+	}
+	affiliation {
+	    ComboBox $w.fr1.attr2 -text $e2l($val) \
+		-values [list $e2l(owner) $e2l(admin) $e2l(member) $e2l(none) $e2l(outcast)] \
+		-editable no \
+		-width $maxl
+	}
+    }
+
+    entry $w.fr1.reason2
+
+    button $w.fr1.add -text [::msgcat::mc "Add new item"] \
+		      -command [namespace code [list list_add_item $lb $val $w.fr1]]
+
+    grid $w.fr1.nick2   -row 2 -column 0 -sticky ew
+    grid $w.fr1.jid2    -row 2 -column 1 -sticky ew
+    grid $w.fr1.attr2   -row 2 -column 2 -sticky ew
+    grid $w.fr1.reason2 -row 2 -column 3 -sticky ew
+    grid $w.fr1.add     -row 2 -column 4 -sticky ew
+
+    frame $w.fr1.fr
+
+    label $w.lall -text [::msgcat::mc "All items:"]
+    pack $w.lall -side left -in $w.fr1.fr -padx 1m -pady 1m
+
+    switch -- $attr {
+	role {
+	    ComboBox $w.roleall -text $e2l($val) \
+		-values [list $e2l(moderator) $e2l(participant) $e2l(visitor) $e2l(none)] \
+		-editable no \
+		-width $maxl \
+		-modifycmd [namespace code [list change_all_items $lb $w.roleall $attr]]
+	    pack $w.roleall -side left -anchor w -in $w.fr1.fr -pady 1m
+	}
+	affiliation {
+	    ComboBox $w.affiliationall -text $e2l($val) \
+		-values [list $e2l(owner) $e2l(admin) $e2l(member) $e2l(none) $e2l(outcast)] \
+		-editable no \
+		-width $maxl \
+		-modifycmd [namespace code [list change_all_items $lb $w.affiliationall $attr]]
+	    pack $w.affiliationall -side left -in $w.fr1.fr -pady 1m
+	}
+    }
+
+    grid $w.fr1.fr -row 3 -column 0 -columnspan 5 -sticky w
+
+    pack $sw -side top -expand yes -fill both
+
+    bindscroll $sw $lb
+
+    $w draw
+}
+
+###############################################################################
+
+proc muc::trim {str} {
+    string range $str 1 end-1
+}
+
+proc muc::untrim {str} {
+    return " $str "
+}
+
+proc muc::update_fields {lb val fr} {
+    set selection [$lb curselection]
+    if {[llength $selection] == 0} {
+	set nick ""
+	set jid ""
+	set attr $val
+	set reason ""
+    } else {
+	set data [$lb get [lindex $selection 0]]
+	lassign $data n nick jid attr reason
+    }
+
+    $fr.nick1 configure -state normal
+    $fr.nick1 delete 0 end
+    $fr.nick1 insert 0 [trim $nick]
+    if {[catch {$fr.nick1 configure -state readonly}]} {
+	$fr.nick1 configure -state disabled
+    }
+    $fr.jid1 configure -state normal
+    $fr.jid1 delete 0 end
+    $fr.jid1 insert 0 [trim $jid]
+    if {[catch {$fr.jid1 configure -state readonly}]} {
+	$fr.jid1 configure -state disabled
+    }
+    $fr.attr1 configure -text [trim $attr]
+    $fr.reason1 delete 0 end
+    $fr.reason1 insert 0 [trim $reason]
+}
+
+proc muc::list_update_item {lb attr fr} {
+    set selection [$lb curselection]
+    if {[llength $selection] == 0} {
+	return
+    }
+    set idx [lindex $selection 0]
+    set data [lrange [$lb get $idx] 0 0]
+
+    lappend data [untrim [$fr.nick1 get]] \
+		 [untrim [$fr.jid1 get]] \
+		 [untrim [$fr.attr1 get]] \
+		 [untrim [$fr.reason1 get]]
+
+    incr idx
+    $lb insert $idx $data
+    incr idx -1
+    $lb delete $idx
+    $lb selection clear 0 end
+    $lb selection set $idx
+}
+
+proc muc::change_all_items {lb combobox attr} {
+    set value [$combobox get]
+
+    set yposition [lindex [$lb yview] 0]
+    set data [$lb get 0 end]
+    set result {}
+    set i 0
+    foreach row $data {
+	lappend result [lreplace $row 3 3 [untrim $value]]
+    }
+    $lb delete 0 end
+    eval $lb insert end $result
+    $lb yview moveto $yposition
+}
+
+###############################################################################
+
+proc muc::fill_list {lb items attr val} {
+    variable listdata
+    variable lastsort
+    variable e2l
+    variable maxl
+
+    set lastsort($lb) jid
+
+    set width(0) 3
+    set name(0) n
+    $lb column add n -label [untrim [::msgcat::mc "#"]]
+
+    set width(1) [expr {[string length [::msgcat::mc "Nick"]] + 2}]
+    set name(1) nick
+    $lb column add nick -label [untrim [::msgcat::mc "Nick"]]
+    $lb label bind nick <ButtonPress-1> [namespace code [list sort %W nick]]
+
+    set width(2) [expr {[string length [::msgcat::mc "JID"]] + 2}]
+    set name(2) jid
+    $lb column add jid -label [untrim [::msgcat::mc "JID"]]
+    $lb label bind jid <ButtonPress-1> [namespace code [list sort %W jid]]
+
+    switch -- $attr {
+	role {
+	    set width(3) [expr {[string length [::msgcat::mc "Role"]] + 2}]
+	    if {$width(3) < $maxl} {
+		set width(3) $maxl
+	    }
+	    set name(3) role
+	    $lb column add role -label [untrim [::msgcat::mc "Role"]]
+	    $lb label bind role <ButtonPress-1> [namespace code [list sort %W role]]
+	}
+	affiliation {
+	    set width(3) [expr {[string length [::msgcat::mc "Affiliation"]] + 2}]
+	    if {$width(3) < $maxl} {
+		set width(3) $maxl
+	    }
+	    set name(3) affiliation
+	    $lb column add affiliation -label [untrim [::msgcat::mc "Affiliation"]]
+	    $lb label bind affiliation <ButtonPress-1> [namespace code [list sort %W affiliation]]
+	}
+    }
+
+    set width(4) [expr {[string length [::msgcat::mc "Reason"]] + 2}]
+    set name(4) reason
+    $lb column add reason -label [untrim [::msgcat::mc "Reason"]]
+    $lb label bind reason <ButtonPress-1> [namespace code [list sort %W reason]]
+
+    $lb column add lastcol -label "" -width 0
+    $lb configure -fillcolumn lastcol
+
+    set items2 {}
+    foreach item $items {
+	lassign $item nick jid attribute reason
+	lappend items2 [list $nick $jid $e2l($attribute) $reason]
+    }
+
+    set listdata($lb) [lsort -dictionary -index 1 $items2]
+    set row 1
+    foreach item $listdata($lb) {
+	set itemdata {}
+	set i 0
+	foreach data [linsert $item 0 $row] {
+	    set wd [string length [untrim $data]]
+	    if {$wd > $width($i)} {
+		if {$wd > 50} {
+		    set width($i) 50
+		} else {
+		    set width($i) $wd
+		}
+	    }
+	    lappend itemdata [untrim $data]
+	    incr i
+	}
+	lappend itemdata ""
+	$lb insert end $itemdata
+
+	incr row
+    }
+
+    for {set i 0} {$i < 5} {incr i} {
+	$lb column configure $name($i) -width $width($i)
+    }
+}
+
+proc muc::sort {lb tag} {
+    variable lastsort
+
+    set data [$lb get 0 end]
+    set index [lsearch -exact [$lb column names] $tag]
+    if {$lastsort($lb) != $tag} {
+	set result [lsort -dictionary -index $index $data]
+	set lastsort($lb) $tag
+    } else {
+	set result [lsort -decreasing -dictionary -index $index $data]
+	set lastsort($lb) ""
+    }
+    set result1 {}
+    set i 0
+    foreach row $result {
+	lappend result1 [lreplace $row 0 0 [untrim [incr i]]]
+    }
+    $lb delete 0 end
+    eval $lb insert end $result1
+}
+
+###############################################################################
+
+proc muc::list_add_item {lb val fr} {
+    variable e2l
+
+    set n [trim [lindex [$lb get end] 0]]
+    if {[string equal $n ""]} {
+	set n 0
+    }
+
+    set data [list [untrim [incr n]] \
+	     [untrim [$fr.nick2 get]] \
+	     [untrim [$fr.jid2 get]] \
+	     [untrim [$fr.attr2 get]] \
+	     [untrim [$fr.reason2 get]]]
+
+    $lb insert end $data
+
+    $fr.nick2 delete 0 end
+    $fr.jid2 delete 0 end
+    $fr.attr2 configure -text $e2l($val)
+    $fr.reason2 delete 0 end
+}
+
+###############################################################################
+
+proc muc::send_list {chatid attr val w lb} {
+    variable listdata
+    variable l2e
+
+    foreach item $listdata($lb) {
+	set tmp([lrange $item 0 1]) $item
+    }
+
+    set items {}
+    foreach item [$lb get 0 end] {
+	lassign $item n nick jid val reason
+	set nick [trim $nick]
+	set jid [trim $jid]
+
+	if {$nick == "" && $jid == ""} {
+	    continue
+	}
+
+	set val [trim $val]
+	set reason [trim $reason]
+
+	if {[info exists tmp([list $nick $jid])] && \
+		$tmp([list $nick $jid]) == [list $nick $jid $val $reason]} continue
+
+	lappend items [list $nick $jid $l2e($val) $reason]
+    }
+
+    if {$items != {}} {
+	set xlib [chat::get_xlib $chatid]
+	set group [chat::get_jid $chatid]
+
+	set newArgs [list \
+	    -command [list muc::test_error_res \
+			   [::msgcat::mc "Sending %s %s list" $attr $val] \
+			   $xlib $group $chatid]]
+
+	switch -- $attr {
+	    affiliation {
+		eval [list ::xmpp::muc::sendAffiliations $xlib $group $items] \
+			   $newArgs
+	    }
+	    role {
+		eval [list ::xmpp::muc::sendRoles $xlib $group $items] $newArgs
+	    }
+	}
+    }
+    destroy $w
+}
+
+proc muc::list_cleanup {f} {
+    variable listdata
+    variable lastsort
+
+    catch {unset listdata($f)}
+    catch {unset lastsort($f)}
+}
+
+###############################################################################
+
+proc muc::joingroup_disco_node_menu_setup {m bw tnode data parentdata} {
+    lassign $data type xlib jid node
+    switch -- $type {
+	item -
+	item2 {
+	    set identities [::disco::browser::get_identities $bw $tnode]
+
+	    if {[lempty $identities]} {
+		set identities \
+		    [::disco::browser::get_parent_identities $bw $tnode]
+	    }
+
+	    # JID with resource is not a room JID
+	    if {[::xmpp::jid::resource $jid] != ""} return
+
+	    foreach id $identities {
+		if {[string equal [::xmpp::xml::getAttr $id category] \
+				  conference]} {
+		    $m add command -label [::msgcat::mc "Join group..."] \
+			-command [namespace code \
+				    [list join_group_dialog $xlib \
+					  -server [::xmpp::jid::server $jid] \
+					  -group [::xmpp::jid::node $jid]]]
+		    break
+		}
+	    }
+	}
+    }
+}
+
+hook::add disco_node_menu_hook \
+	  [namespace current]::muc::joingroup_disco_node_menu_setup 45
+
+###############################################################################
+
+proc muc::join {xlib jid args} {
+    set category conference
+    foreach {opt val} $args {
+	switch -- $opt {
+	    -category { set category $val }
+	}
+    }
+
+    if {![string equal $category conference]} return
+
+    if {![string equal [::xmpp::jid::node $jid] ""]} {
+	muc::join_group_raise $xlib $jid [get_group_nick $xlib $jid] ""
+    } else {
+	join_group_dialog $xlib -server [::xmpp::jid::server $jid] -group {}
+    }
+}
+
+hook::add postload_hook \
+    [list disco::browser::register_feature_handler jabber:iq:conference \
+	 [namespace current]::muc::join \
+	 -desc [list conference [::msgcat::mc "Join conference"]]]
+hook::add postload_hook \
+    [list disco::browser::register_feature_handler http://jabber.org/protocol/muc \
+	 [namespace current]::muc::join \
+	 -desc [list conference [::msgcat::mc "Join conference"]]]
+hook::add postload_hook \
+    [list disco::browser::register_feature_handler "gc-1.0" \
+	 [namespace current]::muc::join \
+	 -desc [list conference [::msgcat::mc "Join groupchat"]]]
+
+###############################################################################
+
+namespace eval :: {
+    namespace import ::ifacetk::muc::join_group_dialog \
+		     ::ifacetk::muc::add_muc_menu_items
+}
+
+###############################################################################
+
+# vim:ts=8:sw=4:sts=4:noet


Property changes on: trunk/tkabber/ifacetk/muc.tcl
___________________________________________________________________
Added: svn:keywords
   + Author Date Id Revision
Added: svn:eol-style
   + native

Copied: trunk/tkabber/ifacetk/roster.tcl (from rev 1914, trunk/tkabber/ifacetk/iroster.tcl)
===================================================================
--- trunk/tkabber/ifacetk/roster.tcl	                        (rev 0)
+++ trunk/tkabber/ifacetk/roster.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -0,0 +1,2225 @@
+# $Id$
+
+Tree .faketree
+foreach {k v} [list background       White \
+		    foreground       Black] {
+    if {[string equal [set t$k [option get .faketree $k Tree]] ""]} {
+	set t$k $v
+    }
+}
+destroy .faketree
+
+Button .fakebutton
+foreach {k v} [list background       Gray \
+		    activeBackground LightGray] {
+    if {[string equal [set b$k [option get .fakebutton $k Button]] ""]} {
+	set b$k $v
+    }
+}
+destroy .fakebutton
+
+option add *Roster.cbackground            $tbackground       widgetDefault
+option add *Roster.groupindent            22                 widgetDefault
+option add *Roster.jidindent              24                 widgetDefault
+option add *Roster.jidmultindent          40                 widgetDefault
+option add *Roster.subjidindent           34                 widgetDefault
+option add *Roster.groupiconindent         2                 widgetDefault
+option add *Roster.subgroupiconindent     22                 widgetDefault
+option add *Roster.iconindent              3                 widgetDefault
+option add *Roster.subitemtype             1                 widgetDefault
+option add *Roster.subiconindent          13                 widgetDefault
+option add *Roster.textuppad               1                 widgetDefault
+option add *Roster.textdownpad             1                 widgetDefault
+option add *Roster.linepad                 2                 widgetDefault
+option add *Roster.foreground             $tforeground       widgetDefault
+option add *Roster.jidfill                $tbackground       widgetDefault
+option add *Roster.jidhlfill              $bactiveBackground widgetDefault
+option add *Roster.jidborder              $tbackground       widgetDefault
+option add *Roster.metajidfill            $bbackground       widgetDefault
+option add *Roster.metajidhlfill          $bactiveBackground widgetDefault
+option add *Roster.metajidborder          $bbackground       widgetDefault
+option add *Roster.groupfill              $bbackground       widgetDefault
+option add *Roster.groupcfill             $bbackground       widgetDefault
+option add *Roster.grouphlfill            $bactiveBackground widgetDefault
+option add *Roster.groupborder            $tforeground       widgetDefault
+option add *Roster.connectionfill         $tbackground       widgetDefault
+option add *Roster.connectioncfill        $tbackground       widgetDefault
+option add *Roster.connectionhlfill       $bactiveBackground widgetDefault
+option add *Roster.connectionborder       $tforeground       widgetDefault
+option add *Roster.unsubscribedforeground #663333            widgetDefault
+option add *Roster.unavailableforeground  #666666            widgetDefault
+option add *Roster.dndforeground          #666633            widgetDefault
+option add *Roster.xaforeground           #004d80            widgetDefault
+option add *Roster.awayforeground         #004d80            widgetDefault
+option add *Roster.availableforeground    #0066cc            widgetDefault
+option add *Roster.chatforeground         #0099cc            widgetDefault
+
+unset tbackground tforeground bbackground bactiveBackground
+
+namespace eval roster {
+    custom::defgroup Roster [::msgcat::mc "Roster options."] -group Tkabber
+    custom::defvar show_only_online 0 \
+	[::msgcat::mc "Show only online users in roster."] \
+	-type boolean -group Roster \
+	-command [namespace current]::redraw_after_idle
+    custom::defvar show_transport_icons 0 \
+	[::msgcat::mc "Show native icons for transports/services in roster."] \
+	-type boolean -group Roster \
+	-command [namespace current]::redraw_after_idle
+    custom::defvar show_transport_user_icons 0 \
+	[::msgcat::mc "Show native icons for contacts, connected to transports/services in roster."] \
+	-type boolean -group Roster \
+	-command [namespace current]::redraw_after_idle
+    custom::defvar options(nested) 0 \
+	[::msgcat::mc "Enable nested roster groups."] \
+	-type boolean -group Roster \
+	-command [namespace current]::redraw_after_idle
+    custom::defvar options(nested_delimiter) "::" \
+	[::msgcat::mc "Default nested roster group delimiter."] \
+	-type string -group Roster \
+	-command [namespace current]::redraw_after_idle
+    custom::defvar options(show_own_resources) 0 \
+	[::msgcat::mc "Show my own resources in the roster."] \
+	-type boolean -group Roster \
+	-command [namespace current]::redraw_after_idle
+    custom::defvar options(chats_group) 0 \
+	[::msgcat::mc "Add chats group in roster."] \
+	-type boolean -group Roster \
+	-command [namespace current]::redraw_after_idle
+    custom::defvar options(use_filter) 0 \
+	[::msgcat::mc "Use roster filter."] \
+	-type boolean -group Roster \
+	-command [namespace current]::pack_filter_entry
+    custom::defvar options(match_jids) 0 \
+	[::msgcat::mc "Match contact JIDs in addition to nicknames in roster filter."] \
+	-type boolean -group Roster \
+	-command [namespace current]::redraw_after_idle
+    custom::defvar options(enable_metacontacts) 1 \
+	[::msgcat::mc "Enable grouping contacts into a single metacontact in roster."] \
+	-type boolean -group Roster \
+	-command [namespace current]::redraw_after_idle
+    custom::defvar options(enable_metacontact_labels) 1 \
+	[::msgcat::mc "Always use main JID label for metacontact."] \
+	-type boolean -group Roster \
+	-command [namespace current]::redraw_after_idle
+    custom::defvar options(free_drop) 1 \
+	[::msgcat::mc "Roster item may be dropped not only over group name\
+but also over any item in group."] \
+	-type boolean -group Roster
+    custom::defvar options(show_subscription) 0 \
+	[::msgcat::mc "Show subscription type in roster item tooltips."] \
+	-type boolean -group Roster
+    custom::defvar options(show_conference_user_info) 0 \
+	[::msgcat::mc "Show detailed info on conference room members in roster item tooltips."] \
+	-type boolean -group Roster
+    custom::defvar options(chat_on_doubleclick) 1 \
+	[::msgcat::mc "If set then open chat window/tab when user doubleclicks roster item.\
+		       Otherwise open normal message window."] \
+	-type boolean -group Roster
+
+#   {jid1 {group1 1 group2 0} jid2 {group3 0 group4 0}}
+    custom::defvar collapsed_group_list {} \
+	[::msgcat::mc "Stored collapsed roster groups."] \
+	-type string -group Hidden
+#   {jid1 {group1 1 group2 0} jid2 {group3 0 group4 0}}
+    custom::defvar show_offline_group_list {} \
+	[::msgcat::mc "Stored show offline roster groups."] \
+	-type string -group Hidden
+    custom::defvar options(filter) "" \
+	[::msgcat::mc "Roster filter."] \
+	-type string -group Hidden \
+	-command [namespace current]::redraw_after_idle
+
+    variable menu_item_idx 0
+
+    variable undef_group_name $::roster::undef_group_name
+    variable chats_group_name $::roster::chats_group_name
+    variable own_resources_group_name $roster::own_resources_group_name
+}
+
+proc roster::get_group_lists {xlib} {
+    variable collapsed_group_list
+    variable show_offline_group_list
+    variable options
+    variable roster
+
+    set jid [::xmpp::jid::normalize [connection_bare_jid $xlib]]
+    array set c $collapsed_group_list
+    array set s $show_offline_group_list
+
+    if {[info exists c($jid)]} {
+	foreach {group val} $c($jid) {
+	    if {$options(nested)} {
+		set gid [list $xlib [msplit $group $options(nested_delimiter)]]
+	    } else {
+		set gid [list $xlib [list $group]]
+	    }
+	    set roster(collapsed,$gid) $val
+	}
+    }
+
+    if {[info exists s($jid)]} {
+	foreach {group val} $s($jid) {
+	    if {$options(nested)} {
+		set gid [list $xlib [msplit $group $options(nested_delimiter)]]
+	    } else {
+		set gid [list $xlib [list $group]]
+	    }
+	    set roster(show_offline,$gid) $val
+	}
+    }
+}
+
+hook::add connected_hook [namespace current]::roster::get_group_lists 1
+
+proc roster::set_group_lists {xlib} {
+    variable collapsed_group_list
+    variable show_offline_group_list
+    variable options
+    variable roster
+    variable undef_group_name
+    variable chats_group_name
+
+    set jid [::xmpp::jid::normalize [connection_bare_jid $xlib]]
+    array set c $collapsed_group_list
+    array set s $show_offline_group_list
+
+    set groups [roster::get_groups $xlib -raw 1]
+    lappend groups $undef_group_name $chats_group_name
+
+    if {$options(nested)} {
+	set tmp {}
+	foreach group $groups {
+	    set tmp1 [msplit $group $options(nested_delimiter)]
+	    for {set i 0} {$i < [llength $tmp1]} {incr i} {
+		lappend tmp [lrange $tmp1 0 $i]
+	    }
+	}
+	set groups [lsort -unique $tmp]
+    }
+
+    set c($jid) {}
+    set s($jid) {}
+    foreach group $groups {
+	if {$options(nested)} {
+	    set grname [join $group $options(nested_delimiter)]
+	    set gid [list $xlib $group]
+	} else {
+	    set grname $group
+	    set gid [list $xlib [list $group]]
+	}
+	if {[info exists roster(collapsed,$gid)] && $roster(collapsed,$gid)} {
+	    lappend c($jid) $grname $roster(collapsed,$gid)
+	}
+	if {[info exists roster(show_offline,$gid)] && $roster(show_offline,$gid)} {
+	    lappend s($jid) $grname $roster(show_offline,$gid)
+	}
+    }
+
+    if {[llength $c($jid)] == 0} {
+	unset c($jid)
+    }
+    if {[llength $s($jid)] == 0} {
+	unset s($jid)
+    }
+
+    set collapsed_group_list [array get c]
+    set show_offline_group_list [array get s]
+}
+
+hook::add disconnected_hook [namespace current]::roster::set_group_lists 40
+
+proc roster::process_item {xlib jid name groups subsc ask} {
+    after cancel [namespace parent]::update_chat_titles
+    after idle [namespace parent]::update_chat_titles
+}
+
+hook::add roster_push_hook [namespace current]::roster::process_item 90
+
+proc roster::create_filter_entry {} {
+    entry .roster_filter -textvariable [namespace current]::options(filter) \
+			 -width 1
+    bind .roster_filter <Escape> \
+	 [list set [namespace current]::options(filter) ""]
+    pack_filter_entry
+}
+
+hook::add finload_hook [namespace current]::roster::create_filter_entry
+
+proc roster::pack_filter_entry {args} {
+    global usetabbar
+    variable options
+
+    if {$options(use_filter)} {
+	if {$usetabbar} {
+	    pack .roster_filter -before .roster \
+				-anchor w \
+				-side bottom \
+				-fill x \
+				-expand no \
+				-in $::ifacetk::rw
+	} else {
+	    grid .roster_filter -row 2 \
+				-column 1 \
+				-sticky we \
+				-in [$::ifacetk::mf getframe]
+	}
+	focus .roster_filter
+    } else {
+	if {$usetabbar} {
+	    pack forget .roster_filter
+	} else {
+	    grid forget .roster_filter
+	}
+    }
+    redraw_after_idle
+}
+
+proc roster::filter_match {xlib jid} {
+    variable options
+
+    if {$options(use_filter) && $options(filter) != ""} {
+	if {[string first [string tolower $options(filter)] \
+			  [string tolower [::roster::get_label $xlib $jid]]] < 0} {
+	    if {!$options(match_jids) || \
+		    [string first [string tolower $options(filter)] \
+				  [string tolower $jid]] < 0} {
+		return 0
+	    }
+	}
+    }
+
+    return 1
+}
+
+proc roster::redraw {} {
+    variable roster
+    variable options
+    variable config
+    variable show_only_online
+    variable show_transport_user_icons
+    variable undef_group_name
+    variable chats_group_name
+    variable own_resources_group_name
+
+    clear .roster 0
+
+    set connections [connections]
+    switch -- [llength $connections] {
+	0 {
+	    update_scrollregion .roster
+	    return
+	}
+	1 {
+	    set draw_connection 0
+	}
+	default {
+	    set draw_connection 1
+	}
+    }
+
+    foreach xlib $connections {
+
+	# TODO: Move to a plugin
+	# Preparing metacontacts.
+	array unset metajids
+	array unset metagroups
+	array unset groups_from_meta
+	set metacontacted {}
+	if {$options(enable_metacontacts) && \
+		[llength [info procs ::plugins::metacontacts::*]] > 0} {
+	    foreach tag [::plugins::metacontacts::get_all_tags $xlib] {
+		set jids [::plugins::metacontacts::get_jids $xlib $tag]
+		set metagroups($tag) {}
+
+		set cgroups {}
+		foreach jid $jids {
+		    # Skip JID if it doesn't match filter pattern or if it
+		    # isn't a JID of use
+		    set isuser [::roster::itemconfig $xlib $jid -isuser]
+		    if {$isuser == ""} {
+			set isuser 1
+		    }
+		    if {$isuser} {
+			set cgroups [concat $cgroups [::roster::itemconfig $xlib $jid -group]]
+		    }
+		    if {$isuser && [filter_match $xlib $jid]} {
+			lappend metagroups($tag) $jid
+			lappend metacontacted $jid
+		    }
+		}
+
+		if {[llength $metagroups($tag)] == 0} continue
+
+		set mjid [lindex $metagroups($tag) 0]
+		set pjid [get_jid_of_user $xlib $mjid]
+		set priority [get_jid_presence_info priority $xlib $pjid]
+		set status [get_jid_status $xlib $pjid]
+
+		foreach rjid [lrange $metagroups($tag) 1 end] {
+		    set jid  [get_jid_of_user $xlib $rjid]
+		    set stat [get_jid_status $xlib $jid]
+
+		    if {[string equal $stat unavailable]} continue
+
+		    set prio [get_jid_presence_info priority $xlib $jid]
+		    if {$prio == ""} {
+			set prio 0
+		    }
+
+		    if {$prio > $priority || \
+			    ($prio == $priority && [compare_status $stat $status] > 0)} {
+			set mjid $rjid
+			set pjid $jid
+			set status $stat
+			set priority $prio
+		    }
+		}
+
+		lappend metajids($mjid) $tag
+		if {![info exists groups_from_meta($mjid)]} {
+		    set groups_from_meta($mjid) $cgroups
+		} else {
+		    set groups_from_meta($mjid) [concat $groups_from_meta($mjid) $cgroups]
+		}
+	    }
+
+	    foreach jid [array names metajids] {
+		set metajids($jid) [lsort -unique $metajids($jid)]
+	    }
+	    set metacontacted [lsort -unique $metacontacted]
+	}
+
+	# Draw connection group
+	if {$draw_connection} {
+	    if {![info exists roster(collapsed,[list xlib $xlib])]} {
+		set roster(collapsed,[list xlib $xlib]) 0
+	    }
+	    addline .roster connection \
+			    [connection_jid $xlib] \
+			    [list xlib $xlib] \
+			    [list xlib $xlib] \
+			    {} 0
+
+	    if {$roster(collapsed,[list xlib $xlib])} {
+		continue
+	    }
+	}
+
+	# Don't draw roster if it doesn't contain items
+	if {[llength [::roster::get_jids $xlib]] == 0} {
+	    continue
+	}
+
+	# List of pairs {"group string for sorting", "group split on delimiter"}
+	set groups {}
+
+	# Array of lists of JIDs in group
+	array unset jidsingroup
+
+	# Array of lists of JIDs in group descendants
+	array unset jidsundergroup
+
+	# Array of lists of subgroups
+	array unset groupsundergroup
+
+	array unset jstat
+	array unset useronline
+
+	foreach jid [::roster::get_jids $xlib] {
+	    # Skip JID if it doesn't match filter pattern
+	    if {![filter_match $xlib $jid]} continue
+
+	    # TODO: Move to a plugin
+	    # Skip JID if it should be ignored because of metacontacts
+	    if {[lsearch -exact $metacontacted $jid] >= 0 && \
+		    ![info exists metajids($jid)]} continue
+
+	    # Add JID groups to a groups list
+	    set jid_groups [::roster::itemconfig $xlib $jid -group]
+
+	    if {[info exists groups_from_meta($jid)]} {
+		set jid_groups [concat $jid_groups $groups_from_meta($jid)]
+	    }
+
+	    if {[llength $jid_groups] > 0} {
+		foreach group $jid_groups {
+		    if {$options(nested)} {
+			set sgroup [msplit $group $options(nested_delimiter)]
+		    } else {
+			set sgroup [list $group]
+		    }
+
+		    # Use the fact that -dictionary sorting puts \u0000 before
+		    # any other character, so subgroups will be placed just
+		    # after their parent group
+		    lappend groups [list [join $sgroup "\u0000"] $sgroup]
+
+		    lappend jidsingroup($sgroup) $jid
+
+		    if {![info exists jidsundergroup($sgroup)]} {
+			set jidsundergroup($sgroup) {}
+		    }
+
+		    if {![info exists groupsundergroup($sgroup)]} {
+			set groupsundergroup($sgroup) {}
+		    }
+
+		    for {set i [expr {[llength $sgroup] - 2}]} {$i >= 0} {incr i -1} {
+			# Adding all parent groups to a group list
+			set sgr [lrange $sgroup 0 $i]
+
+			lappend groups [list [join $sgr "\u0000"] $sgr]
+
+			lappend jidsundergroup($sgr) $jid
+
+			lappend groupsundergroup($sgr) $sgroup
+
+			if {![info exists jidsingroup($sgr)]} {
+			    set jidsingroup($sgr) {}
+			}
+		    }
+		}
+	    } else {
+		set sgroup [list $undef_group_name]
+
+		lappend jidsingroup($sgroup) $jid
+
+		if {![info exists jidsundergroup($sgroup)]} {
+		    set jidsundergroup($sgroup) {}
+		}
+
+		set groupsundergroup($sgroup) {}
+	    }
+	}
+
+	set groups [lsort -unique -dictionary -index 0 $groups]
+
+	# FIXME: What to do with subgroups of $undef_group_name?
+	# Putting undefined group to the and of list
+	set ugroup [list $undef_group_name]
+	if {[info exists jidsingroup($ugroup)]} {
+	    lappend groups [list [join $ugroup "\u0000"] $ugroup]
+	}
+
+	# Putting active chats group to the beginning
+	if {$options(chats_group)} {
+	    set cgroup [list $chats_group_name]
+	    foreach chatid [chat::opened $xlib] {
+		set jid [chat::get_jid $chatid]
+		lappend jidsingroup($cgroup) $jid
+		if {[string equal [roster::itemconfig $xlib $jid -isuser] ""]} {
+		    roster::itemconfig $xlib $jid \
+			    -name [chat::get_nick $xlib $jid chat]
+		    roster::itemconfig $xlib $jid -subsc none
+		}
+	    }
+	    if {[info exists jidsingroup($cgroup)]} {
+		set groups [linsert $groups 0 [list [join $cgroup "\u0000"] $cgroup]]
+	    }
+	    set groupsundergroup($cgroup) {}
+	    set jidsundergroup($cgroup) {}
+	}
+
+	# Putting own resources group to the beginning
+	if {$options(show_own_resources)} {
+	    set cgroup [list $own_resources_group_name]
+	    set jid [::xmpp::jid::normalize [connection_bare_jid $xlib]]
+	    set jidsingroup($cgroup) [list $jid]
+	    set groups [linsert $groups 0 [list [join $cgroup "\u0000"] $cgroup]]
+	    roster::itemconfig $xlib $jid -subsc both
+	    set groupsundergroup($cgroup) {}
+	    set jidsundergroup($cgroup) {}
+	}
+
+	# Info on whether to show offline users in a group is needed for
+	# subgroups too, so an extra loop
+	foreach group $groups {
+	    set group [lindex $group 1]
+	    set gid [list $xlib $group]
+	    if {![info exists roster(show_offline,$gid)]} {
+		set roster(show_offline,$gid) 0
+	    }
+	}
+
+	# Drawing groups an JIDs in them
+	foreach group $groups {
+	    set group [lindex $group 1]
+	    set gid [list $xlib $group]
+
+	    set jidsingroup($group) [lsort -unique $jidsingroup($group)]
+	    set groupsundergroup($group) [lsort -unique $groupsundergroup($group)]
+
+	    if {![info exists roster(collapsed,$gid)]} {
+		set roster(collapsed,$gid) 0
+	    }
+
+	    # How to indent the group (also, the number of its ancestors)
+	    set indent [expr {[llength $group] - 1}]
+
+	    # Whether to draw group at all
+	    set collapse 0
+
+	    # Whether to show offline users in the group
+	    set show_offline_users 0
+
+	    # Whether to show the group ???
+	    set show_offline_group 0
+
+	    foreach undergroup $groupsundergroup($group) {
+		if {$roster(show_offline,[list $xlib $undergroup])} {
+		    set show_offline_group 1
+		    break
+		}
+	    }
+	    for {set i 0} {$i < $indent} {incr i} {
+		set sgr [list $xlib [lrange $group 0 $i]]
+		if {$roster(collapsed,$sgr)} {
+		    # Whether some ancestor is collapsed
+		    set collapse 1
+		    break
+		}
+		if {$roster(show_offline,$sgr)} {
+		    # Whether showing offline users is required for some
+		    # ancestor
+		    set show_offline_users 1
+		    set show_offline_group 1
+		}
+	    }
+
+	    # If some ancestor is collapsed don't draw the group
+	    if {$collapse} continue
+
+	    set group_label [lindex $group end]
+	    set online 0
+	    set users 0
+	    set not_users 0
+	    foreach jid [concat $jidsingroup($group) $jidsundergroup($group)] {
+		if {[::roster::itemconfig $xlib $jid -isuser]} {
+		    incr users
+		    if {![info exists jstat($jid)]} {
+			set jstat($jid) [get_user_status $xlib $jid]
+		    }
+		    if {$jstat($jid) != "unavailable"} {
+			incr online
+			set useronline($jid) 1
+		    } else {
+			set useronline($jid) 0
+		    }
+		} else {
+		    incr not_users
+		}
+	    }
+
+	    # Draw group label
+	    if {!$show_only_online || $show_offline_group || \
+		    $roster(show_offline,$gid) || \
+		    ($options(use_filter) && $options(filter) != "") || \
+		    $online + $not_users > 0} {
+		if {$users > 0} {
+		    append group_label " ($online/$users)"
+		}
+		addline .roster group $group_label $gid $gid {} $indent
+	    }
+
+	    # Draw group contents if it isn't collapsed
+	    if {!$roster(collapsed,$gid)} {
+		set jid_labels {}
+		foreach jid $jidsingroup($group) {
+		    lappend jid_labels [list $jid [::roster::get_label $xlib $jid]]
+		}
+		set jid_labels [lsort -index 1 -dictionary $jid_labels]
+
+		foreach jid_label $jid_labels {
+		    lassign $jid_label jid label
+
+		    if {$options(chats_group)} {
+			set chatid [chat::chatid $xlib $jid]
+			if {[info exists chat::chats(messages,$chatid)] && \
+				$chat::chats(messages,$chatid) > 0} {
+			    append label " ($chat::chats(messages,$chatid))"
+			}
+		    }
+
+		    set condition [expr {!$show_only_online || $show_offline_users || \
+					 $roster(show_offline,$gid) || \
+					 ($options(use_filter) && $options(filter) != "")}]
+		    set cjid [list $xlib $jid]
+		    if {$condition || ![info exists useronline($jid)] || $useronline($jid)} {
+
+			if {[info exists metajids($jid)]} {
+
+			    set jids {}
+			    foreach tag $metajids($jid) {
+				foreach subjid $metagroups($tag) {
+				    # Metacontact members are necessarily users, so don't
+				    # check for this
+				    if {![info exists jstat($subjid)]} {
+					set jstat($subjid) [get_user_status $xlib $subjid]
+				    }
+				    if {$jstat($subjid) != "unavailable"} {
+					set useronline($subjid) 1
+				    } else {
+					set useronline($subjid) 0
+				    }
+				    if {$condition || $useronline($subjid)} {
+					lappend jids $subjid
+				    }
+				}
+			    }
+			    set jids [lsort -unique $jids]
+			    set numjids [llength $jids]
+
+			    if {$options(enable_metacontact_labels)} {
+				set tag [lindex $metajids($jid) 0]
+				set mjid [lindex [::plugins::metacontacts::get_jids $xlib $tag] 0]
+				set label [::roster::get_label $xlib $mjid]
+			    }
+
+			    if {($options(enable_metacontact_labels) && $numjids > 0) || \
+				    $numjids > 1} {
+				# Draw as a metacontact
+				if {$config(subitemtype) & 1} {
+				    append label " ($numjids)"
+				}
+				addline .roster metajid $label $cjid $gid \
+					        [list $xlib $metajids($jid)] $indent $jids \
+					        [get_jid_icon $xlib $jid] \
+					        [get_jid_foreground $xlib $jid]
+
+				if {!$roster(metacollapsed,$gid,[list $xlib $metajids($jid)])} {
+				    set subjid_labels {}
+				    foreach subjid $jids {
+					lappend subjid_labels \
+					        [list $subjid [::roster::get_label $xlib $subjid]]
+				    }
+				    set subjid_labels [lsort -index 1 -dictionary $subjid_labels]
+				    foreach subjid_label $subjid_labels {
+					lassign $subjid_label subjid label
+					draw_jid $xlib $subjid $label $gid \
+					         [list $xlib $metajids($jid)] $indent jstat
+				    }
+				}
+			    } else {
+				# Draw as an ordinary contact using a hack with indent
+				# which allows to add metajid tag. The hack depends on
+				# metajid indent equals to group indent
+				draw_jid $xlib $jid $label $gid \
+					 [list $xlib $metajids($jid)] \
+					 [expr {$indent - 1}] jstat
+			    }
+			} else {
+			    draw_jid $xlib $jid $label $gid {} $indent jstat
+			}
+		    }
+		}
+	    }
+	}
+    }
+
+    update_scrollregion .roster
+}
+
+proc roster::draw_jid {xlib jid label gid metajids indent jstatVar} {
+    variable config
+    variable roster
+    variable show_transport_user_icons
+    upvar $jstatVar jstat
+
+    set cjid [list $xlib $jid]
+    lassign [::roster::get_category_and_subtype $xlib $jid] category type
+
+    set jids [get_jids_of_user $xlib $jid]
+    set numjids [llength $jids]
+
+    if {$category == "user" && $numjids > 1 && $config(subitemtype) > 0} {
+
+	if {$config(subitemtype) & 1} {
+	    append label " ($numjids)"
+	}
+	addline .roster jid $label $cjid $gid $metajids $indent $jids \
+			[get_jid_icon $xlib $jid] \
+			[get_jid_foreground $xlib $jid]
+
+	if {!$roster(jidcollapsed,$gid,$cjid)} {
+	    foreach subjid $jids {
+		set subjid_resource [::xmpp::jid::resource $subjid]
+		if {$subjid_resource != ""} {
+		    addline .roster jid2 \
+				    $subjid_resource [list $xlib $subjid] \
+				    $gid $metajids $indent \
+				    [list $subjid] \
+				    [get_jid_icon $xlib $subjid] \
+				    [get_jid_foreground $xlib $subjid]
+		}
+	    }
+	}
+    } elseif {$category == "user" && $numjids <= 1 && !$show_transport_user_icons} {
+
+	if {[info exists jstat($jid)]} {
+	    set status $jstat($jid)
+	} else {
+	    set status [get_user_status $xlib $jid]
+	}
+
+	set subsc [::roster::itemconfig $xlib $jid -subsc]
+	if {([string equal $subsc from] || [string equal $subsc none]) && \
+		$status == "unavailable"} {
+	    set status unsubscribed
+	}
+	addline .roster jid $label $cjid $gid $metajids $indent $jids \
+			roster/user/$status \
+			$config(${status}foreground)
+    } else {
+	if {$category == "conference" && ($config(subitemtype) & 1) && $numjids > 1} {
+	    append label " ([incr numjids -1])"
+	}
+	addline .roster jid $label $cjid $gid $metajids $indent $jids \
+			[get_jid_icon $xlib $jid] \
+			[get_jid_foreground $xlib $jid]
+    }
+}
+
+proc roster::redraw_after_idle {args} {
+    variable redraw_afterid
+
+    if {[info exists redraw_afterid]} return
+
+    if {![winfo exists .roster.canvas]} return
+
+    set redraw_afterid \
+	[after idle "[namespace current]::redraw
+		     unset [namespace current]::redraw_afterid"]
+}
+
+# Callback
+proc ::redraw_roster {args} {
+    ifacetk::roster::redraw_after_idle
+}
+
+proc roster::get_jids_of_user {xlib user} {
+    # TODO: metacontacts
+    return [::get_jids_of_user $xlib $user]
+}
+
+proc roster::get_foreground {status} {
+    variable config
+
+    return $config(${status}foreground)
+}
+
+proc roster::get_jid_foreground {xlib jid} {
+    variable config
+
+    lassign [::roster::get_category_and_subtype $xlib $jid] category type
+
+    switch -- $category {
+	"" -
+	user {
+	    return [get_user_foreground $xlib $jid]
+	}
+	conference {
+	    if {[get_jid_status $xlib $jid] != "unavailable"} {
+		return $config(availableforeground)
+	    } else {
+		return $config(unavailableforeground)
+	    }
+	}
+	server  -
+	gateway -
+	service {
+	    return [get_service_foreground $xlib $jid $type]
+	}
+	default {
+	    return $config(foreground)
+	}
+    }
+}
+
+proc roster::get_service_foreground {xlib service type} {
+    variable config
+
+    switch -- $type {
+	jud {
+	    return $config(foreground)
+	}
+    }
+
+    if {![string equal [::roster::itemconfig $xlib $service -subsc] none]} {
+	set status [get_user_status $xlib $service]
+	return $config(${status}foreground)
+    } else {
+	return $config(unsubscribedforeground)
+    }
+}
+
+proc roster::get_user_foreground {xlib user} {
+    variable config
+
+    set status [get_user_status $xlib $user]
+
+    set subsc [::roster::itemconfig $xlib $user -subsc]
+    if {[string equal $subsc ""]} {
+	set subsc [::roster::itemconfig $xlib \
+		       [::roster::find_jid $xlib $user] -subsc]
+    }
+
+    if {([string equal $subsc from] || [string equal $subsc none]) && \
+	    $status == "unavailable"} {
+	return $config(unsubscribedforeground)
+    } else {
+	return $config(${status}foreground)
+    }
+}
+
+proc roster::get_jid_icon {xlib jid {status ""}} {
+    lassign [::roster::get_category_and_subtype $xlib $jid] category type
+
+    switch -- $category {
+	"" -
+	user {
+	    if {$status == ""} {
+		set status [get_user_status $xlib $jid]
+	    }
+	    return [get_user_icon $xlib $jid $status]
+	}
+	conference {
+	    if {$status == ""} {
+		set status [get_jid_status $xlib $jid]
+	    }
+	    if {$status != "unavailable"} {
+		return roster/conference/available
+	    }
+	    return roster/conference/unavailable
+	}
+	server  -
+	gateway -
+	service {
+	    if {$status == ""} {
+		set status [get_user_status $xlib $jid]
+	    }
+	    return [get_service_icon $xlib $jid $type $status]
+	}
+	default {
+	    if {$status == ""} {
+		set status [get_jid_status $xlib $jid]
+	    }
+	    return [get_user_icon $xlib $jid $status]
+	}
+    }
+}
+
+proc roster::get_service_icon {xlib service type status} {
+    variable show_transport_icons
+
+    if {$show_transport_icons} {
+	switch -- $type {
+	    jud {return services/jud}
+	    sms {return services/sms}
+	}
+	if {![string equal [::roster::itemconfig $xlib $service -subsc] none]} {
+	    if {![catch { image type services/$type/$status }]} {
+		return services/$type/$status
+	    } else {
+		return roster/user/$status
+	    }
+	} else {
+	    return roster/user/unsubscribed
+	}
+    } else {
+	if {![string equal [::roster::itemconfig $xlib $service -subsc] none]} {
+	    return roster/user/$status
+	} else {
+	    return roster/user/unsubscribed
+	}
+    }
+}
+
+proc roster::get_user_icon {xlib user status} {
+    variable show_transport_user_icons
+
+    set subsc [::roster::itemconfig $xlib $user -subsc]
+    if {[string equal $subsc ""]} {
+	set subsc [::roster::itemconfig $xlib \
+	               [::roster::find_jid $xlib $user] -subsc]
+    }
+
+    if {!([string equal $subsc from] || [string equal $subsc none]) || \
+	    $status != "unavailable"} {
+	if {$show_transport_user_icons} {
+	    set service [::xmpp::jid::server $user]
+	    lassign [::roster::get_category_and_subtype $xlib $service] category type
+	    switch -glob -- $category/$type {
+		directory/* -
+		*/jud {
+		    return services/jud
+		}
+		*/sms {
+		    return services/sms
+		}
+	    }
+	    if {![catch { image type services/$type/$status }]} {
+		return services/$type/$status
+	    } else {
+		return roster/user/$status
+	    }
+	} else {
+	    return roster/user/$status
+	}
+    } else {
+	return roster/user/unsubscribed
+    }
+}
+
+proc roster::changeicon {w jid icon} {
+    set c $w.canvas
+    set tag [jid_to_tag $jid]
+
+    $c itemconfigure jid$tag&&icon -image $icon
+}
+
+proc roster::changeforeground {w jid color} {
+    set c $w.canvas
+    set tag [jid_to_tag $jid]
+
+    $c itemconfigure jid$tag&&text -fill $color
+}
+
+proc roster::create {w args} {
+    variable iroster
+    variable config
+
+    set c $w.canvas
+
+    set width 150
+    set height 100
+    set popupproc {}
+    set grouppopupproc {}
+    set singleclickproc {}
+    set doubleclickproc {}
+    foreach {attr val} $args {
+	switch -- $attr {
+	    -width {set width $val}
+	    -height {set height $val}
+	    -popup {set popupproc $val}
+	    -grouppopup {set grouppopupproc $val}
+	    -singleclick {set singleclickproc $val}
+	    -doubleclick {set doubleclickproc $val}
+	    -draginitcmd {set draginitcmd $val}
+	    -dropovercmd {set dropovercmd $val}
+	    -dropcmd {set dropcmd $val}
+	}
+    }
+
+    frame $w -relief sunken -borderwidth $::tk_borderwidth -class Roster
+    set sw [ScrolledWindow $w.sw]
+    pack $sw -fill both -expand yes
+
+    set config(groupindent)            [option get $w groupindent            Roster]
+    set config(jidindent)              [option get $w jidindent              Roster]
+    set config(jidmultindent)          [option get $w jidmultindent          Roster]
+    set config(jid2indent)             [option get $w subjidindent           Roster]
+    set config(groupiconindent)        [option get $w groupiconindent        Roster]
+    set config(subgroupiconindent)     [option get $w subgroupiconindent     Roster]
+    set config(iconindent)             [option get $w iconindent             Roster]
+    set config(subitemtype)            [option get $w subitemtype            Roster]
+    set config(subiconindent)          [option get $w subiconindent          Roster]
+    set config(textuppad)              [option get $w textuppad              Roster]
+    set config(textdownpad)            [option get $w textdownpad            Roster]
+    set config(linepad)	               [option get $w linepad                Roster]
+    set config(background)             [option get $w cbackground            Roster]
+    set config(metajidfill)	       [option get $w metajidfill            Roster]
+    set config(metajidhlfill)          [option get $w metajidhlfill          Roster]
+    set config(metajidborder)          [option get $w metajidborder          Roster]
+    set config(jidfill)	               [option get $w jidfill                Roster]
+    set config(jidhlfill)              [option get $w jidhlfill              Roster]
+    set config(jidborder)              [option get $w jidborder              Roster]
+    set config(jid2fill)               $config(jidfill)
+    set config(jid2hlfill)             $config(jidhlfill)
+    set config(jid2border)             $config(jidborder)
+    set config(groupfill)              [option get $w groupfill              Roster]
+    set config(groupcfill)             [option get $w groupcfill             Roster]
+    set config(grouphlfill)            [option get $w grouphlfill            Roster]
+    set config(groupborder)            [option get $w groupborder            Roster]
+    set config(connectionfill)         [option get $w connectionfill         Roster]
+    set config(connectioncfill)        [option get $w connectioncfill        Roster]
+    set config(connectionhlfill)       [option get $w connectionhlfill       Roster]
+    set config(connectionborder)       [option get $w connectionborder       Roster]
+    set config(foreground)             [option get $w foreground             Roster]
+    set config(unsubscribedforeground) [option get $w unsubscribedforeground Roster]
+    set config(unavailableforeground)  [option get $w unavailableforeground  Roster]
+    set config(dndforeground)          [option get $w dndforeground          Roster]
+    set config(xaforeground)           [option get $w xaforeground           Roster]
+    set config(awayforeground)         [option get $w awayforeground         Roster]
+    set config(availableforeground)    [option get $w availableforeground    Roster]
+    set config(chatforeground)         [option get $w chatforeground         Roster]
+
+    canvas $c -bg $config(background) \
+	-highlightthickness $::tk_highlightthickness \
+	-scrollregion {0 0 0 0} \
+	-width $width -height $height
+
+    $sw setwidget $c
+
+    set iroster($w,ypos) 1
+    set iroster($w,width) 0
+    set iroster($w,popup) $popupproc
+    set iroster($w,grouppopup) $grouppopupproc
+    set iroster($w,singleclick) $singleclickproc
+    set iroster($w,doubleclick) $doubleclickproc
+
+    bindscroll $c
+
+    if {[info exists draginitcmd]} {
+	DragSite::register $c -draginitcmd $draginitcmd
+    }
+
+    set args {}
+    if {[info exists dropovercmd]} {
+	lappend args -dropovercmd $dropovercmd
+    }
+    if {[info exists dropcmd]} {
+	lappend args -dropcmd $dropcmd
+    }
+    if {[llength $args] > 0} {
+	eval [list DropSite::register $c -droptypes {JID}] $args
+    }
+}
+
+proc roster::addline {w type text jid group metajids indent {jids {}} {icon ""} {foreground ""}} {
+    variable options
+    variable roster
+    variable iroster
+    variable config
+
+    set c $w.canvas
+
+    set tag [jid_to_tag $jid]
+    set grouptag [jid_to_tag $group]
+    set metatag [jid_to_tag $metajids]
+
+    set ypad 1
+    set linespace [font metric $::RosterFont -linespace]
+    set lineheight [expr {$linespace + $ypad}]
+
+    set uy $iroster($w,ypos)
+    set ly [expr {$uy + $lineheight + $config(textuppad) + $config(textdownpad)}]
+
+    set levindent [expr $config(groupindent)*$indent]
+
+    if {[string equal $metajids {}]} {
+	set metaindent 0
+    } else {
+	set metaindent $config(groupindent)
+    }
+
+    set border $config(${type}border)
+    set hlfill $config(${type}hlfill)
+
+    if {([string equal $type group] || [string equal $type connection]) && \
+	    [info exists roster(collapsed,$jid)] && $roster(collapsed,$jid)} {
+	set rfill $config(${type}cfill)
+    } else {
+	set rfill $config(${type}fill)
+    }
+
+    if {[string equal $type connection]} {
+	set type group
+    }
+
+    $c create rectangle [expr {1 + $levindent}] $uy 10000 $ly \
+	      -fill $rfill \
+	      -outline $border \
+	      -tags [list jid$tag group$grouptag meta$metatag $type rect]
+
+    switch -- $type {
+	metajid {
+	    set isuser 1
+	    set y [expr {($uy + $ly)/2}]
+	    set x [expr {$config(iconindent) + $levindent}]
+
+	    if {$icon == ""} {
+		set icon roster/user/unavailable
+	    }
+	    $c create image $x $y -image $icon \
+				  -anchor w \
+				  -tags [list jid$tag group$grouptag meta$metatag $type icon]
+
+	    if {[llength $jids] > 0} {
+		if {[info exists roster(metacollapsed,$group,$metajids)] && \
+			!$roster(metacollapsed,$group,$metajids)} {
+		    set jid_state opened
+		} else {
+		    set roster(metacollapsed,$group,$metajids) 1
+		    set jid_state closed
+		}
+		if {$config(subitemtype) > 0 && ($config(subitemtype) & 2)} {
+		    set y [expr {($uy + $ly)/2}]
+		    set x [expr {$config(subgroupiconindent) + $levindent}]
+		    $c create image $x $y -image roster/group/$jid_state \
+					  -anchor w \
+					  -tags [list jid$tag group$grouptag meta$metatag $type group]
+		}
+	    } else {
+		set roster(metacollapsed,$group,$metajids) 1
+	    }
+	}
+	jid {
+	    lassign $jid xlib jjid
+	    set isuser [::roster::itemconfig $xlib $jjid -isuser]
+	    if {[string equal $isuser ""]} {
+		set isuser 1
+	    }
+
+	    set y [expr {($uy + $ly)/2}]
+	    set x [expr {$config(iconindent) + $levindent + $metaindent}]
+
+	    if {$icon == ""} {
+		set icon roster/user/unavailable
+	    }
+	    $c create image $x $y -image $icon \
+				  -anchor w \
+				  -tags [list jid$tag group$grouptag meta$metatag $type icon]
+
+	    if {[llength $jids] > 1} {
+		if {[info exists roster(jidcollapsed,$group,$jid)] && !$roster(jidcollapsed,$group,$jid)} {
+		    set jid_state opened
+		} else {
+		    set roster(jidcollapsed,$group,$jid) 1
+		    set jid_state closed
+		}
+		if {$config(subitemtype) > 0 && ($config(subitemtype) & 2) && $isuser} {
+		    set y [expr {($uy + $ly)/2}]
+		    set x [expr {$config(subgroupiconindent) + $levindent + $metaindent}]
+		    $c create image $x $y -image roster/group/$jid_state \
+					  -anchor w \
+					  -tags [list jid$tag group$grouptag meta$metatag $type group]
+		}
+	    } else {
+		set roster(jidcollapsed,$group,$jid) 1
+	    }
+	}
+	jid2 {
+	    set y [expr {($uy + $ly)/2}]
+	    set x [expr {$config(subiconindent) + $levindent + $metaindent}]
+
+	    if {$icon == ""} {
+		set icon roster/user/unavailable
+	    }
+	    $c create image $x $y -image $icon \
+				  -anchor w \
+				  -tags [list jid$tag group$grouptag meta$metatag $type icon]
+	}
+	group {
+	    set y [expr {($uy + $ly)/2}]
+	    set x [expr {$config(groupiconindent) + $levindent}]
+	    if {[info exists roster(collapsed,$jid)] && $roster(collapsed,$jid)} {
+		set group_state closed
+	    } else {
+		set group_state opened
+	    }
+	    $c create image $x $y -image roster/group/$group_state \
+				  -anchor w \
+				  -tags [list jid$tag group$grouptag meta$metatag $type icon]
+	}
+    }
+
+    switch -- $type {
+	metajid {
+	    if {($config(subitemtype) > 0) && ($config(subitemtype) & 2) && \
+		    (($options(enable_metacontact_labels) && [llength $jids] > 0) || \
+		    [llength $jids] > 1)} {
+		set x [expr {$config(jidmultindent) + $levindent}]
+	    } else {
+		set x [expr {$config(jidindent) + $levindent}]
+	    }
+	}
+	jid {
+	    if {($config(subitemtype) > 0) && ($config(subitemtype) & 2) && \
+		    $isuser && ([llength $jids] > 1)} {
+		set x [expr {$config(jidmultindent) + $levindent}]
+	    } else {
+		set x [expr {$config(jidindent) + $levindent}]
+	    }
+	}
+	default {
+	    set x [expr {$config(${type}indent) + $levindent}]
+	}
+    }
+    switch -- $type {
+	jid -
+	jid2 {
+	    incr x $metaindent
+	}
+    }
+
+    incr uy $config(textuppad)
+
+    if {[string equal $foreground ""]} {
+	switch -- $type {
+	    metajid -
+	    jid -
+	    jid2 {
+		set foreground $config(unavailableforeground)
+	    }
+	    default {
+		set foreground $config(foreground)
+	    }
+	}
+    }
+    $c create text $x $uy -text $text \
+			  -anchor nw \
+			  -font $::RosterFont \
+			  -fill $foreground \
+			  -tags [list jid$tag group$grouptag meta$metatag $type text]
+
+    set iroster($w,width) [max $iroster($w,width) \
+			       [expr {$x + [font measure $::RosterFont $text]}]]
+
+    $c bind jid$tag&&$type <Any-Enter> \
+	    [double% [list $c itemconfig jid$tag&&$type&&rect -fill $hlfill]]
+    $c bind jid$tag&&$type <Any-Leave> \
+	    [double% [list $c itemconfig jid$tag&&$type&&rect -fill $rfill]]
+
+    set doubledjid  [double% $jid]
+    set doubledjids [double% $jids]
+
+    set iroster($w,ypos) [expr {$ly + $config(linepad)}]
+
+    switch -- $type {
+	metajid -
+	jid -
+	jid2 {
+	    $c bind jid$tag&&$type <Button-1> \
+		    [list [namespace current]::on_singleclick \
+			  [double% $iroster($w,singleclick)] \
+			  [double% $c] %x %y $doubledjid $doubledjids]
+
+	    $c bind jid$tag&&$type <Double-Button-1> \
+		    [list [namespace current]::on_doubleclick \
+			  [double% $iroster($w,doubleclick)] $doubledjid $doubledjids]
+
+	    $c bind jid$tag&&$type <Any-Enter> \
+		    +[list eval balloon::set_text \
+			   \[[namespace current]::jids_popup_info \
+			   [list $doubledjid] [list $doubledjids]\]]
+
+	    $c bind jid$tag&&$type <Any-Motion> \
+		    [list eval balloon::on_mouse_move \
+			  \[[namespace current]::jids_popup_info \
+			  [list $doubledjid] [list $doubledjids]\] %X %Y]
+
+	    $c bind jid$tag&&$type <Any-Leave> {+ balloon::destroy}
+
+	    if {![string equal $iroster($w,popup) ""]} {
+		$c bind jid$tag&&$type <3> [list [double% $iroster($w,popup)] \
+						 $doubledjid $doubledjids]
+	    }
+	}
+	default {
+	    if {$w == ".roster"} {
+		$c bind jid$tag&&group <Button-1> \
+		        [list [namespace current]::group_click $doubledjid]
+	    }
+
+	    if {![string equal $iroster($w,grouppopup) {}]} {
+		$c bind jid$tag&&group <3> \
+		        [list [double% $iroster($w,grouppopup)] $doubledjid]
+	    }
+	}
+    }
+}
+
+proc roster::clear {w {updatescroll 1}} {
+    variable iroster
+
+    $w.canvas delete rect||icon||text||group
+
+    set iroster($w,ypos) 1
+    set iroster($w,width) 0
+    if {$updatescroll} {
+	update_scrollregion $w
+    }
+}
+
+proc roster::update_scrollregion {w} {
+    variable iroster
+
+    $w.canvas configure \
+	-scrollregion [list 0 0 $iroster($w,width) $iroster($w,ypos)]
+}
+
+###############################################################################
+
+proc roster::on_singleclick {command c x y cjid jids} {
+    variable click_afterid
+
+    if {$command == ""} return
+
+    set xc [$c canvasx $x]
+    set yc [$c canvasy $y]
+    set tags [$c gettags [lindex [$c find closest $xc $yc] 0]]
+
+    if {![info exists click_afterid]} {
+	set click_afterid \
+	    [after 300 [list [namespace current]::singleclick_run $command $tags $cjid $jids]]
+    } else {
+	after cancel $click_afterid
+	unset click_afterid
+    }
+}
+
+proc roster::singleclick_run {command tags cjid jids} {
+    variable click_afterid
+
+    if {[info exists click_afterid]} {
+	unset click_afterid
+    }
+
+    eval $command [list $tags $cjid $jids]
+}
+
+proc roster::on_doubleclick {command cjid jids} {
+    variable click_afterid
+
+    if {[info exists click_afterid]} {
+	after cancel $click_afterid
+	unset click_afterid
+    }
+
+    if {$command == ""} return
+
+    eval $command [list $cjid $jids]
+}
+
+###############################################################################
+
+proc roster::jid_doubleclick {id ids} {
+    lassign $id xlib jid
+    lassign [::roster::get_category_and_subtype $xlib $jid] category subtype
+
+    hook::run roster_jid_doubleclick $xlib $jid $category $subtype
+}
+
+###############################################################################
+
+proc roster::doubleclick_fallback {xlib jid category subtype} {
+    variable options
+
+    if {$options(chat_on_doubleclick)} {
+	chat::open_to_user $xlib $jid
+    } else {
+	message::send_dialog -to $jid
+    }
+}
+
+hook::add roster_jid_doubleclick \
+    [namespace current]::roster::doubleclick_fallback 100
+
+###############################################################################
+
+proc roster::group_click {gid} {
+    variable roster
+
+    set roster(collapsed,$gid) [expr {!$roster(collapsed,$gid)}]
+    redraw_after_idle
+}
+
+proc roster::jids_popup_info {id jids} {
+    lassign $id xlib jid
+
+    # TODO: metacontacts
+    if {$jids == {}} {
+	set jids [list $jid]
+    }
+
+    set text {}
+    set i 0
+    foreach j $jids {
+	append text "\n[[namespace current]::user_popup_info $xlib $j $i]"
+	incr i
+    }
+    set text [string trimleft $text "\n"]
+    return $text
+}
+
+proc roster::user_popup_info {xlib user i} {
+    variable options
+    variable user_popup_info
+    global statusdesc
+
+    lassign [::roster::get_category_and_subtype $xlib $user] category subtype
+    set bare_user [::roster::find_jid $xlib $user]
+    lassign [::roster::get_category_and_subtype $xlib $bare_user] \
+	category1 subtype1
+
+    set name $user
+    switch -- $category {
+	conference {
+	    set status $statusdesc([get_jid_status $xlib $user])
+	    set desc ""
+	}
+	user -
+	default {
+	    set status $statusdesc([get_user_status $xlib $user])
+	    set desc   [get_user_status_desc $xlib $user]
+	    if {[string equal $category1 conference] && $i > 0} {
+		if {$options(show_conference_user_info)} {
+		    set name "     [::xmpp::jid::resource $user]"
+		} else {
+		    set name "\t[::xmpp::jid::resource $user]"
+		}
+	    }
+	}
+    }
+
+    if {(![string equal -nocase  $status $desc]) && (![string equal $desc ""])} {
+	append status " ($desc)"
+    }
+
+    set subsc [::roster::itemconfig $xlib $bare_user -subsc]
+    if {($options(show_subscription) && ![string equal $subsc ""]) &&
+	    !([string equal $category1 conference] && [string equal $category user])} {
+	set subsc [format "\n\t[::msgcat::mc {Subscription:}] %s" $subsc]
+	set ask [::roster::itemconfig $xlib $bare_user -ask]
+	if {![string equal $ask ""]} {
+	    set ask [format "  [::msgcat::mc {Ask:}] %s" $ask]
+	}
+    } else {
+	set subsc ""
+	set ask ""
+    }
+
+    set user_popup_info "$name: $status$subsc$ask"
+
+    if {!([string equal $category1 conference] && $i > 0) || \
+	    $options(show_conference_user_info)} {
+	hook::run roster_user_popup_info_hook \
+	    [namespace which -variable user_popup_info] $xlib $user
+    }
+
+    return $user_popup_info
+}
+
+proc roster::switch_only_online {args} {
+    variable show_only_online
+
+    set show_only_online [expr {!$show_only_online}]
+}
+
+proc roster::is_online {xlib jid} {
+    if {[::roster::itemconfig $xlib $jid -isuser]} {
+	switch -- [get_user_status $xlib $jid] {
+	    unavailable {return 0}
+	    default {return 1}
+	}
+    } else {
+	return 1
+    }
+}
+
+###############################################################################
+
+proc roster::add_remove_item_menu_item {m xlib jid} {
+    set rjid [roster::find_jid $xlib $jid]
+    if {$rjid == ""} {
+	set state disabled
+    } else {
+	set state normal
+    }
+    $m add command -label [::msgcat::mc "Remove from roster..."] \
+	-command [list ifacetk::roster::remove_item_dialog $xlib $rjid] \
+	-state $state
+}
+
+hook::add chat_create_user_menu_hook \
+    [namespace current]::roster::add_remove_item_menu_item 90
+hook::add roster_conference_popup_menu_hook \
+    [namespace current]::roster::add_remove_item_menu_item 90
+hook::add roster_service_popup_menu_hook \
+    [namespace current]::roster::add_remove_item_menu_item 90
+hook::add roster_jid_popup_menu_hook \
+    [namespace current]::roster::add_remove_item_menu_item 90
+
+###############################################################################
+
+proc roster::remove_item_dialog {xlib jid} {
+    set res [MessageDlg .remove_item -aspect 50000 -icon question -type user \
+	-buttons {yes no} -default 0 -cancel 1 \
+	-message [::msgcat::mc "Are you sure to remove %s from roster?" $jid]]
+    if {$res == 0} {
+	::roster::remove_item $xlib $jid
+    }
+}
+
+proc roster::update_chat_activity {args} {
+    variable options
+
+    if {$options(chats_group)} {
+	redraw_after_idle
+    }
+}
+
+hook::add open_chat_post_hook [namespace current]::roster::redraw_after_idle
+hook::add close_chat_post_hook [namespace current]::roster::redraw_after_idle
+hook::add draw_message_hook [namespace current]::roster::update_chat_activity
+hook::add raise_chat_tab_hook [namespace current]::roster::update_chat_activity
+
+
+###############################################################################
+
+proc roster::dropcmd {target source X Y op type data} {
+    variable options
+
+    debugmsg roster "$target $source $X $Y $op $type $data"
+
+    set c .roster.canvas
+
+    set x [expr {$X-[winfo rootx $c]}]
+    set y [expr {$Y-[winfo rooty $c]}]
+    set xc [$c canvasx $x]
+    set yc [$c canvasy $y]
+
+    set tags [$c gettags [lindex [$c find closest $xc $yc] 0]]
+
+    if {$options(free_drop) && ![string equal $tags ""]} {
+	lassign [tag_to_jid [string range [lindex $tags 1] 5 end]] xlib gr
+	if {$xlib == "xlib"} {
+	    set xlib $gr
+	    set gr {}
+	}
+    } elseif {[lsearch -exact $tags group] >= 0} {
+	lassign [tag_to_jid [string range [lindex $tags 0] 3 end]] xlib gr
+	if {$xlib == "xlib"} {
+	    set xlib $gr
+	    set gr {}
+	}
+    } elseif {![string equal $tags ""]} {
+	lassign [tag_to_jid [string range [lindex $tags 1] 5 end]] xlib
+	set gr {}
+    } else {
+	set xlib [lindex [connections] 0]
+	set gr {}
+    }
+    if {$options(nested)} {
+	set gr [join $gr $options(nested_delimiter)]
+    } else {
+	set gr [lindex $gr 0]
+    }
+
+    debugmsg roster "GG: $gr; $tags"
+
+    lassign $data _xlib jid category type name version fromgid
+    set subsc ""
+
+    if {[info exists fromgid]} {
+	lassign $fromgid fromxlib fromgr
+	if {$options(nested)} {
+	    set fromgr [join $fromgr $options(nested_delimiter)]
+	} else {
+	    set fromgr [lindex $fromgr 0]
+	}
+    }
+
+    if {[lsearch -exact [::roster::get_jids $xlib] $jid] < 0} {
+	if {$gr != {}} {
+	    set groups [list $gr]
+	} else {
+	    set groups {}
+	}
+	::roster::itemconfig $xlib $jid -category $category -subtype $type \
+	    -name $name -group $groups
+
+	lassign [::roster::get_category_and_subtype $xlib $jid] ccategory ctype
+	switch -- $ccategory {
+	    conference {
+		::roster::itemconfig $xlib $jid -subsc bookmark
+	    }
+	    user {
+		::xmpp::sendPresence $xlib -to $jid -type subscribe
+	    }
+	}
+    } else {
+	set groups [::roster::itemconfig $xlib $jid -group]
+
+	if {[info exists fromgid] && ($fromxlib == $xlib)} {
+	    set idx [lsearch -exact $groups $fromgr]
+	    if {$idx >= 0} {
+		set groups [lreplace $groups $idx $idx]
+	    }
+	}
+	if {$gr != ""} {
+	    lappend groups $gr
+	    set groups [lsort -unique $groups]
+	    debugmsg roster $groups
+	}
+
+	::roster::itemconfig $xlib $jid -category $category -subtype $type \
+	    -name $name -group $groups
+    }
+
+    ::roster::send_item $xlib $jid
+}
+
+proc roster::draginitcmd {target x y top} {
+    debugmsg roster "$target $x $y $top"
+
+    balloon::destroy
+    set c .roster.canvas
+
+    set tags [$c gettags current]
+    if {[lsearch -exact $tags jid] >= 0} {
+	set grouptag [string range [lindex $tags 1] 5 end]
+	set gid [tag_to_jid $grouptag]
+	set tag [string range [lindex $tags 0] 3 end]
+	set cjid [tag_to_jid $tag]
+	lassign $cjid xlib jid
+
+	set data [list $xlib $jid \
+		      [::roster::itemconfig $xlib $jid -category] \
+		      [::roster::itemconfig $xlib $jid -subtype] \
+		      [::roster::itemconfig $xlib $jid -name] {} \
+		      $gid]
+
+	debugmsg roster $data
+	return [list JID {move} $data]
+    } else {
+	return {}
+    }
+}
+
+###############################################################################
+
+proc roster::user_singleclick {tags cjid jids} {
+    variable options
+    variable roster
+
+    lassign $cjid xlib jid
+    set type [lindex $tags 3]
+    set cgroup [tag_to_jid [string range [lindex $tags 1] 5 end]]
+    set cmeta [tag_to_jid [string range [lindex $tags 2] 4 end]]
+
+    switch -- $type {
+	metajid {
+	    if {[llength $jids] > 1 || \
+		    ($options(enable_metacontact_labels) && [llength $jids] > 0)} {
+		set roster(metacollapsed,$cgroup,$cmeta) \
+		    [expr {!$roster(metacollapsed,$cgroup,$cmeta)}]
+		redraw_after_idle
+	    }
+	}
+	default {
+	    if {[roster::itemconfig $xlib $jid -isuser] && [llength $jids] > 1} {
+		set roster(jidcollapsed,$cgroup,$cjid) \
+		    [expr {!$roster(jidcollapsed,$cgroup,$cjid)}]
+		redraw_after_idle
+	    }
+	}
+    }
+}
+
+###############################################################################
+
+proc roster::popup_menu {id jids} {
+    lassign $id xlib jid
+
+    lassign [::roster::get_category_and_subtype $xlib $jid] category subtype
+
+    switch -- $category {
+	user       {set menu [create_user_menu $xlib $jid $jids]}
+	conference {set menu [conference_popup_menu $xlib $jid]}
+	server  -
+	gateway -
+	service    {set menu [service_popup_menu $xlib $jid]}
+	default    {set menu [jid_popup_menu $xlib $jid]}
+    }
+
+    tk_popup $menu [winfo pointerx .] [winfo pointery .]
+}
+
+###############################################################################
+
+proc roster::group_popup_menu {id} {
+    variable options
+
+    lassign $id xlib name
+    if {$options(nested)} {
+	set name [join $name $options(nested_delimiter)]
+    } else {
+	set name [lindex $name 0]
+    }
+    if {$xlib != "xlib"} {
+	tk_popup [create_group_popup_menu $xlib $name] \
+	    [winfo pointerx .] [winfo pointery .]
+    }
+}
+
+###############################################################################
+
+proc roster::groupchat_popup_menu {id jids} {
+    lassign $id xlib jid
+    tk_popup [create_groupchat_user_menu $xlib $jid] \
+	[winfo pointerx .] [winfo pointery .]
+}
+
+###############################################################################
+
+proc roster::create_user_menu {xlib user jids} {
+    set m .jidpopupmenu
+    if {[winfo exists $m]} { destroy $m }
+    menu $m -tearoff 0
+
+    set jids1 {}
+    foreach jid $jids {
+	set resources [get_jids_of_user $xlib $jid]
+	if {[llength $resources] == 0} {
+	    lappend jids1 $jid
+	} else {
+	    set jids1 [concat $jids1 [get_jids_of_user $xlib $jid]]
+	}
+    }
+    set jids $jids1
+
+    switch -- [llength $jids] {
+	0 {
+	    hook::run roster_jid_popup_menu_hook $m $xlib $user
+	    return $m
+	}
+	1 {
+	    hook::run roster_jid_popup_menu_hook $m $xlib [lindex $jids 0]
+	    return $m
+	}
+	default {
+	    foreach jid $jids {
+		set m1 .jidpopupmenu[jid_to_tag $jid]
+		if {[winfo exists $m1]} { destroy $m1 }
+		menu $m1 -tearoff 0
+		hook::run roster_jid_popup_menu_hook $m1 $xlib $jid
+	    }
+
+	    add_menu_submenu $m .jidpopupmenu "" $jids
+
+	    foreach jid $jids {
+		set m1 .jidpopupmenu[jid_to_tag $jid]
+		if {[winfo exists $m1]} { destroy $m1 }
+	    }
+
+	    return $m
+	}
+    }
+}
+
+###############################################################################
+
+proc roster::add_menu_submenu {m prefix suffix jids} {
+    set m1 $prefix[jid_to_tag [lindex $jids 0]]$suffix
+
+    for {set i 0} {[$m1 index $i] == $i} {incr i} {
+	switch -- [$m1 type $i] {
+	    separator {
+		$m add separator
+	    }
+	    cascade {
+		set label [$m1 entrycget $i -label]
+		set menu [$m1 entrycget $i -menu]
+		set state [$m1 entrycget $i -state]
+		set suffix2 [join [lrange [split $menu .] 2 end] .]
+		set suffix3 [lindex [split $menu .] end]
+		set m2 [menu $m.$suffix3 -tearoff 0]
+		# TODO: Check if state is the same for all menus
+		$m add cascade -label $label -menu $m2 -state $state
+		add_menu_submenu $m2 $prefix .$suffix2 $jids
+	    }
+	    checkbutton {
+		set label [$m1 entrycget $i -label]
+		add_checkbutton_submenu $m $prefix $suffix $i $label $jids
+	    }
+	    radiobutton {
+		set label [$m1 entrycget $i -label]
+		add_radiobutton_submenu $m $prefix $suffix $i $label $jids
+	    }
+	    command {
+		set label [$m1 entrycget $i -label]
+		add_command_submenu $m $prefix $suffix $i $label $jids
+	    }
+	}
+    }
+}
+
+###############################################################################
+
+proc roster::get_popup_command_list {m prefix suffix label jids args} {
+    set command_list0 {}
+    set command_list1 {}
+    set command_list2 {}
+    foreach jid $jids {
+	set bjid [::xmpp::jid::stripResource $jid]
+	set m1 $prefix[jid_to_tag $jid]$suffix
+	if {![catch {$m1 index $label} idx] && $idx != "none"} {
+	    set command {}
+	    foreach opt $args {
+		lappend command [$m1 entrycget $idx $opt]
+	    }
+	    lappend command_list0 [list $label $command]
+	    lappend command_list1 [list $jid $command]
+	    lappend command_list2 [list $bjid $command]
+	}
+    }
+
+    set command_list0 [lsort -unique $command_list0]
+    set command_list2 [lsort -unique $command_list2]
+    set command_list3 [lsort -unique -index 0 $command_list2]
+
+    if {[llength $command_list0] == 1} {
+	return $command_list0
+    } elseif {[llength $command_list2] != [llength $command_list3]} {
+	return $command_list1
+    } else {
+	return $command_list2
+    }
+}
+
+proc roster::add_command_submenu {m prefix suffix i label jids} {
+    set command_list [get_popup_command_list $m $prefix $suffix $label $jids \
+					     -command -state]
+    if {[llength $command_list] > 1} {
+	set m2 [menu $m.$i -tearoff 0]
+	$m add cascade -label $label -menu $m2
+
+	foreach jid_command $command_list {
+	    lassign $jid_command jid command
+	    $m2 add command -label $jid \
+			    -command [lindex $command 0] \
+			    -state [lindex $command 1]
+	}
+    } else {
+	lassign [lindex [lindex $command_list 0] 1] command state
+	$m add command -label $label \
+		       -command $command \
+		       -state $state
+    }
+}
+
+proc roster::add_checkbutton_submenu {m prefix suffix i label jids} {
+    set command_list [get_popup_command_list $m $prefix $suffix $label $jids \
+					     -variable -command -state]
+
+    if {[llength $command_list] > 1} {
+	set m2 [menu $m.$i -tearoff 0]
+	$m add cascade -label $label -menu $m2
+
+	foreach jid_command $command_list {
+	    lassign $jid_command jid command
+	    $m2 add checkbutton -label $jid \
+				-variable [lindex $command 0] \
+				-command [lindex $command 1] \
+				-state [lindex $command 2]
+	}
+    } else {
+	lassign [lindex [lindex $command_list 0] 1] var command state
+	$m add checkbutton -label $label \
+			   -variable $var \
+			   -command $command \
+			   -state $state
+    }
+}
+
+proc roster::add_radiobutton_submenu {m prefix suffix i label jids} {
+    set command_list [get_popup_command_list $m $prefix $suffix $label $jids \
+					     -value -variable -command -state]
+
+    if {[llength $command_list] > 1} {
+	set m2 [menu $m.$i -tearoff 0]
+	$m add cascade -label $label -menu $m2
+
+	foreach jid_command $command_list {
+	    lassign $jid_command jid command
+	    $m2 add radiobutton -label $jid \
+				-value [lindex $command 0] \
+				-variable [lindex $command 1] \
+				-command [lindex $command 2] \
+				-state [lindex $command 3]
+	}
+    } else {
+	lassign [lindex [lindex $command_list 0] 1] value var command state
+	$m add radiobutton -label $label \
+			   -value $value \
+			   -variable $var \
+			   -command $command \
+			   -state $state
+    }
+}
+
+###############################################################################
+
+proc roster::add_separator {m xlib jid} {
+    $m add separator
+}
+
+###############################################################################
+
+proc roster::jid_popup_menu {xlib jid} {
+    if {[winfo exists [set m .jidpopupmenu]]} {
+	destroy $m
+    }
+    menu $m -tearoff 0
+
+    hook::run roster_jid_popup_menu_hook $m $xlib $jid
+
+    return $m
+}
+
+hook::add roster_jid_popup_menu_hook \
+    [namespace current]::roster::add_separator 40
+hook::add roster_jid_popup_menu_hook \
+    [namespace current]::roster::add_separator 50
+hook::add roster_jid_popup_menu_hook \
+    [namespace current]::roster::add_separator 70
+hook::add roster_jid_popup_menu_hook \
+    [namespace current]::roster::add_separator 85
+
+###############################################################################
+
+proc roster::conference_popup_menu {xlib jid} {
+    if {[winfo exists [set m .confpopupmenu]]} {
+	destroy $m
+    }
+    menu $m -tearoff 0
+
+    hook::run roster_conference_popup_menu_hook $m $xlib $jid
+
+    return $m
+}
+
+hook::add roster_conference_popup_menu_hook \
+    [namespace current]::roster::add_separator 50
+hook::add roster_conference_popup_menu_hook \
+    [namespace current]::roster::add_separator 70
+hook::add roster_conference_popup_menu_hook \
+    [namespace current]::roster::add_separator 85
+
+###############################################################################
+
+proc roster::service_popup_menu {xlib jid} {
+    if {[winfo exists [set m .servicepopupmenu]]} {
+	destroy $m
+    }
+    menu $m -tearoff 0
+
+    hook::run roster_service_popup_menu_hook $m $xlib $jid
+
+    return $m
+}
+
+hook::add roster_service_popup_menu_hook \
+    [namespace current]::roster::add_separator 50
+hook::add roster_service_popup_menu_hook \
+    [namespace current]::roster::add_separator 70
+hook::add roster_service_popup_menu_hook \
+    [namespace current]::roster::add_separator 85
+
+###############################################################################
+
+proc roster::create_groupchat_user_menu {xlib jid} {
+    if {[winfo exists [set m .groupchatpopupmenu]]} {
+	destroy $m
+    }
+    menu $m -tearoff 0
+
+    hook::run roster_create_groupchat_user_menu_hook $m $xlib $jid
+
+    return $m
+}
+
+hook::add roster_create_groupchat_user_menu_hook \
+    [namespace current]::roster::add_separator 40
+hook::add roster_create_groupchat_user_menu_hook \
+    [namespace current]::roster::add_separator 50
+
+###############################################################################
+
+proc roster::create_group_popup_menu {xlib name} {
+    variable options
+    variable chats_group_name
+
+    if {$name == $chats_group_name} {
+	set state disabled
+    } else {
+	set state normal
+    }
+
+    if {[winfo exists [set m .grouppopupmenu]]} {
+	destroy $m
+    }
+    if {$options(nested)} {
+	set oname [msplit $name $options(nested_delimiter)]
+    } else {
+	set oname $name
+    }
+    menu $m -tearoff 0
+    $m add command \
+	-label [::msgcat::mc "Send message to all users in group..."] \
+	-command [list ::message::send_dialog \
+		       -to $name -group 1 -connection $xlib]
+    $m add command \
+	-label [::msgcat::mc "Resubscribe to all users in group..."] \
+	-command [list ::roster::resubscribe_group $xlib $name]
+
+    add_group_custom_presence_menu $m $xlib $name
+
+    $m add checkbutton -label [::msgcat::mc "Show offline users"] \
+	-variable [namespace current]::roster(show_offline,[list $xlib $oname]) \
+	-command [list [namespace current]::redraw_after_idle]
+    $m add command -label [::msgcat::mc "Rename group..."] \
+	-command [list [namespace current]::rename_group_dialog $xlib $name] \
+	-state $state
+    $m add command -label [::msgcat::mc "Remove group..."] \
+	-command [list [namespace current]::remove_group_dialog $xlib $name] \
+	-state $state
+    $m add command -label [::msgcat::mc "Remove all users in group..."] \
+	-command [list [namespace current]::remove_users_group_dialog $xlib $name]
+
+    set last [$m index end]
+
+    ::hook::run roster_group_popup_menu_hook $m $xlib $name
+
+    if {[$m index end] > $last} {
+	$m insert [expr $last + 1] separator
+    }
+
+    return $m
+}
+
+###############################################################################
+
+proc roster::remove_group_dialog {xlib name} {
+    set res [MessageDlg .remove_item -aspect 50000 -icon question -type user \
+		 -buttons {yes no} -default 0 -cancel 1 \
+		 -message [::msgcat::mc "Are you sure to remove group '%s' from roster?\
+\n(Users which are in this group only, will be in undefined group.)" $name]]
+
+    if {$res == 0} {
+	roster::send_rename_group $xlib $name ""
+    }
+}
+
+proc roster::remove_users_group_dialog {xlib name} {
+    set res [MessageDlg .remove_item -aspect 50000 -icon question -type user \
+		 -buttons {yes no} -default 0 -cancel 1 \
+		 -message [::msgcat::mc "Are you sure to remove all users in group '%s' from roster?\
+\n(Users which are not in this group only, will be removed from the roster as well.)" $name]]
+
+    if {$res == 0} {
+	roster::send_remove_users_group $xlib $name
+    }
+}
+
+proc roster::rename_group_dialog {xlib name} {
+    global new_roster_group_name
+
+    set new_roster_group_name $name
+
+    set w .roster_group_rename
+    if {[winfo exists $w]} {
+	destroy $w
+    }
+
+    Dialog $w -title [::msgcat::mc "Rename roster group"] \
+	-separator 1 -anchor e -default 0 -cancel 1
+
+    $w add -text [::msgcat::mc "OK"] -command \
+	[list [namespace current]::confirm_rename_group $w $xlib $name]
+    $w add -text [::msgcat::mc "Cancel"] -command [list destroy $w]
+
+    set p [$w getframe]
+
+    label $p.lgroupname -text [::msgcat::mc "New group name:"]
+    ecursor_entry [entry $p.groupname -textvariable new_roster_group_name]
+
+    grid $p.lgroupname  -row 0 -column 0 -sticky e
+    grid $p.groupname   -row 0 -column 1 -sticky ew
+
+    focus $p.groupname
+    $w draw
+}
+
+proc roster::confirm_rename_group {w xlib name} {
+    global new_roster_group_name
+    variable roster
+
+    destroy $w
+
+    ::roster::send_rename_group $xlib $name $new_roster_group_name
+
+    set gid [list $xlib $name]
+    set newgid [list $xlib $new_roster_group_name]
+
+    if {[info exists roster(collapsed,$gid)]} {
+	set roster(collapsed,$newgid) $roster(collapsed,$gid)
+	unset roster(collapsed,$gid)
+    }
+    if {[info exists roster(show_offline,$gid)]} {
+	set roster(show_offline,$newgid) $roster(show_offline,$gid)
+	unset roster(show_offline,$gid)
+    }
+}
+
+proc roster::add_group_by_jid_regexp_dialog {} {
+    global new_roster_group_rname
+    global new_roster_group_regexp
+
+    set w .roster_group_add_by_jid_regexp
+    if {[winfo exists $w]} {
+	destroy $w
+    }
+
+    Dialog $w -title [::msgcat::mc "Add roster group by JID regexp"] \
+	-separator 1 -anchor e -default 0 -cancel 1
+
+    $w add -text [::msgcat::mc "OK"] -command "
+	destroy [list $w]
+	roster::add_group_by_jid_regexp \
+	    \$new_roster_group_rname \$new_roster_group_regexp
+    "
+    $w add -text [::msgcat::mc "Cancel"] -command [list destroy $w]
+
+    set p [$w getframe]
+
+    label $p.lgroupname -text [::msgcat::mc "New group name:"]
+    ecursor_entry [entry $p.groupname -textvariable new_roster_group_rname]
+    label $p.lregexp -text [::msgcat::mc "JID regexp:"]
+    ecursor_entry [entry $p.regexp -textvariable new_roster_group_regexp]
+
+    grid $p.lgroupname -row 0 -column 0 -sticky e
+    grid $p.groupname  -row 0 -column 1 -sticky ew
+    grid $p.lregexp    -row 1 -column 0 -sticky e
+    grid $p.regexp     -row 1 -column 1 -sticky ew
+
+    focus $p.groupname
+    $w draw
+}
+
+###############################################################################
+
+proc roster::add_group_custom_presence_menu {m xlib name} {
+    set mm [menu $m.custom_presence -tearoff 0]
+
+    $mm add command -label [::msgcat::mc "Available"] \
+	-command [list roster::send_custom_presence_group $xlib $name available]
+    $mm add command -label [::msgcat::mc "Free to chat"] \
+	-command [list roster::send_custom_presence_group $xlib $name chat]
+    $mm add command -label [::msgcat::mc "Away"] \
+	-command [list roster::send_custom_presence_group $xlib $name away]
+    $mm add command -label [::msgcat::mc "Extended away"] \
+	-command [list roster::send_custom_presence_group $xlib $name xa]
+    $mm add command -label [::msgcat::mc "Do not disturb"] \
+	-command [list roster::send_custom_presence_group $xlib $name dnd]
+    $mm add command -label [::msgcat::mc "Unavailable"] \
+	-command [list roster::send_custom_presence_group $xlib $name unavailable]
+
+    $m add cascade -label [::msgcat::mc "Send custom presence"] -menu $mm
+}
+
+###############################################################################
+
+# vim:ts=8:sw=4:sts=4:noet


Property changes on: trunk/tkabber/ifacetk/roster.tcl
___________________________________________________________________
Added: svn:keywords
   + Author Date Id Revision
Added: svn:mergeinfo
   + 
Added: svn:eol-style
   + native

Deleted: trunk/tkabber/joingrdialog.tcl
===================================================================
--- trunk/tkabber/joingrdialog.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/joingrdialog.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -1,221 +0,0 @@
-# $Id$
-
-#set gr_nick $user
-set gr_group "talks"
-set gr_server conference.jabber.org
-set gr_passwd ""
-
-custom::defvar gr_nick_list {} \
-        [::msgcat::mc "Join group dialog data (nicks)."] -group Hidden
-custom::defvar gr_group_list {} \
-        [::msgcat::mc "Join group dialog data (groups)."] -group Hidden
-custom::defvar gr_server_list {} \
-        [::msgcat::mc "Join group dialog data (servers)."] -group Hidden
-
-proc ecursor_entry {e} {
-    $e icursor end
-}
-
-proc pack_input {fr row var lab args} {
-
-    label $fr.l$var -text $lab
-    ecursor_entry [eval entry $fr.$var -textvar $var $args]
-
-    grid $fr.l$var -row $row -column 0 -sticky e
-    grid $fr.$var  -row $row -column 1 -sticky ew
-}
-
-proc pack_combo {fr row var lab args} {
-
-    label $fr.l$var -text $lab
-    ecursor_entry [eval ComboBox $fr.$var -textvariable $var $args].e
-
-    grid $fr.l$var -row $row -column 0 -sticky e
-    grid $fr.$var  -row $row -column 1 -sticky ew
-}
-
-proc update_combo_list {list entry num} {
-
-    set ind [lsearch -exact $list $entry]
-    if {$ind >= 0} {
-	set newlist [linsert [lreplace $list $ind $ind] 0 $entry]
-    } else {
-	set newlist [linsert $list 0 $entry]
-    }
-    if {[llength $newlist] > $num} {
-	return [lreplace $newlist end end]
-    } else {
-	return $newlist
-    }
-}
-
-proc join_group_dialog {xlib args} {
-    global gr_nick gr_group gr_server gr_xlib gr_passwd
-    global gr_nick_list gr_group_list gr_server_list
-
-    if {[llength [connections]] == 0} return
-
-    set gr_passwd ""
-    foreach {opt val} $args {
-	switch -- $opt {
-	    -server     {set server $val}
-	    -group      {set group $val}
-	    -nick       {set nick $val}
-	    -password   {set gr_passwd $val}
-	}
-    }
-
-    set gw .joingroup
-    catch { destroy $gw }
-
-    Dialog $gw -title [::msgcat::mc "Join group"] -separator 1 -anchor e \
-	    -default 0 -cancel 1 -modal none
-
-    set gf [$gw getframe]
-    grid columnconfigure $gf 1 -weight 1
-
-    if {[info exists group]} {
-	set gr_group $group
-#	set gr_group_list [update_combo_list $gr_group_list $gr_group 20]
-    } elseif {[llength $gr_group_list]} {
-	set gr_group [lindex $gr_group_list 0]
-    }
-    if {[info exists server]} {
-	set gr_server $server
-#	set gr_server_list [update_combo_list $gr_server_list $gr_server 10]
-    } elseif {[llength $gr_server_list]} {
-	set gr_server [lindex $gr_server_list 0]
-    }
-    if {[info exists nick]} {
-	set gr_nick $nick
-#	set gr_nick_list [update_combo_list $gr_nick_list $gr_nick 10]
-    } else {
-	set gr_nick [get_group_nick [::xmpp::jid::jid $gr_group $gr_server] $gr_nick]
-    }
-    if {$xlib == ""} {
-	set xlib [lindex [connections] 0]
-    }
-    set gr_xlib [connection_jid $xlib]
-
-    pack_combo $gf 0 gr_group [::msgcat::mc "Group:"] -values $gr_group_list
-    pack_combo $gf 1 gr_server [::msgcat::mc "Server:"] -values $gr_server_list
-    pack_combo $gf 2 gr_nick [::msgcat::mc "Nick:"] -values $gr_nick_list
-    pack_input $gf 3 gr_passwd [::msgcat::mc "Password:"] -width 30 -show *
-
-    if {[llength [connections]] > 1} {
-	foreach c [connections] {
-	    lappend connections [connection_jid $c]
-	}
-	pack_combo $gf 5 gr_xlib [::msgcat::mc "Connection:"] \
-	    -values $connections
-    }
-
-    $gw add -text [::msgcat::mc "Join"] -command [list join_group1 $gw]
-	    
-    $gw add -text [::msgcat::mc "Cancel"] -command [list destroy $gw]
-
-    $gw draw $gf.gr_group
-}
-
-proc join_group1 {gw} {
-    global gr_nick gr_group gr_server gr_xlib gr_passwd
-    global gr_nick_list gr_group_list gr_server_list
-
-    destroy $gw
-
-    set gr_nick_list [update_combo_list $gr_nick_list $gr_nick 10]
-    set gr_group_list [update_combo_list $gr_group_list $gr_group 20]
-    set gr_server_list [update_combo_list $gr_server_list $gr_server 10]
-
-    foreach c [connections] {
-	if {[connection_jid $c] == $gr_xlib} {
-	    set xlib $c
-	}
-    }
-    if {![info exists xlib]} {
-	set xlib [lindex [connections] 0]
-    }
-
-    join_group $xlib [::xmpp::jid::jid $gr_group $gr_server] \
-	       -nick $gr_nick \
-	       -password $gr_passwd
-}
-
-proc join_group {xlib jid args} {
-    global gr_nick
-
-    set password ""
-    foreach {opt val} $args {
-	switch -- $opt {
-	    -password   { set password $val }
-	    -nick       { set nick $val }
-	}
-    }
-    if {$xlib == ""} {
-	set xlib [lindex [connections] 0]
-    }
-    if {![info exists nick]} {
-	set nick [get_group_nick $jid $gr_nick]
-    }
-
-    set group [xmpp::jid::normalize $jid]
-    muc::join_group $xlib $group $nick $password
-
-    chat::activate [chat::chatid $xlib $group]
-}
-
-proc set_our_groupchat_nick {chatid nick} {
-    global groupchats
-
-    debugmsg conference "SET NICK: $chatid '$nick'"
-
-    set xlib [chat::get_xlib $chatid]
-    set group [::xmpp::jid::normalize [chat::get_jid $chatid]]
-    set groupchats(nick,$xlib,$group) $nick
-}
-
-proc get_our_groupchat_nick {chatid} {
-    global groupchats
-    
-    debugmsg conference "GET NICK: $chatid"
-
-    set xlib [chat::get_xlib $chatid]
-    set group [::xmpp::jid::normalize [chat::get_jid $chatid]]
-    return $groupchats(nick,$xlib,$group)
-}
-
-###############################################################################
-
-proc joingroup_disco_node_menu_setup {m bw tnode data parentdata} {
-    lassign $data type xlib jid node
-    switch -- $type {
-	item -
-	item2 {
-	    set identities [::disco::browser::get_identities $bw $tnode]
-
-	    if {[lempty $identities]} {
-		set identities [::disco::browser::get_parent_identities $bw $tnode]
-	    }
-
-	    # JID with resource is not a room JID
-	    if {[::xmpp::jid::resource $jid] != ""} return
-
-	    foreach id $identities {
-		if {[::xmpp::xml::getAttr $id category] == "conference"} {
-		    $m add command -label [::msgcat::mc "Join group..."] \
-			-command [list join_group_dialog $xlib \
-				       -server [::xmpp::jid::server $jid] \
-				       -group [::xmpp::jid::node $jid]]
-		    break
-		}
-	    }
-	}
-    }
-}
-
-hook::add disco_node_menu_hook \
-	  [namespace current]::joingroup_disco_node_menu_setup 45
-
-###############################################################################
-
-# vim:ts=8:sw=4:sts=4:noet

Modified: trunk/tkabber/login.tcl
===================================================================
--- trunk/tkabber/login.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/login.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -582,16 +582,11 @@
 proc login_login {xlib logindata} {
     global use_tls have_compress have_sasl
     global loginconf_hist
-    global gr_nick gr_server gra_server
 
     set loginconf_hist($xlib) $logindata
 
     array set lc $logindata
 
-    set gr_nick $lc(user)
-    set gr_server conference.$lc(server)
-    set gra_server conference.$lc(server)
-
     if {($use_tls && $lc(stream_options) == "encrypted") || \
 	    ($have_compress && $lc(stream_options) == "compressed") || \
 	    ($have_sasl && $lc(usesasl))} {

Modified: trunk/tkabber/muc.tcl
===================================================================
--- trunk/tkabber/muc.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/muc.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -46,32 +46,9 @@
 	[::msgcat::mc "Report the list of current MUC rooms on\
 		       disco#items query."] \
 	-type boolean -group IQ
-
-    # MUC affiliations and roles:
-
-    array set e2l [list owner       [::msgcat::mc "owner"] \
-			admin       [::msgcat::mc "admin"] \
-			member      [::msgcat::mc "member"] \
-			outcast     [::msgcat::mc "outcast"] \
-			none        [::msgcat::mc "none"] \
-			moderator   [::msgcat::mc "moderator"] \
-			participant [::msgcat::mc "participant"] \
-			visitor     [::msgcat::mc "visitor"]]
-
-    variable maxl 0
-    foreach n [array names e2l] {
-	set l2e($e2l($n)) $n
-	set l [string length $e2l($n)]
-	if {$l + 2 > $maxl} {
-	    set maxl [expr {$l + 2}]
-	}
-    }
 }
 
-###############################################################################
-
 set ::NS(muc)       http://jabber.org/protocol/muc
-set ::NS(muc#admin) http://jabber.org/protocol/muc#admin
 set ::NS(muc#owner) http://jabber.org/protocol/muc#owner
 set ::NS(muc#user)  http://jabber.org/protocol/muc#user
 
@@ -79,249 +56,28 @@
 
 ###############################################################################
 
-proc muc::add_groupchat_user_menu_items {type m xlib jid} {
-    set group [::xmpp::jid::stripResource $jid]
+proc set_our_groupchat_nick {chatid nick} {
+    global groupchats
 
-    if {![is_compatible $group]} return
+    debugmsg conference "SET NICK: $chatid '$nick'"
 
-    set chatid [chat::chatid $xlib $group]
-
-    if {![chat::is_groupchat $chatid]} {
-	return
-    }
-
-    switch -- $type {
-	chat { set reschatid [chat::chatid $xlib $jid] }
-	roster { set reschatid $chatid }
-    }
-
-    set mm [menu $m.muc -tearoff 0]
-    $mm add command -label [::msgcat::mc "Whois"] \
-	-command [list muc::whois $xlib $jid $reschatid]
-    $mm add command -label [::msgcat::mc "Kick"] \
-	-command [list muc::change_item_param \
-		      {role none} down $xlib $jid "" $reschatid]
-    $mm add command -label [::msgcat::mc "Ban"] \
-	-command [list muc::change_item_param \
-		      {affiliation outcast} down $xlib $jid "" $reschatid]
-    $mm add command -label [::msgcat::mc "Grant Voice"] \
-	-command [list muc::change_item_param \
-		      {role participant} up $xlib $jid "" $reschatid]
-    $mm add command -label [::msgcat::mc "Revoke Voice"] \
-	-command [list muc::change_item_param \
-		      {role visitor} down $xlib $jid "" $reschatid]
-    $mm add command -label [::msgcat::mc "Grant Membership"] \
-	-command [list muc::change_item_param \
-		      {affiliation member} up $xlib $jid "" $reschatid]
-    $mm add command -label [::msgcat::mc "Revoke Membership"] \
-	-command [list muc::change_item_param \
-		      {affiliation none} down $xlib $jid "" $reschatid]
-    $mm add command -label [::msgcat::mc "Grant Moderator Privileges"] \
-	-command [list muc::change_item_param \
-		      {role moderator} up $xlib $jid "" $reschatid]
-    $mm add command -label [::msgcat::mc "Revoke Moderator Privileges"] \
-	-command [list muc::change_item_param \
-		      {role participant} down $xlib $jid "" $reschatid]
-    $mm add command -label [::msgcat::mc "Grant Admin Privileges"] \
-	-command [list muc::change_item_param \
-		      {affiliation admin} up $xlib $jid "" $reschatid]
-    $mm add command -label [::msgcat::mc "Revoke Admin Privileges"] \
-	-command [list muc::change_item_param \
-		      {affiliation member} down $xlib $jid "" $reschatid]
-    #$mm add command -label [::msgcat::mc "Grant Owner Privileges"] \
-    #    -command [list muc::change_item_param \
-    #    	      {affiliation owner} up $xlib $jid "" $reschatid]
-    #$mm add command -label [::msgcat::mc "Revoke Owner Privileges"] \
-    #    -command [list muc::change_item_param \
-    #		      {affiliation admin} down $xlib $jid "" $reschatid]
-
-    $m add cascade -label [::msgcat::mc "MUC"] -menu $mm
+    set xlib [chat::get_xlib $chatid]
+    set group [::xmpp::jid::normalize [chat::get_jid $chatid]]
+    set groupchats(nick,$xlib,$group) $nick
 }
 
-hook::add chat_create_user_menu_hook \
-	  [list muc::add_groupchat_user_menu_items chat] 43
-hook::add roster_create_groupchat_user_menu_hook \
-	  [list muc::add_groupchat_user_menu_items roster] 39
+proc get_our_groupchat_nick {chatid} {
+    global groupchats
 
-###############################################################################
+    debugmsg conference "GET NICK: $chatid"
 
-proc muc::add_muc_menu_items {m xlib group {state disabled}} {
-    if {[is_compatible $group]} {
-	set state normal
-    }
-
-    set chatid [chat::chatid $xlib $group]
-
-    set mm [menu $m.muc -tearoff 0]
-
-    $mm add command -label [::msgcat::mc "Configure room"] \
-	-command [list muc::request_config $chatid]
-    $mm add command -label [::msgcat::mc "Edit voice list"] \
-	-command [list muc::request_list role participant $chatid]
-    $mm add command -label [::msgcat::mc "Edit ban list"] \
-	-command [list muc::request_list affiliation outcast $chatid]
-    $mm add command -label [::msgcat::mc "Edit member list"] \
-	-command [list muc::request_list affiliation member $chatid]
-    $mm add command -label [::msgcat::mc "Edit moderator list"] \
-	-command [list muc::request_list role moderator $chatid]
-    $mm add command -label [::msgcat::mc "Edit admin list"] \
-	-command [list muc::request_list affiliation admin $chatid]
-    $mm add command -label [::msgcat::mc "Edit owner list"] \
-	-command [list muc::request_list affiliation owner $chatid]
-    $mm add separator
-    $mm add command -label [::msgcat::mc "Destroy room"] \
-	-command [list muc::request_destruction_dialog $chatid "" ""]
-
-    $m add cascade -label [::msgcat::mc "MUC"] -menu $mm -state $state
-}
-
-hook::add chat_create_conference_menu_hook muc::add_muc_menu_items 37
-
-proc muc::disco_node_menu_setup {m bw tnode data parentdata} {
-    lassign $data type xlib jid node
-    switch -- $type {
-	item -
-	item2 {
-	    set identities [disco::browser::get_identities $bw $tnode]
-
-	    if {[lempty $identities]} {
-		set identities [disco::browser::get_parent_identities $bw $tnode]
-	    }
-
-	    set features [disco::browser::get_features $bw $tnode]
-
-	    if {[lempty $features]} {
-		set features [disco::browser::get_parent_features $bw $tnode]
-	    }
-
-	    # JID with resource is not a room JID
-	    if {[::xmpp::jid::stripResource $jid] != $jid} return
-
-	    # A room must have non-empty node
-	    if {[::xmpp::jid::node $jid] == ""} return
-
-	    foreach id $identities {
-		if {[::xmpp::xml::getAttr $id category] == "conference"} {
-		    foreach f $features {
-			if {$f == $::NS(muc)} {
-			    add_muc_menu_items $m $xlib $jid normal
-			    return
-			}
-		    }
-		}
-	    }
-	}
-    }
-}
-
-hook::add disco_node_menu_hook muc::disco_node_menu_setup 60
-
-###############################################################################
-
-proc muc::handle_commands {chatid user body type} {
-    if {$type != "groupchat"} return
-
     set xlib [chat::get_xlib $chatid]
-    set group [chat::get_jid $chatid]
-    if {[cequal [crange $body 0 5] "/kick "]} {
-	set params {role none}
-	set dir down
-	lassign [parse_nick_reason $body 6] nick reason
-    } elseif {[cequal [crange $body 0 4] "/ban "]} {
-	set params {affiliation outcast}
-	set dir down
-	lassign [parse_nick_reason $body 5] nick reason
-    } elseif {[cequal [crange $body 0 6] "/unban "]} {
-	set jid [parse_nick $body 7]
-	unban $xlib $group $jid
-	return stop
-    } elseif {[cequal [crange $body 0 6] "/whois "]} {
-	set nick [parse_nick $body 7]
-	whois $xlib $group/$nick $chatid
-	return stop
-    } elseif {[cequal [crange $body 0 6] "/voice "]} {
-	set params {role participant}
-	set dir up
-	lassign [parse_nick_reason $body 7] nick reason
-    } elseif {[cequal [crange $body 0 8] "/devoice "]} {
-	set params {role visitor}
-	set dir down
-	lassign [parse_nick_reason $body 9] nick reason
-    } elseif {[cequal [crange $body 0 7] "/member "]} {
-	set params {affiliation member}
-	set dir up
-	lassign [parse_nick_reason $body 8] nick reason
-    } elseif {[cequal [crange $body 0 9] "/demember "]} {
-	set params {affiliation none}
-	set dir down
-	lassign [parse_nick_reason $body 10] nick reason
-    } elseif {[cequal [crange $body 0 10] "/moderator "]} {
-	set params {role moderator}
-	set dir up
-	lassign [parse_nick_reason $body 11] nick reason
-    } elseif {[cequal [crange $body 0 12] "/demoderator "]} {
-	set params {role participant}
-	set dir down
-	lassign [parse_nick_reason $body 13] nick reason
-    } elseif {[cequal [crange $body 0 6] "/admin "]} {
-	set params {affiliation admin}
-	set dir up
-	lassign [parse_nick_reason $body 7] nick reason
-    } elseif {[cequal [crange $body 0 8] "/deadmin "]} {
-	set params {affiliation member}
-	set dir down
-	lassign [parse_nick_reason $body 9] nick reason
-    } else {
-	return
-    }
-    
-    change_item_param $params $dir $xlib $group/$nick $reason $chatid
-
-    return stop
+    set group [::xmpp::jid::normalize [chat::get_jid $chatid]]
+    return $groupchats(nick,$xlib,$group)
 }
 
-hook::add chat_send_message_hook muc::handle_commands 50
-
-proc muc::parse_nick {body n} {
-    return [lindex [parse_nick_reason $body $n] 0]
-}
-
-proc muc::parse_nick_reason {body n} {
-    # Parse nickname and reason
-    # first line is a nick, rest are reason
-    set nick_reason [crange $body $n end]
-    set ne [string first "\n" $nick_reason]
-    if {$ne < 0} {
-	set nick $nick_reason
-	set reason ""
-    } else {
-	set nick [string range $nick_reason 0 [expr {$ne - 1}]]
-	set reason [string range $nick_reason [expr {$ne + 1}] end]
-    }
-    return [list $nick [string trim $reason]]
-}
-
 ###############################################################################
 
-proc muc::commands_comps {chatid compsvar wordstart line} {
-    set group [chat::get_jid $chatid]
-    if {![is_compatible $group]} return
-
-    upvar 0 $compsvar comps
-
-    if {!$wordstart} {
-	lappend comps {/whois } {/kick } {/ban } {/unban } \
-	    {/voice } {/devoice } \
-	    {/member } {/demember } \
-	    {/moderator } {/demoderator } \
-	    {/admin } {/deadmin }
-    }
-}
-
-hook::add generate_completions_hook muc::commands_comps
-
-###############################################################################
-
 proc muc::get_real_jid {xlib jid} {
     variable tokens
 
@@ -382,687 +138,61 @@
 
 ###############################################################################
 
-proc muc::compare_roles {role1 role2} {
-    set roles {none visitor participant moderator}
+proc muc::change_item_attr {xlib user attr value dir reason reschatid} {
+    variable tokens
 
-    set idx1 [lsearch -exact $roles $role1]
-    set idx2 [lsearch -exact $roles $role2]
-    expr {$idx1 - $idx2}
-}
-
-proc muc::compare_affs {aff1 aff2} {
-    set affs {outcast none member admin owner}
-
-    set idx1 [lsearch -exact $affs $aff1]
-    set idx2 [lsearch -exact $affs $aff2]
-    expr {$idx1 - $idx2}
-}
-
-proc muc::change_item_param {params dir xlib user reason reschatid} {
     set group [::xmpp::jid::stripResource $user]
     set chatid [chat::chatid $xlib $group]
     set nick  [chat::get_nick $xlib $user groupchat]
 
-    lassign $params key val
-    if {[set aff [get_affiliation $xlib $user]] != "" && \
-	    [set role [get_role $xlib $user]] != ""} {
-	switch -- $key/$dir {
-	    affiliation/up {
-		if {[compare_affs $aff $val] >= 0} {
-		    chat::add_message $reschatid $group error \
-			"affiliation $val '$nick':\
-			 [::msgcat::mc {User already %s} $aff]" {}
-		    return
-		}
-	    }
-	    affiliation/down {
-		if {[compare_affs $aff $val] <= 0} {
-		    chat::add_message $reschatid $group error \
-			"affiliation $val '$nick':\
-			 [::msgcat::mc {User already %s} $aff]" {}
-		    return
-		}
-	    }
-	    role/up {
-		if {[compare_roles $role $val] >= 0} {
-		    chat::add_message $reschatid $group error \
-			"role $val '$nick':\
-			 [::msgcat::mc {User already %s} $role]" {}
-		    return
-		}
-	    }
-	    role/down {
-		if {[compare_roles $role $val] <= 0} {
-		    chat::add_message $reschatid $group error \
-			"role $val '$nick':\
-			 [::msgcat::mc {User already %s} $role]" {}
-		    return
-		}
-	    }
-	    default {
-		return
-	    }
-	}
-    }
-
-    set itemsubtags {}
-    if {$reason != ""} {
-	lappend itemsubtags [::xmpp::xml::create reason -cdata $reason]
-    }
-    set vars [list nick $nick]
-    if {$params == {affiliation outcast}} {
-	# For unknown reason banning request MUST be based on
-	# user's bare JID (which may be not known by admin)
-	set real_jid [get_real_jid $xlib $user]
-	if {$real_jid != ""} {
-	    set vars [list jid [::xmpp::jid::stripResource $real_jid]]
-	}
-	
-    }
-    set item [::xmpp::xml::create item \
-		  -attrs [concat $vars $params] \
-		  -subelements $itemsubtags]
-
-    ::xmpp::sendIQ $xlib set \
-	-query [::xmpp::xml::create query \
-			-xmlns $::NS(muc#admin) \
-			-subelement $item] \
-	-to $group \
-	-command [list muc::test_error_res "$params '$nick'" $xlib $group $reschatid]
-}
-
-###############################################################################
-
-proc muc::unban {xlib group jid} {
-    ::xmpp::sendIQ $xlib get \
-	-query [::xmpp::xml::create query \
-			-xmlns $::NS(muc#admin) \
-			-subelement [::xmpp::xml::create item \
-					    -attrs {affiliation outcast}]] \
-	-to $group \
-	-command [list muc::unban_continue $xlib $group $jid]
-}
-
-proc muc::unban_continue {xlib group jid res child} {
-    if {$res != "ok"} {
-	chat::add_message [chat::chatid $xlib $group] $group error \
-	    "affiliation none '$jid': [error_to_string $child]" {}
+    if {![info exists tokens($chatid)]} {
+	chat::add_message $reschatid $group error \
+		"$attr $value '$nick':\
+		 [::msgcat::mc {You must join the room to set %s} $attr]" {}
 	return
     }
 
-    ::xmpp::xml::split $child tag xmlns attrs cdata subels
-
-    set jid [::xmpp::jid::stripResource $jid]
-    set found 0
-    foreach item $subels {
-	::xmpp::xml::split $item stag sxmlns sattrs scdata ssubels
-	switch -- $stag {
-	    item {
-		set jid1 [::xmpp::xml::getAttr $sattrs jid]
-		if {$jid == $jid1} {
-		    set found 1
-		    break
-		}
-	    }
-	}
+    set args [list -command \
+		   [list muc::test_error_res \
+			 "$attr $value '$nick'" $xlib $group $reschatid]
+    if {![string equal $reason ""]} {
+	lappend args -reason $reason
     }
 
-    if {!$found} {
-	chat::add_message [chat::chatid $xlib $group] $group error \
-	    "affiliation none '$jid': User is not banned" {}
-	return
+    switch -- $attr/$dir {
+	affiliation/up   { set command ::xmpp::muc::raiseAffiliation }
+	affiliation/down { set command ::xmpp::muc::lowerAffiliation }
+	role/up   { set command ::xmpp::muc::raiseRole }
+	role/down { set command ::xmpp::muc::lowerRole }
     }
 
-    set item [::xmpp::xml::create item \
-		  -attrs [list jid $jid affiliation none]]
-
-    ::xmpp::sendIQ $xlib set \
-	-query [::xmpp::xml::create query \
-			-xmlns $::NS(muc#admin) \
-			-subelement $item] \
-	-to $group \
-	-command [list muc::test_unban_res $xlib $group $jid]
+    eval [list $command $tokens($chatid) $nick $value] $args
 }
 
-proc muc::test_unban_res {xlib group jid res child} {
-    if {$res != "ok"} {
-	chat::add_message [chat::chatid $xlib $group] $group error \
-	    "affiliation none '$jid': [error_to_string $child]" {}
-	return
-    }
-
-    chat::add_message [chat::chatid $xlib $group] $group info \
-	"affiliation none $jid: User is unbanned" {}
-}
-
 ###############################################################################
 
-proc muc::request_destruction_dialog {chatid alt reason} {
-    set xlib [chat::get_xlib $chatid]
-    set group [chat::get_jid $chatid]
-
-    set warning \
-	[::msgcat::mc "Conference room %s will be destroyed\
-		       permanently.\n\nProceed?" $group]
-
-    set res [MessageDlg .muc_request_destruction -aspect 50000 -icon warning \
-			-type user -buttons {yes no} -default 1 \
-			-cancel 1 \
-			-message $warning]
-    if {!$res} {
-	request_destruction $chatid $alt $reason
-    }
+proc muc::unban {xlib group jid} {
+    ::xmpp::muc::unsetOutcast $xlib $group $jid \
+		-command [list muc::test_unban_res $xlib $group $jid]
 }
 
-proc muc::request_destruction {chatid alt reason} {
-    set xlib [chat::get_xlib $chatid]
-    set group [chat::get_jid $chatid]
-
-    if {$alt != ""} {
-	set vars [list jid $alt]
-    } else {
-	set vars {}
-    }
-
-    ::xmpp::sendIQ $xlib set \
-	-query [::xmpp::xml::create query \
-		    -xmlns $::NS(muc#owner) \
-		    -subelement [::xmpp::xml::create destroy \
-					-attrs $vars \
-					-subelement [::xmpp::xml::create reason \
-							    -cdata $reason]]] \
-	-to $group \
-	-command [list muc::test_error_res "destroy" $xlib $group $chatid]
-}
-
-###############################################################################
-
-proc muc::request_list {attr val chatid} {
-    set xlib [chat::get_xlib $chatid]
-    set group [chat::get_jid $chatid]
-    ::xmpp::sendIQ $xlib get \
-	-query [::xmpp::xml::create query \
-			-xmlns $::NS(muc#admin) \
-			-subelement [::xmpp::xml::create item \
-					-attrs [list $attr $val]]] \
-	-to $group \
-	-command [list muc::receive_list $attr $val $chatid]
-}
-
-
-proc muc::receive_list {attr val chatid res child} {
-    variable e2l
-    variable maxl
-
-    set xlib [chat::get_xlib $chatid]
-    set group [chat::get_jid $chatid]
-    if {![string equal $res ok]} {
-	chat::add_message $chatid $group error \
-	    "$attr $val list: [error_to_string $child]" {}
-	return
-    }
-
-    ::xmpp::xml::split $child tag xmlns attrs cdata subels
-
-    variable winid
-
-    set w .muc_list$winid
-    incr winid
-
-    if {[winfo exists $w]} {
-	destroy $w
-    }
-
-    # [format] is intentional here. After %s substitution, it becomes
-    # one of the following:
-    # [::msgcat::mc "Edit owner list"]
-    # [::msgcat::mc "Edit admin list"]
-    # [::msgcat::mc "Edit member list"]
-    # [::msgcat::mc "Edit outcast list"]
-    # [::msgcat::mc "Edit moderator list"]
-    # [::msgcat::mc "Edit participant list"]
-    # [::msgcat::mc "Edit visitor list"]
-    Dialog $w -title [::msgcat::mc [format "Edit %s list" $val]] \
-        -modal none -separator 1 -anchor e -default 0 -cancel 1
-
-    set wf [$w getframe]
-
-    set sw [ScrolledWindow $wf.sw]
-
-    set lb [::mclistbox::mclistbox $sw.listbox \
-		    -exportselection 0 \
-		    -resizeonecolumn 1 \
-		    -labelanchor w \
-		    -width 90 \
-		    -height 16]
-
-    bind $lb <Destroy> [namespace code [list list_cleanup %W]]
-
-    bind $lb <<ListboxSelect>> [namespace code [list update_fields %W [double% $val] [double% $w.fr1]]]
-
-    $sw setwidget $lb
-    fill_list $lb $subels $attr $val
- 
-    $w add -text [::msgcat::mc "Send"] \
-	-command [list muc::send_list $chatid $attr $val $w $lb]
-    $w add -text [::msgcat::mc "Cancel"] -command [list destroy $w]
-
-
-    frame $w.fr1
-    pack $w.fr1 -side bottom -in $wf -fill x -pady 1m
-
-    grid columnconfigure $w.fr1 3 -weight 1
-
-    label $w.fr1.lnick -text [::msgcat::mc "Nick"]
-    label $w.fr1.ljid -text [::msgcat::mc "JID"]
-    switch -- $attr {
-	role {
-	    label $w.fr1.lattr -text [::msgcat::mc "Role"]
+proc muc::test_unban_res {xlib group jid status msg} {
+    switch -- $status {
+	ok {
+	    chat::add_message [chat::chatid $xlib $group] $group info \
+		    [format "affiliation none '%s': %s" \
+			    $jid [::msgcat::mc "User is unbanned"]] {}
 	}
-	affiliation {
-	    label $w.fr1.lattr -text [::msgcat::mc "Affiliation"]
+	default {
+	    chat::add_message [chat::chatid $xlib $group] $group error \
+		    [format "affiliation none '%s': %s" \
+			    $jid [error_to_string $msg]] {}
 	}
     }
-    label $w.fr1.lreason -text [::msgcat::mc "Reason"]
-
-    grid $w.fr1.lnick   -row 0 -column 0 -sticky w
-    grid $w.fr1.ljid    -row 0 -column 1 -sticky w
-    grid $w.fr1.lattr   -row 0 -column 2 -sticky w
-    grid $w.fr1.lreason -row 0 -column 3 -sticky w
-
-    entry $w.fr1.nick1 -takefocus 0 -highlightthickness 0
-    if {[catch {$w.fr1.nick1 configure -state readonly}]} {
-	$w.fr1.nick1 configure -state disabled
-    }
-    entry $w.fr1.jid1 -takefocus 0 -highlightthickness 0
-    if {[catch {$w.fr1.jid1 configure -state readonly}]} {
-	$w.fr1.jid1 configure -state disabled
-    }
-
-    switch -- $attr {
-	role {
-	    ComboBox $w.fr1.attr1 -text $e2l($val) \
-		-values [list $e2l(moderator) $e2l(participant) $e2l(visitor) $e2l(none)] \
-		-editable no \
-		-width $maxl
-	}
-	affiliation {
-	    ComboBox $w.fr1.attr1 -text $e2l($val) \
-		-values [list $e2l(owner) $e2l(admin) $e2l(member) $e2l(none) $e2l(outcast)] \
-		-editable no \
-		-width $maxl
-	}
-    }
-
-    entry $w.fr1.reason1
-
-    button $w.fr1.update -text [::msgcat::mc "Update item"] \
-	-command [list muc::list_update_item $lb $attr $w.fr1]
-
-    grid $w.fr1.nick1   -row 1 -column 0 -sticky ew
-    grid $w.fr1.jid1    -row 1 -column 1 -sticky ew
-    grid $w.fr1.attr1   -row 1 -column 2 -sticky ew
-    grid $w.fr1.reason1 -row 1 -column 3 -sticky ew
-    grid $w.fr1.update  -row 1 -column 4 -sticky ew
-
-    entry $w.fr1.nick2
-    entry $w.fr1.jid2
-
-    switch -- $attr {
-	role {
-	    ComboBox $w.fr1.attr2 -text $e2l($val) \
-		-values [list $e2l(moderator) $e2l(participant) $e2l(visitor) $e2l(none)] \
-		-editable no \
-		-width $maxl
-	}
-	affiliation {
-	    ComboBox $w.fr1.attr2 -text $e2l($val) \
-		-values [list $e2l(owner) $e2l(admin) $e2l(member) $e2l(none) $e2l(outcast)] \
-		-editable no \
-		-width $maxl
-	}
-    }
-
-    entry $w.fr1.reason2
-
-    button $w.fr1.add -text [::msgcat::mc "Add new item"] \
-	-command [list muc::list_add_item $lb $val $w.fr1]
-
-    grid $w.fr1.nick2   -row 2 -column 0 -sticky ew
-    grid $w.fr1.jid2    -row 2 -column 1 -sticky ew
-    grid $w.fr1.attr2   -row 2 -column 2 -sticky ew
-    grid $w.fr1.reason2 -row 2 -column 3 -sticky ew
-    grid $w.fr1.add     -row 2 -column 4 -sticky ew
-
-    frame $w.fr1.fr
-
-    label $w.lall -text [::msgcat::mc "All items:"]
-    pack $w.lall -side left -in $w.fr1.fr -padx 1m -pady 1m
-
-    switch -- $attr {
-	role {
-	    ComboBox $w.roleall -text $e2l($val) \
-		-values [list $e2l(moderator) $e2l(participant) $e2l(visitor) $e2l(none)] \
-		-editable no \
-		-width $maxl \
-		-modifycmd [list muc::change_all_items $lb $w.roleall $attr]
-	    pack $w.roleall -side left -anchor w -in $w.fr1.fr -pady 1m
-	}
-	affiliation {
-	    ComboBox $w.affiliationall -text $e2l($val) \
-		-values [list $e2l(owner) $e2l(admin) $e2l(member) $e2l(none) $e2l(outcast)] \
-		-editable no \
-		-width $maxl \
-		-modifycmd [list muc::change_all_items $lb $w.affiliationall $attr]
-	    pack $w.affiliationall -side left -in $w.fr1.fr -pady 1m
-	}
-    }
-
-    grid $w.fr1.fr -row 3 -column 0 -columnspan 5 -sticky w
-
-    pack $sw -side top -expand yes -fill both
-
-    bindscroll $sw $lb
-
-    $w draw
 }
 
 ###############################################################################
 
-proc muc::trim {str} {
-    string range $str 1 end-1
-}
-
-proc muc::untrim {str} {
-    return " $str "
-}
-
-proc muc::update_fields {lb val fr} {
-    set selection [$lb curselection]
-    if {[llength $selection] == 0} {
-	set nick ""
-	set jid ""
-	set attr $val
-	set reason ""
-    } else {
-	set data [$lb get [lindex $selection 0]]
-	lassign $data n nick jid attr reason
-    }
-
-    $fr.nick1 configure -state normal
-    $fr.nick1 delete 0 end
-    $fr.nick1 insert 0 [trim $nick]
-    if {[catch {$fr.nick1 configure -state readonly}]} {
-	$fr.nick1 configure -state disabled
-    }
-    $fr.jid1 configure -state normal
-    $fr.jid1 delete 0 end
-    $fr.jid1 insert 0 [trim $jid]
-    if {[catch {$fr.jid1 configure -state readonly}]} {
-	$fr.jid1 configure -state disabled
-    }
-    $fr.attr1 configure -text [trim $attr]
-    $fr.reason1 delete 0 end
-    $fr.reason1 insert 0 [trim $reason]
-}
-
-proc muc::list_update_item {lb attr fr} {
-    set selection [$lb curselection]
-    if {[llength $selection] == 0} {
-	return
-    }
-    set idx [lindex $selection 0]
-    set data [lrange [$lb get $idx] 0 0]
-
-    lappend data [untrim [$fr.nick1 get]] \
-		 [untrim [$fr.jid1 get]] \
-		 [untrim [$fr.attr1 get]] \
-		 [untrim [$fr.reason1 get]]
-
-    incr idx
-    $lb insert $idx $data
-    incr idx -1
-    $lb delete $idx
-    $lb selection clear 0 end
-    $lb selection set $idx
-}
-
-proc muc::change_all_items {lb combobox attr} {
-    set value [$combobox get]
-
-    set yposition [lindex [$lb yview] 0]
-    set data [$lb get 0 end]
-    set result {}
-    set i 0
-    foreach row $data {
-	lappend result [lreplace $row 3 3 [untrim $value]]
-    }
-    $lb delete 0 end
-    eval $lb insert end $result
-    $lb yview moveto $yposition
-}
-
-###############################################################################
-
-proc muc::fill_list {lb items attr val} {
-    variable listdata
-    variable lastsort
-    variable e2l
-    variable maxl
-
-    set lastsort($lb) jid
-
-    set width(0) 3
-    set name(0) n
-    $lb column add n -label [untrim [::msgcat::mc "#"]]
-
-    set width(1) [expr {[string length [::msgcat::mc "Nick"]] + 2}]
-    set name(1) nick
-    $lb column add nick -label [untrim [::msgcat::mc "Nick"]]
-    $lb label bind nick <ButtonPress-1> [namespace code [list Sort %W nick]]
-
-    set width(2) [expr {[string length [::msgcat::mc "JID"]] + 2}]
-    set name(2) jid
-    $lb column add jid -label [untrim [::msgcat::mc "JID"]]
-    $lb label bind jid <ButtonPress-1> [namespace code [list Sort %W jid]]
-
-    switch -- $attr {
-	role {
-	    set width(3) [expr {[string length [::msgcat::mc "Role"]] + 2}]
-	    if {$width(3) < $maxl} {
-		set width(3) $maxl
-	    }
-	    set name(3) role
-	    $lb column add role -label [untrim [::msgcat::mc "Role"]]
-	    $lb label bind role <ButtonPress-1> [namespace code [list Sort %W role]]
-	}
-	affiliation {
-	    set width(3) [expr {[string length [::msgcat::mc "Affiliation"]] + 2}]
-	    if {$width(3) < $maxl} {
-		set width(3) $maxl
-	    }
-	    set name(3) affiliation
-	    $lb column add affiliation -label [untrim [::msgcat::mc "Affiliation"]]
-	    $lb label bind affiliation <ButtonPress-1> [namespace code [list Sort %W affiliation]]
-	}
-    }
-
-    set width(4) [expr {[string length [::msgcat::mc "Reason"]] + 2}]
-    set name(4) reason
-    $lb column add reason -label [untrim [::msgcat::mc "Reason"]]
-    $lb label bind reason <ButtonPress-1> [namespace code [list Sort %W reason]]
-
-    $lb column add lastcol -label "" -width 0
-    $lb configure -fillcolumn lastcol
-    
-    set items2 {}
-    foreach item $items {
-	::xmpp::xml::split $item tag xmlns attrs cdata subels
-	switch -- $tag {
-	    item {
-		set nick [::xmpp::xml::getAttr $attrs nick]
-		set jid [::xmpp::xml::getAttr $attrs jid]
-		switch -- $attr {
-		    role {
-			set attribute [::xmpp::xml::getAttr $attrs role]
-		    }
-		    affiliation {
-			set attribute [::xmpp::xml::getAttr $attrs affiliation]
-		    }
-		}
-		set reason ""
-		foreach subitem $subels {
-		    ::xmpp::xml::split $subitem stag sxmlns sattrs scdata ssubels
-		    if {$stag == "reason"} {
-			set reason $scdata
-		    }
-		}
-		lappend items2 [list $nick $jid $e2l($attribute) $reason]
-	    }
-	}
-    }
-
-    set listdata($lb) [lsort -dictionary -index 1 $items2]
-    set row 1
-    foreach item $listdata($lb) {
-	set itemdata {}
-	set i 0
-	foreach data [linsert $item 0 $row] {
-	    set wd [string length [untrim $data]]
-	    if {$wd > $width($i)} {
-		if {$wd > 50} {
-		    set width($i) 50
-		} else {
-		    set width($i) $wd
-		}
-	    }
-	    lappend itemdata [untrim $data]
-	    incr i
-	}
-	lappend itemdata ""
-	$lb insert end $itemdata
-
-	incr row
-    }
-
-    for {set i 0} {$i < 5} {incr i} {
-	$lb column configure $name($i) -width $width($i)
-    }
-}
-
-proc muc::Sort {lb tag} {
-    variable lastsort
-
-    set data [$lb get 0 end]
-    set index [lsearch -exact [$lb column names] $tag]
-    if {$lastsort($lb) != $tag} {
-	set result [lsort -dictionary -index $index $data]
-	set lastsort($lb) $tag
-    } else {
-	set result [lsort -decreasing -dictionary -index $index $data]
-	set lastsort($lb) ""
-    }
-    set result1 {}
-    set i 0
-    foreach row $result {
-	lappend result1 [lreplace $row 0 0 [untrim [incr i]]]
-    }
-    $lb delete 0 end
-    eval $lb insert end $result1
-}
-
-###############################################################################
-
-proc muc::list_add_item {lb val fr} {
-    variable e2l
-
-    set n [trim [lindex [$lb get end] 0]]
-    if {[string equal $n ""]} {
-	set n 0
-    }
-
-    set data [list [untrim [incr n]] \
-	     [untrim [$fr.nick2 get]] \
-	     [untrim [$fr.jid2 get]] \
-	     [untrim [$fr.attr2 get]] \
-	     [untrim [$fr.reason2 get]]]
-
-    $lb insert end $data
-
-    $fr.nick2 delete 0 end
-    $fr.jid2 delete 0 end
-    $fr.attr2 configure -text $e2l($val)
-    $fr.reason2 delete 0 end
-}
-
-###############################################################################
-
-proc muc::send_list {chatid attr val w lb} {
-    variable listdata
-    variable l2e
-
-    foreach item $listdata($lb) {
-	set tmp([lrange $item 0 1]) $item
-    }
-
-    set items {}
-    foreach item [$lb get 0 end] {
-	lassign $item n nick jid val reason
-	set nick [trim $nick]
-	set jid [trim $jid]
-
-	if {$nick == "" && $jid == ""} {
-	    continue
-	}
-
-	set val [trim $val]
-	set reason [trim $reason]
-
-	if {[info exists tmp([list $nick $jid])] && \
-		$tmp([list $nick $jid]) == [list $nick $jid $val $reason]} continue
-
-	set attrs [list $attr $l2e($val)]
-	if {$nick != ""} {
-	    lappend attrs nick $nick
-	}
-	if {$jid != ""} {
-	    lappend attrs jid $jid
-	}
-	set itemsubels {}
-	if {$reason != ""} {
-	    lappend itemsubels [::xmpp::xml::create reason -cdata $reason]
-	}
-	lappend items [::xmpp::xml::create item \
-				-attrs $attrs \
-				-subelements $itemsubels]
-    }
-
-    set xlib [chat::get_xlib $chatid]
-    set group [chat::get_jid $chatid]
-
-    if {$items != {}} {
-	::xmpp::sendIQ $xlib set \
-	    -query [::xmpp::xml::create query \
-			       -xmlns $::NS(muc#admin) \
-			       -subelements $items] \
-	    -to $group \
-	    -command [list muc::test_error_res \
-			   [::msgcat::mc "Sending %s %s list" $attr $val] \
-			   $xlib $group $chatid]
-    }
-    destroy $w
-}
-
-proc muc::list_cleanup {f} {
-    variable listdata
-    variable lastsort
-
-    catch {unset listdata($f)}
-    catch {unset lastsort($f)}
-}
-
-###############################################################################
-
 proc muc::request_config_dialog {chatid} {
     variable winid
 
@@ -1091,7 +221,6 @@
 		  [list [namespace current]::request_instant_room $chatid]"
 
     $w draw
-    
 }
 
 proc muc::request_instant_room {chatid} {
@@ -1262,7 +391,7 @@
 					     room became members-only" \
 					    $nick$real_jid] }
 	    }
-			
+
 	    if {[info exists actor] && $actor != ""} {
 		append msg [::msgcat::mc " by %s" $actor]
 	    }
@@ -1306,6 +435,7 @@
     set group [chat::get_jid $chatid]
 
     if {$options(gen_enter_exit_msgs)} {
+	set message [::msgcat::mc "%s has left" $nick]
 	set xlib [chat::get_xlib $chatid]
 	set error [get_jid_presence_info error $xlib $group/$nick]
 	if {$error != ""} {
@@ -1314,12 +444,11 @@
 	    set status [get_jid_presence_info status $xlib $group/$nick]
 	}
 	if {$status != ""} {
-	    set end ": $status"
+	    append message ": $status"
 	} else {
-	    set end ""
+	    append message ""
 	}
-	chat::add_message $chatid $group groupchat \
-	    [cconcat [::msgcat::mc "%s has left" $nick] $end] {}
+	chat::add_message $chatid $group groupchat $message {}
     }
 }
 
@@ -1568,6 +697,40 @@
 
 ###############################################################################
 
+proc muc::new {chatid type} {
+    variable tokens
+
+    # MUC token is created only for groupchat windows
+
+    if {![string equal $type groupchat]} return
+
+    debugmsg conference "NEW_GROUP: $chatid"
+
+    set xlib  [chat::get_xlib $chatid]
+    set group [chat::get_jid  $chatid]
+
+    set tokens($chatid) \
+	[::xmpp::muc::new $xlib $group \
+		-eventcommand  [list muc::report_muc_event $chatid] \
+		-rostercommand [list chat::process_roster_event $chatid]]
+}
+
+hook::add open_chat_pre_hook muc::new
+
+###############################################################################
+
+proc muc::join_group_raise {xlib group nick {password ""}} {
+    if {[llength [connections]] == 0} return
+
+    if {[string equal $xlib ""]} {
+	set xlib [lindex [connections] 0]
+    }
+
+    join_group $xlib $group $nick $password
+
+    chat::activate [chat::chatid $xlib $group]
+}
+
 proc muc::join_group {xlib group nick {password ""} {retries 2}} {
     global userstatus textstatus
     variable options
@@ -1578,13 +741,6 @@
     set group [::xmpp::jid::normalize $group]
     set chatid [chat::chatid $xlib $group]
 
-    if {![info exists tokens($chatid)]} {
-	set tokens($chatid) \
-	    [::xmpp::muc::new $xlib $group \
-		    -eventcommand [list muc::report_muc_event $chatid] \
-		    -rostercommand [list chat::process_roster_event $chatid]]
-    }
-
     privacy::add_to_special_list $xlib conference [::xmpp::jid::server $group]
 
     set_our_groupchat_nick $chatid $nick
@@ -1596,7 +752,6 @@
 
     set x_args {}
 
-    lappend x_args -password $password
     set muc_password($chatid) $password
 
     if {$options(history_maxchars) >= 0} {
@@ -1613,8 +768,14 @@
 
     debugmsg conference "JOIN: $chatid $nick"
 
+    incr retries -1
+
     eval [list ::xmpp::muc::join $tokens($chatid) $nick \
-	       -command [namespace code [list process_join $chatid $nick $password [incr retries -1]]]] \
+	       -password $password \
+	       -command [namespace code [list process_join $chatid \
+							   $nick \
+							   $password \
+							   $retries]]] \
 	 [presence_args $xlib $userstatus -status $textstatus] \
 	 $x_args
 }
@@ -1671,6 +832,30 @@
     }
 }
 
+proc muc::reset_group {chatid} {
+    variable tokens
+
+    debugmsg conference "RESSET_GROUP: $chatid"
+
+    ::xmpp::muc::reset $tokens($chatid)
+}
+
+proc muc::free {chatid} {
+    variable tokens
+
+    # This routine is called not only for groupchats, so checking existence of
+    # tokens($chatid) is necessary.
+
+    if {[info exists tokens($chatid)]} {
+	debugmsg conference "FREE_GROUP: $chatid"
+
+	::xmpp::muc::free $tokens($chatid)
+	unset tokens($chatid)
+    }
+}
+
+hook::add close_chat_post_hook muc::free
+
 ###############################################################################
 
 proc muc::test_connection {chatid args} {
@@ -1793,7 +978,7 @@
 						  -name]
 				    if {$name != ""} break
 				}
-				if {![cequal $name ""]} {
+				if {![string equal $name ""]} {
 				    set inviter "$name ($inviter)"
 				}
 				set muc_body \
@@ -1838,61 +1023,26 @@
 	set body $xconference_body
 	return
     }
-    
+
     return
 }
 
 hook::add message_process_x_hook muc::process_invitation
 
 proc muc::process_x_conference {f xlib group password row} {
-    global gr_nick
-
     label $f.lgroup$row -text [::msgcat::mc "Invited to:"]
     button $f.group$row -text $group \
-	-command [list ::join_group $xlib $group \
-		       -nick [get_group_nick $group $gr_nick] \
-		       -password $password]
-    
+	-command [list muc::join_group_raise $xlib \
+					     $group \
+					     [get_group_nick $xlib $group] \
+					     $password]
+
     grid $f.lgroup$row -row $row -column 0 -sticky e
     grid $f.group$row  -row $row -column 1 -sticky ew
 }
 
 ###############################################################################
 
-proc muc::join {xlib jid args} {
-    global gr_nick
-
-    set category conference
-    foreach {opt val} $args {
-	switch -- $opt {
-	    -category { set category $val }
-	}
-    }
-
-    if {![cequal $category conference]} {
-	return
-    }
-
-    if {![cequal [::xmpp::jid::node $jid] {}]} {
-	::join_group $xlib $jid -nick [get_group_nick $jid $gr_nick]
-    } else {
-	::join_group_dialog $xlib -server [::xmpp::jid::server $jid] -group {}
-    }
-}
-
-hook::add postload_hook \
-    [list disco::browser::register_feature_handler jabber:iq:conference \
-	 muc::join -desc [list conference [::msgcat::mc "Join conference"]]]
-hook::add postload_hook \
-    [list disco::browser::register_feature_handler $::NS(muc) muc::join \
-	 -desc [list conference [::msgcat::mc "Join conference"]]]
-hook::add postload_hook \
-    [list disco::browser::register_feature_handler "gc-1.0" muc::join \
-	 -desc [list conference [::msgcat::mc "Join groupchat"]]]
-
-###############################################################################
-
-
 proc muc::disco_reply {type xlib from lang} {
     variable options
 

Modified: trunk/tkabber/plugins/chat/histool.tcl
===================================================================
--- trunk/tkabber/plugins/chat/histool.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/plugins/chat/histool.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -456,7 +456,7 @@
 
     set tags [list $ftsearch(bg) YEAR-$year MONTH-$month JID-$jid]
 
-    set mynick [get_group_nick $jid ""]
+    set mynick [get_group_nick "" $jid]
 
     if {[catch {array set mparts $msg}]} return
 

Modified: trunk/tkabber/plugins/chat/irc_commands.tcl
===================================================================
--- trunk/tkabber/plugins/chat/irc_commands.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/plugins/chat/irc_commands.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -91,7 +91,7 @@
 
 	    set chatid [chat::chatid $xlib $room]
 	    if {[catch {get_our_groupchat_nick $chatid} nick]} {
-		set nick [get_group_nick $room $::gr_nick]
+		set nick [get_group_nick $xlib $room]
 	    }
 	    # HACK: TODO: remove usage of tokens.
 	    if {[chat::is_opened $chatid] && [info exists ::muc::tokens($chatid)]} {
@@ -107,7 +107,7 @@
 	rejoin {
 	    if {![cequal $type groupchat]} return
 	    if {[catch {get_our_groupchat_nick $chatid} nick]} {
-		set nick [get_group_nick $jid $::gr_nick]
+		set nick [get_group_nick $xlib $jid]
 	    }
 	    muc::leave_group $chatid ""
 	    muc::join_group $xlib $jid $nick $password

Modified: trunk/tkabber/plugins/chat/logger.tcl
===================================================================
--- trunk/tkabber/plugins/chat/logger.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/plugins/chat/logger.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -269,7 +269,7 @@
 
     set logfile [jid_to_filename $jid]
 
-    set mynick [get_group_nick $jid ""]
+    set mynick [get_group_nick $xlib $jid]
 
     toplevel $lw -relief $::tk_relief -borderwidth $::tk_borderwidth -class Chat
     wm group $lw .
@@ -355,7 +355,7 @@
     }
 
     set logfile [jid_to_filename $jid]
-    set mynick [get_group_nick $jid ""]
+    set mynick [get_group_nick "" $jid]
 
     set log $lw.log
     set cbox $lw.mf.mcombo

Added: trunk/tkabber/plugins/chat/muc_commands.tcl
===================================================================
--- trunk/tkabber/plugins/chat/muc_commands.tcl	                        (rev 0)
+++ trunk/tkabber/plugins/chat/muc_commands.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -0,0 +1,120 @@
+# $Id$
+
+namespace eval muc {}
+
+###############################################################################
+
+proc muc::handle_commands {chatid user body type} {
+    if {![string equal $type groupchat]} return
+
+    set xlib [chat::get_xlib $chatid]
+    set group [chat::get_jid $chatid]
+    if {[string equal [string range $body 0 5] "/kick "]} {
+	set attr role
+	set value none
+	set dir down
+	lassign [parse_nick_reason $body 6] nick reason
+    } elseif {[string equal [string range $body 0 4] "/ban "]} {
+	set attr affiliation
+	set value outcast
+	set dir down
+	lassign [parse_nick_reason $body 5] nick reason
+    } elseif {[string equal [string range $body 0 6] "/unban "]} {
+	set jid [parse_nick $body 7]
+	unban $xlib $group $jid
+	return stop
+    } elseif {[string equal [string range $body 0 6] "/whois "]} {
+	set nick [parse_nick $body 7]
+	whois $xlib $group/$nick $chatid
+	return stop
+    } elseif {[string equal [string range $body 0 6] "/voice "]} {
+	set attr role
+	set value participant
+	set dir up
+	lassign [parse_nick_reason $body 7] nick reason
+    } elseif {[string equal [string range $body 0 8] "/devoice "]} {
+	set attr role
+	set value visitor
+	set dir down
+	lassign [parse_nick_reason $body 9] nick reason
+    } elseif {[string equal [string range $body 0 7] "/member "]} {
+	set attr affiliation
+	set value member
+	set dir up
+	lassign [parse_nick_reason $body 8] nick reason
+    } elseif {[string equal [string range $body 0 9] "/demember "]} {
+	set attr affiliation
+	set value none
+	set dir down
+	lassign [parse_nick_reason $body 10] nick reason
+    } elseif {[string equal [string range $body 0 10] "/moderator "]} {
+	set attr role
+	set value moderator
+	set dir up
+	lassign [parse_nick_reason $body 11] nick reason
+    } elseif {[string equal [string range $body 0 12] "/demoderator "]} {
+	set attr role
+	set value participant
+	set dir down
+	lassign [parse_nick_reason $body 13] nick reason
+    } elseif {[string equal [string range $body 0 6] "/admin "]} {
+	set attr affiliation
+	set value admin
+	set dir up
+	lassign [parse_nick_reason $body 7] nick reason
+    } elseif {[string equal [string range $body 0 8] "/deadmin "]} {
+	set attr affiliation
+	set value member
+	set dir down
+	lassign [parse_nick_reason $body 9] nick reason
+    } else {
+	return
+    }
+
+    change_item_attr $xlib $group/$nick $attr $value $reason $chatid
+
+    return stop
+}
+
+hook::add chat_send_message_hook [namespace current]::muc::handle_commands 50
+
+proc muc::parse_nick {body n} {
+    return [lindex [parse_nick_reason $body $n] 0]
+}
+
+proc muc::parse_nick_reason {body n} {
+    # Parse nickname and reason
+    # first line is a nick, rest are reason
+    set nick_reason [string range $body $n end]
+    set ne [string first "\n" $nick_reason]
+    if {$ne < 0} {
+	set nick $nick_reason
+	set reason ""
+    } else {
+	set nick [string range $nick_reason 0 [expr {$ne - 1}]]
+	set reason [string range $nick_reason [expr {$ne + 1}] end]
+    }
+    return [list $nick [string trim $reason]]
+}
+
+###############################################################################
+
+proc muc::commands_comps {chatid compsvar wordstart line} {
+    set group [chat::get_jid $chatid]
+    if {![muc::is_compatible $group]} return
+
+    upvar 0 $compsvar comps
+
+    if {!$wordstart} {
+	lappend comps {/whois } {/kick } {/ban } {/unban } \
+	    {/voice } {/devoice } \
+	    {/member } {/demember } \
+	    {/moderator } {/demoderator } \
+	    {/admin } {/deadmin }
+    }
+}
+
+hook::add generate_completions_hook [namespace current]::muc::commands_comps
+
+###############################################################################
+


Property changes on: trunk/tkabber/plugins/chat/muc_commands.tcl
___________________________________________________________________
Added: svn:keywords
   + Author Date Id Revision
Added: svn:eol-style
   + native

Modified: trunk/tkabber/plugins/general/remote.tcl
===================================================================
--- trunk/tkabber/plugins/general/remote.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/plugins/general/remote.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -853,7 +853,11 @@
     variable unread
 
     if {$::ifacetk::number_msg($chatid) == 0} {
-	set type $::chat::chats(type,$chatid)
+	if {[chat::is_chat $chatid]} {
+	    set type chat
+	} else {
+	    set type groupchat
+	}
 	set id [list $type $chatid]
 	catch {unset unread($id)}
     }

Modified: trunk/tkabber/plugins/roster/conferences.tcl
===================================================================
--- trunk/tkabber/plugins/roster/conferences.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/plugins/roster/conferences.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -367,7 +367,7 @@
     }
 
     if {![info exists gra_nick]} {
-	set gra_nick [get_group_nick [::xmpp::jid::jid $gra_group $gra_server] ""]
+	set gra_nick [get_group_nick $xlib [::xmpp::jid::jid $gra_group $gra_server]]
     }
     set gra_xlib [connection_jid $xlib]
 
@@ -507,7 +507,7 @@
 	set name [::xmpp::jid::node $jid]
     }
     if {![info exists nick]} {
-	set nick [get_group_nick $jid ""]
+	set nick [get_group_nick $xlib $jid]
     }
     if {![info exists password]} {
 	set password ""
@@ -736,12 +736,16 @@
     
     set args {}
     if {$bookmarks($xlib,nick,$jid) != ""} {
-	lappend args -nick $bookmarks($xlib,nick,$jid)
+	set nick $bookmarks($xlib,nick,$jid)
+    } else {
+	set nick [get_group_nick $xlib $jid]
     }
     if {$bookmarks($xlib,password,$jid) != ""} {
-	lappend args -password $bookmarks($xlib,password,$jid)
+	set password $bookmarks($xlib,password,$jid)
+    } else {
+	set password ""
     }
-    eval [list ::join_group $xlib $jid] $args
+    muc::join_group_raise $xlib $jid $nick $password
 }
 
 ###############################################################################
@@ -751,12 +755,11 @@
 
 proc conferences::autojoin_group {xlib jid} {
     variable bookmarks
-    global gr_nick
     
     if {$bookmarks($xlib,nick,$jid) != ""} {
 	set nick $bookmarks($xlib,nick,$jid)
     } else {
-	set nick [get_group_nick $jid $gr_nick]
+	set nick [get_group_nick $xlib $jid]
     }
     if {$bookmarks($xlib,password,$jid) != ""} {
 	set password $bookmarks($xlib,password,$jid)
@@ -811,7 +814,7 @@
 				  $args]
 
     # TODO: Check for real MUC? Move to muc.tcl?
-    muc::add_muc_menu_items $m $xlib $jid
+    ::add_muc_menu_items $m $xlib $jid
 }   
 
 hook::add roster_conference_popup_menu_hook \
@@ -828,9 +831,7 @@
 	    if {[roster::itemconfig $xlib $jid -subsc] == "bookmark"} {
 		join_group $xlib $jid
 	    } else {
-		global gr_nick
-		::join_group $xlib $jid \
-		    -nick [get_group_nick $jid $gr_nick]
+		muc::join_group_raise $xlib $jid [get_group_nick $xlib $jid]
 	    }
 	    return stop
 	}

Modified: trunk/tkabber/tkabber.tcl
===================================================================
--- trunk/tkabber/tkabber.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/tkabber.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -230,7 +230,6 @@
 load_source itemedit.tcl
 load_source messages.tcl
 load_source chats.tcl
-load_source joingrdialog.tcl
 load_source muc.tcl
 load_source login.tcl
 load_source proxy.tcl

Modified: trunk/tkabber/utils.tcl
===================================================================
--- trunk/tkabber/utils.tcl	2010-02-05 17:07:31 UTC (rev 1914)
+++ trunk/tkabber/utils.tcl	2010-02-06 17:13:28 UTC (rev 1915)
@@ -80,10 +80,12 @@
     return [::xmpp::stanzaerror::message $errmsg]
 }
 
-proc get_group_nick {jid fallback} {
+proc get_group_nick {xlib jid} {
     global defaultnick
 
-    set nick $fallback
+    if {[catch {set nick [connection_user $xlib]}]} {
+	set nick ""
+    }
     set tmp_pattern *
     foreach pattern [array names defaultnick] {
 	if {[string equal $pattern $jid]} {
@@ -576,4 +578,28 @@
     return $t
 }
 
+##################################################################
+
+proc ecursor_entry {entry} {
+    $entry icursor end
+    return $entry
+}
+
+##################################################################
+
+proc update_combo_list {list entry num} {
+
+    set ind [lsearch -exact $list $entry]
+    if {$ind >= 0} {
+	set newlist [linsert [lreplace $list $ind $ind] 0 $entry]
+    } else {
+	set newlist [linsert $list 0 $entry]
+    }
+    if {[llength $newlist] > $num} {
+	return [lreplace $newlist end end]
+    } else {
+	return $newlist
+    }
+}
+
 # vim:ts=8:sw=4:sts=4:noet



More information about the Tkabber-dev mailing list