#!/usr/bin/env python

#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.

# TODO: 
# - Allow size for status/protocol icons and resize them
# - Try to get faster/better shadows using Gradients
# - Allow options.conf to import settings from another theme
#   - if the imported theme doesn't exist, use default's options.conf

import dbus

import screenlets
from screenlets.options import StringOption, BoolOption
from screenlets.options import create_option_from_node
from screenlets import DefaultMenuItem
import pango
import gobject
import gtk
import cairo
import operator
import rsvg
import sys
import os
import time
import glob
import re
import xml.dom.minidom

# Added gconf stuff to get Default Gnome Fault
import gconf

from ConfigParser import SafeConfigParser

from ThemeParser import ThemeParser,RowItems,Gradient,Alignment

#import ChatWindow

PI = 3.141596

class UIObject:
	def __init__(self, obj, x, y, w, h):
		self.x = x
		self.y = y
		self.width = w
		self.height = h
		self.obj = obj
		self.__prop = {}
	
	def setProperty(self, key, val):
		self.__prop[key]=val
	
	def getProperty(self, key):
		return self.__prop[key]

	def delProperty(self, key):
		del self.__prop[key]


class Buddy:
	def __init__ (self, id, alias, name=None, status=None, event_status=None,
			status_text=None, protocol=None, iconPixbuf=None, 
			iconSurface=None, group=None):
		self.id = id
		self.alias = alias
		self.name = name
		self.group = group
		self.status = status
		self.event_status = event_status
		self.status_text = status_text
		self.protocol = protocol
		self.iconPixbuf = iconPixbuf
		self.iconSurface = iconSurface 
		# cairo surface is used to make non argb icon images transparent


class Group:
	def __init__ (self, id, name, buddies=None, online_buddies=None):
		self.id = id
		self.name = name
		if buddies: self.buddies = buddies
		else: self.buddies = {}
		if online_buddies: self.online_buddies = online_buddies
		else: self.online_buddies = {}

#use gettext for translation
import gettext

_ = screenlets.utils.get_translator(__file__)

def tdoc(obj):
	obj.__doc__ = _(obj.__doc__)
	return obj

@tdoc
class PidginScreenlet (screenlets.Screenlet):
	"""A Screenlet to show the Pidgin contactlist"""
	__name__ = 'PidginScreenlet'
	__version__ = '0.3.5'
	__author__ = 'meek'
	__desc__ = __doc__

	purple_path = "/im/pidgin/purple/PurpleObject"
	purple_service = "im.pidgin.purple.PurpleService"
	purple_interface = "im.pidgin.purple.PurpleInterface"
	purple_xmlfile = os.environ["HOME"]+"/.purple/blist.xml"

	# Initialize Screenlet Options
	# These should be the stored options for the screenlet
	show_groups = True
	show_offline = False
	show_toponly = False
	group_by_groups = True
	group_by_account = False
	group_by_online_offline = True
	sort_groups_by_name = False
	sort_groups_by_num_users = True
	sort_buddies_by_name = False
	sort_buddies_by_status = True
	autostart_pidgin = False

	p_layout = None

	# Load the purple-config file
	def loadXML(self):
		try:
			self.xmldoc = xml.dom.minidom.parse(self.purple_xmlfile)
		except:
			self.xmldoc = False

	# State Variables 
	def initStateVariables(self): 
		self.bus = None

		# Dict of Buddies indexed by buddyId
		self.buddies = {}
		# Dict of Groups indexed by groupId
		self.groups = {}

		# UI Objects in the screenlet
		self.uiObjects = {}

		# Some Display specific Props
		self.buddyConvoIds = {} # Conversation Ids of buddies
		self.collapsedGroups = {} # Groups that are currently collapsed

		self.typing_timeouts = {}
		self.signonoff_timeouts = {}

		self.recentTypingBuddies = {}
		self.recentSignedOnBuddies = {}
		self.recentSignedOffBuddies = {}

		self.accountNotifications = []

		# Seconds to wait after a buddy signons/changes display pic/changes status
		# - Reason is to defer the large number of signals that we get when Pidgin starts
		self.refresh_timeout = None
		self.refresh_timeout_value = 1

		# Backing pixbuf used to minimize list updates during on_draw
		self.__buffer = None

		# Pidgin/Purple DBus Client
		self.pidgin = None

		# Hovered/Selected buddy ids
		self.hoverObjId = None
		self.selectedObjId = None


	def initThemeVariables (self):
		# BuddyList Layout Options
		# - This should be populated by the Theme
		# - Look at the LayoutOptions Class above for documentation
		self.layout = None
		self.default_icons = {}
		self.icons = {}


	# Some helpful Gconf functions (unused right now)
	def getDesktopFont(self):
		font = self.getGConfValue("/apps/nautilus/preferences/desktop_font")
		if font: return font
		return "Sans 9"
	def getAppFont(self):
		font = self.getGConfValue("/desktop/gnome/interface/font_name")
		if font: return font
		return "Sans 9"
	def getGConfValue(self, key):
		client = gconf.client_get_default()
		if client:
			return client.get_string(key)
		else:
			return None

	# Calculate the screenlet height
	# by looking at the number of buddies, number of groups, number of notifications
	def setScreenletHeight(self):
		ht = 0
		l = self.layout
		sb = l.group_info.border_width/2.0
		sp = l.group.top_spacing + l.group.bottom_spacing
		for groupId in self.groups:
			group = self.groups[groupId]
			if self.show_groups:
				ht += sp
			ht += self.get_group_height(l.group, group)
			if sp: ht += sb

		for notifications in self.accountNotifications:
			ht += l.__dict__["notification"].height
			if sp: ht += sb

		if not ht: ht = 20
		elif not sp or not self.show_groups: ht += sb
		l.window.height = ht

	def getGroupsAndBuddies(self):
		try:
			if self.pidgin:
				self.buddies.clear()
				self.groups.clear()
				# We get Buddy Info here
				node = self.pidgin.PurpleBlistGetRoot()
				while node != 0:
					if self.pidgin.PurpleBlistNodeIsBuddy(node):
						buddyId = node
						contactId = self.pidgin.PurpleBuddyGetContact(buddyId)
						topBuddyId = self.pidgin.PurpleContactGetPriorityBuddy(contactId)
						actId = self.pidgin.PurpleBuddyGetAccount(buddyId)
						if self.pidgin.PurpleAccountIsConnected(actId):
							isOnline = self.pidgin.PurpleBuddyIsOnline(buddyId)
							if (isOnline or self.show_offline) and (buddyId == topBuddyId or not self.show_toponly):
								self.addBuddy(buddyId)

					node = self.pidgin.PurpleBlistNodeNext(node, 0)
		except:
			raise
			None

	def collapseGroup(self, groupId, groupName):
		if groupId not in self.collapsedGroups:
			if self.xmldoc:
				grouplist = self.xmldoc.getElementsByTagName('group')

				for i in range (0, len(grouplist)):
					group = grouplist.item(i)
					xmlGroupName = group.getAttribute("name")
					settinglist = group.getElementsByTagName('setting')
					for c in range (0, len(settinglist)):
						setting = settinglist.item(c)
						if setting.getAttribute("name") == "collapsed" and setting.firstChild.nodeValue == "1":
							if xmlGroupName == groupName:
								self.collapsedGroups[groupId] = 1 # collapse
						


	def addBuddy(self, buddyId):
		(groupName, groupId) = self.getBuddyGroupNameAndId(buddyId)
		if groupName and groupId:
			self.addBuddyAndGroup(buddyId, groupId, groupName)
			# check if group is collapsed
			self.collapseGroup(groupId, groupName)
			return True
	 	return False

	def removeBuddy(self, buddyId):
		if buddyId in self.buddies:
			group = self.buddies[buddyId].group
			if buddyId in group.online_buddies:
				del group.online_buddies[buddyId]
			if self.show_offline:
				self.buddies[buddyId].status = "offline"
			else:
				del group.buddies[buddyId]
				del self.buddies[buddyId]
				if len(group.buddies) == 0:
					del self.groups[group.id]
			return True
	 	return False

	def addBuddyAndGroup(self, buddyId, groupId, groupName):
		group_obj = None
		if groupId in self.groups:
			group_obj = self.groups[groupId]
		else:
			group_obj = Group(groupId, groupName)

		buddy_obj = Buddy(buddyId, self.pidgin.PurpleBuddyGetAlias(buddyId))
		self.buddies[buddyId] = buddy_obj
		self.groups[groupId] = group_obj

		buddy_obj.name = self.pidgin.PurpleBuddyGetName(buddyId)
		buddy_obj.group = group_obj
		(buddy_obj.status, buddy_obj.status_text) = self.getBuddyStatus(buddyId)
		buddy_obj.protocol = self.pidgin.PurpleAccountGetProtocolName(
				self.pidgin.PurpleBuddyGetAccount(buddyId))
		self.setBuddyIconImage(buddy_obj, self.layout.buddy_icon.size)

		group_obj.buddies[buddyId] = buddy_obj
		if buddy_obj.status != "offline":
			group_obj.online_buddies[buddyId] = buddy_obj


	def getBuddyGroupNameAndId(self, buddyId):
		groupName = None
		groupId = None
		isOnline = self.pidgin.PurpleBuddyIsOnline(buddyId)
		if self.show_groups and self.group_by_groups:
			groupId = self.pidgin.PurpleBuddyGetGroup(buddyId)
			groupName = self.pidgin.PurpleGroupGetName(groupId)
		elif self.show_groups and self.group_by_account:
			groupId = self.pidgin.PurpleBuddyGetAccount(buddyId)
			actAlias = self.pidgin.PurpleAccountGetAlias(groupId)
			groupName = self.pidgin.PurpleAccountGetProtocolName(groupId)
			if actAlias:
				groupName = actAlias+" ("+groupName+")"
		elif self.show_groups and self.group_by_online_offline:
			if isOnline:
				groupId = -1
				groupName = _("Online")
			else:
				groupId = -2
				groupName = _("Offline")
		else:
			groupId = -3
			groupName = _("Contacts")
		
		return (groupName, groupId)


	def getBuddyStatus(self, buddyId):
		presence = self.pidgin.PurpleBuddyGetPresence(buddyId)
		isOnline = self.pidgin.PurplePresenceIsOnline(presence)
		isAvail = self.pidgin.PurplePresenceIsAvailable(presence)
		isIdle = self.pidgin.PurplePresenceIsIdle(presence)
		status = self.pidgin.PurplePresenceGetActiveStatus(presence)
		status_text =  self.pidgin.PurpleStatusGetName(status)
		custom_status_text =  self.pidgin.PurpleStatusGetAttrString(status, "message")

		if not isOnline: status = "offline"
		elif isIdle: status = "idle"
		elif status_text==_("Do Not Disturb"): status = "busy"
		elif status_text==_("Busy"): status = "busy"
		elif not isAvail: status = "away"
		elif isOnline and isAvail: status = "online"
		if isIdle: status_text += "/Idle"
		if status_text==_("Available"):
			status_text = None

		if custom_status_text:
			status_text = re.sub(r'<[^>]*?>', '', custom_status_text)
		return (status, status_text)


	def scaleGdkPixbuf(self,pixbuf,size):
		newpixbuf = None
		try:
			icw = pixbuf.get_width()
			ich = pixbuf.get_height()
			aspect = float(icw)/ich
			if aspect < 1: newpixbuf = pixbuf.scale_simple(int(aspect*size),size,1)
			elif aspect > 1: newpixbuf = pixbuf.scale_simple(size,int(size/aspect),1)
			else: newpixbuf = pixbuf.scale_simple(size,size,1)
		except:
			None
		return newpixbuf

	def setBuddyIconImage(self, buddy, size):
		iconPixbuf = None
		buddyId = buddy.id
		ritems = self.layout.layout.row_items
		iconId = self.pidgin.PurpleBuddyGetIcon(buddyId)
		if iconId:
			iconPath = self.pidgin.PurpleBuddyIconGetFullPath(iconId)
			if iconPath and os.path.exists(iconPath) and not iconPath.endswith('.icon'): 
				#iconPixbuf = gtk.gdk.pixbuf_new_from_file_at_scale(iconPath, size, size, 1)
				try:
					iconPixbuf = gtk.gdk.pixbuf_new_from_file(iconPath)
				except GError: 
					iconPixbuf = self.icons["buddy"]["default"]
				iconPixbuf = self.scaleGdkPixbuf(iconPixbuf, size)
				if not iconPixbuf.get_has_alpha():
					format = cairo.FORMAT_ARGB32
					iconSurface = cairo.ImageSurface(format,size, size)
					context = cairo.Context(iconSurface)
					gdkcontext = gtk.gdk.CairoContext(context)
					gdkcontext.set_source_pixbuf(iconPixbuf, 0, 0)
					gdkcontext.paint()
					buddy.iconSurface = iconSurface
			elif "default" in self.icons["buddy"]:
				iconPixbuf = self.icons["buddy"]["default"]
		elif buddy.protocol and RowItems.BUDDY_PROTOCOL_ICON in ritems:
			proto = buddy.protocol.lower()
			if proto in self.icons["buddy"]:
				iconPixbuf = self.icons["buddy"][proto]
		elif "default" in self.icons["buddy"]:
			iconPixbuf = self.icons["buddy"]["default"]
		buddy.iconPixbuf = iconPixbuf


	def loadIconsFromDir(self, dir): 
		icons = {}
		dirlst = glob.glob(dir+"/*")
		plen = len(dir) + 1
		for file in dirlst:
			fname = file[plen:]
			if fname.endswith('.png'):
				fname = fname[:len(fname)-4]
				icons[fname] = gtk.gdk.pixbuf_new_from_file(file)
		return icons


	def loadDefaultIcons(self):
		defaultThemeDir = sys.path[0]+"/themes/default"
		self.default_icons['group_collapse'] = self.loadIconsFromDir(defaultThemeDir+"/group_collapse")
		self.default_icons['status'] = self.loadIconsFromDir(defaultThemeDir+"/status")
		self.default_icons['protocol'] = self.loadIconsFromDir(defaultThemeDir+"/protocol")
		self.default_icons['buddy'] = self.loadIconsFromDir(defaultThemeDir+"/buddy")


	def loadIconsForTheme(self, theme):
		print "Loading Icons (if any) for Theme : "+theme

		themeDir = sys.path[0]+"/themes/"+theme
		self.icons = {}
		for icosection in self.default_icons:
			for icofile in self.default_icons[icosection]:
				icopixbuf = self.default_icons[icosection][icofile]
				themepath = themeDir+"/"+icosection+"/"+icofile+".png"
				if os.path.exists(themepath):
					icopixbuf = gtk.gdk.pixbuf_new_from_file(themepath)
				icostyle = self.layout.__dict__[icosection+"_icon"]
				if "size" in icostyle.__dict__:
					size = icostyle.size
					if size:
						icopixbuf = self.scaleGdkPixbuf(icopixbuf, size)
				if icosection not in self.icons:
					self.icons[icosection] = {}
				self.icons[icosection][icofile] = icopixbuf
	

	def bindPurpleSignal(self, function, signal):
		self.bus.add_signal_receiver(function,dbus_interface=self.purple_interface,signal_name=signal)
	
	def removePurpleSignal(self, function, signal):
		self.bus.remove_signal_receiver(function,dbus_interface=self.purple_interface,signal_name=signal)

	def bindSignals(self):
		try:
			self.bindPurpleSignal(self.onSignOn, _("SignedOn"))
			self.bindPurpleSignal(self.onSignOff, _("SignedOff"))
			self.bindPurpleSignal(self.onBuddySignedOn, _("BuddySignedOn"))
			self.bindPurpleSignal(self.onBuddySignedOff, _("BuddySignedOff"))
			self.bindPurpleSignal(self.onBuddyStatusChanged, _("BuddyStatusChanged"))
			self.bindPurpleSignal(self.onBuddyIconChanged, _("BuddyIconChanged"))
			self.bindPurpleSignal(self.onBuddyTyping, _("BuddyTyping"))
			#self.bindPurpleSignal(self.onMessageReceived, _("ReceivingImMsg"))
			self.bindPurpleSignal(self.onMessageReceived, _("DisplayedImMsg"))
			# FIXME: Need this
			self.bindPurpleSignal(self.onBuddyAliasChanged, _("BlistNodeAliased"))

			self.bindPurpleSignal(self.onUpdateConvo, _("ConversationUpdated"))
			self.bindPurpleSignal(self.onDeleteConvo, _("DeletingConversation"))
			self.bindPurpleSignal(self.onBlistNodeExtMenu, _("BlistNodeExtendedMenu"))
			self.bindPurpleSignal(self.onConvoExtMenu, _("ConversationExtendedMenu"))

			# account signals
			self.bindPurpleSignal(self.onAccountStatusChanged, _("AccountStatusChanged"))
			self.bindPurpleSignal(self.onAccountAuthorizationRequested, _("AccountAuthorizationRequested"))
			self.bindPurpleSignal(self.onAccountConnecting, _("AccountConnecting"))
		except:
			raise
			None

	# Signals 

	# Delayed updates on signon (i.e. accumulating all updates)
	def onSignOn(self, arg1):
		#print "Signed On"
		#Remove the BuddySignon signal while this happens
		#self.removePurpleSignal(self.onBuddySignedOn, "BuddySignedOn")
		self.delayed(self.refreshBuddyList)
		#time.sleep(self.refresh_timeout_value)
		#self.bindPurpleSignal(self.onBuddySignedOn, "BuddySignedOn")

	def onSignOff(self, arg1):
		#print "Signed Off"
		self.refreshBuddyList()

	def removeRecentSignOnState(self, bId):
		#print "Removing SignOn State"
		del self.recentSignedOnBuddies[bId]
		if bId in self.signonoff_timeouts:
			gobject.source_remove(self.signonoff_timeouts[bId])
			del self.signonoff_timeouts[bId]
		self.redraw_canvas()

	def removeRecentSignOffState(self, bId):
		#print "Removing SignOff State"
		del self.recentSignedOffBuddies[bId]
		if bId in self.signonoff_timeouts:
			gobject.source_remove(self.signonoff_timeouts[bId])
			del self.signonoff_timeouts[bId]
		if self.removeBuddy(bId):
			self.resizeAndRedrawBuddyList()

	def removeAccountNotification(self):
		self.accountNotifications.pop(0)
		self.resizeAndRedrawBuddyList()

	def onBuddySignedOn(self, bId):
		#print "Buddy Signed On"
		if self.layout.buddy_signon.enabled:
			if bId in self.recentSignedOffBuddies:
				del self.recentSignedOffBuddies[bId]
			if bId in self.signonoff_timeouts:
				gobject.source_remove(self.signonoff_timeouts[bId])
				del self.signonoff_timeouts[bId]
			self.recentSignedOnBuddies[bId] = 1
			self.signonoff_timeouts[bId] = gobject.timeout_add(
					self.layout.buddy_signon.notify_time,
					self.removeRecentSignOnState, bId)

		self.refreshBuddyList()


	def onBuddySignedOff(self, bId):
		#print "Buddy Signed Off"
		if self.layout.buddy_signoff.enabled:
			if bId in self.recentSignedOnBuddies:
				del self.recentSignedOnBuddies[bId]
			if bId in self.signonoff_timeouts:
				gobject.source_remove(self.signonoff_timeouts[bId])
				del self.signonoff_timeouts[bId]
			self.recentSignedOffBuddies[bId] = 1
			self.signonoff_timeouts[bId] = gobject.timeout_add(
					self.layout.buddy_signoff.notify_time,
					self.removeRecentSignOffState, bId)
			self.redraw_canvas()
		else:
			self.refreshBuddyList
		
	def removeRecentTypingState(self, bId):
		#print "Removing SignOn State"
		if bId in self.recentTypingBuddies:
			self.buddies[bId].event_status = None
			del self.recentTypingBuddies[bId]
			if bId in self.typing_timeouts:
				gobject.source_remove(self.typing_timeouts[bId])
				del self.typing_timeouts[bId]
			self.redraw_canvas()

	def onBuddyTyping(self, actId, name):
		#print "Buddy Signed On"
		if self.layout.buddy_typing.enabled:
			bId = None
			for tmpid in self.buddies:
				if self.buddies[tmpid].name == name:
					bId = tmpid

			if bId:
				if bId in self.typing_timeouts:
					gobject.source_remove(self.typing_timeouts[bId])
					del self.typing_timeouts[bId]
				if bId not in self.recentTypingBuddies:
					self.recentTypingBuddies[bId] = 1
				self.typing_timeouts[bId] = gobject.timeout_add(
					self.layout.buddy_typing.notify_time,
					self.removeRecentTypingState, bId)

				self.buddies[bId].event_status="typing"
				self.redraw_canvas()

	def onMessageReceived(self, actid, name, msg, arg4, arg5):
		bId = None
		for tmpid in self.buddies:
			if self.buddies[tmpid].name == name:
				bId = tmpid
		if bId:
			if bId in self.recentTypingBuddies:
				self.removeRecentTypingState(bId)
			if self.layout.buddy_content.enabled:
				if self.buddies[bId].event_status != "content" and arg5==2:
					self.selectedObjId = None
					self.buddies[bId].event_status = "content"
					self.redrawBuddyList()

	def onBuddyStatusChanged(self, bId, oldstatus, newstatus):
		if bId in self.buddies:
			(self.buddies[bId].status, self.buddies[bId].status_text) = self.getBuddyStatus(bId)
			self.refreshBuddyList()

	def onBuddyAliasChanged(self, bId, oldalias):
		if bId in self.buddies:
			self.buddies[bId].alias = self.pidgin.PurpleBuddyGetAlias(bId)
			self.redrawBuddyList()

	def onBuddyIconChanged(self, bId):
		if bId in self.buddies:
			buddy = self.buddies[bId]
			self.setBuddyIconImage(buddy, self.layout.buddy_icon.size)
			ritems = self.layout.layout.row_items
			if buddy.iconPixbuf or buddy.iconSurface:
				if (RowItems.BUDDY_ICON in ritems or
						RowItems.BUDDY_PROTOCOL_ICON in ritems):
					self.redrawBuddyList()

	def onBlistNodeExtMenu(self, bId, arg2):
		self.selectedObjId = bId

	def onConvoExtMenu(self, convoId, arg2):
		self.buddyConvoIds[convoId] = self.selectedObjId

	def onUpdateConvo(self, convoId, arg2):
		if convoId in self.buddyConvoIds:
			#print self.pidgin.PurpleConversationGetName(convoId)
			bId = self.buddyConvoIds[convoId]
			self.selectedObjId = bId
			try:
				if self.buddies[bId].event_status == "content":
					self.buddies[bId].event_status = None
					self.redrawBuddyList()
				else:
					self.redraw_canvas()
			except KeyError:
				print "KeyError exception"
			except:
				print "something went wrong"
	
	def onDeleteConvo(self, convoId):
		self.selectedObjId = None
		self.redraw_canvas()

	def onAccountStatusChanged(self, account, oldStatus, newStatus):
		self.accountNotifications.append("status changed...")
		gobject.timeout_add( 5000, self.removeAccountNotification)
		self.resizeAndRedrawBuddyList()

	def onAccountAuthorizationRequested(self, account, user):
		self.accountNotifications.append("auth requested!")
		gobject.timeout_add( 15000, self.removeAccountNotification)
		self.resizeAndRedrawBuddyList()

	def onAccountConnecting(self, account):
		self.accountNotifications.append("account connecting...")
		gobject.timeout_add( 5000, self.removeAccountNotification)
		self.resizeAndRedrawBuddyList()

	def onDBusServiceChange(self, service, arg2, arg3):
		#print service
		if service == self.purple_service:
			if arg2 == "":
				print "Pidgin Started ! :)"
				self.pidgin = self.getPidginDbus()
				self.delayed(self.refreshBuddyList)
			if arg3 == "":
				print "Pidgin closed :("
				self.buddyConvoIds.clear()
				self.recentSignedOnBuddies.clear()
				self.recentSignedOffBuddies.clear()
				self.recentTypingBuddies.clear()
				self.pidgin = None
				self.resizeAndRedrawBuddyList()


	def menuitem_callback(self, widget, id):
		screenlets.Screenlet.menuitem_callback(self, widget, id)
		if self.setOptions(id):
			self.refreshBuddyList()
	
	def setOptions (self, id):
		if id=="toggle_offline":
			self.show_offline = not self.show_offline
		elif id=="toggle_toponly":
			self.show_toponly = not self.show_toponly
		elif id=="toggle_groups":
			self.show_groups = not self.show_groups
		elif id=="group_by_groups":
			self.group_by_groups = True
			self.group_by_account = False
			self.group_by_online_offline = False
		elif id=="group_by_account":
			self.group_by_groups = False
			self.group_by_account = True
			self.group_by_online_offline = False
		elif id=="group_by_online_offline":
			self.group_by_groups = False
			self.group_by_account = False
			self.group_by_online_offline = True
		elif id=="sort_groups_by_name":
			self.sort_groups_by_name = True
			self.sort_groups_by_num_users = False
		elif id=="sort_groups_by_num_users":
			self.sort_groups_by_name = False
			self.sort_groups_by_num_users = True
		elif id=="sort_buddies_by_name":
			self.sort_buddies_by_name = True
			self.sort_buddies_by_status = False
		elif id=="sort_buddies_by_status":
			self.sort_buddies_by_name = False
			self.sort_buddies_by_status = True
		else:
			return False
		return True


	def getPidginDbus(self):
		try:
			# Associate Pidgin's D-Bus interface with Python objects
			obj = self.bus.get_object(
			    self.purple_service, self.purple_path)
			self.pidgin = dbus.Interface(obj, self.purple_interface)
		except:
			print 'The Pidgin Session is not present'
			self.pidgin = None

		if self.pidgin:
			self.bindSignals()
			# Update after some timeout or it doesn't work properly
			print "Found Pidgin.."

		return self.pidgin


	def init_buffer (self):
		# Do not initialize a new buffer if the size required is smaller that current
		# buffer size
		self.__buffer = None
		self.__buffer = gtk.gdk.Pixmap(self.window.window, 
			int(self.width * self.scale), int(self.height * self.scale), -1)
		

	# quad: 0 - top_left, 1 - top_right, 2 - bottom_left, 3 - bottom_right
	def draw_quadrant_shadow(self, ctx, x, y, from_r, to_r, quad, col):
		gradient = cairo.RadialGradient(x,y,from_r,x,y,to_r)
		gradient.add_color_stop_rgba(0,col[0],col[1],col[2],col[3])
		gradient.add_color_stop_rgba(1,col[0],col[1],col[2],0)
		ctx.set_source(gradient)
		ctx.new_sub_path()
		if quad==0: ctx.arc(x,y,to_r, -PI, -PI/2)
		elif quad==1: ctx.arc(x,y,to_r, -PI/2, 0)
		elif quad==2: ctx.arc(x,y,to_r, PI/2, PI)
		elif quad==3: ctx.arc(x,y,to_r, 0, PI/2)
		ctx.line_to(x,y)
		ctx.close_path()
		ctx.fill()

	# side: 0 - left, 1 - right, 2 - top, 3 - bottom
	def draw_side_shadow(self, ctx, x, y, w, h, side, col):
		gradient = None
		if side==0:
			gradient = cairo.LinearGradient(x+w,y,x,y)
		elif side==1:
			gradient = cairo.LinearGradient(x,y,x+w,y)
		elif side==2:
			gradient = cairo.LinearGradient(x,y+h,x,y)
		elif side==3:
			gradient = cairo.LinearGradient(x,y,x,y+h)
		if gradient:
			gradient.add_color_stop_rgba(0,col[0],col[1],col[2],col[3])
			gradient.add_color_stop_rgba(1,col[0],col[1],col[2],0)
			ctx.set_source(gradient)
		ctx.rectangle(x,y,w,h)
		ctx.fill()

	def draw_shadow(self, ctx, x, y, w, h, shadow_size, col):
		s = shadow_size
		#r = self.layout.window.radius
		r = s
		rr = r+s
		h = h-r
		if h < 2*r: h = 2*r

		# TODO: Offsets [Will need to change all places doing 
		#       x+=shadow_size/2 or such to use the offsets then
		ctx.save()
		ctx.translate(x,y)

		# Top Left
		self.draw_quadrant_shadow(ctx, rr, rr, 0, rr, 0, col)
		# Left
		self.draw_side_shadow(ctx, 0, rr, r+s, h-2*r, 0, col)
		# Bottom Left
		self.draw_quadrant_shadow(ctx, rr, h-r+s, 0, rr, 2, col)
		# Bottom
		self.draw_side_shadow(ctx, rr, h-r+s, w-2*r, s+r, 3, col)
		# Bottom Right
		self.draw_quadrant_shadow(ctx, w-r+s, h-r+s, 0, rr, 3, col)
		# Right
		self.draw_side_shadow(ctx, w-r+s, rr, s+r, h-2*r, 1, col)
		# Top Right
		self.draw_quadrant_shadow(ctx, w-r+s, rr, 0, rr, 1, col)
		# Top
		self.draw_side_shadow(ctx, rr, 0, w-2*r, s+r, 2, col)

		ctx.restore()


	# r1: top-left radius, r2: top-right radius, 
	# r3: bottom-left radius, r4: bottom-right radius
	def round_rectangle(self, ctx, x, y, width, height, r1, r2, r3, r4):
		ctx.move_to(x+r1, y)
		ctx.line_to(x+width-r2,y)
		# top right
		if r2 > 0:
			ctx.arc(x+width-r2, y+r2, r2, -PI/2.0, 0)
		ctx.line_to(x+width,y+height-r4)
		# bottom right
		if r4 > 0:
			ctx.arc(x+width-r4, y+height-r4, r4, 0, PI/2.0)
		ctx.line_to(x+r3, y+height)
		# bottom left
		if r3 > 0:
			ctx.arc(x+r3, y+height-r3, r3, PI/2.0, PI)
		ctx.line_to(x,y+r1)
		# top left
		if r1 > 0:
			ctx.arc(x+r1, y+r1, r1, PI, PI*1.5)

	# curvestyle = 0 to 16 
	# - Binary : 0000 - 1111, 
	#   1000 = 8 : Top-left
	#   0100 = 4 : Top-right
	#   0010 = 2 : Bottom-left
	#   0001 = 1 : Bottom-right
	def draw_list_item_background (self, ctx, x, y, prop, width, curvestyle):
		ctx.save()
		ctx.translate(x, y)
		r1=r2=r3=r4 = 0
		r = self.layout.window.radius
		if curvestyle&8: r1=r
		if curvestyle&4: r2=r
		if curvestyle&2: r3=r
		if curvestyle&1: r4=r
		if curvestyle > 0:
			self.round_rectangle(ctx,0,0,prop.width,prop.height,r1,r2,r3,r4)
		else:
			ctx.rectangle(0, 0, prop.width, prop.height)

		# Clear the background first
		ctx.set_operator(cairo.OPERATOR_CLEAR)
		ctx.set_source_rgba(1,1,1,1)
		ctx.fill_preserve()

		ctx.set_operator(cairo.OPERATOR_OVER)

		if prop.use_gradient:
			gradient = Gradient.createGradient(prop.gradient_type, prop.background, 
					prop.background_gradient, prop.width, prop.height)
			if gradient:
				ctx.set_source(gradient)
		else:
			col = prop.background
			ctx.set_source_rgba(col[0],col[1],col[2],col[3])

		ctx.fill()
		ctx.restore()


	def draw_list_item_text (self, ctx, x, y, prop, width, height, text):
		w = width

		if prop.capital_letters:
			text = text.upper()

		ctx.save()
		ctx.translate(x,y)

		# memory leak fix layout
		if self.p_layout == None :
			self.p_layout = ctx.create_layout()
		else:
			ctx.update_layout(self.p_layout)

		p_fdesc = pango.FontDescription(prop.font)
		self.p_layout.set_font_description(p_fdesc)
		self.p_layout.set_text(text)
		#self.p_layout.set_width(prop.width)

		(text_width,text_height) = self.p_layout.get_pixel_size()
		ypad = (height-text_height)/2

		# truncate long text
		if text_width > (w - 5):
			(ind,tmp) = self.p_layout.xy_to_index(w*pango.SCALE, ypad*pango.SCALE)
			text = text[0:ind-4]+"..."
			self.p_layout.set_text(text)
			(text_width,text_height) = self.p_layout.get_pixel_size()
			ypad = (height-text_height)/2

		# FIXME : lpadding, rpadding shouldn't be checked here !?
		if prop.text_alignment == Alignment.LEFT:
			ctx.translate(prop.lpadding,ypad)
		elif prop.text_alignment == Alignment.MIDDLE:
			ctx.translate((w - text_width)/2,ypad)
		elif prop.text_alignment == Alignment.RIGHT:
			ctx.translate(w - text_width - prop.rpadding,ypad)

		col = prop.color
		if prop.text_shadow:
			# calc shadow color: white for dark font color, black for bright font color
			shadow_color = 1
			if col[0] or col[1] or col[2] > 0.5:
				shadow_color = 0
			shadow_alpha = col[3] / 2

			shadow_style = 1 # FIXME : 1 for normal shadow, 2 for shadow around text
			# text-shadow
			if shadow_style == 1:
				ctx.translate(0,1) # bottom
				ctx.set_source_rgba(shadow_color, shadow_color, shadow_color, shadow_alpha)
				ctx.show_layout(self.p_layout)
				ctx.translate(0,-1) # text pos
			else:
				ctx.translate(0,1) # bottom
				ctx.set_source_rgba(shadow_color, shadow_color, shadow_color, shadow_alpha)
				ctx.show_layout(self.p_layout)
				ctx.translate(0,-2) # top
				ctx.set_source_rgba(shadow_color, shadow_color, shadow_color, shadow_alpha)
				ctx.show_layout(self.p_layout)
				ctx.translate(-1,1) # right
				ctx.set_source_rgba(shadow_color, shadow_color, shadow_color, shadow_alpha)
				ctx.show_layout(self.p_layout)
				ctx.translate(2,0) # left
				ctx.set_source_rgba(shadow_color, shadow_color, shadow_color, shadow_alpha)
				ctx.show_layout(self.p_layout)
				ctx.translate(-1,0) # text pos

		# text
		ctx.set_source_rgba(col[0],col[1],col[2],col[3])
		ctx.show_layout(self.p_layout)
		ctx.restore()
		return (text_width,text_height)


	def on_draw (self, ctx):
		# Paint the bufferred contact list
		if self.__buffer: 
			ctx.set_operator(cairo.OPERATOR_OVER)
			ctx.set_source_pixmap(self.__buffer, 0, 0)
			ctx.paint()

		try:
			ctx.scale(self.scale, self.scale)
			ctx.set_operator(cairo.OPERATOR_OVER)
			for bId in self.recentSignedOnBuddies:
				if bId in self.uiObjects:
					self.draw_buddy_row (ctx, self.uiObjects[bId], 
							self.layout.buddy_signon, True)

			for bId in self.recentSignedOffBuddies:
				if bId in self.uiObjects:
					self.draw_buddy_row (ctx, self.uiObjects[bId], 
							self.layout.buddy_signoff, True)

			if self.hoverObjId and self.hoverObjId in self.uiObjects:
				if self.hoverObjId in self.buddies:
					self.draw_buddy_row (ctx, self.uiObjects[self.hoverObjId], 
						self.layout.buddy_hover, True)

			if self.selectedObjId and self.selectedObjId in self.uiObjects: 
				if self.selectedObjId in self.buddies:
					bId = self.selectedObjId
					self.draw_buddy_row (ctx, self.uiObjects[self.selectedObjId], 
						self.layout.buddy_selected, True)

			for bId in self.recentTypingBuddies:
				if bId in self.uiObjects:
					self.draw_buddy_row (ctx, self.uiObjects[bId], 
							self.layout.buddy_typing, True)


		except:
			raise
			None


	# Get Extents (width, height) of status icon
	def get_status_icon_extents (self, buddy):
		l = self.layout.layout
		status = buddy.event_status or buddy.status
		if RowItems.STATUS_ICON in l.row_items and status:
			sf = self.icons["status"][status]
			return (sf.get_width(), sf.get_height())
		return (0,0)

	# Get Extents (width, height) of protocol icon
	def get_protocol_icon_extents (self, buddy):
		l = self.layout.layout
		if RowItems.PROTOCOL_ICON in l.row_items and buddy.protocol:
			pf = self.icons["protocol"][buddy.protocol.lower()]
			return (pf.get_width(), pf.get_height())
		return (0,0)


	# Get Extents (width, height) of buddy icon
	# - Note that the buddy icon is already resized 
	#   However due to aspect ratio w, and h below might not be the same 
	#   as self.layout.buddy_icon.size

	def get_buddy_icon_extents (self, buddy):
		l = self.layout.layout
		if buddy.iconPixbuf:
			if (RowItems.BUDDY_ICON in l.row_items or 
					RowItems.BUDDY_PROTOCOL_ICON in l.row_items):
				w = buddy.iconPixbuf.get_width()
				h = buddy.iconPixbuf.get_height()
				return (w,h)
		return (0,0)


	# Draw the Status Icon 
	def draw_status_icon (self, ctx, x, y, buddy):
		ctx.save()
		ctx.translate(x, y)
		status = buddy.event_status or buddy.status
		sf = self.icons["status"][status]
		ctx.set_source_pixbuf(sf, 0, 0)
		ctx.paint()
		#self.theme.render(ctx, 'status_'+status)
		ctx.restore()


	# Draw the Status Icon 
	def draw_protocol_icon (self, ctx, x, y, transparency, buddy):
		ctx.save()
		ctx.translate(x, y)
		#ctx.set_source_surface(self.theme['protocol_'+buddy.protocol.lower()+'.png'])
		pf = self.icons["protocol"][buddy.protocol.lower()]
		ctx.set_source_pixbuf(pf, 0, 0)
		ctx.paint_with_alpha(transparency)
		ctx.restore()


	# Draw Buddy Icon (Display Picture)
	def draw_buddy_icon (self, ctx, x, y, w, h, transparency, rounding_radius, buddy):
		if buddy.iconPixbuf:
			ctx.save()
			ctx.translate(x, y)
			r = rounding_radius
			if r: self.round_rectangle(ctx,0,0,w,h,r,r,r,r)
			else: ctx.rectangle(0,0,w,h)
			ctx.clip()
			if buddy.iconSurface:
				ctx.set_source_surface(buddy.iconSurface, 0, 0)
			else:
				ctx.set_source_pixbuf(buddy.iconPixbuf, 0, 0)
			ctx.paint_with_alpha(transparency)
			#ctx.paint()
			ctx.restore()


	def draw_buddy_info (self, ctx, x, y, w, h, prop, buddy):
		l = self.layout
		if l.status_text.enabled and buddy.status_text:
			th = h - l.status_text.height - l.status_text.ypadding
			ty = y + th 
			self.draw_list_item_text (ctx, x, y, prop, w, th, buddy.alias)

			fontbak, colorbak = prop.font, prop.color
			prop.font = l.status_text.font
			if l.status_text.color: prop.color = l.status_text.color

			self.draw_list_item_text (ctx, x, ty, prop, w, l.status_text.height, buddy.status_text)

			prop.font, prop.color = fontbak, colorbak
		else:
			self.draw_list_item_text (ctx, x, y, prop, w, h, buddy.alias)


	def draw_buddy_row (self, ctx, buddyUI, prop, use_opaque_icons=False):
		if self.pidgin:
			buddy = buddyUI.obj
			x = buddyUI.x
			y = buddyUI.y
			w = buddyUI.width
			h = buddyUI.height
			curvestyle = buddyUI.getProperty("curvestyle")

			l = self.layout

			# Draw the background first
			self.draw_list_item_background (ctx, x, y, prop, w, curvestyle)

			# Get sizes of the various icons
			(siw, sih) = self.get_status_icon_extents (buddy)
			(piw, pih) = self.get_protocol_icon_extents (buddy)
			(biw, bih) = self.get_buddy_icon_extents (buddy)
			bis = l.buddy_icon.size # This is the buddy icon default width/height

			# Calculate the buddy text width remaining
			textw = w - siw - piw
			for item in l.layout.row_items:
				if item == RowItems.STATUS_ICON and siw:
					textw -= l.status_icon.lpadding + l.status_icon.rpadding
				elif item == RowItems.PROTOCOL_ICON and piw:
					textw -= l.protocol_icon.lpadding + l.protocol_icon.rpadding
				elif biw and (item == RowItems.BUDDY_ICON or
						item == RowItems.BUDDY_PROTOCOL_ICON):
					textw -= bis
					textw -= l.buddy_icon.lpadding + l.buddy_icon.rpadding

			# Calculate the y pos. and height after the spacings
			y = y + prop.top_spacing
			h = h - prop.top_spacing - prop.bottom_spacing

			# Check the buddy_icon/protocol_icon transparency
			trans = prop.icon_transparency
			if use_opaque_icons: trans = 1

			# Draw all the items in this buddy row
			for item in l.layout.row_items:
				if item == RowItems.STATUS_ICON and siw:
					x += l.status_icon.lpadding
					self.draw_status_icon(ctx, x, y + (h-sih)/2, buddy)
					x += siw + l.status_icon.rpadding
				elif item == RowItems.PROTOCOL_ICON and piw:
					x += l.protocol_icon.lpadding
					self.draw_protocol_icon(ctx, x, y + (h-pih)/2, trans, buddy)
					x += piw + l.protocol_icon.rpadding
				elif biw and (item == RowItems.BUDDY_ICON or
						item == RowItems.BUDDY_PROTOCOL_ICON):
					x += l.buddy_icon.lpadding
					radius = float(l.buddy_icon.rounding_radius)
					self.draw_buddy_icon(ctx, x + (bis-biw)/2, y + (h-bih)/2, 
							biw, bih, trans, radius, buddy)
					x += bis + l.buddy_icon.rpadding
				elif item == RowItems.BUDDY_INFO:
					self.draw_buddy_info(ctx, x, y, textw, h, prop, buddy)
					x += textw


	def get_status_num(self, status):
		if status=="online": return 1
		if status=="away": return 2
		if status=="busy": return 3
		if status=="idle": return 4
		if status=="offline": return 5
		return 6

	def group_sort_func(self, g1,g2):
		if self.sort_groups_by_name:
			return cmp(g1[1].name,g2[1].name)
		elif self.sort_groups_by_num_users:
			return cmp(len(g1[1].buddies),len(g2[1].buddies))

	def buddy_sort_func(self, b1,b2):
		if self.sort_buddies_by_name:
			return cmp(b1[1].alias,b2[1].alias)
		elif self.sort_buddies_by_status:
			return cmp(self.get_status_num(b1[1].status), 
					self.get_status_num(b2[1].status))


	def get_buddy_style(self, buddy, bnum):
		l = self.layout
		status = buddy.event_status or buddy.status
		st = "buddy_"+status
		if l.layout.use_alternating_style and bnum%2==0 and st+"_alt" in l.__dict__:
			return l.__dict__[st+"_alt"]
		elif st in l.__dict__:
			return l.__dict__[st]
		return None
	
	# Get the height of the group content
	# (without the top/bottom spacing, border, shadows, etc)
	def get_group_height(self, prop, group):
		l = self.layout
		group_height = 0
		if self.show_groups:
			group_height += prop.height

		if group.id in self.collapsedGroups:
			return group_height

		cnt = 1;
		#sortedBuddies = sorted(group.buddies.items(), lambda x,y: self.buddy_sort_func(x,y))
		for id in group.buddies:
			bprop = self.get_buddy_style(group.buddies[id], cnt)
			if bprop:
				group_height += bprop.height
			cnt+=1
		return group_height


	def draw_group_shadow_and_border(self, ctx, x, y, prop, group):
		l = self.layout
		sz = l.window.shadow_size/2.0
		sb = l.group_info.border_width/2.0
		#x = sz
		if self.show_groups: 
			y += prop.top_spacing

		group_height = 0
		if prop.top_spacing + prop.bottom_spacing == 0:
			# Just draw one big shadow/border if no spacing between the groups
			group_height = l.window.height
		else:
			group_height = self.get_group_height(prop, group)

		if l.window.shadowed:
			self.draw_shadow(ctx, x, y, l.window.width+sb*2, group_height+sb,
				sz, l.window.shadow_color)
			# Remove shadow artifacts
			ctx.save()
			ctx.translate(x+sz,y)
			# Draw the Group shape
			if l.window.round_style:
				r = l.window.radius
				self.round_rectangle(ctx,0,0,l.window.width+sb*2,group_height+sb,r,r,r,r)
			else:
				ctx.rectangle(0,0,l.window.width+sb*2,group_height+sb)
			# Clear up shadow artifacts inside the shape
			ctx.set_operator(cairo.OPERATOR_CLEAR)
			ctx.set_source_rgba(0,0,0,1)
			ctx.fill()
			ctx.restore()

		# Show border of the Group if required
		if l.group_info.show_border:
			# Draw the Group shape
			ctx.save()
			ctx.translate(x+sz,y-sb)
			if l.window.round_style:
				r = l.window.radius
				self.round_rectangle(ctx,0,0,l.window.width+sb*2,group_height+sb*2,r,r,r,r)
			else:
				ctx.rectangle(0,0,l.window.width+sb*2,group_height+sb*2)
			#ctx.set_operator(cairo.OPERATOR_OVER)
			cl = l.group_info.border_color
			ctx.set_source_rgba(cl[0],cl[1],cl[2],cl[3])
			ctx.fill()
			#ctx.set_line_width(l.group_info.border_width)
			#ctx.stroke()
			ctx.restore()



	def get_group_collapse_icon_extents (self, collapsed):
		l = self.layout.layout
		if self.layout.group_info.collapsible:
			icon = "group_open"
			if collapsed: icon = "group_closed"
			cf = self.icons["group_collapse"][icon]
			return (cf.get_width(), cf.get_height())
		return (0,0)


	def draw_group_collapse_icon (self, ctx, x, y, collapsed):
		ctx.save()
		ctx.translate(x, y) 
		icon = "group_open"
		if collapsed: icon = "group_closed"
		cf = self.icons["group_collapse"][icon]
		ctx.set_source_pixbuf(cf, 0, 0)
		ctx.paint()
		ctx.restore()


	def redraw_buddy_list (self):
		if not self.__buffer: return
		ctx = self.__buffer.cairo_create()
		self.clear_cairo_context(ctx)

		ctx.scale(self.scale, self.scale)
		ctx.set_operator(cairo.OPERATOR_OVER)


		#check for connection
		if self.pidgin and self.layout:
			y = 0
			l = self.layout

			self.uiObjects.clear()

			#for groupId in self.groups.items().sort(self.group_sort_func):
			sortedGroups = sorted(self.groups.items(), 
				lambda x,y: self.group_sort_func(x,y))

			gnum = 1
			for groupId, group in sortedGroups:
				g_property = l.group
				x = 0

				collapsed = False
				if groupId in self.collapsedGroups:
					collapsed = True

				# Show Shadow (if asked for)
				if l.window.shadowed or l.group_info.show_border:
					y += l.group_info.border_width/2
					if (g_property.top_spacing + g_property.bottom_spacing) > 0 or gnum == 1:
						self.draw_group_shadow_and_border (ctx, x, y, g_property, group)

				x += l.window.shadow_size/2.0 + l.group_info.border_width/2.0

				# If we should show Groups
				if self.show_groups:
					y += g_property.top_spacing

					# Get the curve style for displaying this group
					curvestyle = 0
					if l.window.round_style:
						if (g_property.top_spacing + g_property.bottom_spacing > 0 or gnum==1):
							curvestyle = 8|4
						if ((collapsed or len(group.buddies) == 0) and 
								(g_property.top_spacing + g_property.bottom_spacing > 0 or 
									gnum==len(self.groups))):
							curvestyle |= 2|1

					# Append Group count to Group Name if asked
					# Show (online/offline) if show_offline is set
					#  else just show (online)
					grouptext = group.name
					if l.group_info.show_buddy_count:
						if not self.show_offline: grouptext += " ("
						else: grouptext += " ("+str(len(group.online_buddies))+"/"
						grouptext += str(len(group.buddies))+")"

					# Get the full width
					w = g_property.width

					# Draw Group Background
					self.draw_list_item_background (ctx, x, y, g_property, w, curvestyle)

					# Get size of the collapse icon
					(cbw, cbh) = self.get_group_collapse_icon_extents(collapsed)

					# Calculate the group text width remaining
					textw = w - cbw
					if l.group_info.collapsible:
						textw -= l.group_collapse_icon.lpadding + l.group_collapse_icon.rpadding

					# Draw Group expand/collapse icon if asked for
					textdrawn = False
					if l.group_info.collapsible and cbw:
						# If Collapse icon to the right, then draw Group text first
						if l.group_collapse_icon.alignment == Alignment.RIGHT:
							self.draw_list_item_text (ctx, x, y, g_property, textw, g_property.height, grouptext)
							x += textw
							textdrawn = True

						ox = x
						h = g_property.height
						x += l.group_collapse_icon.lpadding
						self.draw_group_collapse_icon(ctx, x, y+(h-cbh)/2, collapsed)
						x += cbw + l.group_collapse_icon.rpadding

						total_w = cbw + l.group_collapse_icon.lpadding + l.group_collapse_icon.rpadding
						grpBtnUI = UIObject(group, ox, y, total_w, h)
						self.uiObjects[group.id] = grpBtnUI

					# Draw the Group Text (if not already drawn)
					if not textdrawn:
						self.draw_list_item_text (ctx, x, y, g_property, textw, g_property.height, grouptext)

					y += g_property.height


				if not collapsed:

					# Show All Buddies
					sortedBuddies = sorted(group.buddies.items(), 
							lambda x,y: self.buddy_sort_func(x,y))

					bnum = 1
					for buddyId, buddy in sortedBuddies:
						x = 0
						x += l.window.shadow_size/2.0 + l.group_info.border_width/2.0
						b_property = self.get_buddy_style(buddy, bnum)

						# Get the buddy curve style
						curvestyle = 0
						if l.window.round_style:
							if (bnum == len(group.buddies) 
									and (g_property.top_spacing + g_property.bottom_spacing > 0 or 
										gnum == len(self.groups))):
								curvestyle = 2|1
							if bnum == 1 and not self.show_groups:
								curvestyle |= 8|4

						# Save the UI-specific info for each buddy
						buddyUI = UIObject(buddy, x, y, b_property.width, b_property.height)
						buddyUI.setProperty("curvestyle",curvestyle)
						self.uiObjects[buddy.id] = buddyUI

						# Draw Contact Details
						self.draw_buddy_row (ctx, buddyUI, b_property)

						y += b_property.height
						bnum+=1

				if self.show_groups:
					y += g_property.bottom_spacing
					gnum+= 1

			if not len(self.groups):
				y = 20
				self.infoMessage(ctx, _("No Pidgin Contacts Active !"))

			# Account-signals
			for notificationMessage in self.accountNotifications:
				n_property = l.__dict__["notification"]
				x = 0
				x += l.window.shadow_size/2.0 + l.group_info.border_width/2.0
				y += n_property.top_spacing
				self.draw_list_item_background (ctx, x, y, n_property, w, 0)
				self.draw_list_item_text (ctx, x, y, n_property, textw, n_property.height, notificationMessage)
				y += l.group.height
				y += n_property.bottom_spacing

		else:
			if self.autostart_pidgin:
				os.system('pidgin  &')
			if not self.autostart_pidgin:
				self.infoMessage(ctx, _("Pidgin not running"))
			if self.layout:
				self.layout.window.height = 20


	def on_load_theme(self): 
		# TODO: Load theme and set "self.layout"
		if not self.default_icons:
			print "Loading Default Icons"
			self.loadDefaultIcons()

		options_file = sys.path[0]+"/themes/"+self.theme_name+"/options.conf"
		if os.path.exists(options_file): 
			self.layout = ThemeParser(options_file)
			self.loadIconsForTheme(self.theme_name)
			# Recache Buddy Icons
			for bId in self.buddies:
				buddy = self.buddies[bId]
				self.setBuddyIconImage(buddy, self.layout.buddy_icon.size)
			self.resizeAndRedrawBuddyList()
		else:
			raise _("Invalid Theme. No options.conf")

	def infoMessage(self, ctx, message):
		ctx.save()
		ctx.translate(30, 5)

		# memory leak fix layout
		if self.p_layout == None :
			self.p_layout = ctx.create_layout()
		else:
			ctx.update_layout(self.p_layout)

		p_fdesc = pango.FontDescription(self.getDesktopFont())
		self.p_layout.set_font_description(p_fdesc)
		self.p_layout.set_markup("<b>"+message+"</b>")
		ctx.set_source_rgba(1, 1, 1, 0.8)
		ctx.show_layout(self.p_layout)
		ctx.restore()

	def __init__ (self, **keyword_args):
		#call super (width/height MUST match the size of graphics in the theme)
		screenlets.Screenlet.__init__(self, width=4, height=4, **keyword_args)
		self.enable_buttons = False
		self.draw_buttons = False
		self.initStateVariables() 
		self.initThemeVariables() 
		self.loadXML()
		if not self.theme:
			self.theme_name="default"

		self.add_options_group(_('Pidgin'), _('The pidgin widget settings'))
		self.add_option(BoolOption(_('Pidgin'), 'show_offline', self.show_offline, 
			_('Show Offline'),''))
		self.add_option(BoolOption(_('Pidgin'), 'show_toponly', self.show_toponly, 
			_('Show Top Buddy Only per Contact'),''))
		self.add_option(BoolOption(_('Pidgin'), 'show_groups', self.show_groups, 
			_('Show Groups'),''))
		self.add_option(BoolOption(_('Pidgin'), 'group_by_groups', self.group_by_groups, 
			_('Group by Groups'),''))
		self.add_option(BoolOption(_('Pidgin'), 'group_by_account', self.group_by_account, 
			_('Group by Account'),''))
		self.add_option(BoolOption(_('Pidgin'), 'group_by_online_offline', self.group_by_online_offline, 
			_('Group by Online/Offline'),''))
		self.add_option(BoolOption(_('Pidgin'), 'sort_groups_by_name', self.sort_groups_by_name, 
			_('Sort Groups by Name'),''))
		self.add_option(BoolOption(_('Pidgin'), 'sort_groups_by_num_users', self.sort_groups_by_num_users, 
			_('Sort Groups by Number of Users'),''))
		self.add_option(BoolOption(_('Pidgin'), 'sort_buddies_by_name', self.sort_buddies_by_name, 
			_('Sort Buddies by Name'),''))
		self.add_option(BoolOption(_('Pidgin'), 'sort_buddies_by_status', self.sort_buddies_by_status, 
			_('Sort Buddies by Status'),''))


	def on_init (self):
		self.add_default_menuitems(DefaultMenuItem.XML)
		self.add_default_menuitems()


		try:
			self.bus = dbus.SessionBus()
			if self.bus:
				self.bus.add_signal_receiver(self.onDBusServiceChange,
					dbus_interface="org.freedesktop.DBus",signal_name="NameOwnerChanged")
				self.pidgin = self.getPidginDbus()
			else:
				print 'dbus is not present!'

			self.hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
			self.default_cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)

			if self.pidgin:
				self.refreshBuddyList()

		except:
			raise

	def __setattr__ (self, name, value):
		screenlets.Screenlet.__setattr__(self, name, value)

		if name in ('show_groups', 'show_offline', 'show_toponly', 'group_by_groups',
					'group_by_account', 'group_by_online_offline', 'sort_groups_by_name',
					'sort_groups_by_num_users', 'sort_buddies_by_name', 'sort_buddies_by_status'):
			self.__dict__[name] = value

	def on_scale (self):
		self.resizeAndRedrawBuddyList()

	def delayed (self, function, start_flag=True):
		if start_flag and not self.refresh_timeout:
			#print "First up ! Wait for a few to ignore all other update requests"
			self.refresh_timeout = gobject.timeout_add(self.refresh_timeout_value*1000, 
					self.delayed, function, False)
		elif start_flag and self.refresh_timeout:
			#print "- Ignoring, someone already made a request"
			None
		elif not start_flag:
			#print "You're it"
			function()
			self.refresh_timeout = None

	def refreshBuddyList(self):
		#print "Refreshing.."
		self.getGroupsAndBuddies()
		self.resizeAndRedrawBuddyList()

	def resizeAndRedrawBuddyList(self):
		#print "Resizing.."
		self.setScreenletHeight()
		if self.layout:
			w = self.layout.window
			g = self.layout.group_info
			if self.height != w.height + w.shadow_size/2.0:
				self.height = w.height + w.shadow_size/2.0
			if self.width != w.width + w.shadow_size + g.border_width:
				self.width = w.width + w.shadow_size + g.border_width
			self.init_buffer()
			self.redrawBuddyList()

	def redrawBuddyList(self):
		#print "Redrawing.."
		self.redraw_buddy_list()
		self.redraw_canvas()

	def handleMouseClick(self, objId):
		if objId in self.buddies:
			self.startConversation(objId)
		elif objId in self.groups:
			if objId in self.collapsedGroups:
				del self.collapsedGroups[objId]
			else:
				self.collapsedGroups[objId] = 1
			self.resizeAndRedrawBuddyList()

	def startConversation(self, bId):
		if self.pidgin:
			self.pidgin.PurpleConversationPresent(self.pidgin.PurpleConversationNew(1, 
				self.pidgin.PurpleBuddyGetAccount(int(bId)), 
				self.pidgin.PurpleBuddyGetName(int(bId))))

	def getObjectId (self, x, y):
		x /= self.scale
		y /= self.scale
		for objId in self.uiObjects:
			obj = self.uiObjects[objId]
			if (obj.y <= y and obj.y > (y - obj.height) and 
					obj.x <= x and obj.x > (x - obj.width)):
				return objId
		return None

	def on_mouse_down (self, event):
		if event.button == 1:
			objId = self.getObjectId(event.x, event.y)
			if objId:
				self.selectedObjId=objId
				self.redraw_canvas()
				return True
		pass

	def on_mouse_up (self, event):
		if event.button == 1:
			objId = self.getObjectId(event.x, event.y)
			if objId:
				self.handleMouseClick(objId)
				return True
		pass
				
	def selectHoverContact(self, x, y):
		objId = self.getObjectId(x, y)
		if objId:
			if objId != self.hoverObjId:
				self.hoverObjId = objId
				self.window.window.set_cursor(self.hand_cursor)
				self.redraw_canvas()
		elif self.hoverObjId:
			self.window.window.set_cursor(self.default_cursor)
			self.hoverObjId = None 
			self.redraw_canvas()

	def on_mouse_enter (self, event):
		self.selectHoverContact(event.x, event.y)

	def on_mouse_move (self, event):
		self.selectHoverContact(event.x, event.y)

	def on_mouse_leave (self, event):
		if self.hoverObjId:
			self.hoverObjId = None 
			self.window.window.set_cursor(self.default_cursor)
			self.redraw_canvas()

	def on_draw_shape (self, ctx):
		ctx.scale(self.scale, self.scale)
		ctx.set_source_rgba(1,1,1,1)
		ctx.rectangle(0,0,self.width,self.height)
		ctx.paint()
		pass
		#print "List height : "+ str(self.height)
	

if __name__ == "__main__":
	# create new session
	import screenlets.session
	screenlets.session.create_session(PidginScreenlet)

