Chatstate notifications in tabs #672
This commit is contained in:
parent
447c5f617b
commit
bb6c0d6a26
64
src/chat.py
64
src/chat.py
|
@ -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:
|
||||||
|
|
|
@ -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' ],
|
||||||
}, {}),
|
}, {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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':
|
||||||
|
|
Loading…
Reference in New Issue