summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorent Le Coz <louiz@louiz.org>2013-01-26 05:22:12 +0100
committerFlorent Le Coz <louiz@louiz.org>2013-01-26 05:35:07 +0100
commitdd2a6d1d6552476db671ad77d55d549122947954 (patch)
treef77d56b9dc6b72733f18db4655bd62cb737b7c54
parent61f469bc128203a39a894f770bca67c18ad11a3e (diff)
downloadpoezio-dd2a6d1d6552476db671ad77d55d549122947954.tar.gz
poezio-dd2a6d1d6552476db671ad77d55d549122947954.tar.bz2
poezio-dd2a6d1d6552476db671ad77d55d549122947954.tar.xz
poezio-dd2a6d1d6552476db671ad77d55d549122947954.zip
Implement XEP 296 for locking resource in conversations.
With a few specific behaviours: When manually opening a conversation with a bare jid, we open a normal conversation that follows the XEP (locked and unlocked accordingly). If the user manually opens a conversation with a fulljid (by selecting a specific resource in the roster, or by specifying a fulljid to the /message command), we open a special tab that doesn’t follow the XEP (it is always locked to the same resource, and cannot be unlocked). When a message is received, unless a special tab has been manually opened by the other with that specific resource, we always send the messages to a uniq normal tab, unlocking or locking it according to the XEP. This means that only one tab can be opened with a given contact, unless the user specifically chooses to open a special tab for a specific resource. fixes #2159
-rw-r--r--src/core.py34
-rw-r--r--src/tabs.py113
-rw-r--r--src/theming.py1
-rw-r--r--src/windows.py19
4 files changed, 134 insertions, 33 deletions
diff --git a/src/core.py b/src/core.py
index 7b536faa..1bb3d071 100644
--- a/src/core.py
+++ b/src/core.py
@@ -654,15 +654,17 @@ class Core(object):
and return it. Otherwise, we return None
"""
jid = safeJID(jid)
- # We first check if we have a conversation opened with this precise resource
- conversation = self.get_tab_by_name(jid.full, tabs.ConversationTab)
+ # We first check if we have a static conversation opened with this precise resource
+ conversation = self.get_tab_by_name(jid.full, tabs.StaticConversationTab)
if not conversation:
# If not, we search for a conversation with the bare jid
- conversation = self.get_tab_by_name(jid.bare, tabs.ConversationTab)
+ conversation = self.get_tab_by_name(jid.bare, tabs.DynamicConversationTab)
if not conversation:
if create:
- # We create the conversation with the full Jid if nothing was found
- conversation = self.open_conversation_window(jid.full, False)
+ # We create a dynamic conversation with the bare Jid if
+ # nothing was found (and we lock it to the resource
+ # later)
+ conversation = self.open_conversation_window(jid.bare, False)
else:
conversation = None
return conversation
@@ -869,15 +871,14 @@ class Core(object):
def open_conversation_window(self, jid, focus=True):
"""
- Open a new conversation tab and focus it if needed
+ Open a new conversation tab and focus it if needed. If a resource is
+ provided, we open a StaticConversationTab, else a
+ DynamicConversationTab
"""
- for tab in self.tabs: # if the room exists, focus it and return
- if isinstance(tab, tabs.ConversationTab):
- if tab.get_name() == jid:
- self.command_win('%s' % tab.nb)
- return tab
- new_tab = tabs.ConversationTab(jid)
- # insert it in the rooms
+ if safeJID(jid).resource:
+ new_tab = tabs.StaticConversationTab(jid)
+ else:
+ new_tab = tabs.DynamicConversationTab(jid)
if not focus:
new_tab.state = "private"
self.add_tab(new_tab, focus)
@@ -2523,6 +2524,8 @@ class Core(object):
body = xhtml.get_body_from_message_stanza(message)
if not body:
return
+ if isinstance(conversation, tabs.DynamicConversationTab):
+ conversation.lock(jid.resource)
if jid.bare in roster:
remote_nick = roster[jid.bare].name or jid.user
else:
@@ -2692,6 +2695,8 @@ class Core(object):
return False
self.events.trigger('normal_chatstate', message, tab)
tab.chatstate = state
+ if state == 'gone' and isinstance(tab, tabs.DynamicConversationTab):
+ tab.unlock()
if tab == self.current_tab():
tab.refresh_info_header()
self.doupdate()
@@ -2802,6 +2807,9 @@ class Core(object):
return
jid = presence['from']
contact = roster[jid.bare]
+ tab = self.get_conversation_by_jid(jid, create=False)
+ if isinstance(tab, tabs.DynamicConversationTab):
+ tab.unlock()
if contact is None:
return
self.events.trigger('normal_presence', presence, contact[jid.full])
diff --git a/src/tabs.py b/src/tabs.py
index 2e39988d..fb47591b 100644
--- a/src/tabs.py
+++ b/src/tabs.py
@@ -525,7 +525,7 @@ class ChatTab(Tab):
self.core.information('Could not send custom xhtml', 'Error')
return
- msg = self.core.xmpp.make_message(self.get_name())
+ msg = self.core.xmpp.make_message(self.get_dest_jid())
msg['body'] = body
msg.enable('html')
msg['html']['body'] = arg
@@ -536,6 +536,9 @@ class ChatTab(Tab):
self.refresh()
msg.send()
+ def get_dest_jid(self):
+ return self.get_name()
+
@refresh_wrapper.always
def command_clear(self, args):
"""
@@ -551,7 +554,7 @@ class ChatTab(Tab):
if not isinstance(self, MucTab) or self.joined:
if state in ('active', 'inactive', 'gone') and self.inactive and not always_send:
return
- msg = self.core.xmpp.make_message(self.get_name())
+ msg = self.core.xmpp.make_message(self.get_dest_jid())
msg['type'] = self.message_type
msg['chat_state'] = state
self.chat_state = state
@@ -2883,6 +2886,7 @@ class RosterInfoTab(Tab):
class ConversationTab(ChatTab):
"""
The tab containg a normal conversation (not from a MUC)
+ Must not be instantiated, use Static or Dynamic version only.
"""
plugin_commands = {}
plugin_keys = {}
@@ -2895,7 +2899,6 @@ class ConversationTab(ChatTab):
self.text_win = windows.TextWin()
self._text_buffer.add_window(self.text_win)
self.upper_bar = windows.ConversationStatusMessageWin()
- self.info_header = windows.ConversationInfoWin()
self.input = windows.MessageInput()
self.check_attention()
# keys
@@ -2938,7 +2941,7 @@ class ConversationTab(ChatTab):
self.complete_commands(self.input)
def command_say(self, line, attention=False, correct=False):
- msg = self.core.xmpp.make_message(self.get_name())
+ msg = self.core.xmpp.make_message(self.get_dest_jid())
msg['type'] = 'chat'
msg['body'] = line
# trigger the event BEFORE looking for colors.
@@ -2966,7 +2969,7 @@ class ConversationTab(ChatTab):
self.core.events.trigger('conversation_say_after', msg, self)
self.last_sent_message = msg
msg.send()
- logger.log_message(safeJID(self.get_name()).bare, self.core.own_nick, line)
+ logger.log_message(safeJID(self.get_dest_jid()).bare, self.core.own_nick, line)
self.cancel_paused_delay()
self.text_win.refresh()
self.input.refresh()
@@ -3007,8 +3010,8 @@ class ConversationTab(ChatTab):
@refresh_wrapper.conditional
def command_info(self, arg):
- contact = roster[self.get_name()]
- jid = safeJID(self.get_name())
+ contact = roster[self.get_dest_jid()]
+ jid = safeJID(self.get_dest_jid())
if jid.resource:
resource = contact[jid.full]
else:
@@ -3021,13 +3024,13 @@ class ConversationTab(ChatTab):
if message is not '':
self.command_say(message, attention=True)
else:
- msg = self.core.xmpp.make_message(self.get_name())
+ msg = self.core.xmpp.make_message(self.get_dest_jid())
msg['type'] = 'chat'
msg['attention'] = True
msg.send()
def check_attention(self):
- self.core.xmpp.plugin['xep_0030'].get_info(jid=self.get_name(), block=False, timeout=5, callback=self.on_attention_checked)
+ self.core.xmpp.plugin['xep_0030'].get_info(jid=self.get_dest_jid(), block=False, timeout=5, callback=self.on_attention_checked)
def on_attention_checked(self, iq):
if 'urn:xmpp:attention:0' in iq['disco_info'].get_features():
@@ -3076,14 +3079,14 @@ class ConversationTab(ChatTab):
self.resize()
log.debug(' TAB Refresh: %s',self.__class__.__name__)
self.text_win.refresh()
- self.upper_bar.refresh(self.get_name(), roster[self.get_name()])
- self.info_header.refresh(self.get_name(), roster[self.get_name()], self.text_win, self.chatstate, ConversationTab.additional_informations)
+ self.upper_bar.refresh(self.get_dest_jid(), roster[self.get_dest_jid()])
+ self.info_header.refresh(self.get_dest_jid(), roster[self.get_dest_jid()], self.text_win, self.chatstate, ConversationTab.additional_informations)
self.info_win.refresh()
self.refresh_tab_win()
self.input.refresh()
def refresh_info_header(self):
- self.info_header.refresh(self.get_name(), roster[self.get_name()],
+ self.info_header.refresh(self.get_dest_jid(), roster[self.get_dest_jid()],
self.text_win, self.chatstate, ConversationTab.additional_informations)
self.input.refresh()
@@ -3108,8 +3111,8 @@ class ConversationTab(ChatTab):
return False
def on_lose_focus(self):
- contact = roster[self.get_name()]
- jid = safeJID(self.get_name())
+ contact = roster[self.get_dest_jid()]
+ jid = safeJID(self.get_dest_jid())
if contact:
if jid.resource:
resource = contact[jid.full]
@@ -3125,8 +3128,8 @@ class ConversationTab(ChatTab):
self.send_chat_state('inactive')
def on_gain_focus(self):
- contact = roster[self.get_name()]
- jid = safeJID(self.get_name())
+ contact = roster[self.get_dest_jid()]
+ jid = safeJID(self.get_dest_jid())
if contact:
if jid.resource:
resource = contact[jid.full]
@@ -3178,6 +3181,83 @@ class ConversationTab(ChatTab):
res.append(contact.name)
return res
+class DynamicConversationTab(ConversationTab):
+ """
+ A conversation tab associated with one bare JID that can be “locked” to
+ a full jid, and unlocked, as described in the XEP-0296.
+ Only one DynamicConversationTab can be opened for a given jid.
+ """
+ def __init__(self, jid, resource=None):
+ self.locked_resource = None
+ self.name = safeJID(jid).bare
+ if resource:
+ self.lock(resource)
+ self.info_header = windows.DynamicConversationInfoWin()
+ ConversationTab.__init__(self, jid)
+
+ def lock(self, resource):
+ """
+ Lock the tab to the resource.
+ """
+ assert(resource)
+ self.locked_resource = resource
+
+ def unlock(self):
+ """
+ Unlock the tab from a resource. It is now “associated” with the bare
+ jid.
+ """
+ self.locked_resource = None
+
+ def get_dest_jid(self):
+ """
+ Returns the full jid (using the locked resource), or the bare jid if
+ the conversation is not locked.
+ """
+ if self.locked_resource:
+ return "%s/%s" % (self.get_name(), self.locked_resource)
+ return self.get_name()
+
+ def refresh(self):
+ """
+ Different from the parent class only for the info_header object.
+ """
+ if self.need_resize:
+ self.resize()
+ log.debug(' TAB Refresh: %s',self.__class__.__name__)
+ self.text_win.refresh()
+ self.upper_bar.refresh(self.get_name(), roster[self.get_name()])
+ if self.locked_resource:
+ displayed_jid = "%s/%s" % (self.get_name(), self.locked_resource)
+ else:
+ displayed_jid = self.get_name()
+ self.info_header.refresh(displayed_jid, roster[self.get_name()], self.text_win, self.chatstate, ConversationTab.additional_informations)
+ self.info_win.refresh()
+ self.refresh_tab_win()
+ self.input.refresh()
+
+ def refresh_info_header(self):
+ """
+ Different from the parent class only for the info_header object.
+ """
+ if self.locked_resource:
+ displayed_jid = "%s/%s" % (self.get_name(), self.locked_resource)
+ else:
+ displayed_jid = self.get_name()
+ self.info_header.refresh(displayed_jid, roster[self.get_name()],
+ self.text_win, self.chatstate, ConversationTab.additional_informations)
+ self.input.refresh()
+
+class StaticConversationTab(ConversationTab):
+ """
+ A conversation tab associated with one Full JID. It cannot be locked to
+ an different resource or unlocked.
+ """
+ def __init__(self, jid):
+ assert(safeJID(jid).resource)
+ self.info_header = windows.ConversationInfoWin()
+ ConversationTab.__init__(self, jid)
+
class MucListTab(Tab):
"""
A tab listing rooms from a specific server, displaying various information,
@@ -3528,7 +3608,6 @@ class XMLTab(Tab):
self.text_win.resize(self.height-2-self.core.information_win_size - Tab.tab_win_height(), self.width, 0, 0)
self.info_header.resize(1, self.width, self.height-2-self.core.information_win_size - Tab.tab_win_height(), 0)
-
class SimpleTextTab(Tab):
"""
A very simple tab, with just a text displaying some
diff --git a/src/theming.py b/src/theming.py
index 07a37c6e..dd933fc7 100644
--- a/src/theming.py
+++ b/src/theming.py
@@ -184,6 +184,7 @@ class Theme(object):
COLOR_SELECTED_ROW = (-1, 33)
COLOR_PRIVATE_NAME = (-1, 4)
COLOR_CONVERSATION_NAME = (2, 4)
+ COLOR_CONVERSATION_RESOURCE = (121, 4)
COLOR_GROUPCHAT_NAME = (7, 4)
COLOR_COLUMN_HEADER = (36, 4)
COLOR_COLUMN_HEADER_SEL = (4, 36)
diff --git a/src/windows.py b/src/windows.py
index f0911086..e507be60 100644
--- a/src/windows.py
+++ b/src/windows.py
@@ -503,7 +503,7 @@ class ConversationInfoWin(InfoWin):
else:
resource = None
# if contact is None, then resource is None too: user is not in the roster
- # so we don't know almost anything about it
+ # so we know almost nothing about it
# If contact is a Contact, then
# resource can now be a Resource: user is in the roster and online
# or resource is None: user is in the roster but offline
@@ -546,8 +546,9 @@ class ConversationInfoWin(InfoWin):
if not contact:
self.addstr("(contact not in roster)", to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
return
- display_name = contact.name or contact.bare_jid
- self.addstr('%s '%(display_name), to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ display_name = contact.name
+ if display_name:
+ self.addstr('%s '%(display_name), to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
def write_contact_jid(self, jid):
"""
@@ -561,6 +562,18 @@ class ConversationInfoWin(InfoWin):
if state:
self.addstr(' %s' % (state,), to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+class DynamicConversationInfoWin(ConversationInfoWin):
+ def write_contact_jid(self, jid):
+ """
+ Just displays the resource in an other color
+ """
+ log.debug("write_contact_jid DynamicConversationInfoWin, jid: %s" % jid.resource)
+ self.addstr('[', to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ self.addstr(jid.bare, to_curses_attr(get_theme().COLOR_CONVERSATION_NAME))
+ if jid.resource:
+ self.addstr("/%s" % (jid.resource,), to_curses_attr(get_theme().COLOR_CONVERSATION_RESOURCE))
+ self.addstr('] ', to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+
class ConversationStatusMessageWin(InfoWin):
"""
The upper bar displaying the status message of the contact