From d6c9c7cbc68136c180088b754bb135371637e951 Mon Sep 17 00:00:00 2001 From: Nikos Kouremenos Date: Tue, 19 Jul 2005 14:38:58 +0000 Subject: [PATCH] [peralta] Chat State Notifications inital patch. I assume that every wm will focus-out before iconify. If that is not the case I am going to use window-state-changed too [to be tested] --- src/common/connection.py | 29 +++++++++++--- src/gajim.py | 14 ++++++- src/gtkgui.glade | 1 - src/roster_window.py | 27 +++++++++++-- src/tabbed_chat_window.py | 82 +++++++++++++++++++++++++++++++++++++-- 5 files changed, 139 insertions(+), 14 deletions(-) diff --git a/src/common/connection.py b/src/common/connection.py index 858f5f4fc..3e466c4d2 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -218,6 +218,7 @@ class Connection: tim = time.strptime(tim, '%Y%m%dT%H:%M:%S') tim = time.localtime(timegm(tim)) encrypted = False + chatstate_tag = None xtags = msg.getTags('x') encTag = None decmsg = '' @@ -225,6 +226,14 @@ class Connection: if xtag.getNamespace() == common.xmpp.NS_ENCRYPTED: encTag = xtag break + + # chatstates - look for chatstate tags in a message + children = msg.getChildren() + for child in children: + if child.getNamespace() == 'http://jabber.org/protocol/chatstates': + chatstate_tag = child.getName() + break + if encTag and USE_GPG: #decrypt encmsg = encTag.getData() @@ -253,9 +262,9 @@ class Connection: gajim.logger.write('incoming', log_msgtxt, str(msg.getFrom()), tim = tim) self.dispatch('MSG', (str(msg.getFrom()), msgtxt, tim, encrypted, - mtype, subject)) + mtype, subject, None)) else: # it's type 'chat' - if not msg.getTag('body'): #no + if not msg.getTag('body') and chatstate_tag is None: #no return log_msgtxt = msgtxt if subject: @@ -263,7 +272,7 @@ class Connection: gajim.logger.write('incoming', log_msgtxt, str(msg.getFrom()), tim = tim) self.dispatch('MSG', (str(msg.getFrom()), msgtxt, tim, encrypted, - mtype, subject)) + mtype, subject, chatstate_tag)) # END messageCB def _presenceCB(self, con, prs): @@ -859,10 +868,10 @@ class Connection: self.connection.send(p) self.dispatch('STATUS', show) - def send_message(self, jid, msg, keyID, type = 'chat', subject=''): + def send_message(self, jid, msg, keyID, type = 'chat', subject='', chatstate = None): if not self.connection: return - if not msg: + if not msg and chatstate is None: return msgtxt = msg msgenc = '' @@ -886,6 +895,12 @@ class Connection: typ = 'normal') if msgenc: msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc) + + # chatstates - if peer supports jep85, send chatstates + # please note that the only valid tag inside a message containing a tag is the active event + if chatstate != None: + msg_iq.setTag(chatstate, {}, namespace='http://jabber.org/protocol/chatstates') + self.to_be_sent.append(msg_iq) gajim.logger.write('outgoing', msg, jid) self.dispatch('MSGSENT', (jid, msg, keyID)) @@ -939,7 +954,7 @@ class Connection: {'agent': agent}) return - def update_user(self, jid, name, groups): + def update_contact(self, jid, name, groups): if self.connection: self.connection.getRoster().setItem(jid = jid, name = name, groups = groups) @@ -1150,6 +1165,7 @@ class Connection: typ = ptype, show = show, status = status)) def gc_set_role(self, room_jid, nick, role, reason = ''): + '''role is for all the life of the room so it's based on nick''' if not self.connection: return iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ @@ -1162,6 +1178,7 @@ class Connection: self.to_be_sent.append(iq) def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''): + '''affiliation is for all the life of the room so it's based on jid''' if not self.connection: return iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ diff --git a/src/gajim.py b/src/gajim.py index 1464ff6c5..dc8266bd6 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -344,9 +344,10 @@ class Interface: self.remote.raise_signal('GCPresence', (account, array)) def handle_event_msg(self, account, array): - #('MSG', account, (contact, msg, time, encrypted, msg_type, subject)) + #('MSG', account, (contact, msg, time, encrypted, msg_type, subject, chatstate_tag)) jid = array[0].split('/')[0] msg_type = array[4] + chatstate_tag = array[6] if jid.find('@') <= 0: jid = jid.replace('@', '') @@ -406,6 +407,17 @@ class Interface: self.play_sound('next_message_received') if self.remote: self.remote.raise_signal('NewMessage', (account, array)) + if self.windows[account]['chats'].has_key(jid): + chat_win = self.windows[account]['chats'][jid] + # chatstates - display jep85 events in window + if chatstate_tag != None: + if chat_win.chatstates[jid] == 'ask': + chat_win.chatstates[jid] = 'active' + chat_win.print_conversation(jid + ' is now ' + chatstate_tag, jid, 'status', tim = array[2]) + else: + # got no valid jep85 answer, peer does not support it + chat_win.chatstates[jid] = -1 + def handle_event_msgerror(self, account, array): #('MSGERROR', account, (jid, error_code, error_msg, msg, time)) diff --git a/src/gtkgui.glade b/src/gtkgui.glade index c47132b41..eaa2b396b 100644 --- a/src/gtkgui.glade +++ b/src/gtkgui.glade @@ -9932,7 +9932,6 @@ Custom - diff --git a/src/roster_window.py b/src/roster_window.py index 88373b7d3..5b36d2a3c 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -309,9 +309,30 @@ class RosterWindow: 'show_offline_contacts_menuitem') profile_avatar_menuitem = self.xml.get_widget('profile_avatar_menuitem') - - xm = gtk.glade.XML(GTKGUI_GLADE, 'advanced_menuitem_menu', APP) - advanced_menuitem_menu = xm.get_widget('advanced_menuitem_menu') + + ui_description = """ + + + + %(menu)s + + + %(menu)s + + + %(menu)s + + + +""" + + menu_description = """ + + + + + +""" if self.add_new_contact_handler_id: add_new_contact_menuitem.handler_disconnect( diff --git a/src/tabbed_chat_window.py b/src/tabbed_chat_window.py index e1c8ee534..2a71e22eb 100644 --- a/src/tabbed_chat_window.py +++ b/src/tabbed_chat_window.py @@ -44,6 +44,7 @@ class TabbedChatWindow(chat.Chat): def __init__(self, user, plugin, account): chat.Chat.__init__(self, plugin, account, 'tabbed_chat_window') self.users = {} + self.chatstates = {} self.new_user(user) self.show_title() self.xml.signal_connect('on_tabbed_chat_window_destroy', @@ -52,6 +53,8 @@ class TabbedChatWindow(chat.Chat): self.on_tabbed_chat_window_delete_event) self.xml.signal_connect('on_tabbed_chat_window_focus_in_event', self.on_tabbed_chat_window_focus_in_event) + self.xml.signal_connect('on_tabbed_chat_window_focus_out_event', + self.on_tabbed_chat_window_focus_out_event) self.xml.signal_connect('on_tabbed_chat_window_button_press_event', self.on_chat_window_button_press_event) self.xml.signal_connect('on_chat_notebook_key_press_event', @@ -60,7 +63,7 @@ class TabbedChatWindow(chat.Chat): self.on_chat_notebook_switch_page) if gajim.config.get('saveposition'): - # get window position and size from config + # get window position and size from config self.window.move(gajim.config.get('chat-x-position'), gajim.config.get('chat-y-position')) self.window.resize(gajim.config.get('chat-width'), @@ -197,7 +200,7 @@ class TabbedChatWindow(chat.Chat): return True #stop the propagation of the event if gajim.config.get('saveposition'): - # save the window size and position + # save the window size and position x, y = self.window.get_position() gajim.config.set('chat-x-position', x) gajim.config.set('chat-y-position', y) @@ -207,10 +210,28 @@ class TabbedChatWindow(chat.Chat): def on_tabbed_chat_window_destroy(self, widget): #clean self.plugin.windows[self.account]['chats'] + # on window destroy, send 'gone' chatstate + self.send_chatstate('gone') chat.Chat.on_window_destroy(self, widget, 'chats') def on_tabbed_chat_window_focus_in_event(self, widget, event): chat.Chat.on_chat_window_focus_in_event(self, widget, event) + # on focus in, send 'active' chatstate + self.send_chatstate('active') + + def on_tabbed_chat_window_focus_out_event(self, widget, event): + gobject.timeout_add(500, self.check_window_state, widget) + + def check_window_state(self, widget): + ''' we want: "minimized" or "focus-out" + not "focus-out, minimized" or "focus-out" ''' + new_state = widget.window.get_state() + if new_state & gtk.gdk.WINDOW_STATE_ICONIFIED: + print 'iconify' + self.send_chatstate('inactive') + else: + print 'just focus-out' + self.send_chatstate('paused') def on_chat_notebook_key_press_event(self, widget, event): chat.Chat.on_chat_notebook_key_press_event(self, widget, event) @@ -237,6 +258,9 @@ class TabbedChatWindow(chat.Chat): if dialog.get_response() != gtk.RESPONSE_OK: return + # chatstates - window is destroyed, send gone + self.send_chatstate('gone') + chat.Chat.remove_tab(self, jid, 'chats') if len(self.xmls) > 0: del self.users[jid] @@ -275,6 +299,9 @@ class TabbedChatWindow(chat.Chat): gajim.connections[self.account].request_vcard(user.jid) self.childs[user.jid].show_all() + # chatstates + self.chatstates[user.jid] = None + def on_message_textview_key_press_event(self, widget, event): """When a key is pressed: if enter is pressed without the shift key, message (if not empty) is sent @@ -326,7 +353,45 @@ class TabbedChatWindow(chat.Chat): if event.state & gtk.gdk.CONTROL_MASK: #Ctrl+Down self.sent_messages_scroll(jid, 'down', widget.get_buffer()) return True # override the default gtk+ thing for ctrl+down + + else: + # chatstates + # if composing, send chatstate + self.send_chatstate('composing') + def send_chatstate(self, state): + # please read jep-85 to get an idea of this + # we keep track of jep85 support by the peer by three extra states: None, -1 and 'ask' + # None if no info about peer + # -1 if peer does not support jep85 + # 'ask' if we sent 'active' chatstate and are waiting for reply + + jid = self.get_active_jid() + + # print jid, self.chatstates[jid], state + if self.chatstates[jid] == -1: + return + + # if current state equals last state, return + if self.chatstates[jid] == state: + return + + if self.chatstates[jid] is None: + # state = 'ask' + # send and return + return + + if self.chatstates[jid] == 'ask': + return + + # if last state was composing, don't send active + if self.chatstates[jid] == 'composing' and state == 'active': + return + + self.chatstates[jid] = state + gajim.connections[self.account].send_message(jid, None, None, chatstate = state) + + def send_message(self, message): """Send the message given to the active tab""" if not message: @@ -350,7 +415,18 @@ class TabbedChatWindow(chat.Chat): if self.xmls[jid].get_widget('gpg_togglebutton').get_active(): keyID = self.users[jid].keyID encrypted = True - gajim.connections[self.account].send_message(jid, message, keyID) + + # chatstates - if no info about peer, discover + if self.chatstates[jid] is None: + + gajim.connections[self.account].send_message(jid, message, keyID, chatstate = 'active') + self.chatstates[jid] = 'ask' + # if peer supports jep85, send 'active' + elif self.chatstates[jid] != -1: + gajim.connections[self.account].send_message(jid, message, keyID, chatstate = 'active') + else: + gajim.connections[self.account].send_message(jid, message, keyID) + print self.chatstates[jid] message_buffer.set_text('', -1) self.print_conversation(message, jid, jid, encrypted = encrypted)