Chatstate notifications in tabs #672

This commit is contained in:
Travis Shirk 2005-09-04 23:30:40 +00:00
parent 447c5f617b
commit bb6c0d6a26
5 changed files with 112 additions and 34 deletions

View File

@ -160,19 +160,59 @@ class Chat:
self.window.set_title(title) self.window.set_title(title)
gtkgui_helpers.set_unset_urgency_hint(self.window, unread) gtkgui_helpers.set_unset_urgency_hint(self.window, unread)
def redraw_tab(self, jid): def redraw_tab(self, contact, chatstate = None):
"""redraw the label of the tab""" '''redraw the label of the tab
start = '' if chatstate is given that means we have HE SENT US a chatstate'''
if isinstance(contact, unicode):
jid = contact
else:
jid = contact.jid
unread = ''
if self.nb_unread[jid] > 1: if self.nb_unread[jid] > 1:
start = '[' + unicode(self.nb_unread[jid]) + '] ' unread = '[' + unicode(self.nb_unread[jid]) + '] '
elif self.nb_unread[jid] == 1: # Update status images
start = '* ' self.set_state_image(jid)
child = self.childs[jid] child = self.childs[jid]
hb = self.notebook.get_tab_label(child).get_children()[0] hb = self.notebook.get_tab_label(child).get_children()[0]
if self.widget_name == 'tabbed_chat_window': if self.widget_name == 'tabbed_chat_window':
nickname = hb.get_children()[1] nickname = hb.get_children()[1]
close_button = hb.get_children()[2] close_button = hb.get_children()[2]
# Draw tab label using chatstate
theme = gajim.config.get('roster_theme')
color = None
if unread and chatstate == 'active':
color = gajim.config.get_per('themes', theme,
'state_unread_color')
elif chatstate is not None:
if chatstate == 'composing':
color = gajim.config.get_per('themes', theme,
'state_composing_color')
elif unread and self.has_focus:
color = gajim.config.get_per('themes', theme,
'state_active_color')
elif unread:
color = gajim.config.get_per('themes', theme,
'state_unread_color')
elif chatstate == 'inactive':
color = gajim.config.get_per('themes', theme,
'state_inactive_color')
elif chatstate == 'gone':
color = gajim.config.get_per('themes', theme,
'state_gone_color')
elif chatstate == 'paused':
color = gajim.config.get_per('themes', theme,
'state_paused_color')
else:
color = gajim.config.get_per('themes', theme,
'state_active_color')
if color:
# FIXME: When tabs are in the "background" the color change has
# no affect
#print jid, chatstate, color
nickname.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
elif self.widget_name == 'groupchat_window': elif self.widget_name == 'groupchat_window':
nickname = hb.get_children()[0] nickname = hb.get_children()[0]
close_button = hb.get_children()[1] close_button = hb.get_children()[1]
@ -183,7 +223,7 @@ class Chat:
close_button.hide() close_button.hide()
nickname.set_max_width_chars(10) nickname.set_max_width_chars(10)
nickname.set_text(start + self.names[jid]) nickname.set_text(unread + self.names[jid])
def on_window_destroy(self, widget, kind): #kind is 'chats' or 'gc' def on_window_destroy(self, widget, kind): #kind is 'chats' or 'gc'
@ -227,9 +267,14 @@ class Chat:
self.plugin.windows['logs'][jid] = history_window.HistoryWindow( self.plugin.windows['logs'][jid] = history_window.HistoryWindow(
self.plugin, jid, self.account) self.plugin, jid, self.account)
def on_chat_window_focus_out_event(self, widget, event):
self.has_focus = False
def on_chat_window_focus_in_event(self, widget, event): def on_chat_window_focus_in_event(self, widget, event):
"""When window gets focus""" """When window gets focus"""
self.has_focus = True
jid = self.get_active_jid() jid = self.get_active_jid()
textview = self.xmls[jid].get_widget('conversation_textview') textview = self.xmls[jid].get_widget('conversation_textview')
buffer = textview.get_buffer() buffer = textview.get_buffer()
end_iter = buffer.get_end_iter() end_iter = buffer.get_end_iter()
@ -239,7 +284,6 @@ class Chat:
#we are at the end #we are at the end
if self.nb_unread[jid] > 0: if self.nb_unread[jid] > 0:
self.nb_unread[jid] = 0 self.nb_unread[jid] = 0
self.redraw_tab(jid)
self.show_title() self.show_title()
if self.plugin.systray_enabled: if self.plugin.systray_enabled:
self.plugin.systray.remove_jid(jid, self.account) self.plugin.systray.remove_jid(jid, self.account)
@ -250,6 +294,8 @@ class Chat:
if gtk.gtk_version >= (2, 8, 0) and gtk.pygtk_version >= (2, 8, 0): if gtk.gtk_version >= (2, 8, 0) and gtk.pygtk_version >= (2, 8, 0):
if widget.props.urgency_hint: if widget.props.urgency_hint:
widget.props.urgency_hint = False widget.props.urgency_hint = False
# Undo "unread" state display, etc. But note, there is not chatstate past here
self.redraw_tab(jid)
def on_compact_view_menuitem_activate(self, widget): def on_compact_view_menuitem_activate(self, widget):
isactive = widget.get_active() isactive = widget.get_active()
@ -1108,6 +1154,8 @@ class Chat:
# FIXME: who gives us text that is not a string? # FIXME: who gives us text that is not a string?
if not text: if not text:
# FIXME: Let's find out...
assert(False)
text = '' text = ''
if buffer.get_char_count() > 0: if buffer.get_char_count() > 0:

View File

@ -215,6 +215,13 @@ class Config:
'contactfont': [ opt_str, 'Sans 10' ], 'contactfont': [ opt_str, 'Sans 10' ],
'bannertextcolor': [ opt_color, '#ffffff' ], 'bannertextcolor': [ opt_color, '#ffffff' ],
'bannerbgcolor': [ opt_color, '#000000' ], 'bannerbgcolor': [ opt_color, '#000000' ],
'state_unread_color': [ opt_color, '#000000' ],
'state_active_color': [ opt_color, '#000000' ],
'state_inactive_color': [ opt_color, '#9e9e9e' ],
'state_composing_color': [ opt_color, '#008b00' ],
'state_paused_color': [ opt_color, '#0000cd' ],
'state_gone_color': [ opt_color, '#bebebe' ],
}, {}), }, {}),
} }

View File

@ -382,9 +382,10 @@ class Interface:
not gajim.contacts[account].has_key(jid): not gajim.contacts[account].has_key(jid):
return return
# Handle chat states
contact = gajim.get_first_contact_instance_from_jid(account, jid)
if self.windows[account]['chats'].has_key(jid): if self.windows[account]['chats'].has_key(jid):
chat_win = self.windows[account]['chats'][jid] chat_win = self.windows[account]['chats'][jid]
contact = gajim.get_first_contact_instance_from_jid(account, jid)
if chatstate is not None: # he sent us reply, so he supports jep85 if chatstate is not None: # he sent us reply, so he supports jep85
if contact.chatstate == 'ask': # we were jep85 disco? if contact.chatstate == 'ask': # we were jep85 disco?
contact.chatstate = 'active' # no more contact.chatstate = 'active' # no more
@ -393,6 +394,10 @@ class Interface:
else: else:
# got no valid jep85 answer, peer does not support it # got no valid jep85 answer, peer does not support it
contact.chatstate = False contact.chatstate = False
else:
# Brand new message, incoming.
if chatstate == 'active':
contact.chatstate = chatstate
if not array[1]: #empty message text if not array[1]: #empty message text
return return

View File

@ -1073,6 +1073,11 @@ class GroupchatWindow(chat.Chat):
self.got_disconnected(room_jid) #init some variables self.got_disconnected(room_jid) #init some variables
conversation_textview.grab_focus() conversation_textview.grab_focus()
self.childs[room_jid].show_all() self.childs[room_jid].show_all()
def set_state_image(self, jid):
# FIXME: Tab notifications?
pass
def on_list_treeview_motion_notify_event(self, widget, event): def on_list_treeview_motion_notify_event(self, widget, event):
model = widget.get_model() model = widget.get_model()
props = widget.get_path_at_pos(int(event.x), int(event.y)) props = widget.get_path_at_pos(int(event.x), int(event.y))

View File

@ -46,7 +46,6 @@ class TabbedChatWindow(chat.Chat):
def __init__(self, user, plugin, account): def __init__(self, user, plugin, account):
chat.Chat.__init__(self, plugin, account, 'tabbed_chat_window') chat.Chat.__init__(self, plugin, account, 'tabbed_chat_window')
self.contacts = {} self.contacts = {}
self.chatstates = {}
# keep check for possible paused timeouts per jid # keep check for possible paused timeouts per jid
self.possible_paused_timeout_id = {} self.possible_paused_timeout_id = {}
# keep check for possible inactive timeouts per jid # keep check for possible inactive timeouts per jid
@ -245,6 +244,9 @@ timestamp, contact):
hb = self.notebook.get_tab_label(child).get_children()[0] hb = self.notebook.get_tab_label(child).get_children()[0]
status_image = hb.get_children()[0] status_image = hb.get_children()[0]
state_images = self.plugin.roster.get_appropriate_state_images(jid) state_images = self.plugin.roster.get_appropriate_state_images(jid)
# If messages are unread show the 'message' image
if self.nb_unread[jid]:
show = 'message'
image = state_images[show] image = state_images[show]
banner_status_image = self.xmls[jid].get_widget('banner_status_image') banner_status_image = self.xmls[jid].get_widget('banner_status_image')
@ -296,18 +298,11 @@ timestamp, contact):
def on_tabbed_chat_window_focus_out_event(self, widget, event): def on_tabbed_chat_window_focus_out_event(self, widget, event):
'''catch focus out and minimized and send inactive chatstate; '''catch focus out and minimized and send inactive chatstate;
minimize action also focuses out first so it's catched here''' minimize action also focuses out first so it's catched here'''
chat.Chat.on_chat_window_focus_out_event(self, widget, event)
window_state = widget.window.get_state() window_state = widget.window.get_state()
if window_state is None: if window_state is None:
return return
# focus-out is also emitted by showing context menu
# so check to see if we're really not paying attention to window/tab
# NOTE: if the user changes tab, switch-tab sends inactive to the tab
# we are leaving so we just send to active tab here
if self.popup_is_shown is False: # we are outside of the window
# so no context menu, so send 'inactive' to active tab
self.send_chatstate('inactive')
def on_chat_notebook_key_press_event(self, widget, event): def on_chat_notebook_key_press_event(self, widget, event):
chat.Chat.on_chat_notebook_key_press_event(self, widget, event) chat.Chat.on_chat_notebook_key_press_event(self, widget, event)
@ -367,6 +362,8 @@ timestamp, contact):
message_tv_buffer = message_textview.get_buffer() message_tv_buffer = message_textview.get_buffer()
message_tv_buffer.connect('insert-text', message_tv_buffer.connect('insert-text',
self.on_message_tv_buffer_insert_text, contact.jid) self.on_message_tv_buffer_insert_text, contact.jid)
message_tv_buffer.connect('changed',
self.on_message_tv_buffer_changed, contact)
if contact.jid in gajim.encrypted_chats[self.account]: if contact.jid in gajim.encrypted_chats[self.account]:
self.xmls[contact.jid].get_widget('gpg_togglebutton').set_active(True) self.xmls[contact.jid].get_widget('gpg_togglebutton').set_active(True)
@ -376,13 +373,12 @@ timestamp, contact):
self.tabbed_chat_popup_menu = xm.get_widget('tabbed_chat_popup_menu') self.tabbed_chat_popup_menu = xm.get_widget('tabbed_chat_popup_menu')
chat.Chat.new_tab(self, contact.jid) chat.Chat.new_tab(self, contact.jid)
self.redraw_tab(contact.jid) self.redraw_tab(contact)
self.draw_widgets(contact) self.draw_widgets(contact)
# restore previous conversation # restore previous conversation
self.restore_conversation(contact.jid) self.restore_conversation(contact.jid)
# print queued messages
if gajim.awaiting_messages[self.account].has_key(contact.jid): if gajim.awaiting_messages[self.account].has_key(contact.jid):
self.read_queue(contact.jid) self.read_queue(contact.jid)
@ -392,7 +388,6 @@ timestamp, contact):
# chatstates # chatstates
self.reset_kbd_mouse_timeout_vars() self.reset_kbd_mouse_timeout_vars()
self.chatstates[contact.jid] = None # OUR current chatstate with contact
self.possible_paused_timeout_id[contact.jid] =\ self.possible_paused_timeout_id[contact.jid] =\
gobject.timeout_add(5000, self.check_for_possible_paused_chatstate, gobject.timeout_add(5000, self.check_for_possible_paused_chatstate,
contact.jid) contact.jid)
@ -404,25 +399,33 @@ timestamp, contact):
''' handle incoming chatstate that jid SENT TO us ''' ''' handle incoming chatstate that jid SENT TO us '''
contact = gajim.get_first_contact_instance_from_jid(account, jid) contact = gajim.get_first_contact_instance_from_jid(account, jid)
self.draw_name_banner(contact, chatstate) self.draw_name_banner(contact, chatstate)
# update chatstate in tab for this chat
self.redraw_tab(contact, chatstate)
def check_for_possible_paused_chatstate(self, jid): def check_for_possible_paused_chatstate(self, jid):
''' did we move mouse of that window or wrote something in message textview ''' did we move mouse of that window or write something in message
textview
in the last 5 seconds? in the last 5 seconds?
if yes we go active for mouse, composing for kbd if yes we go active for mouse, composing for kbd
if no we go paused if we were previously composing ''' if no we go paused if we were previously composing '''
if jid not in self.xmls: if jid not in self.xmls:
# the tab with jid is no longer open. stop timer # the tab with jid is no longer open. stop timer
return False # stop looping return False # stop looping
current_state = self.chatstates[jid]
# FIXME: Why don't we just pass contact?
contact = gajim.get_first_contact_instance_from_jid(self.account, jid)
current_state = contact.chatstate
if current_state is False: # jid doesn't support chatstates if current_state is False: # jid doesn't support chatstates
return False # stop looping return False # stop looping
if self.mouse_over_in_last_5_secs: message_buffer = self.xmls[jid].get_widget('message_textview').get_buffer()
self.send_chatstate('active', jid) if self.kbd_activity_in_last_5_secs and message_buffer.get_char_count():
elif self.kbd_activity_in_last_5_secs: # Only composing if the keyboard activity was in text entry
self.send_chatstate('composing', jid) self.send_chatstate('composing', jid)
elif self.mouse_over_in_last_5_secs:
self.send_chatstate('active', jid)
else: else:
if self.chatstates[jid] == 'composing': if current_state == 'composing':
self.send_chatstate('paused', jid) # pause composing self.send_chatstate('paused', jid) # pause composing
# assume no activity and let the motion-notify or 'insert-text' make them True # assume no activity and let the motion-notify or 'insert-text' make them True
@ -431,7 +434,8 @@ timestamp, contact):
return True # loop forever return True # loop forever
def check_for_possible_inactive_chatstate(self, jid): def check_for_possible_inactive_chatstate(self, jid):
''' did we move mouse over that window or wrote something in message textview ''' did we move mouse over that window or wrote something in message
textview
in the last 30 seconds? in the last 30 seconds?
if yes we go active if yes we go active
if no we go inactive ''' if no we go inactive '''
@ -439,7 +443,9 @@ timestamp, contact):
# the tab with jid is no longer open. stop timer # the tab with jid is no longer open. stop timer
return False # stop looping return False # stop looping
current_state = self.chatstates[jid] # FIXME: Why don't we just pass contact?
contact = gajim.get_first_contact_instance_from_jid(self.account, jid)
current_state = contact.chatstate
if current_state is False: # jid doesn't support chatstates if current_state is False: # jid doesn't support chatstates
return False # stop looping return False # stop looping
@ -447,7 +453,7 @@ timestamp, contact):
return True # loop forever return True # loop forever
if not (self.mouse_over_in_last_30_secs or\ if not (self.mouse_over_in_last_30_secs or\
self.kbd_activity_in_last_30_secs): self.kbd_activity_in_last_30_secs):
self.send_chatstate('inactive', jid) self.send_chatstate('inactive', jid)
# assume no activity and let the motion-notify or 'insert-text' make them True # assume no activity and let the motion-notify or 'insert-text' make them True
@ -456,12 +462,19 @@ timestamp, contact):
return True # loop forever return True # loop forever
def on_message_tv_buffer_insert_text(self, textbuffer, textiter, text, def on_message_tv_buffer_insert_text(self, textbuffer, textiter, text, length, jid):
length, jid):
self.kbd_activity_in_last_5_secs = True self.kbd_activity_in_last_5_secs = True
self.kbd_activity_in_last_30_secs = True self.kbd_activity_in_last_30_secs = True
# XXX: only send the event every N'th character after the first N... optimization
self.send_chatstate('composing', jid) self.send_chatstate('composing', jid)
def on_message_tv_buffer_changed(self, textbuffer, contact):
self.kbd_activity_in_last_5_secs = True
self.kbd_activity_in_last_30_secs = True
# All characters have been erased, undo composing
if textbuffer.get_char_count() == 0:
self.send_chatstate('active', contact.jid)
def reset_kbd_mouse_timeout_vars(self): def reset_kbd_mouse_timeout_vars(self):
self.kbd_activity_in_last_5_secs = False self.kbd_activity_in_last_5_secs = False
self.mouse_over_in_last_5_secs = False self.mouse_over_in_last_5_secs = False
@ -563,8 +576,8 @@ timestamp, contact):
# NOTE: # NOTE:
# send 'active', set current state to 'ask' and return is done # send 'active', set current state to 'ask' and return is done
# in self.send_message() because we need REAL message (with <body>) # in self.send_message() because we need REAL message (with <body>)
# for that procedure so return to make sure we send only once 'active' # for that procedure so return to make sure we send only once
# until we know peer supports jep85 # 'active' until we know peer supports jep85
return return
if contact.chatstate == 'ask': if contact.chatstate == 'ask':