Code Search for Developers
 
 
  

abook.tcl from aMSN at Krugle


Show abook.tcl syntax highlighted

#	User Administration (Address Book data)
#	by: Alvaro Jose Iradier Muro
#	D�imo Emilio Grimaldo Tu�n
#=======================================================================

::Version::setSubversionId {$Id: abook.tcl 8977 2007-08-26 00:17:36Z lephilousophe $}

::snit::type Group {

	variable users {}
	option -name
	option -id

	method showInfo { } {
		status_log ----
		status_log id:$options(-id)
		status_log name:$options(-name)
		status_log users:$users
	}

	method addUser { user } {
		llappend users $user
	} 
}



namespace eval ::abook {
#::abook namespace is used to store all information related to users
#and contact lists.

	if { $initialize_amsn == 1 } {
		#
		# P R I V A T E
		#
		variable demographics;	# Demographic Information about user
		
		#When set to 1, the information is in safe state and can be
		#saved to disk without breaking anything
		
		variable consistent 0

		# This list stores the names of the fields about the visual representation of the buddy.
		# When this fields gets changed, we fire an event to redraw that contact on our CL.
		variable VisualData [list nick customnick customfnick cust_p4c_name customcolor customdp]

		global pgc pcc
	}
	
	#########################
	# P U B L I C
	#########################
	
	#An alias for "setContactData myself". Sets data for the "myself" user
	proc setPersonal { field value} {
		setContactData myself $field $value
	}
   
	#An alias for "getContactData myself". Gets data for the "myself" user	
	proc getPersonal { field } {
		return [getContactData myself $field]
	}
	
	#TODO: Remove this
	proc getContact { email cdata } {
		upvar $cdata data

		set groupName    [::groups::GetName [getContactData $email group]]
		set data(group)  [urldecode $groupName]
		set data(handle) [urldecode [getVolatileData $email nick]]
		set data(PHH) [urldecode [getVolatileData $email PHH]]
		set data(PHW) [urldecode [getVolatileData $email PHW]]
		set data(PHM) [urldecode [getVolatileData $email PHM]]
		set data(MOB) [urldecode [getVolatileData $email MOB]]
		set data(available) "Y"
	}

	# Sends a message to the notification server with the
	# new set of phone numbers. Notice this can only be done
	# for the user and not for the buddies!
	# The value is urlencoded by this routine
	proc setPhone { item value } {
		switch $item {
			home { ::MSN::WriteSB ns PRP "PHH $value" }
			work { ::MSN::WriteSB ns PRP "PHW $value" }
			mobile { ::MSN::WriteSB ns PRP "PHM $value" }
			pager { 
				::MSN::WriteSB ns PRP "MOB $value"
				if { $value eq "Y" } {
					::MSN::setClientCap paging
				} else {
					::MSN::setClientCap paging 0
				}
				::MSN::changeStatus [::MSN::myStatusIs]
			 }
			default { status_log "setPhone error, unknown $item $value\n" }
		}
	}

	# This information is sent to us during the initial connection
	# with the server. It comes in a MSG content "text/x-msmsgsprofile"
	# TODO: Change this to use setVolatileData to user myself
	proc setDemographics { cdata } {
		variable demographics 
		upvar $cdata data

    		set demographics(langpreference) $data(langpreference);# 1033 = English
		set demographics(preferredemail) $data(preferredemail)
		set demographics(country) [string toupper $data(country)];# NL
		set demographics(gender) [string toupper $data(gender)]
		set demographics(kids) $data(kids);	  # Number of kids
		set demographics(age) $data(age)
		set demographics(mspauth) $data(mspauth); # MS Portal Authorization?
		set demographics(kv) $data(kv)
		set demographics(sid) $data(sid)
		set demographics(sessionstart) $data(sessionstart)
		set demographics(clientip) $data(clientip)
		set demographics(valid) Y
		abook::getIPConfig
	}

	proc getDemographicField { field } {
		variable demographics
		if { [info exists demographics($field)]} {
			return $demographics($field)
		} else {
			return ""
		}
	}

	proc getDemographics { cdata } {
		variable demographics 
		upvar $cdata d

		if {[info exists d(valid)]} {
			set d(langpreference) $demographics(langpreference);# 1033 = English
			set d(preferredemail) $demographics(preferredemail)
			set d(country) $demographics(country)
			set d(gender) $demographics(gender)
			set d(kids) $demographics(kids)
			set d(age) $demographics(age)
			set d(mspauth) $demographics(mspauth)
			set d(kv) $demographics(kv)
			set d(sid) $demographics(sid)
			set d(sessionstart) $demographics(sessionstart)
			set d(clientip) $demographics(clientip)
			set d(valid) Y
		} else {
			set d(valid) N
		}
	}

	################################################################################################################################
	################################################################################################################################
	################################################################################################################################
	############## MOVE ALL PROTOCOL/IP CHECK RELATED PROCEDURES OUT OF ABOOK!! ABOOK SHOULD CARE ONLY ABOUT #######################
	############## DATA STORAGE, NOT ABOUT SERVERS/IPS/CONNECTIONS OR WHATEVER !!                            #######################
	
	# This proc will configure all ip settings, get private ip, see if firewalled or not, and set netid
	proc getIPConfig { } {
		variable demographics

		status_log "Getting local IP\n"
		set demographics(localip) [getLocalIP]
		status_log "Finished\n"
		set demographics(upnpnat) "false"
		if { [getDemographicField localip] eq ""  && [getDemographicField clientip] eq "" } {
			#Not connected
			set demographics(conntype) ""
			return
		} else {
			set demographics(conntype) [getConnectionType [getDemographicField localip] [getDemographicField clientip]]
		}
		if { $demographics(conntype) eq "Direct-Connect" || $demographics(conntype) eq "Firewall" } {
			set demographics(netid) 0
			set demographics(upnpnat) "false"
		} else {
			set demographics(netid) [GetNetID [getDemographicField clientip]]
			if { [getFirewalled [::config::getKey initialftport]] eq "Firewall" } {
				set demographics(upnpnat) "false"
			} else {
				set demographics(upnpnat) "true"	
			}
		}

		set demographics(listening) [getListening [getDemographicField conntype]]
	}

	# This proc will get the localip (private ip, NAT or not)
	proc getLocalIP { } {
		set sk [ns cget -sock]

		if { $sk ne "" } {
			foreach ip [fconfigure $sk -sockname] {break}
			return $ip
		} else {
			return ""
		}
	}

	# This will return the connection type : ip-restrict-nat, direct-connect or firewall
	proc getConnectionType { localip clientip } {
 
		if { $localip eq "" || $clientip eq "" } { 
			return [getFirewalled [::config::getKey initialftport]]
		} 
		if { $localip ne $clientip } {
			return "IP-Restrict-NAT"
		} else { 
			return [getFirewalled [::config::getKey initialftport]]
		}
	}

	# This will create a server, and try to connect to it in order to see if firewalled or not
	proc getFirewalled { {port ""} } {
		global connection_success
		variable random_id

		set random_id [expr rand()]
		set random_id [expr {$random_id * 10000}]
		set random_id [expr {int($random_id)}]

		if { $port eq "" } { set port $::config(initialftport) }

		while { [catch {set sock [socket -server "abook::dummysocketserver" $port] } ] } {
			incr port
		}
		status_log "::abook::getFirewalled: Connecting to [getDemographicField clientip] port $port\n" blue
		
		#Need this timeout thing to avoid the socket blocking...
		set connection_success -2
		
		status_log "Connecting to $::weburl/check_connectivity.php?port=$port&id=$random_id" blue
		if { [catch { ::http::geturl "$::weburl/check_connectivity.php?port=$port&id=$random_id" -command "::abook::gotConnectivityReply" -timeout 9500 } res] } { 
			after 500 [list set ::connection_success -1]
			status_log "::abook::getFirewalled failed: $res" red
		}
		tkwait variable connection_success

		status_log "::abook::getFirewalled: connection_success ($connection_success)\n" blue
		
		if { $connection_success == -1 } {
			set connection_success -2
			if {[catch {set clientsock [socket -async [getDemographicField clientip] $port]}] == 0} {
				fileevent $clientsock readable [list ::abook::connectionHandler $clientsock]
				after 1000 ::abook::connectionTimeout
				tkwait variable connection_success
				catch { close $clientsock }
			}
		}
		
		status_log "::abook::getFirewalled: connection_success ($connection_success)\n" blue
		catch {close $sock}

		if { $connection_success == 1 } {
			return "Direct-Connect"
		} else {
			return "Firewall"
		}
	}

	proc gotConnectivityReply { token} {
		global connection_success
		if { [::http::status $token] ne "ok" || [::http::ncode $token ] != 200 } {
			set connection_success -1
			status_log "::abook::gotConnectivityReply error : [http::status $token] - [::http::ncode $token]" green
		} else {
			set connection_success [::http::data $token]
			status_log "::abook::gotConnectivityReply ok : [::http::data $token]" green
		} 
		::http::cleanup $token
	}

	proc connectionTimeout {} {
		global connection_success
		status_log "::abook::connectionTimeout\n"
		if { $connection_success == -2 } {
			set connection_success -1
		} else {
			set connection_success $connection_success
		}
	}
	
	proc connectionHandler { sock } {
		#CHECK FOR AN ERROR
		global connection_success
		after cancel ::abook::connectionTimeout
		fileevent $sock readable ""
		if { [fconfigure $sock -error] ne ""} {
			status_log "::abook::connectionHandler: connection failed\n" red
			set connection_success 0
		} else {
			# TODO : We need to check here byte per byte because we might connect to an emule server for example which sends binary data
			# and the [gets] will be blocking until there is a newline in the data... which might never happen... 
			# this will cause amsn to hang...
			gets $sock server_data
			if { [string first "AMSNPING" "$server_data"] == 0 } {
				status_log "::abook::connectionHandler: port in use by another application!\n" red
				set connection_success 0
			} else {
				status_log "::abook::connectionHandler: connection succesful\n" green
				set connection_success 1
			}
		}
	}

	proc getListening { conntype } {
		if {$conntype eq "Firewall" } {
			return "false"
		} elseif { $conntype eq "Direct-Connect" } {
		        return "true"
		} else { 
			return [abook::getDemographicField upnpnat]
		}
	}

	# This proc is a dummy socket server proc, because we need a command to be called which the client connects to the test server (if not firewalled)
	proc dummysocketserver { sock ip port } {
		variable random_id
		if {[catch {
			#puts $sock "AMSNPING"
			if { [info exists random_id] } {
				puts $sock "AMSNPING${random_id}"
			} else {
				puts $sock "AMSNPING"
			}
			flush $sock
			close $sock
			status_log "::abook::dummysocketserver: Received connection on $sock" blue
		} res]} {
			status_log "::abook::dummysocketserver: Error writing to socket: $res\n"
		}
	}

	# This will transform the ip adress into a netID adress (which is the 32 bits unsigned integer represent the ip)
	proc GetNetID { ip } {
		set val 0
		set inverted_ip ""
		foreach x [split $ip .] { 
			set inverted_ip "${x} ${inverted_ip}"	
		}
		
		foreach x $inverted_ip { 
			set val [expr {($val << 8) | ($x & 0xff)}] 
		}
		return [format %u $val]
 
	}
	################################################################################################################################
	################################################################################################################################
	################################################################################################################################

	#Clears all ::abook stored information
	proc clearData {} {
		variable users_data
		array unset users_data *
		
		variable users_volatile_data
		array unset users_volatile_data *
		
		unsetConsistent
	}

	#Sets some data to a user.
	#user_login: the user_login you want to set data to
	#field: the field you want to set
	#data: the data that will be contained in the given field
	proc setContactData { user_login field data } {
		global pgc
		variable users_data

		set field [string tolower $field]
		
		# There can't be double arrays, so users_data(user) is just a
		# list like {entry1 data1 entry2 data2 ...}
		if { [info exists users_data($user_login)] } {
			#We convert the list to an array, to make it easier
			array set user_data $users_data($user_login)
		} else {
			array set user_data [list]
		}

		# An event used by guicontactlist to know when a user changed his nick (or state)
		#if { [lsearch -exact $::abook::VisualData $field] > -1 } {
		#	if { [info exists user_data($field)] && $user_data($field) ne $data } {
		#		#puts stdout "ATTENTION! Visual Data has changed! Redraw CL! $field - $data"
		#		::Event::fireEvent contactDataChange abook $user_login
		#	} elseif { ![info exists user_data($field)] && $data ne ""} {
		#		#puts stdout "ATTENTION! Visual Data has changed! Redraw CL! $field - $data"
		#		::Event::fireEvent contactDataChange abook $user_login
		#	}
		#}

		if { $data eq "" } {
			if { [info exists user_data($field)] } {
				unset user_data($field)
			}
		} else {
			set user_data($field) $data
		}
		
		if { $field eq "nick" || $field eq "mfn" || $field eq "psm" || $field eq "customnick" || $field eq "customfnick" } {
			set data [::smiley::parseMessageToList [list [ list "text" "$data" ]] 1]
			set evpar(variable) data
			set evpar(login) $user_login
			::plugins::PostEvent parse_contact evpar

			::abook::setVolatileData $user_login "parsed_$field" $data
		}

		set users_data($user_login) [array get user_data]
		
		#We make this to notify preferences > groups to be refreshed
		set pgc 1
	}

	proc setContactForGuid { guid user_login } {
		variable guid_contact
		if { $user_login eq "" } {
			if { [info exists guid_contact($guid)] } {
				unset guid_contact($guid)
			}
		} else {
			set guid_contact($guid) $user_login
		}
	}

	proc getContactForGuid { guid } {
		variable guid_contact
		if { [info exists guid_contact($guid)] } {
			return $guid_contact($guid)
		} else {
			return ""
		}
	}

	proc setAtomicContactData { user_login fields_list data_list } {
		global pgc
		variable users_data

		set fields_list [string tolower $fields_list]

		if { [info exists users_data($user_login)] } {
			array set user_data $users_data($user_login)
		} else {
			array set user_data [list]
		}

		# This loop iterates over the lists of fields and data, so when it finds that one of
		# the fields has to do with visual information, it throws an event and breaks the loop
		foreach field $fields_list data $data_list {
			if { [lsearch -exact $::abook::VisualData $field] > -1 } {
				if { [info exists user_data($field)] && $user_data($field) ne $data } {
					#puts stdout "ATTENTION! Visual Data has changed! Redraw CL! $field - $data"
					::Event::fireEvent contactDataChange abook $user_login
					break
				} elseif { ![info exists user_data($field)] && $data ne ""} {
					#puts stdout "ATTENTION! Visual Data has changed! Redraw CL! $field - $data"
					::Event::fireEvent contactDataChange abook $user_login
					break
				}
			}
		}

		# This other loop iterates over the entire lists, replacing the value in user_data(array)
		# if needed. There are two different loops because this one cannot be broke.
		foreach field $fields_list data $data_list {
			if { $data eq "" } {
				if { [info exists user_data($field)] } {
					unset user_data($field)
				}
			} else {
				set user_data($field) $data
			}
		}

		set users_data($user_login) [array get user_data]
		set pgc 1
	}

	#Sets some volatile data to a user. Volatile data won't be written to disk
	#user_login: the user_login you want to set data to
	#field: the field you want to set
	#data: the data that will be contained in the given field
	proc setVolatileData { user_login field data } {
		variable users_volatile_data
		
		set field [string tolower $field]
		
		if { [info exists users_volatile_data($user_login)] } {
			array set volatile_data $users_volatile_data($user_login)
		} else {
			array set volatile_data [list]
		}
		
		if { $data eq "" } {
			if { [info exists volatile_data($field)] } {
				unset volatile_data($field)
			}
		} else {
			set volatile_data($field) $data
		}

		if { $field eq "psm" } {
			set data [::smiley::parseMessageToList [list [ list "text" "$data" ]] 1]
			set evpar(variable) data
			set evpar(login) $user_login
			::plugins::PostEvent parse_contact evpar
			set volatile_data(parsed_$field) $data
		}
		
		set users_volatile_data($user_login) [array get volatile_data]
	}

	#clears all volatile date of a user
	proc clearVolatileData { user_login } {
		variable users_volatile_data

		unset users_volatile_data($user_login)
	}
	
		
	#Returns some previously stored data from a user
	proc getContactData { user_login field {defaultval ""}} {
		variable users_data
		
		set field [string tolower $field]
		
		if { ![info exists users_data($user_login)] } {
			return $defaultval
		}

		array set user_data $users_data($user_login)

		if { ![info exists user_data($field)] } {
			return $defaultval
		}
		
		return $user_data($field)
		
	}

	#Returns some previously stored volatile data from a user		
	proc getVolatileData { user_login field {defaultval ""}} {
		variable users_volatile_data
		
		set field [string tolower $field]
		
		if { ![info exists users_volatile_data($user_login)] } {
			return $defaultval
		}

		array set user_data $users_volatile_data($user_login)	

		if { ![info exists user_data($field)] } {
			return $defaultval
		}
		
		return $user_data($field)
		
	}	
	
	#Return a list of all stored contacts
	proc getAllContacts { } {
		variable users_data
		return [array names users_data]
	}
	
	
	###########################################################################
	# Auxiliary functions, macros, or shortcuts
	###########################################################################

	proc getPassportfromContactguid { contactguid } {
		foreach contact [::abook::getAllContacts] {
			if { [::abook::getContactData $contact contactguid] eq $contactguid } {
				return $contact
			}
		}
	}

	proc lastSeen { } {
		foreach contact [::abook::getAllContacts] {
			set user_state_code [::abook::getVolatileData $contact state FLN]
			if {$user_state_code ne "FLN"} {
				::abook::setContactData $contact last_seen [clock format [clock seconds] -format "%D - %H:%M:%S"]
			}
		}
	}

	proc CreateCopyMenu { w } {
		set menu $w.copy
		menu $menu -tearoff 0 -type normal
		$menu add command -label [trans copy] \
			-command "::abook::copyFromText $w"

		return $menu
	}

	proc copyFromText { w } {

		set index [$w tag ranges sel]
		clipboard clear

		if { [llength $index] < 2 } {
			set index [list 0.0 end]
		}

		set dump [$w dump -text [lindex $index 0] [lindex $index 1]]
		foreach { text output index } $dump {
			clipboard append "$output"
		}

	}

	proc dateconvert {time} {
		set date [clock format [clock seconds] -format "%D"]
		if {$time eq ""} {
			return ""
		} else {
			set delm [string first " " $time]
			if { $delm == -1 } { set delm [string length $time]}
			if {![catch {clock scan [string range $time 0 $delm]}]} {
				#Checks if the time is today, and in this case puts today instead of the date
				if {[clock scan $date] == [clock scan [string range $time 0 $delm]]} {
					return "[trans today][string range $time $delm end]"
					#Checks if the time is yesterday, and in this case puts yesterday instead of the date
				} elseif { [expr { [clock scan $date] - [clock scan [string range $time 0 $delm]] }] == 86400} {
					return "[trans yesterday][string range $time $delm end]"
				} else {
					#set month [string range $time 0 1]
					#set day [string range $time 3 4]
					#set year [string range $time 6 7]
					#set end [string range $time 8 end]
					set dateoftime [string range $time 0 $delm]
					set timeTicks [clock scan $dateoftime]
					set month [clock format $timeTicks -format %m]
					set day [clock format $timeTicks -format %d]
					set year [clock format $timeTicks -format %Y]
					set end [string range $time $delm end]
					#Month/Day/Year
					if {[::config::getKey dateformat] eq "MDY"} {
						return $time
					#Day/Month/Year
					} elseif {[::config::getKey dateformat] eq "DMY"} {
						return "$day/$month/$year$end"
					#Year/Month/Day
					} elseif {[::config::getKey dateformat] eq "YMD"} {
						return "$year/$month/$day$end"
					}
				}
			} else {
				return $time
			}
		}
	}

	proc parseCurrentMedia {currentMedia} {
		if {$currentMedia eq ""} { return "" }
	
		set currentMedia [string map {"\\0" "\0"} $currentMedia]
		set infos [split $currentMedia "\0"]
	
		if {[lindex $infos 2] eq "0"} { return "" }
	
		if {[lindex $infos 1] eq "Music"} {
			set out [list [list "smiley" [::skin::loadPixmap note] "-"] [list "text" " "]]
		} else {
			set out [list [list "text" "- "]]
		}
	
		set pattern [lindex $infos 3]
	
		set nrParams [expr {[llength $infos] - 4}]
		set lstMap [list]
		for {set idx 0} {$idx < $nrParams} {incr idx} {
			lappend lstMap "\{$idx\}"
			lappend lstMap [lindex $infos [expr {$idx + 4}]]
		}
	
		lappend out [list "text" "[string map $lstMap $pattern]"]
	
		return $out
	}

	# Get PSM and currentMedia
	proc getpsmmedia { { user_login "" } { use_styled_psm 0}} {
		if { [::config::getKey protocol] < 11 } { return [list ]}
		set psmmedia [list ]
		if { $user_login eq "" } {
			set psm [::abook::getVolatileData myself parsed_psm]
        	        set currentMedia [::abook::parseCurrentMedia [::abook::getPersonal currentMedia]]
		} else {
                	set psm [::abook::getVolatileData $user_login parsed_psm]
                	set currentMedia [::abook::parseCurrentMedia [::abook::getVolatileData $user_login currentMedia]]
		}
		if {$psm ne ""} {
			set psmmedia [concat $psmmedia $psm]
		}
		if {$currentMedia ne ""} {
			if { $psm ne ""} {
				lappend psmmedia [list "colour" "reset"]
				lappend psmmedia [list "font" "reset"]
				lappend psmmedia [list "text" " "]
			}
			set psmmedia [concat $psmmedia $currentMedia]
		}
		if { !$use_styled_psm } {
			set psmmedia [::abook::removeStyles $psmmedia]
		}

		return $psmmedia
	}

	#Returns the user nickname
	proc getNick { user_login {use_styled_nick 0}} {
		set nick [::abook::getVolatileData $user_login parsed_nick]
		if { $nick eq "" } {
			return [list [list "text" $user_login]]
		}
		if { !$use_styled_nick } {
			set nick [::abook::removeStyles $nick]
		}
		return $nick
	}

	#Parser to replace special characters and variables in the right way
	proc parseCustomNick { input nick user_login customnick {psm ""} } {
		#If there's no customnick set, default to user_login
		if { $customnick eq "" } {
			if { [::config::getKey protocol] >= 11 && $psm ne "" } {
				set customnick $user_login\n$psm
			} else {
				set customnick $user_login
			}
		}
		#By default, quote backslashes, angle brackets and variables
		set input [string map { "\\" "\\\\" "\$" "\\\$" "\(" "\\\(" } $input]
		#Now, let's unquote the variables we want to replace
		set input [string map { "\\\$nick" "\${nick}" "\\\$user_login" "\${user_login}" "\\\$customnick" "\${customnick}" "\\\$psm" "\${psm}" } $input]
		#Return the custom nick, replacing backslashses and variables
		return [subst -nocommands $input]
	}

	#Parser to replace special characters and variables in the right way
	proc parseCustomNickStyled { input nick user_login customnick psm } {
		#If there's no customnick set, default to user_login
		if { [::abook::removeStyles $customnick] eq "" } {
			if { [::config::getKey protocol] >= 11 && $psm ne "" } {
				set customnick [list [list "text" "$user_login"] [list "newline" "\n"]]
				set customnick [concat $customnick $psm]
			} else {
				set customnick [list [list "text" "$user_login"]]
			}
		}

		set user_login [list [list "text" "$user_login"]]

		set l $input
		set llength [llength $l]
		set listpos 0
		set npos 0
		#Keep searching until no matches
		while { $listpos < $llength } {
			if { ([lindex $l $listpos 0] ne "text") } {
				incr listpos
				continue
			}
			set txt [lindex $l $listpos 1]
			if { [set pos [string first "\$" $txt $npos]] == -1 } {
				set npos 0
				incr listpos
			} else {
				#in case the $ isn't a matching $ we will search for the next in the same list item
				set npos [expr {$pos + 1}]
				foreach substitute { "\$nick" "\$user_login" "\$customnick" "\$psm" } {
					if { [string range $txt $pos [expr {$pos + [string length $substitute] - 1}]] eq $substitute } {
						set content [set [string range $substitute 1 end]]
						set p1 [string range $txt 0 [expr {$pos - 1}]]
						set p3 [string range $txt [expr {$pos + [string length $substitute]}] end]
		
						set l [lreplace $l $listpos $listpos]
						incr llength -1
		
						if { $p1 ne "" } {
							set l [linsert $l $listpos [list text $p1]]
							incr llength 1
							incr listpos 1
						}
		
						foreach unit $content {
							set l [linsert $l $listpos $unit]
							incr listpos 1
							incr llength 1
						}
		
						if { $p3 ne "" } {
							set l [linsert $l $listpos [list text $p3]]
							incr llength 1
							#We must parse p3
						}
						#We will search from the begining of p3
						set npos 0
						break
					}
				}
			}
		}
		#Return the custom nick, replacing backslashses and variables
		return $l
	}
	
	#Returns the user nickname, or just email, or custom nick,
	#depending on configuration
	proc getDisplayNick { user_login {use_styled_nick 0}} {
		if { [::config::getKey emailsincontactlist] } {
			set out [list [list "text" $user_login]]
		} else {
			set nick [::abook::getNick $user_login 1]
			set customnick [::abook::getVolatileData $user_login parsed_customnick]
			set psm [::abook::getpsmmedia $user_login 1]

			set customnicktxt [::abook::removeStyles $customnick]
			set globalnicktxt [::abook::removeStyles $::globalnick]

			if { [::config::getKey globaloverride] == 0 } {
				if { $customnicktxt ne "" } {
					set out [parseCustomNickStyled $customnick $nick $user_login $customnick $psm]
				} else {
					if { $globalnicktxt ne "" } {
						set out [parseCustomNickStyled $::globalnick $nick $user_login $customnick $psm]
					} else {
						set out $nick
					}
				}
			} elseif { [::config::getKey globaloverride] == 1 } {
				if { $customnicktxt ne "" && $globalnicktxt eq "" } {
					set out [parseCustomNickStyled $customnick $nick $user_login $customnick $psm]
				} elseif { $globalnicktxt ne "" } {
					set out [parseCustomNickStyled $::globalnick $nick $user_login $customnick $psm]
				} else {
					set out $nick
				}
			}
		}
		if { !$use_styled_nick } {
			set out [::abook::removeStyles $out]
		}
		return $out
	}
	
	#Used to remove styles from the nickname/psm and returns full text
	proc removeStyles {list_styles} {
		set output ""
		foreach unit $list_styles {
			switch [lindex $unit 0] {
				"text" {
					# Store the text as a string
					append output [lindex $unit 1]
				}
				"smiley" {
					append output [lindex $unit 2]
				}
				"newline" {
					append output "\n"
				}
				"colour" -
				"font" {
				}
				default {
					status_log "Unknown item in parsed nickname: $unit"
				}
			}
		}
		return $output
	}

	# Used to fetch the groups ID so that the caller can order by
	# group if needed. Returns -1 on error.
	# ::abook::getGroups my@passport.com    : returns group ids
	proc getGroups {passport} {
		return [getContactData $passport group]
	}

	proc getGroupsname {passport} {

		set groups ""
                foreach gid [::abook::getGroups $passport] {
                        set groups "$groups[::groups::GetName $gid], "
                }

                set groups [string range $groups 0 end-2]
		return $groups
	}

	

   
	proc addContactToGroup { email grId } {
		global pcc
		
		set groups [getContactData $email group]
		set idx [lsearch $groups $grId]
		if { $idx == -1 } {
			setContactData $email group [linsert $groups 0 $grId]
		}
		#we make this to notify preferences > groups to be refreshed
		set pcc 1
	}

	proc emptyUserGroups { email } {
		global pcc

		setContactData $email group [list ]

		set pcc 1
	}

	proc removeContactFromGroup { email grId } {
		global pcc
		
		set groups [getContactData $email group]
		set idx [lsearch $groups $grId]
		
		if { $idx != -1 } {
			#The last group -> we move to nogroup
			if { [llength $groups] == 1 } {
				setContactData $email group [lreplace $groups $idx $idx 0]
			} else {
				setContactData $email group [lreplace $groups $idx $idx]
			}
		}

		set pcc 1
	}	

	proc getLists {passport} {
		return [getContactData $passport lists]
	}
   
	proc addContactToList { email listId } {
		global pcc
		
		set lists [getContactData $email lists]
		set idx [lsearch $lists $listId]
		if { $idx == -1 } {
			setContactData $email lists [linsert $lists 0 $listId]
		}
		set pcc 1
	}

	proc removeContactFromList { email listId } {
		global pcc
		
		set lists [getContactData $email lists]
		set idx [lsearch $lists $listId]
		
		if { $idx != -1 } {
			setContactData $email lists [lreplace $lists $idx $idx]
		}
		set pcc 1
	}	
	
	proc setConsistent {} {
		variable consistent
		set consistent 1
	}
	
	proc unsetConsistent {} {
		variable consistent
		set consistent 0
	}
	
	
	proc isConsistent {} {
		variable consistent
		return $consistent
	}
	
	#Save the contactlist to disk
	#filename - the filename to save to
	#type - the type to save as
	#possible types: csv, amsn
	proc saveToDisk { {filename ""} {type "amsn"} } {
		
		if { ![isConsistent] || ![LoginList lockexists "" [::config::getKey login]] } {
			return
		}
	
		global HOME
		variable users_data
		
		if { $filename eq "" } {
			set filename [file join $HOME abook.xml]
		}
		
		if {[catch { set file_id [open $filename w]} res ]} {
			status_log "::saveToDisk $res"
			msg_box "Can't save contact list, $res"
			return
		}
		
		
		fconfigure $file_id -encoding utf-8

		if { [string equal $type "amsn"] } {
			puts $file_id "<?xml version=\"1.0\" standalone=\"yes\" encoding=\"UTF-8\"?>"
			puts $file_id "<AMSN_AddressBook time=\"[clock seconds]\">"
			foreach user [array names users_data] {
				puts $file_id "<contact name=\"[::sxml::xmlreplace $user]\">"

				array set temp_array $users_data($user)
				foreach field [array names temp_array] {
					puts -nonewline $file_id "\t<$field>"
					puts -nonewline $file_id "[::sxml::xmlreplace $temp_array($field)]"	
					puts $file_id "</$field>"
				}
				puts $file_id "</contact>"
				array unset temp_array
			}
			puts $file_id "</AMSN_AddressBook>"
		} elseif { [string equal $type "csv"] } {
			puts $file_id "email,name"
			foreach contact [::abook::getAllContacts] {
				if { [string last "FL" [::abook::getContactData $contact lists]] != -1 } {
					array set temp_array $users_data($contact)
					if { [info exists temp_array([array names temp_array "nick"])] } {
						puts $file_id "$contact,$temp_array([array names temp_array "nick"])"
					} else {
						puts $file_id "$contact,"
					}
				}
			}
		} elseif { [string equal $type "ctt"] } {
			puts $file_id "<?xml version=\"1.0\"?>\n<messenger>\n\t<service name=\".NET Messenger Service\">\n\t\t<contactlist>"
			foreach contact [::abook::getAllContacts] {
				if { [string last "FL" [::abook::getContactData $contact lists]] != -1 } {
					puts $file_id "\t\t\t<contact>$contact</contact>"
				}
			}
			puts $file_id "\t\t</contactlist>\n\t</service>\n</messenger>"
		}
			

		close $file_id
	}

	
	proc loadFromDisk { {filename ""} } {
	
		global HOME
		
		if { $filename eq "" } {
			set filename [file join $HOME abook.xml]
		}
		
		if {[file readable $filename] == 0} {
			return -1
		}
		
		status_log "Loading address book data...\n" blue
		set abook_id [::sxml::init $filename]
		sxml::register_routine $abook_id "AMSN_AddressBook:contact" "::abook::loadXMLContact"
				
		set ret -1
		
		clearData
		
		catch {
			set ret [sxml::parse $abook_id]
		}
	
		sxml::end $abook_id			
		if { $ret < 0 } {
			clearData
			status_log "::abook::loadFromDisk Error\n" red
			return $ret
		} else {			
			status_log "Address book data loaded...\n" green
			setConsistent
			return 0
		}
	}
	
	proc loadXMLContact {cstack cdata saved_data cattr saved_attr args } {
		variable users_data
		upvar $saved_data sdata 
		upvar $saved_attr sattr
		
		array set attr $cattr
		
		set parentlen [string length $cstack]
		foreach child [array names sattr] {
			if { $child eq "_dummy_" } {
				continue
			}
			set fieldname [string range $child [expr {$parentlen+1}] end]
			#Remove this. Only leave it for some days to remove old ::abook stored data
			if { $fieldname eq "field" } {
				continue
			}
			setContactData $attr(name) $fieldname $sdata($child)
		}
		
		return 0	
		
	}
	
	
	proc importContact { } {
	
		set filename [chooseFileDialog]

		if { $filename ne "" } {
			if { [string match -nocase "*.ctt" "$filename"] } {
				::abook::importContactctt $filename
			} elseif { [string match -nocase "*.csv" "$filename"] } {
				::abook::importContactcsv $filename
			}
		}
		
	}
	
	proc importContactcsv { filename } {
	
		set ImportedContact [list]
		
		set file_id [open $filename r]
		fconfigure $file_id -encoding utf-8
		set content [read $file_id]
		close $file_id
		set lines [split $content "\n"]
		
		foreach line $lines {
			if { [string first "@" $line] != -1 } {
				set coma [string first "," $line]
				set contact [string range $line 0 [expr {$coma - 1}]]
				set ImportedContact [lappend ImportedContact $contact]
			}
		}
		
		::abook::importContactList $ImportedContact
		
	}
	
	proc importContactctt { filename } {
	
		status_log "Salut\n" red
	
		set ImportedContact [list]
		
		set file_id [open $filename r]
		fconfigure $file_id -encoding utf-8
		set content [read $file_id]
		close $file_id
		set lines [split $content "\n"]
		
		status_log "$lines"
		
		foreach line $lines {
			set id1 [string first "<contact>" $line]
			set id2 [string first "</contact>" $line]
			if { $id1 != -1 && $id2 != -1 } {
				incr id1 9
				incr id2 -1
				set contact [string range "$line" $id1 $id2]
				set ImportedContact [lappend ImportedContact $contact]
			}
		}
		
		::abook::importContactList $ImportedContact

	}
	
	proc importContactList { ImportedContact } {
		foreach contact $ImportedContact {
			status_log "Importation of contacts : $contact\n" red
			if { [::config::getKey protocol] >= 11 } {
				::MSN::WriteSB ns "ADC" "FL N=$contact F=$contact" "::MSN::ADCHandler 0"
			} else {
				::MSN::WriteSB ns "ADD" "FL $contact $contact 0" "::MSN::ADDHandler"
			}
		}
				
	}

	

}

namespace eval ::abookGui {
   namespace export Init showEntry

    if { $initialize_amsn == 1 } {

	#
	# P R I V A T E
	#
	variable bgcol #ABC8CE;	# Background color used in MSN Messenger
    }

   #
   # P R O T E C T E D
   #
    proc updatePhones { t h w m p} {
	set phome [urlencode [$t.$h get]]
	set pwork [urlencode [$t.$w get]]
	set pmobile [urlencode [$t.$m get]]
	::abook::setPhone home $phome
	::abook::setPhone work $pwork
	::abook::setPhone mobile $pmobile
	::abook::setPhone pager N
    }

   #
   # P U B L I C
   #
   	proc Init {} {
		variable bgcol
		::themes::AddClass ABook * {-background $bgcol} 90
		::themes::AddClass ABook Label {-background $bgcol} 90
		::themes::AddClass NoteBook * {-background $bgcol} 90
	}
   

	proc userDPs_raise_cmd { nb email } { 
		package require dpbrowser
		set nbUserDPs [$nb getframe userDPs]

#		set browser $nbUserDPs.otherdpscontainer.browser
#		set actions $nbUserDPs.otherdpscontainer.actions
#
#		if { ![winfo exists $browser]} {
#			::dpbrowser $browser -user $email -mode "selector" -width 6 -command [list\
#				::abookGui::activate_dpbrowser_actions $nbUserDPs.otherdpscontainer $email]
#
#			pack $browser -side left -expand true -fill both\
#				-before $nbUserDPs.otherdpscontainer.actions
#		}

		if { ![winfo exists $nbUserDPs.otherpics]} {
			::dpbrowser $nbUserDPs.otherpics  -width 7 -user $email -mode "properties"
			pack $nbUserDPs.otherpics -expand true -fill both
		}
	}

#	proc activate_dpbrowser_actions {widget email} {
#
#		set browser $widget.browser
#		set actions $widget.actions
#
#		set filepath [lindex [$browser getSelected] 1]
#		#activate the action buttons now an image is selected
#		if {$filepath ne ""} {
#			$actions.setasmine configure -state normal -command [list set_displaypic $filepath ]
#			$actions.setascustom configure -state normal -command [list ::abookGui::setCustomDp $email $filepath $widget ]
#			$actions.copyfileuri configure -state normal -command [list ::abookGui::copyDpToClipboard $filepath]
#		} else {
#			$actions.setasmine configure -state disabled
#			$actions.setascustom configure -state disabled
#			$actions.copyfileuri configure -state disabled
#		}
#	}

	#menu when right-clicking the user's dp on the first tab
	proc dp_mypicpopup_menu { X Y filename user} {
		
		#if user is self have another menu ?		
		
		# Create pop-up menu if it doesn't yet exists
		set the_menu .userDPs_menu
		catch {destroy $the_menu}
		menu $the_menu -tearoff 0 -type normal
		$the_menu add command \
			-label "[trans copytoclipboard [string tolower [trans filename]]]" \
			-command [list copyDpToClipboard $filename]
		$the_menu add command -label "[trans setasmydp]" \
			-command [list set_displaypic $filename]
		tk_popup $the_menu $X $Y
	}

	proc showUserProperties { email } {
		global colorval_$email customdp_$email showcustomsmileys_$email ignorecontact_$email HOME customdp_img_$email dontshowdp_$email
		set w ".user_[::md5::md5 $email]_prop"
		if { [winfo exists $w] } {
			raise $w
			return
		}
		toplevel $w
		wm title $w [trans userproperties $email]
		
		NoteBook $w.nb
		$w.nb insert 0 userdata -text [trans userdata]
		$w.nb insert 1 usersettings -text [trans usersettings]
		$w.nb insert 2 alarms -text [trans alarms]
		$w.nb insert 3 userDPs -text [trans userdps] \
			-raisecmd [list ::abookGui::userDPs_raise_cmd $w.nb $email]

		##############
		#Userdata page
		##############
		set nbIdent [$w.nb getframe userdata]
		ScrolledWindow $nbIdent.sw
		set sw $nbIdent.sw
		ScrollableFrame $nbIdent.sw.sf -constrainedwidth 1
		$nbIdent.sw setwidget $nbIdent.sw.sf
		set nbIdent [$nbIdent.sw.sf getframe]
		
		labelframe $nbIdent.fBasicInfo -relief groove -text [trans identity]
		
		label $nbIdent.fBasicInfo.displaypic -image [::skin::getDisplayPicture $email] -highlightthickness 2 -highlightbackground black -borderwidth 0
		bind $nbIdent.fBasicInfo.displaypic <ButtonPress-3> \
			[list ::abookGui::dp_mypicpopup_menu %X %Y\
			[file join $HOME displaypic cache [filenoext [::abook::getContactData $email displaypicfile ""]].png] $email]
		
		set nick [::abook::getNick $email]
		set h [expr {[string length $nick]/50 +1}]
		text $nbIdent.fBasicInfo.h1 -font bigfont -fg blue -height $h -wrap word -bd 0
		$nbIdent.fBasicInfo.h1 delete 0.0 end
		$nbIdent.fBasicInfo.h1 insert 0.0 $nick
		$nbIdent.fBasicInfo.h1 configure -state disabled
		set h1copymenu [::abook::CreateCopyMenu $nbIdent.fBasicInfo.h1]
		bind $nbIdent.fBasicInfo.h1 <Button3-ButtonRelease> "tk_popup $h1copymenu %X %Y"
		
		if { [::config::getKey protocol] >= 11 } {
			set psm [::abook::getpsmmedia $email]
			set h [expr {[string length $psm]/50 +1}]
			text $nbIdent.fBasicInfo.psm1 -font sitalf -fg blue -height $h -wrap word -bd 0
			$nbIdent.fBasicInfo.psm1 delete 0.0 end
			$nbIdent.fBasicInfo.psm1 insert 0.0 $psm
			$nbIdent.fBasicInfo.psm1 configure -state disabled
			set psm1copymenu [::abook::CreateCopyMenu $nbIdent.fBasicInfo.psm1]
			bind $nbIdent.fBasicInfo.psm1 <Button3-ButtonRelease> "tk_popup $psm1copymenu %X %Y"
		}

		set h [expr {[string length $email]/50 +1}]
		text $nbIdent.fBasicInfo.e1 -font splainf -fg blue -height $h -wrap word -bd 0
		$nbIdent.fBasicInfo.e1 delete 0.0 end
		$nbIdent.fBasicInfo.e1 insert 0.0 $email
		$nbIdent.fBasicInfo.e1 configure -state disabled
		set e1copymenu [::abook::CreateCopyMenu $nbIdent.fBasicInfo.e1]
		bind $nbIdent.fBasicInfo.e1 <Button3-ButtonRelease> "tk_popup $e1copymenu %X %Y"
		
		frame $nbIdent.fBasicInfo.fGroup
		label $nbIdent.fBasicInfo.fGroup.g -text "[trans group]:" -font splainf
		label $nbIdent.fBasicInfo.fGroup.g1 -text "[::abook::getGroupsname $email]" -font splainf -fg blue -justify left -wraplength 300
		pack $nbIdent.fBasicInfo.fGroup.g -side left
		pack $nbIdent.fBasicInfo.fGroup.g1 -side left
		
		grid $nbIdent.fBasicInfo.displaypic -row 0 -column 0 -sticky nwe -rowspan 4 -padx [list 0 8]
		grid $nbIdent.fBasicInfo.h1 -row 0 -column 1 -sticky w
		if { [::config::getKey protocol] >= 11 } {
			grid $nbIdent.fBasicInfo.psm1 -row 1 -column 1 -sticky w
		}
		grid $nbIdent.fBasicInfo.e1 -row 2 -column 1 -sticky w
		grid $nbIdent.fBasicInfo.fGroup -row 3 -column 1 -sticky w
		grid columnconfigure $nbIdent.fBasicInfo 1 -weight 1

		labelframe $nbIdent.fPhone -text [trans phones]
		label $nbIdent.fPhone.phh -text "[trans home]:" 
		label $nbIdent.fPhone.phh1 -font splainf -text [::abook::getVolatileData $email phh] -fg blue \
		-justify left -wraplength 300 
		label $nbIdent.fPhone.phw -text "[trans work]:"
		label $nbIdent.fPhone.phw1 -font splainf -text [::abook::getVolatileData $email phw] -fg blue \
			-justify left -wraplength 300 
		label $nbIdent.fPhone.phm -text "[trans mobile]:" 
		label $nbIdent.fPhone.phm1 -font splainf -text [::abook::getVolatileData $email phm] -fg blue \
		-justify left -wraplength 300 
		label $nbIdent.fPhone.php -text "[trans pager]:" 
		label $nbIdent.fPhone.php1 -font splainf -text [::abook::getVolatileData $email mob] -fg blue \
		-justify left -wraplength 300 

		grid $nbIdent.fPhone.phh -row 0 -column 0 -sticky e
		grid $nbIdent.fPhone.phh1 -row 0 -column 1 -sticky w
		grid $nbIdent.fPhone.phw -row 1 -column 0 -sticky e
		grid $nbIdent.fPhone.phw1 -row 1 -column 1 -sticky w
		grid $nbIdent.fPhone.phm -row 2 -column 0 -sticky e
		grid $nbIdent.fPhone.phm1 -row 2 -column 1 -sticky w
		grid $nbIdent.fPhone.php -row 3 -column 0 -sticky e
		grid $nbIdent.fPhone.php1 -row 3 -column 1 -sticky w
		grid columnconfigure $nbIdent.fPhone 1 -weight 1

		labelframe $nbIdent.fStats -text [trans others]
		label $nbIdent.fStats.lastlogin -text "[trans lastlogin]:"
		label $nbIdent.fStats.lastlogin1 -text [::abook::dateconvert "[::abook::getContactData $email last_login]"] -font splainf -fg blue 
		
		label $nbIdent.fStats.lastlogout -text "[trans lastlogout]:"
		label $nbIdent.fStats.lastlogout1 -text [::abook::dateconvert "[::abook::getContactData $email last_logout]"] -font splainf -fg blue 

		label $nbIdent.fStats.lastseen -text "[trans lastseen]:"
		if { [::abook::getVolatileData $email state] eq "FLN" || [lsearch [::abook::getContactData $email lists] "FL"] eq -1} {
			label $nbIdent.fStats.lastseen1 -text [::abook::dateconvert "[::abook::getContactData $email last_seen]"] -font splainf -fg blue
		} elseif { [::abook::getContactData $email last_seen] eq "" } {		
			label $nbIdent.fStats.lastseen1 -text "" -font splainf -fg blue
		} else {
			label $nbIdent.fStats.lastseen1 -text [trans online] -font splainf -fg blue
		}
		
		label $nbIdent.fStats.lastmsgedme -text "[trans lastmsgedme]:"
		label $nbIdent.fStats.lastmsgedme1 -text [::abook::dateconvert "[::abook::getContactData $email last_msgedme]"] -font splainf -fg blue
		#Client-name of the user (from Gaim, dMSN, etc)
		label $nbIdent.fStats.clientname -text "[trans clientname]:"
		label $nbIdent.fStats.clientname1 -text "[::abook::getContactData $email clientname] ([::abook::getContactData $email client])" -font splainf -fg blue
		
		#Does the user record the conversation or not
		if { [::abook::getContactData $email chatlogging] eq "Y" } {
			set chatlogging [trans yes]
		} elseif { [::abook::getContactData $email chatlogging] eq "N" } {
			set chatlogging [trans no]
		} else {
			set chatlogging [trans unknown]
		}
		
		label $nbIdent.fStats.chatlogging -text "[trans logschats]:"
		label $nbIdent.fStats.chatlogging1 -text $chatlogging -font splainf -fg blue
		grid $nbIdent.fStats.lastlogin -row 0 -column 0 -sticky e
		grid $nbIdent.fStats.lastlogin1 -row 0 -column 1 -sticky w
		grid $nbIdent.fStats.lastlogout -row 1 -column 0 -sticky e
		grid $nbIdent.fStats.lastlogout1 -row 1 -column 1 -sticky w
		grid $nbIdent.fStats.lastmsgedme -row 2 -column 0 -sticky e
		grid $nbIdent.fStats.lastmsgedme1 -row 2 -column 1 -sticky w
		grid $nbIdent.fStats.lastseen -row 3 -column 0 -sticky e
		grid $nbIdent.fStats.lastseen1 -row 3 -column 1 -sticky w
		grid $nbIdent.fStats.clientname -row 4 -column 0 -sticky e
		grid $nbIdent.fStats.clientname1 -row 4 -column 1 -sticky w
		grid $nbIdent.fStats.chatlogging -row 5 -column 0 -sticky e
		grid $nbIdent.fStats.chatlogging1 -row 5 -column 1 -sticky w
		grid columnconfigure $nbIdent.fStats 1 -weight 1
		
		grid $nbIdent.fBasicInfo -row 0 -column 0 -sticky nwse -columnspan 2 -ipadx 4 -ipady 4
		grid $nbIdent.fPhone -row 1 -column 0 -sticky nwse -padx [list 0 4] -pady [list 8 0]
		grid $nbIdent.fStats -row 1 -column 1 -sticky nwse -padx [list 4 0] -pady [list 8 0]
		grid columnconfigure $nbIdent { 0 1 } -weight 1
		
		pack $sw -expand true -fill both
		
		##############
		#User settings page
		#############
		set nbSettings [$w.nb getframe usersettings]
		ScrolledWindow $nbSettings.sw
		set sw $nbSettings.sw
		ScrollableFrame $nbSettings.sw.sf -constrainedwidth 1
		$nbSettings.sw setwidget $nbSettings.sw.sf
		set nbSettings [$nbSettings.sw.sf getframe]
		
		labelframe $nbSettings.fNick -relief groove -text [trans nick]
		label $nbSettings.fNick.customnickl -text "[trans customnick]:"
		frame $nbSettings.fNick.customnick
		entry $nbSettings.fNick.customnick.ent -font splainf -bg white
		menubutton $nbSettings.fNick.customnick.help -font sboldf -text "<-" -menu $nbSettings.fNick.customnick.help.menu
		menu $nbSettings.fNick.customnick.help.menu -tearoff 0
		$nbSettings.fNick.customnick.help.menu add command -label [trans nick] -command "$nbSettings.fNick.customnick.ent insert insert \\\$nick"
		$nbSettings.fNick.customnick.help.menu add command -label [trans email] -command "$nbSettings.fNick.customnick.ent insert insert \\\$user_login"
		if { [::config::getKey protocol] >= 11 } {
			$nbSettings.fNick.customnick.help.menu add command -label [trans psm] -command "$nbSettings.fNick.customnick.ent insert insert \\\$psm"
		}
		$nbSettings.fNick.customnick.help.menu add separator
		$nbSettings.fNick.customnick.help.menu add command -label [trans delete] -command "$nbSettings.fNick.customnick.ent delete 0 end"
		$nbSettings.fNick.customnick.ent insert end [::abook::getContactData $email customnick]
		pack $nbSettings.fNick.customnick.ent -side left -expand true -fill x
		pack $nbSettings.fNick.customnick.help -side left
		label $nbSettings.fNick.customfnickl -text "[trans friendlyname]:"
		frame $nbSettings.fNick.customfnick
		entry $nbSettings.fNick.customfnick.ent -font splainf -bg white
		menubutton $nbSettings.fNick.customfnick.help -font sboldf -text "<-" -menu $nbSettings.fNick.customfnick.help.menu
		menu $nbSettings.fNick.customfnick.help.menu -tearoff 0
		$nbSettings.fNick.customfnick.help.menu add command -label [trans nick] -command "$nbSettings.fNick.customfnick.ent insert insert \\\$nick"
		$nbSettings.fNick.customfnick.help.menu add command -label [trans email] -command "$nbSettings.fNick.customfnick.ent insert insert \\\$user_login"
                if { [::config::getKey protocol] >= 11 } {
                        $nbSettings.fNick.customfnick.help.menu add command -label [trans psm] -command "$nbSettings.fNick.customfnick.ent insert insert \\\$psm"
                }
		$nbSettings.fNick.customfnick.help.menu add separator
		$nbSettings.fNick.customfnick.help.menu add command -label [trans delete] -command "$nbSettings.fNick.customfnick.ent delete 0 end"
		$nbSettings.fNick.customfnick.ent insert end [::abook::getContactData $email customfnick]
		pack $nbSettings.fNick.customfnick.ent -side left -expand true -fill x
		pack $nbSettings.fNick.customfnick.help -side left
	
		label $nbSettings.fNick.ycustomfnickl -text "[trans myfriendlyname]:"
		frame $nbSettings.fNick.ycustomfnick
		entry $nbSettings.fNick.ycustomfnick.ent -font splainf -bg white
		menubutton $nbSettings.fNick.ycustomfnick.help -font sboldf -text "<-" -menu $nbSettings.fNick.ycustomfnick.help.menu
		menu $nbSettings.fNick.ycustomfnick.help.menu -tearoff 0
		$nbSettings.fNick.ycustomfnick.help.menu add command -label [trans nick] -command "$nbSettings.fNick.ycustomfnick.ent insert insert \\\$nick"
		$nbSettings.fNick.ycustomfnick.help.menu add command -label [trans email] -command "$nbSettings.fNick.ycustomfnick.ent insert insert \\\$user_login"
                if { [::config::getKey protocol] >= 11 } {
                        $nbSettings.fNick.ycustomfnick.help.menu add command -label [trans psm] -command "$nbSettings.fNick.ycustomfnick.ent insert insert \\\$psm"
                }
		$nbSettings.fNick.ycustomfnick.help.menu add separator
		$nbSettings.fNick.ycustomfnick.help.menu add command -label [trans delete] -command "$nbSettings.fNick.ycustomfnick.ent delete 0 end"
		$nbSettings.fNick.ycustomfnick.ent insert end [::abook::getContactData $email cust_p4c_name]
		pack $nbSettings.fNick.ycustomfnick.ent -side left -expand true -fill x
		pack $nbSettings.fNick.ycustomfnick.help -side left
		
		# The custom color frame
		label $nbSettings.fNick.lColor -text "[trans customcolor]:"
		frame $nbSettings.fNick.fColor -relief flat
		set colorval_$email [::abook::getContactData $email customcolor] 
		set showcustomsmileys_$email [::abook::getContactData $email showcustomsmileys]
		set ignorecontact_$email [::abook::getContactData $email ignored]
		set dontshowdp_$email [::abook::getContactData $email dontshowdp]

		frame $nbSettings.fNick.fColor.col -width 96 -bd 1 -relief flat -highlightbackground black -highlightcolor black
		if { [set colorval_$email] ne "" } {
			if { [string index [set colorval_$email] 0] eq "#" } {
				set colorval_$email [string range [set colorval_$email] 1 end]
			}
			set colorval_$email "#[string repeat 0 [expr {6-[string length [set colorval_$email]]}]][set colorval_$email]"
			#If the color is white we can't see the contact on the list : we ignore the custom color
			$nbSettings.fNick.fColor.col configure -background [set colorval_${email}] -highlightthickness 1 
		} else {
			$nbSettings.fNick.fColor.col configure -background [$nbSettings.fNick.fColor cget -background] -highlightthickness 0
		}
		button $nbSettings.fNick.fColor.bset -text "[trans change]" -command "::abookGui::ChangeColor $email $nbSettings" 
		button $nbSettings.fNick.fColor.brem -text "[trans delete]" -command "::abookGui::RemoveCustomColor $email $nbSettings" 
		pack $nbSettings.fNick.fColor.col -side left -expand true -fill y -pady 5 -padx 8
		pack $nbSettings.fNick.fColor.bset -side left -padx 3 -pady 2
		pack $nbSettings.fNick.fColor.brem -side left -padx 3 -pady 2
		
		# The custom display pic frame
		label $nbSettings.fNick.lDispl -text "[trans customdp]:"
		frame $nbSettings.fNick.fDispl -relief flat
		
		set customdp_$email [::abook::getContactData $email customdp ""] 
		if {[set customdp_$email] ne ""} {
			image create photo customdp_img_$email -file [set customdp_$email]
			label $nbSettings.fNick.fDispl.dp -height 96 -width 96 -image customdp_img_$email -borderwidth 0 -relief flat
		} else {
			label $nbSettings.fNick.fDispl.dp -height 96 -width 96 -image [::skin::loadPixmap nullimage] -borderwidth 0 -relief flat
		}
		
		button $nbSettings.fNick.fDispl.bset -text "[trans change]" -command "::abookGui::ChangeCustomDp $email $nbSettings" 
		button $nbSettings.fNick.fDispl.brem -text "[trans delete]" -command "::abookGui::RemoveCustomDp $email $nbSettings" 
		pack $nbSettings.fNick.fDispl.dp -side left -pady 5 -padx 8
		pack $nbSettings.fNick.fDispl.bset -side left -padx 3 -pady 2
		pack $nbSettings.fNick.fDispl.brem -side left -padx 3 -pady 2
		
		grid $nbSettings.fNick.customnickl -row 0 -column 0 -sticky e
		grid $nbSettings.fNick.customnick -row 0 -column 1 -sticky we
		grid $nbSettings.fNick.customfnickl -row 1 -column 0 -sticky e
		grid $nbSettings.fNick.customfnick -row 1 -column 1 -sticky we
		grid $nbSettings.fNick.ycustomfnickl -row 2 -column 0 -sticky e
		grid $nbSettings.fNick.ycustomfnick -row 2 -column 1 -sticky we
		grid $nbSettings.fNick.lColor -row 3 -column 0 -sticky e
		grid $nbSettings.fNick.fColor -row 3 -column 1 -sticky w
		grid $nbSettings.fNick.lDispl -row 4 -column 0 -sticky e
		grid $nbSettings.fNick.fDispl -row 4 -column 1 -sticky w
		grid columnconfigure $nbSettings.fNick 1 -weight 1
		
		labelframe $nbSettings.fChat -relief groove -text [trans chat]
		checkbutton $nbSettings.fChat.showcustomsmileys -variable showcustomsmileys_$email -text "[trans custshowcustomsmileys]" -anchor w
		checkbutton $nbSettings.fChat.ignoreuser -variable ignorecontact_$email -text "[trans ignorecontact]" -anchor w
		checkbutton $nbSettings.fChat.dontshowdp -variable dontshowdp_$email -text "[trans dontshowdp]" -anchor w
		pack $nbSettings.fChat.showcustomsmileys -side top -fill x
		pack $nbSettings.fChat.ignoreuser -side top -fill x
		pack $nbSettings.fChat.dontshowdp -side top -fill x
		
		labelframe $nbSettings.fGroup -relief groove -text [trans groups]
		::groups::Groupmanager $email $nbSettings.fGroup

		labelframe $nbSettings.fNotify -relief groove -text [trans notifywin]
		label $nbSettings.fNotify.default -font sboldf -text "*" -justify center
		label $nbSettings.fNotify.yes -font sboldf -text [trans yes] -justify center
		label $nbSettings.fNotify.no -font sboldf -text [trans no] -justify center
		
		#Set default values
		set ::notifyonline($email) [::abook::getContactData $email notifyonline ""]
		set ::notifyoffline($email) [::abook::getContactData $email notifyoffline ""]
		set ::notifystatus($email) [::abook::getContactData $email notifystatus ""]
		set ::notifymsg($email) [::abook::getContactData $email notifymsg ""]
		
		#Add the checkboxes
		AddOption $nbSettings.fNotify notifyonline notifyonline($email) [trans custnotifyonline] 1
		AddOption $nbSettings.fNotify notifyoffline notifyoffline($email) [trans custnotifyoffline] 2
		AddOption $nbSettings.fNotify notifystatus notifystatus($email) [trans custnotifystatus] 3
		AddOption $nbSettings.fNotify notifymsg notifymsg($email) [trans custnotifymsg] 4
		
		grid $nbSettings.fNotify.default -row 0 -column 0 -sticky we -padx 5
		grid $nbSettings.fNotify.yes -row 0 -column 1 -sticky we -padx 5
		grid $nbSettings.fNotify.no -row 0 -column 2 -sticky we -padx 5
		grid columnconfigure $nbSettings.fNotify 3 -weight 1
		
		grid $nbSettings.fNick -row 0 -column 0 -sticky nwse -columnspan 2 -pady [list 0 4]
		grid $nbSettings.fChat -row 1 -column 0 -sticky nwse -padx [list 0 4] -pady 4
		grid $nbSettings.fGroup -row 1 -column 1 -sticky nwse -padx [list 4 0] -pady 4
		grid $nbSettings.fNotify -row 2 -column 0 -sticky nwse -columnspan 2 -pady [list 4 0]
		grid columnconfigure $nbSettings { 0 1 } -weight 1
		
		pack $sw -expand true -fill both

		##############
		#Alarms frame
		##############
		set nbAlarm [$w.nb getframe alarms]
		ScrolledWindow $nbAlarm.sw
		pack $nbAlarm.sw -expand true -fill both
		ScrollableFrame $nbAlarm.sw.sf
		$nbAlarm.sw setwidget $nbAlarm.sw.sf

		set nbAlarm [$nbAlarm.sw.sf getframe]
		::alarms::configDialog $email $nbAlarm

		##############
		#UserDPs page
		##############
		set nbUserDPs [$w.nb getframe userDPs]
		# User's current display picture
#		label $nbUserDPs.titlepic1 -text "[trans curdisplaypic]" -font bboldunderf
#		label $nbUserDPs.displaypic -image [::skin::getDisplayPicture $email]
#		bind $nbUserDPs.displaypic <ButtonPress-3> \
			[list ::abookGui::dp_mypicpopup_menu %X %Y\
			[file join $HOME displaypic cache [filenoext [::abook::getContactData $email displaypicfile ""]].png] $email]

		# Other display pictures of user
		label $nbUserDPs.titlepic2 -text "[trans otherdisplaypic]" \
			-font bboldunder

#		frame $nbUserDPs.otherdpscontainer
#		set actions $nbUserDPs.otherdpscontainer.actions
#		#$nbUserDPs.otherdpscontainer.browser is created in userDPs_raise_cmd
#
#		frame $actions
#		label $actions.titlepic1 -text "[trans curdisplaypic]" -font bboldunderf -anchor n
#		label $actions.displaypic -image [::skin::getDisplayPicture $email] -anchor n
#		
#		button $actions.setasmine -text "[trans setasmydp]" -state disabled -justify left
#		button $actions.setascustom -text "[trans setascustom]" -state disabled
#		if {[::abook::getContactData $email customdp ""] ne ""} {
#			button $actions.removecustom -text "[trans removecustom]" -state normal -command [list ::abookGui::unsetCustomDp $email $w]
#		} else {
#			button $actions.removecustom -text "[trans removecustom]" -state disabled
#		}
#		button $actions.copyfileuri -text "[trans copytoclipboard [string tolower [trans filename]]]" -state disabled
#		pack $actions.titlepic1 $actions.displaypic -fill x
#		pack $actions.setasmine $actions.setascustom $actions.removecustom $actions.copyfileuri  -anchor w -fill x
#		pack $actions -side left -fill y


		#Other display pictures get loaded when the dp tab is raised
		#See proc userDPs_raise_cmd
		
#		pack $nbUserDPs.titlepic1 -anchor w -padx 5 -pady 5
#		pack $nbUserDPs.displaypic -anchor w -padx 7 -pady 5
		pack $nbUserDPs.titlepic2 -anchor w -padx 5 -pady 5
#		pack $nbUserDPs.otherdpscontainer -anchor w -padx 5 -pady 5 -fill both -expand true

		##########
		#Common
		##########
		$w.nb compute_size
		[$w.nb getframe userdata].sw.sf compute_size
		[$w.nb getframe usersettings].sw.sf compute_size
		[$w.nb getframe alarms].sw.sf compute_size
		$w.nb compute_size
		$w.nb raise userdata
		
		frame $w.buttons
		
		button $w.buttons.ok -text [trans accept] -command [list ::abookGui::PropOk $email $w]
		button $w.buttons.cancel -text [trans cancel] -command [list ::abookGui::PropCancel $email $w]
		
		pack $w.buttons.ok $w.buttons.cancel -side right -padx 5 -pady 3
		
		pack $w.buttons -fill x -side bottom
		pack $w.nb -expand true -fill both -side bottom -padx 3 -pady 3
		#pack $w.nb
		
		#Ask to save or not to save when we close the user properties window on Mac OS X
		#Request from users with 800X600 screen (they can't see accept/cancel button)
		if { [OnMac] } {
			wm protocol $w WM_DELETE_WINDOW "[list ::abookGui::closeProperties $email $w]"
			bind $w <<Escape>> "[list ::abookGui::closeProperties $email $w]"
		} else {
			bind $w <<Escape>> "[list ::abookGui::PropCancel $email $w]"
		}

		bind $w <Destroy> [list ::abookGui::PropDestroyed $email $w %W]
		
		moveinscreen $w 30
	}
	
	proc showUserAlarmSettings { email } {
		showUserProperties $email
		.user_[::md5::md5 $email]_prop.nb raise alarms
	}
	
	proc showCustomNickScreen { email } {
		set w ".user_[::md5::md5 $email]_cnick"
		if { [winfo exists $w] } {
			raise $w
			return
		}
		toplevel $w
		wm title $w "[trans customnick]: $email"
	
		label $w.customnickl -text "[trans customnick]:"
		frame $w.customnick
		entry $w.customnick.ent -font splainf -bg white
		menubutton $w.customnick.help -font sboldf -text "<-" -menu $w.customnick.help.menu
		menu $w.customnick.help.menu -tearoff 0
		$w.customnick.help.menu add command -label [trans nick] -command "$w.customnick.ent insert insert \\\$nick"
		$w.customnick.help.menu add command -label [trans email] -command "$w.customnick.ent insert insert \\\$user_login"
		$w.customnick.help.menu add separator
		$w.customnick.help.menu add command -label [trans delete] -command "$w.customnick.ent delete 0 end"
		$w.customnick.ent insert end [::abook::getContactData $email customnick]
		pack $w.customnick.ent -side left -expand true -fill x
		pack $w.customnick.help -side left
		label $w.customfnickl -text "[trans friendlyname]:"
		frame $w.customfnick
		entry $w.customfnick.ent -font splainf -bg white
		menubutton $w.customfnick.help -font sboldf -text "<-" -menu $w.customfnick.help.menu
		menu $w.customfnick.help.menu -tearoff 0
		$w.customfnick.help.menu add command -label [trans nick] -command "$w.customfnick.ent insert insert \\\$nick"
		$w.customfnick.help.menu add command -label [trans email] -command "$w.customfnick.ent insert insert \\\$user_login"
		$w.customfnick.help.menu add separator
		$w.customfnick.help.menu add command -label [trans delete] -command "$w.customfnick.ent delete 0 end"
		$w.customfnick.ent insert end [::abook::getContactData $email customfnick]
		pack $w.customfnick.ent -side left -expand true -fill x
		pack $w.customfnick.help -side left
		
		grid $w.customnickl -row 0 -column 0 -sticky ne
		grid $w.customnick -row 0 -column 1 -sticky we -padx 6
		grid $w.customfnickl -row 1 -column 0 -sticky ne
		grid $w.customfnick -row 1 -column 1 -sticky we -padx 6
		
		grid columnconfigure $w 1 -weight 1
	}
	
	#Ask the user if he wants to save or not the user properties window
	proc closeProperties {email w} {
		#Ask the user yes/no if he wants to save, parent=window to attach the question, title= totally useless on Mac
		set answer [::amsn::messageBox "[trans save] ?" yesno question "[trans save] ?"]
		#When the user answer yes, save preferences and close the window
		if {$answer eq "yes"} {
			::abookGui::PropOk $email $w
		#When the user do not answer yes (no), then Restore previous preferences and close the window
		} else {
			::abookGui::PropCancel $email $w
		}
	}
	
	proc PropDestroyed { email w win } {
		global colorval_$email showcustomsmileys_$email ignorecontact_$email dontshowdp_$email

		if { $w eq $win } {
			#Clean temporal variables
			unset ::notifyonline($email)
			unset ::notifyoffline($email)
			unset ::notifystatus($email)
			unset ::notifymsg($email)
			catch {unset colorval_$email}
			catch {unset showcustomsmileys_$email}
			catch {unset ignorecontact_$email}
			catch {unset dontshowdp_$email}
		}
	}
	
	proc AddOption { nbNotify name var text row} {
		radiobutton $nbNotify.${name}_default -value "" -variable $var
		radiobutton $nbNotify.${name}_yes -value 1 -variable $var
		radiobutton $nbNotify.${name}_no -value 0 -variable $var
		label $nbNotify.${name} -font splainf -text $text -justify left
		
		grid $nbNotify.${name}_default -row $row -column 0 -sticky we
		grid $nbNotify.${name}_yes -row $row -column 1 -sticky we
		grid $nbNotify.${name}_no -row $row -column 2 -sticky we
		
		grid $nbNotify.${name} -row $row -column 3 -sticky w
	}

	
	proc ChangeColor { email w } {
		global colorval_$email
		set color  [SelectColor $w.customcolordialog  -type dialog  -title "[trans customcolor]" -parent $w]
		if { $color eq "" } { return }

		set colorval_$email $color
		$w.fNick.fColor.col configure -background [set colorval_${email}] -highlightthickness 1 
	}
	
	proc RemoveCustomColor { email w } {	
	   	global colorval_$email	
		set colorval_$email ""
		$w.fNick.fColor.col configure -background [$w.fNick.fColor cget -background] -highlightthickness 0
	}

#	proc setCustomDp { email path widget } {
#		# Backup old custom dp
#		set old_customdp [::abook::getContactData $email customdp ""]	
#		# Store custom display information options
#		::abook::setAtomicContactData $email customdp $path
#		# Update display picture
#		if {$path ne $old_customdp} {
#			::skin::getDisplayPicture $email 1
#			::skin::getLittleDisplayPicture $email 1
#			$widget.actions.displaypic configure -image [::skin::getDisplayPicture $email]
#		}
#		if {$old_customdp eq ""} {
#			$widget.actions.removecustom configure -state normal -command [list ::abookGui::unsetCustomDp $email $widget]
#		}
#	}

#	proc unsetCustomDp { email widget } {
#		# Backup old custom dp
#		set old_customdp [::abook::getContactData $email customdp ""]	
#		# Store custom display information options
#		::abook::setAtomicContactData $email customdp ""
#		# Update display picture
#		if {$old_customdp ne ""} {
#			::skin::getDisplayPicture $email 1
#			::skin::getLittleDisplayPicture $email 1
#			$widget.actions.displaypic configure -image [::skin::getDisplayPicture $email]
#		}
#		$widget.actions.removecustom configure -state disabled
#	}

	proc copyDpToClipboard { file } {
		clipboard clear
		clipboard append $file
	}


	# These procedures change the custom DP. They need to be launched from within the properties screen,
	# as the actual change is done through the PropOk procedure
	proc ChangeCustomDp { email w } {
		global customdp_$email
		set customdp_$email [::abook::getContactData $email customdp ""]
		dpBrowser $email
		tkwait window .dpbrowser
		catch {image delete customdp_img_$email}
		image create photo customdp_img_$email -file [set customdp_$email]
		$w.fNick.fDispl.dp configure -image customdp_img_$email
	}


	proc RemoveCustomDp { email w } {
	   	global customdp_$email
		set customdp_$email ""
		$w.fNick.fDispl.dp configure -image [::skin::loadPixmap nullimage]
	}

	proc SetGlobalNick { } {
		
		if {[winfo exists .globalnick]} {
			return
		}
	
		toplevel .globalnick
		wm title .globalnick "[trans globalnicktitle]"
		frame .globalnick.frm -bd 1 
		label .globalnick.frm.lbl -text "[trans globalnick]" -font sboldf -justify left -wraplength 400
		entry .globalnick.frm.nick -width 50 -bg #FFFFFF -font splainf
		menubutton .globalnick.frm.help -font splainf -text "<-" -menu .globalnick.frm.help.menu
		menu .globalnick.frm.help.menu -tearoff 0
		.globalnick.frm.help.menu add command -label [trans nick] -command ".globalnick.frm.nick insert insert \\\$nick"
		.globalnick.frm.help.menu add command -label [trans email] -command ".globalnick.frm.nick insert insert \\\$user_login"
		.globalnick.frm.help.menu add command -label [trans psm] -command ".globalnick.frm.nick insert insert \\\$psm"
		.globalnick.frm.help.menu add command -label [trans customnick] -command ".globalnick.frm.nick insert insert \\\$customnick"
		.globalnick.frm.help.menu add separator
		.globalnick.frm.help.menu add command -label [trans delete] -command ".globalnick.frm.nick delete 0 end"

		pack .globalnick.frm.lbl -pady 2 -side top
		pack .globalnick.frm.nick -pady 2 -side left
		pack .globalnick.frm.help -side left
		bind .globalnick.frm.nick <Return> [list .globalnick.btn.ok invoke]
		frame .globalnick.btn 
		button .globalnick.btn.ok -text "[trans ok]"  \
			-command {
			set nick [.globalnick.frm.nick get]
			set data [::smiley::parseMessageToList [list [ list "text" $nick ]] 1]
			set evpar(variable) data
			set evpar(login) ""
			::plugins::PostEvent parse_contact evpar

			set ::globalnick $data
		
			::config::setKey globalnick $nick;
			::MSN::contactListChanged;
			::Event::fireEvent changedNickDisplay gui
			destroy .globalnick
			}
		button .globalnick.btn.cancel -text "[trans cancel]"  \
			-command [list destroy .globalnick]
		bind .globalnick <<Escape>> [list .globalnick.btn.cancel invoke]
		pack .globalnick.btn.ok .globalnick.btn.cancel -side right -padx 5
		pack .globalnick.frm -side top -pady 3 -padx 5
		pack .globalnick.btn  -side top -anchor e -pady 3

		.globalnick.frm.nick insert end [::config::getKey globalnick]
	}

	proc PropOk { email w } {
		global colorval_$email customdp_$email showcustomsmileys_$email ignorecontact_$email dontshowdp_$email
		
		if {[::alarms::SaveAlarm $email] != 0 } {
			return
		}
	
		set nbSettings [$w.nb getframe usersettings]
		set nbSettings [$nbSettings.sw.sf getframe]
		
		# Backup old custom dp
		set old_customdp [::abook::getContactData $email customdp ""]

		# Store custom display information options
		::abook::setAtomicContactData $email [list cust_p4c_name customcolor customdp showcustomsmileys ignored dontshowdp] \
			[list [$nbSettings.fNick.ycustomfnick.ent get] [set colorval_$email] [set customdp_$email] [set showcustomsmileys_$email] [set ignorecontact_$email] [set dontshowdp_$email]]
		
		::abook::setContactData $email customnick [$nbSettings.fNick.customnick.ent get]
		::abook::setContactData $email customfnick [$nbSettings.fNick.customfnick.ent get]
		# Update display picture
		if {[set customdp_$email] ne $old_customdp} {
			::skin::getDisplayPicture $email 1
			::skin::getLittleDisplayPicture $email 1
		}
		
		# Store groups
		::groups::GroupmanagerOk $email
		
		# Store custom notification options
		::abook::setAtomicContactData $email [list notifyonline notifyoffline notifystatus notifymsg] \
			[list [set ::notifyonline($email)] [set ::notifyoffline($email)] [set ::notifystatus($email)] [set ::notifymsg($email)]]
		
		catch {image delete customdp_img_$email}
		destroy $w
		::MSN::contactListChanged
		::Event::fireEvent contactDataChange gui $email

		::abook::saveToDisk
	}
	
	proc PropCancel { email w } {
		::groups::GroupmanagerClose $email
		catch {image delete customdp_img_$email}
		destroy $w
	}

}




See more files for this project here

aMSN

A very nice MSN compatible messenger application, aMSN Messenger is a multiplatform MSN messenger clone. Works pretty much like its Windows based counterpart. Perfect for keeping in touch with those friends who have not yet seen the light. Works on linux

Project homepage: http://sourceforge.net/projects/amsn
Programming language(s): C,C++,PHP,Tcl,XML
License: other

  autopackage/
    @tcl.sourceforge.net/
      tcl/
        skeleton.1
      tk/
        skeleton.1
    default.apspec
  debian/
    changelog.in
    compat
    control
    copyright
    dirs
    package.postinst
    package.postrm
    rules
  desktop-icons/
    128x128/
    16x16/
    22x22/
    32x32/
    48x48/
    64x64/
    72x72/
    96x96/
  docs/
    DOCS-HOWTO
    FAQbs
    FAQca
    FAQca_VC
    FAQde
    FAQdu
    FAQee
    FAQel
    FAQes
    FAQfr
    FAQfr.html
    FAQfr_CA
    FAQfur
    FAQhu
    FAQit
    FAQmn
    FAQnl
    FAQno
    FAQpt
    FAQpt_BR
    FAQsl
    FAQsv
    FAQtr
    HELPca
    HELPca_VC
    HELPcs
    HELPda
    HELPde
    HELPee
    HELPel
    HELPes
    HELPfr
    HELPfr_CA
    HELPfur
    HELPhu
    HELPit
    HELPmn
    HELPnl
    HELPno
    HELPpt
    HELPpt_BR
    HELPro
    HELPru
    HELPtr
    HELPzh_TW
    READMEca
    READMEca_VC
    READMEcs
    READMEda
    READMEde
    READMEes
    READMEfr
    READMEfr_CA
    READMEfur
    READMEhu
    READMEit
    READMEmn
    READMEnl
    READMEpt
    READMEpt_BR
    READMEro
    READMEru
    READMEsv
    READMEtr
    READMEzh_TW
  lang/
    LANG-HOWTO
    addkey.tcl
    complete.pl
    convert.tcl
    genlangfiles.c
    genpage.c
    lang1.tmpl
    lang2.tmpl
    lang3.tmpl
    langal
    langast
    langca
    langca_VC
    langchk.sh
    langcs
    langcy
    langda
    langde
    langee
    langel
    langen
    langes
    langeu
    langfi
    langfr
    langfr_CA
    langfri
    langglg
    langgr2
    langhu
    langid
    langis
    langit
    langko
    langlt
    langmk
    langnl
    langno
    langoc
    langpl
    langpt
    langpt_BR
    langro
    langru
    langsk
    langsl
    langsr
    langsv
    langtr
    langzh-CN
    langzh-TW
    missing.py
    sortlang
  plugins/
    Nudge/
    PowerTool/
    WebcamShooter/
    inkdraw/
    remind/
    winks/
  skins/
    default/
  utils/
    TkCximage/
    base64/
    bwidget1.8.0/
    combobox/
    contentmanager/
    dpbrowser/
    drawboard/
    framec/
    http2.4/
    linux/
    log/
    macosx/
    pixmapbutton/
    pixmapmenu/
    pixmapoption/
    pixmapprogbar/
    pixmapscroll/
    scalable-bg/
    sexytile/
    sha1/
  AGREEMENT
  AppMain.tcl
  BWidget_mods.tcl
  CREDITS
  Compile.mk
  FAQ
  GNUGPL
  HELP
  INSTALL
  Makefile.in
  README
  README.macosx
  TODO
  abook.tcl
  alarm.tcl
  amsn
  amsn-remote
  amsn-remote-CLI
  amsn.debianmenu
  amsn.desktop
  amsn.spec
  amsn_des.tcl
  amsncore.tcl
  assistant.tcl
  audio.tcl
  automsg.tcl
  autoupdate.tcl
  balloon.tcl
  bugs.tcl
  chatwindow.tcl
  clgui.tcl
  config.tcl
  configure
  configure.ac
  console.tcl
  contactlist.tcl
  ctthemes.tcl
  debug.tcl
  dock.tcl
  groups.tcl
  gui.tcl
  guicontactlist.tcl
  hotmail.tcl
  hotmlog.htm
  lang.tcl
  langlist
  login_screen.tcl
  loging.tcl
  migmd5.tcl
  msncam.tcl
  msnp2p.tcl
  mutex.tcl
  notes.tcl
  picture.tcl
  plugins.tcl
  pluginslog.tcl
  preferences.tcl
  progressbar.tcl
  protocol.tcl
  proxy.tcl
  remote.help
  remote.tcl
  searchdialog.tcl
  skins.tcl
  smileys.tcl
  soap.tcl
  socks.tcl
  spaces.tcl
  sxml.tcl
  trayicon.tcl