From a67eaba727485b353c01aed5dfa7eb8b54c24a40 Mon Sep 17 00:00:00 2001
From: Yann Leboulanger <asterix@lagaule.org>
Date: Sat, 2 Sep 2006 21:01:11 +0000
Subject: [PATCH] events are now saved in an Event class. show in
 roster/systray options in Advanced Notification Control (for incomming
 messages) now work.

---
 src/chat_control.py         | 165 +++++++++++++++-----------
 src/common/events.py        | 230 ++++++++++++++++++++++++++++++++++++
 src/common/gajim.py         |  23 +---
 src/common/helpers.py       |  24 +++-
 src/config.py               |  17 +--
 src/filetransfers_window.py |  11 +-
 src/gajim.py                | 123 +++++++++----------
 src/groupchat_control.py    |  23 ++--
 src/message_control.py      |   6 +-
 src/message_window.py       |   9 +-
 src/notify.py               |  51 ++++----
 src/remote_control.py       |   2 +-
 src/roster_window.py        |  91 +++++++-------
 src/systray.py              |  37 ++----
 src/systraywin32.py         |  54 ++-------
 src/tooltips.py             |  42 ++-----
 16 files changed, 540 insertions(+), 368 deletions(-)
 create mode 100644 src/common/events.py

diff --git a/src/chat_control.py b/src/chat_control.py
index f3d7a4d655..e175590250 100644
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -24,6 +24,7 @@ import gtkgui_helpers
 import message_control
 import dialogs
 import history_window
+import notify
 
 from common import gajim
 from common import helpers
@@ -50,7 +51,7 @@ class ChatControlBase(MessageControl):
 		theme = gajim.config.get('roster_theme')
 		bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
 		bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs')
-		
+
 		if bannerfont:
 			font = pango.FontDescription(bannerfont)
 		else:
@@ -61,16 +62,26 @@ class ChatControlBase(MessageControl):
 				font.set_weight(pango.WEIGHT_HEAVY)
 			if 'I' in bannerfontattrs:
 				font.set_style(pango.STYLE_ITALIC)
-		
+
 		font_attrs = 'font_desc="%s"' % font.to_string()
-		
+
 		# in case there is no font specified we use x-large font size
 		if font.get_size() == 0:
 			font_attrs = '%s size="x-large"' % font_attrs
 		font.set_weight(pango.WEIGHT_NORMAL)
 		font_attrs_small = 'font_desc="%s" size="small"' % font.to_string()
 		return (font_attrs, font_attrs_small)
-			
+
+	def get_nb_unread(self):
+		jid = self.contact.jid
+		if self.resource:
+			jid += '/' + self.resource
+		type_ = self.type_id
+		if type_ == message_control.TYPE_GC:
+			type_ = 'gc_msg'
+		return len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
+			type_]))
+
 	def draw_banner(self):
 		self._paint_banner()
 		self._update_banner_state_image()
@@ -99,7 +110,7 @@ class ChatControlBase(MessageControl):
 		widget = self.xml.get_widget('emoticons_button')
 		id = widget.connect('clicked', self.on_emoticons_button_clicked)
 		self.handlers[id] = widget
-		
+
 		id = self.widget.connect('key_press_event', self._on_keypress_event)
 		self.handlers[id] = self.widget
 
@@ -107,10 +118,10 @@ class ChatControlBase(MessageControl):
 		id = widget.connect('button-press-event',
 			self._on_banner_eventbox_button_press_event)
 		self.handlers[id] = widget
-	
+
 		# Create textviews and connect signals
 		self.conv_textview = ConversationTextview(self.account)
-		
+
 		self.conv_scrolledwindow = self.xml.get_widget(
 			'conversation_scrolledwindow')
 		self.conv_scrolledwindow.add(self.conv_textview.tv)
@@ -144,8 +155,6 @@ class ChatControlBase(MessageControl):
 		self.typing_new = False
 		self.orig_msg = ''
 
-		self.nb_unread = 0
-
 		# Emoticons menu
 		# set image no matter if user wants at this time emoticons or not
 		# (so toggle works ok)
@@ -480,12 +489,25 @@ class ChatControlBase(MessageControl):
 			gajim.last_message_time[self.account][full_jid] = time.time()
 		urgent = True
 		if (not self.parent_win.get_active_jid() or \
-				full_jid != self.parent_win.get_active_jid() or \
-				not self.parent_win.is_active() or not end) and \
-				kind in ('incoming', 'incoming_queue'):
-			self.nb_unread += 1
-			if gajim.interface.systray_capabilities and self.notify_on_new_messages():
-				gajim.interface.systray.add_jid(full_jid, self.account, self.type_id)
+		full_jid != self.parent_win.get_active_jid() or \
+		not self.parent_win.is_active() or not end) and \
+		kind in ('incoming', 'incoming_queue'):
+			if self.notify_on_new_messages():
+				type_ = 'printed_' + self.type_id
+				if self.type_id == message_control.TYPE_GC:
+					type_ = 'printed_gc_msg'
+				show_in_roster = notify.get_show_in_roster('message_received',
+					self.account, self.contact)
+				show_in_systray = notify.get_show_in_systray('message_received',
+					self.account, self.contact)
+				event = gajim.events.create_event(type_, None,
+					show_in_roster = show_in_roster,
+					show_in_systray = show_in_systray)
+				gajim.events.add_event(self.account, full_jid, event)
+				# We need to redraw contact if we show in roster
+				if show_in_roster:
+					gajim.interface.roster.draw_contact(self.contact.jid,
+						self.account)
 			self.parent_win.redraw_tab(self)
 			if not self.parent_win.is_active():
 				ctrl = gajim.interface.msg_win_mgr.get_control(full_jid,
@@ -510,6 +532,7 @@ class ChatControlBase(MessageControl):
 		else: # we are the beginning of buffer
 			buffer.insert_at_cursor('%s ' % str_)
 		self.msg_textview.grab_focus()
+
 	def on_emoticons_button_clicked(self, widget):
 		'''popup emoticons menu'''
 		gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
@@ -554,15 +577,18 @@ class ChatControlBase(MessageControl):
 		if state:
 			jid = self.contact.jid
 			if self.conv_textview.at_the_end():
-				#we are at the end
-				if self.nb_unread > 0:
-					self.nb_unread = self.get_specific_unread()
+				# we are at the end
+				type_ = 'printed_' + self.type_id
+				if self.type_id == message_control.TYPE_GC:
+					type_ = 'printed_gc_msg'
+				if not gajim.events.remove_events(self.account, self.get_full_jid(),
+				types = [type_]):
+					# There were events to remove
 					self.parent_win.redraw_tab(self)
 					self.parent_win.show_title()
-					if gajim.interface.systray_capabilities:
-						gajim.interface.systray.remove_jid(self.get_full_jid(),
-										self.account,
-										self.type_id)
+					# redraw roster
+					gajim.interface.roster.draw_contact(jid, self.account)
+					gajim.interface.roster.show_title()
 			self.msg_textview.grab_focus()
 			# Note, we send None chatstate to preserve current
 			self.parent_win.redraw_tab(self)
@@ -635,22 +661,28 @@ class ChatControlBase(MessageControl):
 		return True
 
 	def on_conversation_vadjustment_value_changed(self, widget):
-		if not self.nb_unread:
-			return
 		if self.resource:
 			jid = self.contact.get_full_jid()
 		else:
 			jid = self.contact.jid
+		type_ = self.type_id
+		if type_ == message_control.TYPE_GC:
+			type_ = 'gc_msg'
+		if not len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
+		type_])):
+			return
 		if self.conv_textview.at_the_end() and \
 				self.parent_win.get_active_control() == self and \
 				self.parent_win.window.is_active():
-			#we are at the end
-			self.nb_unread = self.get_specific_unread()
-			self.parent_win.redraw_tab(self)
-			self.parent_win.show_title()
-			if gajim.interface.systray_capabilities:
-				gajim.interface.systray.remove_jid(jid, self.account,
-					self.type_id)
+			# we are at the end
+			type_ = self.type_id
+			if type_ == message_control.TYPE_GC:
+				type_ = 'gc_msg'
+			if not gajim.events.remove_events(self.account, self.get_full_jid(),
+			types = ['printed_' + type_, type_]):
+				# There were events to remove
+				self.parent_win.redraw_tab(self)
+				self.parent_win.show_title()
 
 	def sent_messages_scroll(self, direction, conv_buf):
 		size = len(self.sent_history) 
@@ -1140,7 +1172,12 @@ class ChatControl(ChatControlBase):
 
 	def get_tab_label(self, chatstate):
 		unread = ''
-		num_unread = self.nb_unread
+		if self.resource:
+			jid = self.contact.get_full_jid()
+		else:
+			jid = self.contact.jid
+		num_unread = len(gajim.events.get_events(self.account, jid,
+			['printed_' + self.type_id, self.type_id]))
 		if num_unread == 1 and not gajim.config.get('show_unread_tab_icon'):
 			unread = '*'
 		elif num_unread > 1:
@@ -1183,7 +1220,12 @@ class ChatControl(ChatControlBase):
 		return (label_str, color)
 
 	def get_tab_image(self):
-		num_unread = self.nb_unread
+		if self.resource:
+			jid = self.contact.get_full_jid()
+		else:
+			jid = self.contact.jid
+		num_unread = len(gajim.events.get_events(self.account, jid,
+			['printed_' + self.type_id, self.type_id]))
 		# Set tab image (always 16x16); unread messages show the 'message' image
 		tab_img = None
 		
@@ -1192,8 +1234,8 @@ class ChatControl(ChatControlBase):
 				self.contact.jid, icon_name = 'message')
 			tab_img = img_16['message']
 		else:
-			contact = gajim.contacts.get_contact_with_highest_priority(self.account,
-				self.contact.jid)
+			contact = gajim.contacts.get_contact_with_highest_priority(
+				self.account, self.contact.jid)
 			if not contact or self.resource:
 				# For transient contacts
 				contact = self.contact
@@ -1369,10 +1411,9 @@ class ChatControl(ChatControlBase):
 		# Remove bigger avatar window
 		if self.bigger_avatar_window:
 			self.bigger_avatar_window.destroy()
-		# Clean up systray
-		if gajim.interface.systray_capabilities and self.nb_unread > 0:
-			gajim.interface.systray.remove_jid(self.contact.jid, self.account,
-								self.type_id)
+		# Clean events
+		gajim.events.remove_events(self.account, self.get_full_jid(),
+			types = ['printed_' + self.type_id, self.type_id])
 		# remove all register handlers on wigets, created by self.xml
 		# to prevent circular references among objects
 		for i in self.handlers.keys():
@@ -1474,14 +1515,11 @@ class ChatControl(ChatControlBase):
 		if restore_how_many <= 0:
 			return
 		timeout = gajim.config.get('restore_timeout') # in minutes
-		# number of messages that are in queue and are already logged
-		pending_how_many = 0 # we want to avoid duplication
 
-		if gajim.awaiting_events[self.account].has_key(jid):
-			events = gajim.awaiting_events[self.account][jid]
-			for event in events:
-				if event[0] == 'chat':
-					pending_how_many += 1
+		events = gajim.events.get_events(self.account, jid, ['chat'])
+		# number of messages that are in queue and are already logged, we want
+		# to avoid duplication
+		pending_how_many = len(events)
 
 		rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many,
 			pending_how_many, timeout, self.account)
@@ -1522,7 +1560,7 @@ class ChatControl(ChatControlBase):
 		jid_with_resource = jid
 		if self.resource:
 			jid_with_resource += '/' + self.resource
-		l = gajim.awaiting_events[self.account][jid_with_resource]
+		events = gajim.events.get_events(self.account, jid_with_resource)
 
 		# Is it a pm ?
 		is_pm = False
@@ -1530,15 +1568,12 @@ class ChatControl(ChatControlBase):
 		control = gajim.interface.msg_win_mgr.get_control(room_jid, self.account)
 		if control and control.type_id == message_control.TYPE_GC:
 			is_pm = True
-		events_to_keep = []
 		# list of message ids which should be marked as read
 		message_ids = []
-		for event in l:
-			typ = event[0]
-			if typ != 'chat':
-				events_to_keep.append(event)
+		for event in events:
+			if event.type_ != 'chat':
 				continue
-			data = event[1]
+			data = event.parameters
 			kind = data[2]
 			if kind == 'error':
 				kind = 'info'
@@ -1548,22 +1583,16 @@ class ChatControl(ChatControlBase):
 						encrypted = data[4], subject = data[1])
 			if len(data) > 6 and isinstance(data[6], int):
 				message_ids.append(data[6])
-			# remove from gc nb_unread if it's pm or from roster
-			if is_pm:
-				control.nb_unread -= 1
-			else:
-				gajim.interface.roster.nb_unread -= 1
 		if message_ids:
 			gajim.logger.set_read_messages(message_ids)
-		if is_pm:
-			control.parent_win.show_title()
-		else:
-			gajim.interface.roster.show_title()
-		# Keep only non-messages events
-		if len(events_to_keep):
-			gajim.awaiting_events[self.account][jid_with_resource] = events_to_keep
-		else:
-			del gajim.awaiting_events[self.account][jid_with_resource]
+		gajim.events.remove_events(self.account, jid_with_resource,
+			types = ['chat'])
+
+		self.parent_win.show_title()
+		self.parent_win.redraw_tab(self)
+		# redraw roster
+		gajim.interface.roster.show_title()
+
 		typ = 'chat' # Is it a normal chat or a pm ?
 		# reset to status image in gc if it is a pm
 		if is_pm:
@@ -1573,8 +1602,6 @@ class ChatControl(ChatControlBase):
 		gajim.interface.roster.draw_contact(jid, self.account)
 		# Redraw parent too
 		gajim.interface.roster.draw_parent_contact(jid, self.account)
-		if gajim.interface.systray_capabilities:
-			gajim.interface.systray.remove_jid(jid_with_resource, self.account, typ)
 		if (self.contact.show == 'offline' or self.contact.show == 'error'):
 			showOffline = gajim.config.get('showoffline')
 			if not showOffline and typ == 'chat' and \
diff --git a/src/common/events.py b/src/common/events.py
new file mode 100644
index 0000000000..bc190f6c4b
--- /dev/null
+++ b/src/common/events.py
@@ -0,0 +1,230 @@
+## common/events.py
+##
+## Contributors for this file:
+##	- Yann Le Boulanger <asterix@lagaule.org>
+##
+## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>
+##                    Vincent Hanquez <tab@snarc.org>
+##                    Nikos Kouremenos <nkour@jabber.org>
+##                    Dimitur Kirov <dkirov@gmail.com>
+##                    Travis Shirk <travis@pobox.com>
+##                    Norman Rasmussen <norman@rasmussen.co.za>
+##
+## 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; version 2 only.
+##
+## 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.
+##
+
+import time
+import gajim
+
+class Event:
+	'''Information concerning each event'''
+	def __init__(self, type_, time_, parameters, show_in_roster = False,
+	show_in_systray = True):
+		''' type_ in chat, normal, file-request, file-error, file-completed,
+		file-request-error, file-send-error, file-stopped, gc_msg, pm,
+		printed_chat, printed_gc_msg, printed_pm
+		parameters is (per type_):
+			chat, normal: [message, subject, kind, time, encrypted, resource,
+			msg_id]
+				where kind in error, incoming
+			file-*: file_props
+			gc_msg: None
+			printed_*: None
+				messages that are already printed in chat, but not read'''
+		self.type_ = type_
+		self.time_ = time_
+		self.parameters = parameters
+		self.show_in_roster = show_in_roster
+		self.show_in_systray = show_in_systray
+
+class Events:
+	'''Information concerning all events'''
+	def __init__(self):
+		self._events = {} # list of events {acct: {jid1: [E1, E2]}, }
+
+	def change_account_name(self, old_name, new_name):
+		if self._events.has_key(old_name):
+			self._events[new_name] = self._events[old_name]
+			del self._events[old_name]
+
+	def add_account(self, account):
+		self._events[account] = {}
+
+	def get_accounts(self):
+		return self._events.keys()
+
+	def remove_account(self, account):
+		del self._events[account]
+
+	def create_event(self, type_, parameters, time_ = time.time(),
+	show_in_roster = False, show_in_systray = True):
+		return Event(type_, time_, parameters, show_in_roster,
+			show_in_systray)
+
+	def add_event(self, account, jid, event):
+		# No such account before ?
+		if not self._events.has_key(account):
+			self._events[account] = {jid: [event]}
+		# no such jid before ?
+		elif not self._events[account].has_key(jid):
+			self._events[account][jid] = [event]
+		else:
+			self._events[account][jid].append(event)
+		if event.show_in_systray:
+			gajim.interface.systray.set_img()
+
+	def remove_events(self, account, jid, event = None, types = []):
+		'''if event is not speficied, remove all events from this jid,
+		optionnaly only from given type
+		return True if no such event found'''
+		if not self._events.has_key(account):
+			return True
+		if not self._events[account].has_key(jid):
+			return True
+		if event: # remove only one event
+			if event in self._events[account][jid]:
+				if len(self._events[account][jid]) == 1:
+					del self._events[account][jid]
+				else:
+					self._events[account][jid].remove(event)
+				gajim.interface.systray.set_img()
+				return
+			else:
+				return True
+		if types:
+			new_list = [] # list of events to keep
+			for ev in self._events[account][jid]:
+				if ev.type_ not in types:
+					new_list.append(ev)
+			if len(new_list) == len(self._events[account][jid]):
+				return True
+			if new_list:
+				self._events[account][jid] = new_list
+			else:
+				del self._events[account][jid]
+			gajim.interface.systray.set_img()
+			return
+		# no event nor type given, remove them all
+		del self._events[account][jid]
+		gajim.interface.systray.set_img()
+
+	def get_nb_events(self, types = []):
+		return self._get_nb_events(types = types)
+
+	def get_events(self, account, jid = None, types = []):
+		'''if event is not speficied, remove all events from this jid,
+		optionnaly only from given type'''
+		if not self._events.has_key(account):
+			return []
+		if not jid:
+			return self._events[account]
+		if not self._events[account].has_key(jid):
+			return []
+		events_list = [] # list of events
+		for ev in self._events[account][jid]:
+			if not types or ev.type_ in types:
+				events_list.append(ev)
+		return events_list
+
+	def get_first_event(self, account, jid = None, type_ = None):
+		'''Return the first event of type type_ if given'''
+		events_list = self.get_events(account, jid, type_)
+		# be sure it's bigger than latest event
+		first_event_time = time.time() + 1
+		first_event = None
+		for event in events_list:
+			if event.time_ < first_event_time:
+				first_event_time = event.time_
+				first_event = event
+		return first_event
+
+	def _get_nb_events(self, account = None, jid = None, attribute = None, types = []):
+		'''return the number of events'''
+		nb = 0
+		if account:
+			accounts = [account]
+		else:
+			accounts = self._events.keys()
+		for acct in accounts:
+			if not self._events.has_key(acct):
+				continue
+			if jid:
+				jids = [jid]
+			else:
+				jids = self._events[acct].keys()
+			for j in jids:
+				if not self._events[acct].has_key(j):
+					continue
+				for event in self._events[acct][j]:
+					if types and event.type_ not in types:
+						continue
+					if not attribute or \
+					attribute == 'systray' and event.show_in_systray or \
+					attribute == 'roster' and event.show_in_roster:
+						nb += 1
+		return nb
+
+	def _get_some_events(self, attribute):
+		'''attribute in systray, roster'''
+		events = {}
+		for account in self._events:
+			events[account] = {}
+			for jid in self._events[account]:
+				events[account][jid] = []
+				for event in self._events[account][jid]:
+					if attribute == 'systray' and event.show_in_systray or \
+					attribute == 'roster' and event.show_in_roster:
+						events[account][jid].append(event)
+				if not events[account][jid]:
+					del events[account][jid]
+			if not events[account]:
+				del events[account]
+		return events
+
+	def _get_first_event_with_attribute(self, events):
+		'''get the first event
+		events is in the form {account1: {jid1: [ev1, ev2], },. }'''
+		# be sure it's bigger than latest event
+		first_event_time = time.time() + 1
+		first_account = None
+		first_jid = None
+		first_event = None
+		for account in events:
+			for jid in events[account]:
+				for event in events[account][jid]:
+					if event.time_ < first_event_time:
+						first_event_time = event.time_
+						first_account = account
+						first_jid = jid
+						first_event = event
+		return first_account, first_jid, first_event
+
+	def get_nb_systray_events(self, types = []):
+		'''returns the number of events displayedin roster'''
+		return self._get_nb_events(attribute = 'systray', types = types)
+
+	def get_systray_events(self):
+		'''return all events that must be displayed in systray:
+		{account1: {jid1: [ev1, ev2], },. }'''
+		return self._get_some_events('systray')
+
+	def get_first_systray_event(self):
+		events = self.get_systray_events()
+		return self._get_first_event_with_attribute(events)
+
+	def get_nb_roster_events(self, account = None, jid = None, types = []):
+		'''returns the number of events displayedin roster'''
+		return self._get_nb_events(attribute = 'roster', account = account,
+			jid = jid, types = types)
+
+	def get_roster_events(self):
+		'''return all events that must be displayed in roster:
+		{account1: {jid1: [ev1, ev2], },. }'''
+		return self._get_some_events('roster')
diff --git a/src/common/gajim.py b/src/common/gajim.py
index edf1db2cb7..dc7794e6e3 100644
--- a/src/common/gajim.py
+++ b/src/common/gajim.py
@@ -23,6 +23,7 @@ import locale
 
 import config
 from contacts import Contacts
+from events import Events
 
 interface = None # The actual interface (the gtk one for the moment)
 config = config.Config()
@@ -98,14 +99,8 @@ groups = {} # list of groups
 newly_added = {} # list of contacts that has just signed in
 to_be_removed = {} # list of contacts that has just signed out
 
-awaiting_events = {} # list of messages/FT reveived but not printed
-	# awaiting_events[jid] = (type, (data1, data2, ...))
-	# if type in ('chat', 'normal'): data = (message, subject, kind, time,
-		# encrypted, resource)
-		# kind can be (incoming, error)
-	# if type in file-request, file-request-error, file-send-error, file-error,
-	# file-completed, file-stopped:
-		# data = file_props
+events = Events()
+
 nicks = {} # list of our nick names in each account
 # should we block 'contact signed in' notifications for this account?
 # this is only for the first 30 seconds after we change our show
@@ -287,18 +282,6 @@ def get_hostname_from_account(account_name, use_srv = False):
 		return config.get_per('accounts', account_name, 'custom_host')
 	return config.get_per('accounts', account_name, 'hostname')
 
-def get_first_event(account, jid, typ = None):
-	'''returns the first event of the given type from the awaiting_events queue'''
-	if not awaiting_events[account].has_key(jid):
-		return None
-	q = awaiting_events[account][jid]
-	if not typ:
-		return q[0]
-	for ev in q:
-		if ev[0] == typ:
-			return ev
-	return None
-
 def get_notification_image_prefix(jid):
 	'''returns the prefix for the notification images'''
 	transport_name = get_transport_name_from_jid(jid)
diff --git a/src/common/helpers.py b/src/common/helpers.py
index edccad675e..3ff4b4978d 100644
--- a/src/common/helpers.py
+++ b/src/common/helpers.py
@@ -513,9 +513,9 @@ def get_global_status():
 
 def get_icon_name_to_show(contact, account = None):
 	'''Get the icon name to show in online, away, requested, ...'''
-	if account and gajim.awaiting_events[account].has_key(contact.jid):
+	if account and gajim.events.get_nb_roster_events(account, contact.jid):
 		return 'message'
-	if account and gajim.awaiting_events[account].has_key(
+	if account and gajim.events.get_nb_roster_events(account,
 	contact.get_full_jid()):
 		return 'message'
 	if contact.jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent
@@ -772,3 +772,23 @@ def allow_sound_notification(sound_event, advanced_notif_num = None):
 	if gajim.config.get_per('soundevents', sound_event, 'enabled'):
 		return True
 	return False
+
+def get_chat_control(account, contact):
+	full_jid_with_resource = contact.jid
+	if contact.resource:
+		full_jid_with_resource += '/' + contact.resource
+	highest_contact = gajim.contacts.get_contact_with_highest_priority(
+		account, contact.jid)
+	# Look for a chat control that has the given resource, or default to
+	# one without resource
+	ctrl = gajim.interface.msg_win_mgr.get_control(full_jid_with_resource,
+		account)
+	if ctrl:
+		return ctrl
+	elif not highest_contact or not highest_contact.resource:
+		# unknow contact or offline message
+		return gajim.interface.msg_win_mgr.get_control(contact.jid, account)
+	elif highest_contact and contact.resource != \
+	highest_contact.resource:
+		return None
+	return gajim.interface.msg_win_mgr.get_control(contact.jid, account)
diff --git a/src/config.py b/src/config.py
index 93c8c6716a..70692307f6 100644
--- a/src/config.py
+++ b/src/config.py
@@ -1213,7 +1213,7 @@ class AccountModificationWindow:
 						_('You are currently connected to the server'),
 						_('To change the account name, you must be disconnected.'))
 					return
-				if len(gajim.awaiting_events[self.account]):
+				if len(gajim.events.get_events(self.account)):
 					dialogs.ErrorDialog(_('Unread events'),
 						_('To change the account name, you must read all pending '
 						'events.'))
@@ -1322,7 +1322,6 @@ class AccountModificationWindow:
 		if name != self.account:
 			#update variables
 			gajim.interface.instances[name] = gajim.interface.instances[self.account]
-			gajim.awaiting_events[name] = gajim.awaiting_events[self.account]
 			gajim.nicks[name] = gajim.nicks[self.account]
 			gajim.block_signed_in_notifications[name] = \
 				gajim.block_signed_in_notifications[self.account]
@@ -1339,23 +1338,17 @@ class AccountModificationWindow:
 				gajim.status_before_autoaway[self.account]
 
 			gajim.contacts.change_account_name(self.account, name)
+			gajim.events.change_account_name(self.account, name)
 
-			#upgrade account variable in opened windows
+			# upgrade account variable in opened windows
 			for kind in ('infos', 'disco', 'chats', 'gc', 'gc_config'):
 				for j in gajim.interface.instances[name][kind]:
 					gajim.interface.instances[name][kind][j].account = name
 
-			#upgrade account in systray
-			if gajim.interface.systray_capabilities:
-				for list in gajim.interface.systray.jids:
-					if list[0] == self.account:
-						list[0] = name
-
 			# ServiceCache object keep old property account
 			if hasattr(gajim.connections[self.account], 'services_cache'):
 				gajim.connections[self.account].services_cache.account = name
 			del gajim.interface.instances[self.account]
-			del gajim.awaiting_events[self.account]
 			del gajim.nicks[self.account]
 			del gajim.block_signed_in_notifications[self.account]
 			del gajim.groups[self.account]
@@ -1780,7 +1773,7 @@ class AccountsWindow:
 		if not iter:
 			return
 		account = model.get_value(iter, 0).decode('utf-8')
-		if len(gajim.awaiting_events[account]):
+		if len(gajim.events.get_events(account)):
 			dialogs.ErrorDialog(_('Unread events'),
 				_('Read all pending events before removing this account.'))
 			return
@@ -2285,7 +2278,6 @@ class RemoveAccountWindow:
 		gajim.config.del_per('accounts', self.account)
 		gajim.interface.save_config()
 		del gajim.interface.instances[self.account]
-		del gajim.awaiting_events[self.account]
 		del gajim.nicks[self.account]
 		del gajim.block_signed_in_notifications[self.account]
 		del gajim.groups[self.account]
@@ -2911,7 +2903,6 @@ _('You can set advanced account options by pressing Advanced button, or later by
 		# update variables
 		gajim.interface.instances[self.account] = {'infos': {}, 'disco': {},
 			'chats': {}, 'gc': {}, 'gc_config': {}}
-		gajim.awaiting_events[self.account] = {}
 		gajim.connections[self.account].connected = 0
 		gajim.groups[self.account] = {}
 		gajim.contacts.add_account(self.account)
diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py
index fbd522cadc..29b2477115 100644
--- a/src/filetransfers_window.py
+++ b/src/filetransfers_window.py
@@ -445,12 +445,11 @@ _('Connection with peer cannot be established.'))
 				jid = gajim.get_jid_without_resource(other)
 			else: # It's a Contact instance
 				jid = other.jid
-			if gajim.awaiting_events[account].has_key(jid):
-				for event in gajim.awaiting_events[account][jid]:
-					if event[0] in ('file-error', 'file-completed',
-						'file-request-error', 'file-send-error', 'file-stopped') and \
-						event[1]['sid'] == file_props['sid']:
-						gajim.interface.remove_event(account, jid, event)
+			for ev_type in ('file-error', 'file-completed', 'file-request-error',
+			'file-send-error', 'file-stopped'):
+				for event in gajim.events.get_events(account, jid, [ev_type]):
+					if event.parameters[1]['sid'] == file_props['sid']:
+						gajim.events.remove_events(account, jid, event)
 		del(self.files_props[sid[0]][sid[1:]])
 		del(file_props)
 		
diff --git a/src/gajim.py b/src/gajim.py
index 978bc3fa61..d1f7fa7c94 100755
--- a/src/gajim.py
+++ b/src/gajim.py
@@ -389,7 +389,7 @@ class Interface:
 			else:
 				contact1 = gajim.contacts.get_first_contact_from_jid(account, ji)
 				if not contact1:
-					# presence of another resource of out jid
+					# presence of another resource of our jid
 					if resource == gajim.connections[account].server_resource:
 						return
 					contact1 = gajim.contacts.create_contact(jid = ji,
@@ -570,8 +570,8 @@ class Interface:
 
 		# Is it a first or next message received ?
 		first = False
-		if not chat_control and not gajim.awaiting_events[account].has_key(
-		jid_of_control):
+		if not chat_control and not gajim.events.get_events(account,
+		jid_of_control, ['chat']):
 			# It's a first message and not a Private Message
 			first = True
 
@@ -973,7 +973,7 @@ class Interface:
 				c = contacts[0]
 				self.roster.remove_contact(c, account)
 				gajim.contacts.remove_jid(account, jid)
-				if gajim.awaiting_events[account].has_key(c.jid):
+				if gajim.events.get_events(account, c.jid):
 					keyID = ''
 					attached_keys = gajim.config.get_per('accounts', account,
 						'attached_gpg_keys').split()
@@ -1108,60 +1108,48 @@ class Interface:
 				path_to_bw_file = path_to_file + '_notif_size_bw.png'
 				bwbuf.save(path_to_bw_file, 'png')
 
-	def add_event(self, account, jid, typ, args):
-		'''add an event to the awaiting_events var'''
-		# We add it to the awaiting_events queue
+	def add_event(self, account, jid, type_, args):
+		'''add an event to the gajim.events var'''
+		# We add it to the gajim.events queue
 		# Do we have a queue?
 		jid = gajim.get_jid_without_resource(jid)
-		qs = gajim.awaiting_events[account]
-		no_queue = False
-		if not qs.has_key(jid):
-			no_queue = True
-			qs[jid] = []
-		qs[jid].append((typ, args))
-		self.roster.nb_unread += 1
+		no_queue = len(gajim.events.get_events(account, jid)) == 0
+		event_type = None
+		# type_ can be gc-invitation file-send-error file-error file-request-error
+		# file-request file-completed file-stopped
+		# event_type can be in advancedNotificationWindow.events_list
+		event_types = {'file-request': 'ft_request',
+			'file-completed': 'ft_finished'}
+		if type_ in event_types:
+			event_type = event_types[type_]
+		show_in_roster = notify.get_show_in_roster(event_type, account, jid)
+		show_in_systray = notify.get_show_in_systray(event_type, account, jid)
+		event = gajim.events.create_event(type_, args,
+			show_in_roster = show_in_roster,
+			show_in_systray = show_in_systray)
+		gajim.events.add_event(account, jid, event)
 
 		self.roster.show_title()
 		if no_queue: # We didn't have a queue: we change icons
 			self.roster.draw_contact(jid, account)
-		if self.systray_capabilities:
-			self.systray.add_jid(jid, account, typ)
 
-	def redraw_roster_systray(self, account, jid, typ = None):
-		self.roster.nb_unread -= 1
-		self.roster.show_title()
-		self.roster.draw_contact(jid, account)
-		if self.systray_capabilities:
-			self.systray.remove_jid(jid, account, typ)
-
-	def remove_first_event(self, account, jid, typ = None):
-		qs = gajim.awaiting_events[account]
-		event = gajim.get_first_event(account, jid, typ)
-		qs[jid].remove(event)
-		# Is it the last event?
-		if not len(qs[jid]):
-			del qs[jid]
-			if not gajim.config.get('showoffline'):
-				contact = gajim.contacts.get_contact_with_highest_priority(account,
-					jid)
-				if contact:	
-					self.roster.really_remove_contact(contact, account)
-		self.redraw_roster_systray(account, jid, typ)
+	def remove_first_event(self, account, jid, type_ = None):
+		event = gajim.events.get_first_event(account, jid, type_)
+		self.remove_event(account, jid, event)
 
 	def remove_event(self, account, jid, event):
-		qs = gajim.awaiting_events[account]
-		if not event in qs[jid]:
+		if gajim.events.remove_events(account, jid, event):
+			# No such event found
 			return
-		qs[jid].remove(event)
-		# Is it the last event?
-		if not len(qs[jid]):
-			del qs[jid]
+		# no other event?
+		if not len(gajim.events.get_events(account, jid)):
 			if not gajim.config.get('showoffline'):
 				contact = gajim.contacts.get_contact_with_highest_priority(account,
 					jid)
-				if contact:		
+				if contact:	
 					self.roster.really_remove_contact(contact, account)
-		self.redraw_roster_systray(account, jid, event[0])
+		self.roster.show_title()
+		self.roster.draw_contact(jid, account)
 
 	def handle_event_file_request_error(self, account, array):
 		jid = array[0]
@@ -1187,7 +1175,7 @@ class Interface:
 		if helpers.allow_showing_notification(account):
 			# check if we should be notified
 			img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'ft_error.png')
-			
+
 			path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
 			event_type = _('File Transfer Error')
 			notify.popup(event_type, jid, account, msg_type, path,
@@ -1210,7 +1198,8 @@ class Interface:
 		if helpers.allow_showing_notification(account):
 			img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
 				'ft_request.png')
-			txt = _('%s wants to send you a file.') % gajim.get_name_from_jid(account, jid)
+			txt = _('%s wants to send you a file.') % gajim.get_name_from_jid(
+				account, jid)
 			path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
 			event_type = _('File Transfer Request')
 			notify.popup(event_type, jid, account, 'file-request',
@@ -1219,7 +1208,7 @@ class Interface:
 	def handle_event_file_progress(self, account, file_props):
 		self.instances['file_transfers'].set_progress(file_props['type'], 
 			file_props['sid'], file_props['received-len'])
-			
+
 	def handle_event_file_rcv_completed(self, account, file_props):
 		ft = self.instances['file_transfers']
 		if file_props['error'] == 0:
@@ -1245,13 +1234,14 @@ class Interface:
 
 		msg_type = ''
 		event_type = ''
-		if file_props['error'] == 0 and gajim.config.get('notify_on_file_complete'):
+		if file_props['error'] == 0 and gajim.config.get(
+		'notify_on_file_complete'):
 			msg_type = 'file-completed'
 			event_type = _('File Transfer Completed')
 		elif file_props['error'] == -1:
 			msg_type = 'file-stopped'
 			event_type = _('File Transfer Stopped')
-		
+
 		if event_type == '': 
 			# FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs)
 			# this should never happen but it does. see process_result() in socks5.py
@@ -1261,7 +1251,7 @@ class Interface:
 
 		if msg_type:
 			self.add_event(account, jid, msg_type, file_props)
-			
+
 		if file_props is not None:
 			if file_props['type'] == 'r':
 				# get the name of the sender, as it is in the roster
@@ -1712,14 +1702,14 @@ class Interface:
 				err_str)
 			sys.exit()
 
-	def handle_event(self, account, jid, typ):
+	def handle_event(self, account, jid, type_):
 		w = None
 		fjid = jid
 		resource = gajim.get_resource_from_jid(jid)
 		jid = gajim.get_jid_without_resource(jid)
-		if typ == message_control.TYPE_GC:
+		if type_ in ('printed_gc_msg', 'gc_msg'):
 			w = self.msg_win_mgr.get_window(jid, account)
-		elif typ == message_control.TYPE_CHAT:
+		elif type_ in ('printed_chat', 'chat'):
 			if self.msg_win_mgr.has_window(fjid, account):
 				w = self.msg_win_mgr.get_window(fjid, account)
 			else:
@@ -1729,30 +1719,30 @@ class Interface:
 				self.roster.new_chat(contact, account, resource = resource)
 				w = self.msg_win_mgr.get_window(fjid, account)
 				gajim.last_message_time[account][jid] = 0 # long time ago
-		elif typ == message_control.TYPE_PM:
+		elif type_ in ('printed_pm', 'pm'):
 			if self.msg_win_mgr.has_window(fjid, account):
 				w = self.msg_win_mgr.get_window(fjid, account)
 			else:
 				room_jid = jid
 				nick = resource
 				gc_contact = gajim.contacts.get_gc_contact(account, room_jid,
-										nick)
+					nick)
 				if gc_contact:
 					show = gc_contact.show
 				else:
 					show = 'offline'
-					gc_contact = gajim.contacts.create_gc_contact(room_jid = room_jid,
-						name = nick, show = show)
+					gc_contact = gajim.contacts.create_gc_contact(
+						room_jid = room_jid, name = nick, show = show)
 				c = gajim.contacts.contact_from_gc_contact(gc_contact)
 				self.roster.new_chat(c, account, private_chat = True)
 				w = self.msg_win_mgr.get_window(fjid, account)
-		elif typ in ('normal', 'file-request', 'file-request-error',
-			'file-send-error', 'file-error', 'file-stopped', 'file-completed'):
+		elif type_ in ('normal', 'file-request', 'file-request-error',
+		'file-send-error', 'file-error', 'file-stopped', 'file-completed'):
 			# Get the first single message event
-			ev = gajim.get_first_event(account, jid, typ)
+			event = gajim.events.get_first_event(account, jid, type_)
 			# Open the window
-			self.roster.open_event(account, jid, ev)
-		elif typ == 'gmail':
+			self.roster.open_event(account, jid, event)
+		elif type_ == 'gmail':
 			if gajim.config.get_per('accounts', account, 'savepass'):
 				url = ('http://www.google.com/accounts/ServiceLoginAuth?service=mail&Email=%s&Passwd=%s&continue=https://mail.google.com/mail') %\
 				(urllib.quote(gajim.config.get_per('accounts', account, 'name')),
@@ -1760,12 +1750,12 @@ class Interface:
 			else:
 				url = ('http://mail.google.com/')
 			helpers.launch_browser_mailer('url', url)
-		elif typ == 'gc-invitation':
-			ev = gajim.get_first_event(account, jid, typ)
-			data = ev[1]
+		elif type_ == 'gc-invitation':
+			event = gajim.events.get_first_event(account, jid, type_)
+			data = event.parameters
 			dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
 				data[1])
-			self.remove_first_event(account, jid, typ)
+			gajim.events.remove_events(account, jid, event)
 		if w:
 			w.set_active_tab(fjid, account)
 			w.window.present()
@@ -1860,7 +1850,6 @@ class Interface:
 			gajim.automatic_rooms[a] = {}
 			gajim.newly_added[a] = []
 			gajim.to_be_removed[a] = []
-			gajim.awaiting_events[a] = {}
 			gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name')
 			gajim.block_signed_in_notifications[a] = True
 			gajim.sleeper_state[a] = 0
diff --git a/src/groupchat_control.py b/src/groupchat_control.py
index 0817faca5e..5ad6c9c4ed 100644
--- a/src/groupchat_control.py
+++ b/src/groupchat_control.py
@@ -446,10 +446,7 @@ class GroupchatControl(ChatControlBase):
 	def on_private_message(self, nick, msg, tim):
 		# Do we have a queue?
 		fjid = self.room_jid + '/' + nick
-		qs = gajim.awaiting_events[self.account]
-		no_queue = True
-		if qs.has_key(fjid):
-			no_queue = False
+		no_queue = len(gajim.events.get_events(self.account, fjid)) == 0
 
 		# We print if window is opened
 		pm_control = gajim.interface.msg_win_mgr.get_control(fjid, self.account)
@@ -457,9 +454,9 @@ class GroupchatControl(ChatControlBase):
 			pm_control.print_conversation(msg, tim = tim)
 			return
 
-		if no_queue:
-			qs[fjid] = []
-		qs[fjid].append(('chat', (msg, '', 'incoming', tim, False, '')))
+		event = gajim.events.create_event('chat', (msg, '', 'incoming', tim,
+			False, '', None))
+		gajim.events.add_event(self.account, fjid, event)
 
 		autopopup = gajim.config.get('autopopup')
 		autopopupaway = gajim.config.get('autopopupaway')
@@ -474,8 +471,6 @@ class GroupchatControl(ChatControlBase):
 						self.room_jid, icon_name = 'message')
 				image = state_images['message']
 				model[iter][C_IMG] = image
-				if gajim.interface.systray_capabilities:
-					gajim.interface.systray.add_jid(fjid, self.account, 'pm')
 			self.parent_win.show_title()
 		else:
 			self._start_private_message(nick)
@@ -697,7 +692,7 @@ class GroupchatControl(ChatControlBase):
 		model = self.list_treeview.get_model()
 		gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
 		state_images = gajim.interface.roster.jabber_state_images['16']
-		if gajim.awaiting_events[self.account].has_key(self.room_jid + '/' + nick):
+		if len(gajim.events.get_events(self.account, self.room_jid + '/' + nick)):
 			image = state_images['message']
 		else:
 			image = state_images[gc_contact.show]
@@ -801,7 +796,8 @@ class GroupchatControl(ChatControlBase):
 						os.rename(old_file, files[old_file])
 				self.print_conversation(s, 'info')
 
-			if not gajim.awaiting_events[self.account].has_key(self.room_jid + '/' + nick):
+			if len(gajim.events.get_events(self.account,
+			self.room_jid + '/' + nick)) == 0:
 				self.remove_contact(nick)
 			else:
 				c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
@@ -1291,9 +1287,8 @@ class GroupchatControl(ChatControlBase):
 		nb = 0
 		for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
 			fjid = self.room_jid + '/' + nick
-			if gajim.awaiting_events[self.account].has_key(fjid):
-				# gc can only have messages as event
-				nb += len(gajim.awaiting_events[self.account][fjid])
+			nb += len(gajim.events.get_events(self.account, fjid))
+			# gc can only have messages as event
 		return nb
 
 	def _on_change_subject_menuitem_activate(self, widget):
diff --git a/src/message_control.py b/src/message_control.py
index 728c609227..1c1b0aa50c 100644
--- a/src/message_control.py
+++ b/src/message_control.py
@@ -39,7 +39,6 @@ class MessageControl:
 		self.account = account
 		self.hide_chat_buttons_always = False
 		self.hide_chat_buttons_current = False
-		self.nb_unread = 0
 		self.resource = resource
 
 		gajim.last_message_time[self.account][self.get_full_jid()] = 0
@@ -117,10 +116,7 @@ class MessageControl:
 		pass
 
 	def get_specific_unread(self):
-		n = 0
-		if gajim.awaiting_events[self.account].has_key(self.contact.jid):
-			n = len(gajim.awaiting_events[self.account][self.contact.jid])
-		return n
+		return len(gajim.events.get_events(self.account, self.contact.jid))
 
 	def send_message(self, message, keyID = '', type = 'chat',
 	chatstate = None, msg_id = None, composing_jep = None, resource = None,
diff --git a/src/message_window.py b/src/message_window.py
index 61cf4b7137..f190956c8a 100644
--- a/src/message_window.py
+++ b/src/message_window.py
@@ -224,7 +224,7 @@ class MessageWindow:
 				gajim.config.get('notify_on_all_muc_messages') and not \
 				ctrl.attention_flag:
 				continue
-			unread += ctrl.nb_unread
+			unread += ctrl.get_nb_unread()
 
 		unread_str = ''
 		if unread > 1:
@@ -280,9 +280,8 @@ class MessageWindow:
 			ctrl.shutdown()
 
 		# Update external state
-		if gajim.interface.systray_capabilities:
-			gajim.interface.systray.remove_jid(ctrl.get_full_jid(), ctrl.account,
-				ctrl.type_id)
+		gajim.events.remove_events(ctrl.account, ctrl.get_full_jid,
+			types = ['printed_msg', 'chat', 'gc_msg'])
 		del gajim.last_message_time[ctrl.account][ctrl.get_full_jid()]
 
 		self.disconnect_tab_dnd(ctrl.widget)
@@ -432,7 +431,7 @@ class MessageWindow:
 				if ind < 0:
 					ind = self.notebook.get_n_pages() - 1
 			ctrl = self.get_control(ind, None)
-			if ctrl.nb_unread > 0:
+			if ctrl.get_nb_unread() > 0:
 				found = True
 				break # found
 			elif gajim.config.get('ctrl_tab_go_to_next_composing') : # Search for a composing contact
diff --git a/src/notify.py b/src/notify.py
index 4f7818a02f..e4578e072c 100644
--- a/src/notify.py
+++ b/src/notify.py
@@ -32,7 +32,37 @@ if dbus_support.supported:
 		import dbus.glib
 		import dbus.service
 
+def get_show_in_roster(event, account, contact):
+	'''Return True if this event must be shown in roster, else False'''
+	num = get_advanced_notification(event, account, contact)
+	if num != None:
+		if gajim.config.get_per('notifications', str(num), 'roster') == 'yes':
+			return True
+		if gajim.config.get_per('notifications', str(num), 'roster') == 'no':
+			return False
+	if event == 'message_received':
+		chat_control = helpers.get_chat_control(account, contact)
+		if not chat_control:
+			return True
+	elif event == 'ft_request':
+		return True
+	return False
+
+def get_show_in_systray(event, account, contact):
+	'''Return True if this event must be shown in roster, else False'''
+	num = get_advanced_notification(event, account, contact)
+	if num != None:
+		if gajim.config.get_per('notifications', str(num), 'systray') == 'yes':
+			return True
+		if gajim.config.get_per('notifications', str(num), 'systray') == 'no':
+			return False
+	if event in ('message_received', 'ft_request', 'gc_msg_highlight',
+	'ft_request'):
+		return True
+	return False
+
 def get_advanced_notification(event, account, contact):
+	'''Returns the number of the first advanced notification or None'''
 	num = 0
 	notif = gajim.config.get_per('notifications', str(num))
 	while notif:
@@ -68,26 +98,7 @@ def get_advanced_notification(event, account, contact):
 			if tab_opened == 'both':
 				tab_opened_ok = True
 			else:
-				chat_control = False
-				full_jid_with_resource = contact.jid
-				if contact.resource:
-					full_jid_with_resource += '/' + contact.resource
-				highest_contact = gajim.contacts.get_contact_with_highest_priority(
-					account, contact.jid)
-				# Look for a chat control that has the given resource, or default to
-				# one without resource
-				if gajim.interface.msg_win_mgr.get_control(full_jid_with_resource,
-				account):
-					chat_control = True
-				elif not highest_contact or not highest_contact.resource:
-					# unknow contact or offline message
-					if gajim.interface.msg_win_mgr.get_control(contact.jid, account):
-						chat_control = True
-				elif highest_contact and contact.resource != \
-				highest_contact.resource:
-					chat_control = False
-				elif gajim.interface.msg_win_mgr.get_control(contact.jid, account):
-					chat_control = True
+				chat_control = helper.get_chat_control(account, contact)
 				if (chat_control and tab_opened == 'yes') or (not chat_control and \
 				tab_opened == 'no'):
 					tab_opened_ok = True
diff --git a/src/remote_control.py b/src/remote_control.py
index 64deb55b04..802bf64c8f 100644
--- a/src/remote_control.py
+++ b/src/remote_control.py
@@ -596,7 +596,7 @@ class SignalObject(DbusPrototype):
 		return contact_dict
 
 	def get_unread_msgs_number(self, *args):
-		return str(gajim.interface.roster.nb_unread)
+		return str(gajim.events.get_nb_events)
 
 	def start_chat(self, *args):
 		[account] = self._get_real_arguments(args, 1)
diff --git a/src/roster_window.py b/src/roster_window.py
index 0c3391f0d9..6b1330d74a 100644
--- a/src/roster_window.py
+++ b/src/roster_window.py
@@ -29,6 +29,7 @@ import gtkgui_helpers
 import cell_renderer_image
 import tooltips
 import message_control
+import notify
 
 from common import gajim
 from common import helpers
@@ -263,7 +264,7 @@ class RosterWindow:
 			if big_brother_jid != jid or big_brother_account != account:
 				# We are adding a child contact
 				if contact.show in ('offline', 'error') and \
-				not showOffline and not gajim.awaiting_events[account].has_key(jid):
+				not showOffline and len(gajim.events.get_events(account, jid)) == 0:
 					return
 				parent_iters = self.get_contact_iter(big_brother_jid,
 					big_brother_account)
@@ -281,7 +282,7 @@ class RosterWindow:
 		if (contact.show in ('offline', 'error') or hide) and \
 			not showOffline and (not _('Transports') in contact.groups or \
 			gajim.connections[account].connected < 2) and \
-			not gajim.awaiting_events[account].has_key(jid) and \
+			len(gajim.events.get_events(account, jid)) == 0 and \
 			not _('Not in Roster') in contact.groups:
 			return
 
@@ -355,7 +356,7 @@ class RosterWindow:
 			return
 		showOffline = gajim.config.get('showoffline')
 		if (contact.show in ('offline', 'error')) and not showOffline and \
-			not gajim.awaiting_events[account].has_key(jid):
+			len(gajim.events.get_events(account, jid)) == 0:
 			return
 
 		model = self.tree.get_model()
@@ -396,7 +397,7 @@ class RosterWindow:
 		if (contact.show in ('offline', 'error') or hide) and \
 			not showOffline and (not _('Transports') in contact.groups or \
 			gajim.connections[account].connected < 2) and \
-			not gajim.awaiting_events[account].has_key(contact.jid):
+			len(gajim.events.get_events(account, contact.jid)) == 0:
 			self.remove_contact(contact, account)
 		else:
 			self.draw_contact(contact.jid, account)
@@ -506,7 +507,7 @@ class RosterWindow:
 
 		iter = iters[0] # choose the icon with the first iter
 		icon_name = helpers.get_icon_name_to_show(contact, account)
-		# look if anotherresource has awaiting events
+		# look if another resource has awaiting events
 		for c in contact_instances:
 			c_icon_name = helpers.get_icon_name_to_show(c, account)
 			if c_icon_name == 'message':
@@ -530,7 +531,7 @@ class RosterWindow:
 					# a child has awaiting messages ?
 					child_jid = model[child_iter][C_JID].decode('utf-8')
 					child_account = model[child_iter][C_ACCOUNT].decode('utf-8')
-					if gajim.awaiting_events[child_account].has_key(child_jid):
+					if len(gajim.events.get_events(child_account, child_jid)):
 						icon_name = 'message'
 						break
 					child_iter = model.iter_next(child_iter)
@@ -1061,7 +1062,7 @@ class RosterWindow:
 		contact.show = show
 		contact.status = status
 		if show in ('offline', 'error') and \
-		not gajim.awaiting_events[account].has_key(contact.jid):
+		len(gajim.events.get_events(account, contact.jid)) == 0:
 			if len(contact_instances) > 1:
 				# if multiple resources
 				gajim.contacts.remove_contact(account, contact)
@@ -2077,7 +2078,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 					contact.sub = 'from'
 					gajim.contacts.add_contact(account, contact)
 					self.add_contact_to_roster(contact.jid, account)
-				elif gajim.awaiting_events[account].has_key(contact.jid):
+				elif len(gajim.events.get_events(account, contact.jid)):
 					need_readd = True
 				elif gajim.interface.msg_win_mgr.has_window(contact.jid, account):
 					if _('Not in Roster') in contact.groups:
@@ -2397,7 +2398,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 
 		mw.new_tab(chat_control)
 
-		if gajim.awaiting_events[account].has_key(fjid):
+		if len(gajim.events.get_events(account, fjid)):
 			# We call this here to avoid race conditions with widget validation
 			chat_control.read_queue()
 
@@ -2493,10 +2494,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 				resource_for_chat = None
 
 		# Do we have a queue?
-		qs = gajim.awaiting_events[account]
-		no_queue = True
-		if qs.has_key(fjid):
-			no_queue = False
+		no_queue = len(gajim.events.get_events(account, fjid)) == 0
 
 		popup = helpers.allow_popup_window(account, advanced_notif_num)
 
@@ -2518,14 +2516,17 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			return
 
 		# We save it in a queue
-		if no_queue:
-			qs[fjid] = []
-		kind = 'chat'
+		type_ = 'chat'
 		if msg_type == 'normal':
-			kind = 'normal'
-		qs[fjid].append((kind, (msg, subject, msg_type, tim, encrypted,
-			resource, msg_id)))
-		self.nb_unread += 1
+			type_ = 'normal'
+		show_in_roster = notify.get_show_in_roster('message_received', account,
+			contact)
+		show_in_systray = notify.get_show_in_systray('message_received', account,
+			contact)
+		event = gajim.events.create_event(type_, (msg, subject, msg_type, tim,
+			encrypted, resource, msg_id), show_in_roster = show_in_roster,
+			show_in_systray = show_in_systray)
+		gajim.events.add_event(account, fjid, event)
 		if popup:
 			if not ctrl:
 				self.new_chat(contact, account, resource = resource_for_chat)
@@ -2555,8 +2556,6 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			self.tree.expand_row(path[0:2], False)
 			self.tree.scroll_to_cell(path)
 			self.tree.set_cursor(path)
-		if gajim.interface.systray_capabilities:
-			gajim.interface.systray.add_jid(fjid, account, kind, advanced_notif_num)
 
 	def on_preferences_menuitem_activate(self, widget):
 		if gajim.interface.instances.has_key('preferences'):
@@ -2721,14 +2720,14 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			# check if we have unread or recent mesages
 			unread = False
 			recent = False
-			if self.nb_unread > 0:
+			if gajim.events.get_nb_events() > 0:
 				unread = True
 			for win in gajim.interface.msg_win_mgr.windows():
 				unrd = 0
 				for ctrl in win.controls():
 					if ctrl.type_id == message_control.TYPE_GC:
 						if gajim.config.get('notify_on_all_muc_messages'):
-							unrd += ctrl.nb_unread
+							unrd += ctrl.get_nb_unread()
 						else:
 							if ctrl.attention_flag:
 								unrd += 1
@@ -2765,33 +2764,30 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 
 	def open_event(self, account, jid, event):
 		'''If an event was handled, return True, else return False'''
-		if not event:
-			return False 
-		typ = event[0]
-		data = event[1]
+		data = event.parameters
 		ft = gajim.interface.instances['file_transfers']
-		if typ == 'normal':
+		if event.type_ == 'normal':
 			dialogs.SingleMessageWindow(account, jid,
 				action = 'receive', from_whom = jid, subject = data[1],
 				message = data[0], resource = data[5])
-			gajim.interface.remove_first_event(account, jid, typ)
+			gajim.interface.remove_first_event(account, jid, event.type_)
 			return True
-		elif typ == 'file-request':
+		elif event.type_ == 'file-request':
 			contact = gajim.contacts.get_contact_with_highest_priority(account,
 				jid)
-			gajim.interface.remove_first_event(account, jid, typ)
+			gajim.interface.remove_first_event(account, jid, event.type_)
 			ft.show_file_request(account, contact, data)
 			return True
-		elif typ in ('file-request-error', 'file-send-error'):
-			gajim.interface.remove_first_event(account, jid, typ)
+		elif event.type_ in ('file-request-error', 'file-send-error'):
+			gajim.interface.remove_first_event(account, jid, event.type_)
 			ft.show_send_error(data)
 			return True
-		elif typ in ('file-error', 'file-stopped'):
-			gajim.interface.remove_first_event(account, jid, typ)
+		elif event.type_ in ('file-error', 'file-stopped'):
+			gajim.interface.remove_first_event(account, jid, event.type_)
 			ft.show_stopped(jid, data)
 			return True
-		elif typ == 'file-completed':
-			gajim.interface.remove_first_event(account, jid, typ)
+		elif event.type_ == 'file-completed':
+			gajim.interface.remove_first_event(account, jid, event.type_)
 			ft.show_completed(jid, data)
 			return True
 		return False
@@ -2825,12 +2821,12 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			else:
 				self.tree.expand_row(path, False)
 		else:
-			first_ev = gajim.get_first_event(account, jid)
+			first_ev = gajim.events.get_first_event(account, jid)
 			if not first_ev:
 				# look in other resources
 				for c in gajim.contacts.get_contact(account, jid):
 					fjid = c.get_full_jid()
-					first_ev = gajim.get_first_event(account, fjid)
+					first_ev = gajim.events.get_first_event(account, fjid)
 					if first_ev:
 						resource = c.resource
 						break
@@ -2838,7 +2834,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 				child_iter = model.iter_children(iter)
 				while not first_ev and child_iter:
 					child_jid = model[child_iter][C_JID].decode('utf-8')
-					first_ev = gajim.get_first_event(account, child_jid)
+					first_ev = gajim.events.get_first_event(account, child_jid)
 					if first_ev:
 						jid = child_jid
 					else:
@@ -3088,8 +3084,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 				model[iter][1] = self.jabber_state_images['16'][model[iter][2]]
 			iter = model.iter_next(iter)
 		# Update the systray
-		if gajim.interface.systray_enabled:
-			gajim.interface.systray.set_img()
+		gajim.interface.systray.set_img()
 
 		for win in gajim.interface.msg_win_mgr.windows():
 			for ctrl in win.controls():
@@ -3565,13 +3560,14 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		change_title_allowed = gajim.config.get('change_roster_title')
 		if change_title_allowed:
 			start = ''
-			if self.nb_unread > 1:
-				start = '[' + str(self.nb_unread) + ']  '
-			elif self.nb_unread == 1:
+			nb_unread = gajim.events.get_nb_events()
+			if nb_unread > 1:
+				start = '[' + str(nb_unread) + ']  '
+			elif nb_unread == 1:
 				start = '*  '
 			self.window.set_title(start + 'Gajim')
 
-		gtkgui_helpers.set_unset_urgency_hint(self.window, self.nb_unread)
+		gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread)
 
 	def iter_is_separator(self, model, iter):
 		if model[iter][0] == 'SEPARATOR':
@@ -3640,7 +3636,6 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		self.transports_state_images = {'16': {}, '32': {}, 'opened': {},
 			'closed': {}}
 		
-		self.nb_unread = 0 # number of unread messages
 		self.last_save_dir = None
 		self.editing_path = None  # path of row with cell in edit mode
 		self.add_new_contact_handler_id = False
diff --git a/src/systray.py b/src/systray.py
index 2599224e02..6c5482d3c6 100644
--- a/src/systray.py
+++ b/src/systray.py
@@ -47,7 +47,6 @@ class Systray:
 	for trayicon in GNU/Linux'''
 
 	def __init__(self):
-		self.jids = [] # Contain things like [account, jid, type_of_msg]
 		self.single_message_handler_id = None
 		self.new_chat_handler_id = None
 		self.t = None
@@ -58,14 +57,10 @@ class Systray:
 		self.xml.signal_autoconnect(self)
 		self.popup_menus = []
 
-	def set_img(self, advanced_notif_num = None):
+	def set_img(self):
 		if not gajim.interface.systray_enabled:
 			return
-		if advanced_notif_num:
-			if gajim.config.get_per('notifications', str(advanced_notif_num),
-			'systray') == 'no':
-				return
-		if len(self.jids) > 0:
+		if gajim.events.get_nb_systray_events():
 			state = 'message'
 		else:
 			state = self.status
@@ -75,19 +70,6 @@ class Systray:
 		elif image.get_storage_type() == gtk.IMAGE_PIXBUF:
 			self.img_tray.set_from_pixbuf(image.get_pixbuf())
 
-	def add_jid(self, jid, account, typ, advanced_notif_num = None):
-		l = [account, jid, typ]
-		# We can keep several single message because we open them one by one
-		if not l in self.jids or typ == 'normal':
-			self.jids.append(l)
-			self.set_img(advanced_notif_num)
-
-	def remove_jid(self, jid, account, typ):
-		l = [account, jid, typ]
-		if l in self.jids:
-			self.jids.remove(l)
-			self.set_img()
-
 	def change_status(self, global_status):
 		''' set tray image to 'global_status' '''
 		# change image and status, only if it is different 
@@ -244,8 +226,11 @@ class Systray:
 		self.systray_context_menu.show_all()
 
 	def on_show_all_events_menuitem_activate(self, widget):
-		for i in range(len(self.jids)):
-			self.handle_first_event()
+		events = gajim.events.get_systray_events()
+		for account in events:
+			for jid in events[account]:
+				for event in events[account][jid]:
+					gajim.interface.handle_event(account, jid, event.type_)
 
 	def on_show_roster_menuitem_activate(self, widget):
 		win = gajim.interface.roster.window
@@ -262,7 +247,7 @@ class Systray:
 
 	def on_left_click(self):
 		win = gajim.interface.roster.window
-		if len(self.jids) == 0:
+		if len(gajim.events.get_systray_events()) == 0:
 			# no pending events, so toggle visible/hidden for roster window
 			if win.get_property('visible'): # visible in ANY virtual desktop?
 				win.hide() # we hide it from VD that was visible in
@@ -276,10 +261,8 @@ class Systray:
 			self.handle_first_event()
 
 	def handle_first_event(self):
-		account = self.jids[0][0]
-		jid = self.jids[0][1]
-		typ = self.jids[0][2]
-		gajim.interface.handle_event(account, jid, typ)
+		account, jid, event = gajim.events.get_first_systray_event()
+		gajim.interface.handle_event(account, jid, event.type_)
 
 	def on_middle_click(self):
 		'''middle click raises window to have complete focus (fe. get kbd events)
diff --git a/src/systraywin32.py b/src/systraywin32.py
index 54b8e0c1e9..c758247e01 100644
--- a/src/systraywin32.py
+++ b/src/systraywin32.py
@@ -245,49 +245,10 @@ class SystrayWin32(systray.Systray):
 		elif lparam == win32con.WM_LBUTTONUP: # Left click
 			self.on_left_click()
 
-	def add_jid(self, jid, account, typ):
-		systray.Systray.add_jid(self, jid, account, typ)
-
-		nb = gajim.interface.roster.nb_unread
-		for acct in gajim.connections:
-			# in chat / groupchat windows
-			for kind in ('chats', 'gc'):
-				jids = gajim.interface.instances[acct][kind]
-				for jid in jids:
-					if jid != 'tabbed':
-						nb += jids[jid].nb_unread[jid]
-		
-		text = i18n.ngettext(
-					'Gajim - %d unread message',
-					'Gajim - %d unread messages',
-					nb, nb, nb)
-
-		self.systray_winapi.notify_icon.set_tooltip(text)
-
-	def remove_jid(self, jid, account, typ):
-		systray.Systray.remove_jid(self, jid, account, typ)
-
-		nb = gajim.interface.roster.nb_unread
-		for acct in gajim.connections:
-			# in chat / groupchat windows
-			for kind in ('chats', 'gc'):
-				for jid in gajim.interface.instances[acct][kind]:
-					if jid != 'tabbed':
-						nb += gajim.interface.instances[acct][kind][jid].nb_unread[jid]
-		
-		if nb > 0:
-			text = i18n.ngettext(
-					'Gajim - %d unread message',
-					'Gajim - %d unread messages',
-					nb, nb, nb)
-		else:
-			text = 'Gajim'
-		self.systray_winapi.notify_icon.set_tooltip(text)
-
 	def set_img(self):
 		self.tray_ico_imgs = self.load_icos() #FIXME: do not do this here
 		# see gajim.interface.roster.reload_jabber_state_images() to merge
-		
+
 		if len(self.jids) > 0:
 			state = 'message'
 		else:
@@ -295,12 +256,23 @@ class SystrayWin32(systray.Systray):
 		hicon = self.tray_ico_imgs[state]
 		if hicon is None:
 			return
-		
+
 		self.systray_winapi.remove_notify_icon()
 		self.systray_winapi.add_notify_icon(self.systray_context_menu, hicon,
 			'Gajim')
 		self.systray_winapi.notify_icon.menu = self.systray_context_menu
 
+		nb = gajim.events.get_nb_systray_events()
+
+		if nb > 0:
+			text = i18n.ngettext(
+					'Gajim - %d unread message',
+					'Gajim - %d unread messages',
+					nb, nb, nb)
+		else:
+			text = 'Gajim'
+		self.systray_winapi.notify_icon.set_tooltip(text)
+
 	def load_icos(self):
 		'''load .ico files and return them to a dic of SHOW --> img_obj'''
 		iconset = str(gajim.config.get('iconset'))
diff --git a/src/tooltips.py b/src/tooltips.py
index 6b95561f3e..53915df8ab 100644
--- a/src/tooltips.py
+++ b/src/tooltips.py
@@ -282,34 +282,14 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable):
 		self.table.set_property('column-spacing', 1)
 		text, single_line = '', ''
 
-		unread_chat = gajim.interface.roster.nb_unread
-		unread_single_chat = 0
-		unread_gc = 0
-		unread_pm = 0
+		unread_chat = gajim.events.get_nb_events(types = ['printed_chat', 'chat'])
+		unread_single_chat = gajim.events.get_nb_events(types = ['normal'])
+		unread_gc = gajim.events.get_nb_events(types = ['printed_gc_msg',
+			'gc_msg'])
+		unread_pm = gajim.events.get_nb_events(types = ['printed_pm', 'pm'])
 
 		accounts = self.get_accounts_info()
 
-		for acct in gajim.connections:
-			# Count unread chat messages
-			chat_t = message_control.TYPE_CHAT
-			for ctrl in gajim.interface.msg_win_mgr.get_controls(chat_t, acct):
-				unread_chat += ctrl.nb_unread
-
-			# Count unread PM messages for which we have a control
-			chat_t = message_control.TYPE_PM
-			for ctrl in gajim.interface.msg_win_mgr.get_controls(chat_t, acct):
-				unread_pm += ctrl.nb_unread
-
-			# we count unread gc/pm messages
-			chat_t = message_control.TYPE_GC
-			for ctrl in gajim.interface.msg_win_mgr.get_controls(chat_t, acct):
-				# These are PMs for which the PrivateChatControl has not yet been
-				# created
-				pm_msgs = ctrl.get_specific_unread()
-				unread_gc += ctrl.nb_unread
-				unread_gc -= pm_msgs
-				unread_pm += pm_msgs
-
 		if unread_chat or unread_single_chat or unread_gc or unread_pm:
 			text = 'Gajim '
 			awaiting_events = unread_chat + unread_single_chat + unread_gc + unread_pm
@@ -508,8 +488,9 @@ class RosterTooltip(NotificationAreaTooltip):
 		properties = []
 		jid_markup = '<span weight="bold">' + prim_contact.jid + '</span>'
 		properties.append((jid_markup, None))
+
 		properties.append((_('Name: '), gtkgui_helpers.escape_for_pango_markup(
-												prim_contact.get_shown_name())))
+			prim_contact.get_shown_name())))
 		if prim_contact.sub:
 			properties.append(( _('Subscription: '), 
 				gtkgui_helpers.escape_for_pango_markup(helpers.get_uf_sub(prim_contact.sub))))
@@ -532,10 +513,11 @@ class RosterTooltip(NotificationAreaTooltip):
 					contacts_dict[contact.priority].append(contact)
 				else:
 					contacts_dict[contact.priority] = [contact]
-		
-		if num_resources== 1 and contact.resource:
-			properties.append((_('Resource: '),	gtkgui_helpers.escape_for_pango_markup(
-							contact.resource) + ' (' + unicode(contact.priority) + ')'))
+
+		if num_resources == 1 and contact.resource:
+			properties.append((_('Resource: '),
+				gtkgui_helpers.escape_for_pango_markup(contact.resource) + ' (' + \
+				unicode(contact.priority) + ')'))
 		if num_resources > 1:
 			properties.append((_('Status: '),	' '))
 			contact_keys = contacts_dict.keys()
-- 
GitLab