here is a new widget: conversation_textview: it displays url, emoticons, ...

This commit is contained in:
Yann Leboulanger 2005-10-28 21:46:55 +00:00
parent bf4a1b28ac
commit 0cbb37e865
5 changed files with 683 additions and 675 deletions

View File

@ -26,6 +26,7 @@ import dialogs
import history_window import history_window
import gtkgui_helpers import gtkgui_helpers
import tooltips import tooltips
import conversation_textview
try: try:
import gtkspell import gtkspell
@ -54,9 +55,7 @@ class Chat:
self.account = account self.account = account
self.change_cursor = None self.change_cursor = None
self.xmls = {} self.xmls = {}
self.tagIn = {} # holds tag for nick that talks to us self.conversation_textviews = {}
self.tagOut = {} # holds tag for our nick
self.tagStatus = {} # holds status messages
self.nb_unread = {} self.nb_unread = {}
self.last_time_printout = {} self.last_time_printout = {}
self.print_time_timeout_id = {} self.print_time_timeout_id = {}
@ -91,25 +90,17 @@ class Chat:
# muc attention states (when we are mentioned in a muc) # muc attention states (when we are mentioned in a muc)
# if the room jid is in the list, the room has mentioned us # if the room jid is in the list, the room has mentioned us
self.muc_attentions = [] self.muc_attentions = []
self.line_tooltip = tooltips.BaseTooltip()
def update_font(self): def update_font(self):
font = pango.FontDescription(gajim.config.get('conversation_font')) font = pango.FontDescription(gajim.config.get('conversation_font'))
for jid in self.tagIn: for jid in self.xmls:
conversation_textview = self.xmls[jid].get_widget( self.conversation_textviews[jid].modify_font(font)
'conversation_textview')
conversation_textview.modify_font(font)
message_textview = self.xmls[jid].get_widget('message_textview') message_textview = self.xmls[jid].get_widget('message_textview')
message_textview.modify_font(font) message_textview.modify_font(font)
def update_tags(self): def update_tags(self):
for jid in self.tagIn: for jid in self.conversation_textviews:
self.tagIn[jid].set_property('foreground', self.conversation_textviews[jid].update_tags()
gajim.config.get('inmsgcolor'))
self.tagOut[jid].set_property('foreground',
gajim.config.get('outmsgcolor'))
self.tagStatus[jid].set_property('foreground',
gajim.config.get('statusmsgcolor'))
def update_print_time(self): def update_print_time(self):
if gajim.config.get('print_time') != 'sometimes': if gajim.config.get('print_time') != 'sometimes':
@ -119,20 +110,39 @@ class Chat:
del self.print_time_timeout_id[jid] del self.print_time_timeout_id[jid]
else: else:
for jid in self.xmls: for jid in self.xmls:
if self.print_time_timeout_id.has_key(jid): if not self.print_time_timeout_id.has_key(jid):
continue self.print_time_timeout(jid)
self.print_time_timeout(jid) self.print_time_timeout_id[jid] = gobject.timeout_add(300000,
self.print_time_timeout_id[jid] = \ self.print_time_timeout, jid)
gobject.timeout_add(300000,
self.print_time_timeout, self.conversation_textviews[jid].update_print_time()
jid)
def print_time_timeout(self, jid):
if not jid in self.xmls.keys():
return False
if gajim.config.get('print_time') == 'sometimes':
conv_textview = self.conversation_textviews[jid]
buffer = conv_textview.get_buffer()
end_iter = buffer.get_end_iter()
tim = time.localtime()
tim_format = time.strftime('%H:%M', tim)
buffer.insert_with_tags_by_name(end_iter, '\n' + tim_format,
'time_sometimes')
# scroll to the end of the textview
if conv_textview.at_the_end():
# we are at the end
conv_textview.scroll_to_end()
return True # loop again
if self.print_time_timeout_id.has_key(jid):
del self.print_time_timeout_id[jid]
return False
def show_title(self, urgent = True): def show_title(self, urgent = True):
'''redraw the window's title''' '''redraw the window's title'''
unread = 0 unread = 0
for jid in self.nb_unread: for jid in self.nb_unread:
unread += self.nb_unread[jid] unread += self.nb_unread[jid]
start = "" start = ''
if unread > 1: if unread > 1:
start = '[' + unicode(unread) + '] ' start = '[' + unicode(unread) + '] '
elif unread == 1: elif unread == 1:
@ -182,30 +192,24 @@ class Chat:
theme = gajim.config.get('roster_theme') theme = gajim.config.get('roster_theme')
color = None color = None
if unread and chatstate == 'active': if unread and chatstate == 'active':
color = gajim.config.get_per('themes', theme, color = gajim.config.get_per('themes', theme, 'state_unread_color')
'state_unread_color')
elif chatstate is not None: elif chatstate is not None:
if chatstate == 'composing': if chatstate == 'composing':
color = gajim.config.get_per('themes', theme, color = gajim.config.get_per('themes', theme,
'state_composing_color') 'state_composing_color')
elif chatstate == 'inactive': elif chatstate == 'inactive':
color = gajim.config.get_per('themes', theme, color = gajim.config.get_per('themes', theme,
'state_inactive_color') 'state_inactive_color')
elif chatstate == 'gone': elif chatstate == 'gone':
color = gajim.config.get_per('themes', theme, color = gajim.config.get_per('themes', theme, 'state_gone_color')
'state_gone_color')
elif chatstate == 'paused': elif chatstate == 'paused':
color = gajim.config.get_per('themes', theme, color = gajim.config.get_per('themes', theme, 'state_paused_color')
'state_paused_color')
elif unread and self.window.get_property('has-toplevel-focus'): elif unread and self.window.get_property('has-toplevel-focus'):
color = gajim.config.get_per('themes', theme, color = gajim.config.get_per('themes', theme, 'state_active_color')
'state_active_color')
elif unread: elif unread:
color = gajim.config.get_per('themes', theme, color = gajim.config.get_per('themes', theme, 'state_unread_color')
'state_unread_color')
else: else:
color = gajim.config.get_per('themes', theme, color = gajim.config.get_per('themes', theme, 'state_active_color')
'state_active_color')
if color: if color:
color = gtk.gdk.colormap_get_system().alloc_color(color) color = gtk.gdk.colormap_get_system().alloc_color(color)
# We set the color for when it's the current tab or not # We set the color for when it's the current tab or not
@ -309,12 +313,8 @@ class Chat:
'''When window gets focus''' '''When window gets focus'''
jid = self.get_active_jid() jid = self.get_active_jid()
textview = self.xmls[jid].get_widget('conversation_textview') textview = self.conversation_textviews[jid]
buffer = textview.get_buffer() if textview.at_the_end():
end_iter = buffer.get_end_iter()
end_rect = textview.get_iter_location(end_iter)
visible_rect = textview.get_visible_rect()
if end_rect.y <= (visible_rect.y + visible_rect.height):
#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.get_specific_unread(jid) self.nb_unread[jid] = 0 + self.get_specific_unread(jid)
@ -471,13 +471,8 @@ class Chat:
self.send_chatstate('inactive', old_jid) self.send_chatstate('inactive', old_jid)
self.send_chatstate('active', new_jid) self.send_chatstate('active', new_jid)
conversation_textview = self.xmls[new_jid].get_widget( conv_textview = self.conversation_textviews[new_jid]
'conversation_textview') if conv_textview.at_the_end():
conversation_buffer = conversation_textview.get_buffer()
end_iter = conversation_buffer.get_end_iter()
end_rect = conversation_textview.get_iter_location(end_iter)
visible_rect = conversation_textview.get_visible_rect()
if end_rect.y <= (visible_rect.y + visible_rect.height):
#we are at the end #we are at the end
if self.nb_unread[new_jid] > 0: if self.nb_unread[new_jid] > 0:
self.nb_unread[new_jid] = 0 + self.get_specific_unread(new_jid) self.nb_unread[new_jid] = 0 + self.get_specific_unread(new_jid)
@ -487,7 +482,7 @@ class Chat:
gajim.interface.systray.remove_jid(new_jid, self.account, gajim.interface.systray.remove_jid(new_jid, self.account,
self.get_message_type(new_jid)) self.get_message_type(new_jid))
conversation_textview.grab_focus() conv_textview.grab_focus()
def set_active_tab(self, jid): def set_active_tab(self, jid):
self.notebook.set_current_page(self.notebook.page_num(self.childs[jid])) self.notebook.set_current_page(self.notebook.page_num(self.childs[jid]))
@ -536,9 +531,6 @@ class Chat:
del self.last_time_printout[jid] del self.last_time_printout[jid]
del self.xmls[jid] del self.xmls[jid]
del self.childs[jid] del self.childs[jid]
del self.tagIn[jid]
del self.tagOut[jid]
del self.tagStatus[jid]
del self.sent_history[jid] del self.sent_history[jid]
del self.sent_history_pos[jid] del self.sent_history_pos[jid]
del self.typing_new[jid] del self.typing_new[jid]
@ -552,7 +544,6 @@ class Chat:
def bring_scroll_to_end(self, textview, diff_y = 0): def bring_scroll_to_end(self, textview, diff_y = 0):
''' scrolls to the end of textview if end is not visible ''' ''' scrolls to the end of textview if end is not visible '''
buffer = textview.get_buffer() buffer = textview.get_buffer()
at_the_end = False
end_iter = buffer.get_end_iter() end_iter = buffer.get_end_iter()
end_rect = textview.get_iter_location(end_iter) end_rect = textview.get_iter_location(end_iter)
visible_rect = textview.get_visible_rect() visible_rect = textview.get_visible_rect()
@ -574,11 +565,10 @@ class Chat:
message_scrolledwindow = xml_top.get_widget('message_scrolledwindow') message_scrolledwindow = xml_top.get_widget('message_scrolledwindow')
conversation_scrolledwindow = \ conversation_scrolledwindow = \
xml_top.get_widget('conversation_scrolledwindow') xml_top.get_widget('conversation_scrolledwindow')
conversation_textview = \ conv_textview = conversation_scrolledwindow.get_children()[0]
xml_top.get_widget('conversation_textview')
min_height = conversation_scrolledwindow.get_property('height-request') min_height = conversation_scrolledwindow.get_property('height-request')
conversation_height = conversation_textview.window.get_size()[1] conversation_height = conv_textview.window.get_size()[1]
message_height = message_textview.window.get_size()[1] message_height = message_textview.window.get_size()[1]
# new tab is not exposed yet # new tab is not exposed yet
if conversation_height < 2: if conversation_height < 2:
@ -604,7 +594,7 @@ class Chat:
message_scrolledwindow.set_property('hscrollbar-policy', message_scrolledwindow.set_property('hscrollbar-policy',
gtk.POLICY_NEVER) gtk.POLICY_NEVER)
message_scrolledwindow.set_property('height-request', -1) message_scrolledwindow.set_property('height-request', -1)
self.bring_scroll_to_end(conversation_textview, diff_y - 18) conv_textview.bring_scroll_to_end(diff_y - 18)
return True return True
def on_tab_eventbox_button_press_event(self, widget, event, child): def on_tab_eventbox_button_press_event(self, widget, event, child):
@ -619,6 +609,14 @@ class Chat:
'underline_togglebutton'): 'underline_togglebutton'):
self.xmls[jid].get_widget(w).set_no_show_all(True) self.xmls[jid].get_widget(w).set_no_show_all(True)
conv_textview = self.conversation_textviews[jid] = \
conversation_textview.ConversationTextview(self.account)
conv_textview.show_all()
conversation_scrolledwindow = self.xmls[jid].get_widget(
'conversation_scrolledwindow')
conversation_scrolledwindow.add(conv_textview)
conv_textview.connect('key_press_event', self.on_conversation_textview_key_press_event)
self.set_compact_view(self.always_compact_view) self.set_compact_view(self.always_compact_view)
self.nb_unread[jid] = 0 self.nb_unread[jid] = 0
gajim.last_message_time[self.account][jid] = 0 gajim.last_message_time[self.account][jid] = 0
@ -634,65 +632,11 @@ class Chat:
dialogs.ErrorDialog(unicode(msg), _('If that is not your language for which you want to highlight misspelled words, then please set your $LANG as appropriate. Eg. for French do export LANG=fr_FR or export LANG=fr_FR.UTF-8 in ~/.bash_profile or to make it global in /etc/profile.\n\nHighlighting misspelled words feature will not be used')).get_response() dialogs.ErrorDialog(unicode(msg), _('If that is not your language for which you want to highlight misspelled words, then please set your $LANG as appropriate. Eg. for French do export LANG=fr_FR or export LANG=fr_FR.UTF-8 in ~/.bash_profile or to make it global in /etc/profile.\n\nHighlighting misspelled words feature will not be used')).get_response()
gajim.config.set('use_speller', False) gajim.config.set('use_speller', False)
conversation_textview = self.xmls[jid].get_widget( conv_textview.modify_font(font)
'conversation_textview') conv_buffer = conv_textview.get_buffer()
conversation_textview.modify_font(font) end_iter = conv_buffer.get_end_iter()
conversation_buffer = conversation_textview.get_buffer()
end_iter = conversation_buffer.get_end_iter()
conversation_buffer.create_mark('end', end_iter, False)
self.tagIn[jid] = conversation_buffer.create_tag('incoming')
color = gajim.config.get('inmsgcolor')
self.tagIn[jid].set_property('foreground', color)
self.tagOut[jid] = conversation_buffer.create_tag('outgoing')
color = gajim.config.get('outmsgcolor')
self.tagOut[jid].set_property('foreground', color)
self.tagStatus[jid] = conversation_buffer.create_tag('status')
color = gajim.config.get('statusmsgcolor')
self.tagStatus[jid].set_property('foreground', color)
tag = conversation_buffer.create_tag('marked')
color = gajim.config.get('markedmsgcolor')
tag.set_property('foreground', color)
tag.set_property('weight', pango.WEIGHT_BOLD)
tag = conversation_buffer.create_tag('time_sometimes')
tag.set_property('foreground', '#9e9e9e')
tag.set_property('scale', pango.SCALE_SMALL)
tag.set_property('justification', gtk.JUSTIFY_CENTER)
tag = conversation_buffer.create_tag('small')
tag.set_property('scale', pango.SCALE_SMALL)
tag = conversation_buffer.create_tag('grey')
tag.set_property('foreground', '#9e9e9e')
tag = conversation_buffer.create_tag('url')
tag.set_property('foreground', '#0000ff')
tag.set_property('underline', pango.UNDERLINE_SINGLE)
tag.connect('event', self.hyperlink_handler, 'url')
tag = conversation_buffer.create_tag('mail')
tag.set_property('foreground', '#0000ff')
tag.set_property('underline', pango.UNDERLINE_SINGLE)
tag.connect('event', self.hyperlink_handler, 'mail')
tag = conversation_buffer.create_tag('bold')
tag.set_property('weight', pango.WEIGHT_BOLD)
tag = conversation_buffer.create_tag('italic')
tag.set_property('style', pango.STYLE_ITALIC)
tag = conversation_buffer.create_tag('underline')
tag.set_property('underline', pango.UNDERLINE_SINGLE)
conversation_buffer.create_tag('focus-out-line',
justification = gtk.JUSTIFY_CENTER) # FIXME: make it gtk.JUSTIFY_FILL when GTK+ REALLY supports it
self.xmls[jid].signal_autoconnect(self) self.xmls[jid].signal_autoconnect(self)
conversation_scrolledwindow = self.xmls[jid].get_widget(
'conversation_scrolledwindow')
conversation_scrolledwindow.get_vadjustment().connect('value-changed', conversation_scrolledwindow.get_vadjustment().connect('value-changed',
self.on_conversation_vadjustment_value_changed) self.on_conversation_vadjustment_value_changed)
@ -718,7 +662,7 @@ class Chat:
message_textview.modify_font(font) message_textview.modify_font(font)
message_textview.connect('size-request', self.size_request, message_textview.connect('size-request', self.size_request,
self.xmls[jid]) self.xmls[jid])
#init new sent history for this conversation # init new sent history for this conversation
self.sent_history[jid] = [] self.sent_history[jid] = []
self.sent_history_pos[jid] = 0 self.sent_history_pos[jid] = 0
self.typing_new[jid] = True self.typing_new[jid] = True
@ -740,15 +684,15 @@ class Chat:
elif event.keyval == gtk.keysyms.l or \ elif event.keyval == gtk.keysyms.l or \
event.keyval == gtk.keysyms.L: # CTRL + L event.keyval == gtk.keysyms.L: # CTRL + L
jid = self.get_active_jid() jid = self.get_active_jid()
conversation_textview = self.xmls[jid].get_widget('conversation_textview') conv_textview = self.conversation_textviews[jid]
conversation_textview.get_buffer().set_text('') conv_textview.get_buffer().set_text('')
elif event.keyval == gtk.keysyms.v: # CTRL + V elif event.keyval == gtk.keysyms.v: # CTRL + V
jid = self.get_active_jid() jid = self.get_active_jid()
message_textview = self.xmls[jid].get_widget('message_textview') message_textview = self.xmls[jid].get_widget('message_textview')
if not message_textview.is_focus(): if not message_textview.is_focus():
message_textview.grab_focus() message_textview.grab_focus()
message_textview.emit('key_press_event', event) message_textview.emit('key_press_event', event)
def on_chat_notebook_key_press_event(self, widget, event): def on_chat_notebook_key_press_event(self, widget, event):
st = '1234567890' # alt+1 means the first tab (tab 0) st = '1234567890' # alt+1 means the first tab (tab 0)
jid = self.get_active_jid() jid = self.get_active_jid()
@ -769,19 +713,17 @@ class Chat:
self.set_compact_view(not self.compact_view_current_state) self.set_compact_view(not self.compact_view_current_state)
elif event.keyval == gtk.keysyms.Page_Down: elif event.keyval == gtk.keysyms.Page_Down:
if event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE DOWN if event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE DOWN
conversation_textview = self.xmls[jid].\ conv_textview = self.conversation_textviews[jid]
get_widget('conversation_textview') rect = conv_textview.get_visible_rect()
rect = conversation_textview.get_visible_rect() iter = conv_textview.get_iter_at_location(rect.x,\
iter = conversation_textview.get_iter_at_location(rect.x,\
rect.y + rect.height) rect.y + rect.height)
conversation_textview.scroll_to_iter(iter, 0.1, True, 0, 0) conv_textview.scroll_to_iter(iter, 0.1, True, 0, 0)
elif event.keyval == gtk.keysyms.Page_Up: elif event.keyval == gtk.keysyms.Page_Up:
if event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE UP if event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE UP
conversation_textview = self.xmls[jid].\ conv_textview = self.conversation_textviews[jid]
get_widget('conversation_textview') rect = conv_textview.get_visible_rect()
rect = conversation_textview.get_visible_rect() iter = conv_textview.get_iter_at_location(rect.x, rect.y)
iter = conversation_textview.get_iter_at_location(rect.x, rect.y) conv_textview.scroll_to_iter(iter, 0.1, True, 0, 1)
conversation_textview.scroll_to_iter(iter, 0.1, True, 0, 1)
# or event.keyval == gtk.keysyms.KP_Up # or event.keyval == gtk.keysyms.KP_Up
elif event.keyval == gtk.keysyms.Up: elif event.keyval == gtk.keysyms.Up:
if event.state & gtk.gdk.SHIFT_MASK: # SHIFT + UP if event.state & gtk.gdk.SHIFT_MASK: # SHIFT + UP
@ -804,9 +746,8 @@ class Chat:
self.notebook.set_current_page(0) self.notebook.set_current_page(0)
elif (event.keyval == gtk.keysyms.l or event.keyval == gtk.keysyms.L) \ elif (event.keyval == gtk.keysyms.l or event.keyval == gtk.keysyms.L) \
and event.state & gtk.gdk.CONTROL_MASK: # CTRL + L and event.state & gtk.gdk.CONTROL_MASK: # CTRL + L
conversation_textview = self.xmls[jid].\ conv_textview = self.conversation_textviews[jid]
get_widget('conversation_textview') conv_textview.get_buffer().set_text('')
conversation_textview.get_buffer().set_text('')
elif event.keyval == gtk.keysyms.v and event.state & gtk.gdk.CONTROL_MASK: elif event.keyval == gtk.keysyms.v and event.state & gtk.gdk.CONTROL_MASK:
# CTRL + V # CTRL + V
jid = self.get_active_jid() jid = self.get_active_jid()
@ -832,497 +773,35 @@ class Chat:
jid = self.get_active_jid() jid = self.get_active_jid()
if not self.nb_unread[jid]: if not self.nb_unread[jid]:
return return
textview = self.xmls[jid].get_widget('conversation_textview') conv_textview = self.conversation_textviews[jid]
buffer = textview.get_buffer() if conv_textview.at_the_end() and self.window.is_active():
end_iter = buffer.get_end_iter()
end_rect = textview.get_iter_location(end_iter)
visible_rect = textview.get_visible_rect()
if end_rect.y <= (visible_rect.y + visible_rect.height) and \
self.window.is_active():
#we are at the end #we are at the end
self.nb_unread[jid] = 0 + self.get_specific_unread(jid) self.nb_unread[jid] = self.get_specific_unread(jid)
self.redraw_tab(jid) self.redraw_tab(jid)
self.show_title() self.show_title()
if gajim.interface.systray_enabled: if gajim.interface.systray_enabled:
gajim.interface.systray.remove_jid(jid, self.account, gajim.interface.systray.remove_jid(jid, self.account,
self.get_message_type(jid)) self.get_message_type(jid))
def show_line_tooltip(self):
jid = self.get_active_jid()
textview = self.xmls[jid].get_widget('conversation_textview')
pointer = textview.get_pointer()
x, y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer[0],
pointer[1])
tags = textview.get_iter_at_location(x, y).get_tags()
tag_table = textview.get_buffer().get_tag_table()
over_line = False
for tag in tags:
if tag == tag_table.lookup('focus-out-line'):
over_line = True
break
if over_line and not self.line_tooltip.win:
# check if the current pointer is still over the line
pointer_x, pointer_y, spam = textview.window.get_pointer()
x, y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x,
pointer_y)
position = textview.window.get_origin()
self.line_tooltip.show_tooltip(_('The text below this ruler is what has been said since the last time you paid attention to this group chat'), (0, 8),
(self.window.get_screen().get_display().get_pointer()[1],
position[1] + pointer_y))
def on_conversation_textview_motion_notify_event(self, widget, event): def clear(self, tv):
'''change the cursor to a hand when we are over a mail or an url''' buffer = tv.get_buffer()
jid = self.get_active_jid()
pointer_x, pointer_y, spam = widget.window.get_pointer()
x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x,
pointer_y)
tags = widget.get_iter_at_location(x, y).get_tags()
if self.change_cursor:
widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
gtk.gdk.Cursor(gtk.gdk.XTERM))
self.change_cursor = None
tag_table = widget.get_buffer().get_tag_table()
over_line = False
for tag in tags:
if tag in (tag_table.lookup('url'), tag_table.lookup('mail')):
widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
gtk.gdk.Cursor(gtk.gdk.HAND2))
self.change_cursor = tag
elif self.widget_name == 'groupchat_window' and \
tag == tag_table.lookup('focus-out-line'):
over_line = True
# FIXME: found out (dkirov can help) what those params are supposed to be
if self.line_tooltip.timeout != 0:
# Check if we should hide the line tooltip
if not over_line:
self.line_tooltip.hide_tooltip()
if over_line and not self.line_tooltip.win:
self.line_tooltip.timeout = gobject.timeout_add(500,
self.show_line_tooltip)
widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
self.change_cursor = tag
def on_clear(self, widget, textview):
'''clear text in the given textview'''
buffer = textview.get_buffer()
start, end = buffer.get_bounds() start, end = buffer.get_bounds()
buffer.delete(start, end) buffer.delete(start, end)
def visit_url_from_menuitem(self, widget, link):
'''basically it filters out the widget instance'''
helpers.launch_browser_mailer('url', link)
def on_conversation_textview_populate_popup(self, textview, menu):
'''we override the default context menu and we prepend Clear
and if we have sth selected we show a submenu with actions on the phrase
(see on_conversation_textview_button_press_event)'''
item = gtk.SeparatorMenuItem()
menu.prepend(item)
item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
menu.prepend(item)
item.connect('activate', self.on_clear, textview)
if self.selected_phrase:
s = self.selected_phrase
if len(s) > 25:
s = s[:21] + '...'
item = gtk.MenuItem(_('Actions for "%s"') % s)
menu.prepend(item)
submenu = gtk.Menu()
item.set_submenu(submenu)
always_use_en = gajim.config.get('always_english_wikipedia')
if always_use_en:
link = 'http://en.wikipedia.org/wiki/Special:Search?search=%s'\
% self.selected_phrase
else:
link = 'http://%s.wikipedia.org/wiki/Special:Search?search=%s'\
% (gajim.LANG, self.selected_phrase)
item = gtk.MenuItem(_('Read _Wikipedia Article'))
item.connect('activate', self.visit_url_from_menuitem, link)
submenu.append(item)
item = gtk.MenuItem(_('Look it up in _Dictionary'))
dict_link = gajim.config.get('dictionary_url')
if dict_link == 'WIKTIONARY':
# special link (yeah undocumented but default)
always_use_en = gajim.config.get('always_english_wiktionary')
if always_use_en:
link = 'http://en.wiktionary.org/wiki/Special:Search?search=%s'\
% self.selected_phrase
else:
link = 'http://%s.wiktionary.org/wiki/Special:Search?search=%s'\
% (gajim.LANG, self.selected_phrase)
item.connect('activate', self.visit_url_from_menuitem, link)
else:
if dict_link.find('%s') == -1:
#we must have %s in the url if not WIKTIONARY
item = gtk.MenuItem(_('Dictionary URL is missing an "%s" and it is not WIKTIONARY'))
item.set_property('sensitive', False)
else:
link = dict_link % self.selected_phrase
item.connect('activate', self.visit_url_from_menuitem, link)
submenu.append(item)
search_link = gajim.config.get('search_engine')
if search_link.find('%s') == -1:
#we must have %s in the url
item = gtk.MenuItem(_('Web Search URL is missing an "%s"'))
item.set_property('sensitive', False)
else:
item = gtk.MenuItem(_('Web _Search for it'))
link = search_link % self.selected_phrase
item.connect('activate', self.visit_url_from_menuitem, link)
submenu.append(item)
menu.show_all()
def on_conversation_textview_button_press_event(self, widget, event):
# If we clicked on a taged text do NOT open the standard popup menu
# if normal text check if we have sth selected
self.selected_phrase = ''
if event.button != 3: # if not right click
return False
win = widget.get_window(gtk.TEXT_WINDOW_TEXT)
x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
int(event.x), int(event.y))
iter = widget.get_iter_at_location(x, y)
tags = iter.get_tags()
if tags: # we clicked on sth special (it can be status message too)
for tag in tags:
tag_name = tag.get_property('name')
if 'url' in tag_name or 'mail' in tag_name:
return True # we block normal context menu
# we check if sth was selected and if it was we assign
# selected_phrase variable
# so on_conversation_textview_populate_popup can use it
buffer = widget.get_buffer()
return_val = buffer.get_selection_bounds()
if return_val: # if sth was selected when we right-clicked
# get the selected text
start_sel, finish_sel = return_val[0], return_val[1]
self.selected_phrase = buffer.get_text(start_sel, finish_sel).decode('utf-8')
def print_time_timeout(self, jid):
if not jid in self.xmls.keys():
return False
if gajim.config.get('print_time') == 'sometimes':
textview = self.xmls[jid].get_widget('conversation_textview')
buffer = textview.get_buffer()
end_iter = buffer.get_end_iter()
tim = time.localtime()
tim_format = time.strftime('%H:%M', tim)
buffer.insert_with_tags_by_name(end_iter,
'\n' + tim_format,
'time_sometimes')
#scroll to the end of the textview
end_rect = textview.get_iter_location(end_iter)
visible_rect = textview.get_visible_rect()
if end_rect.y <= (visible_rect.y + visible_rect.height):
#we are at the end
self.scroll_to_end(textview)
return True # loop again
if self.print_time_timeout_id.has_key(jid):
del self.print_time_timeout_id[jid]
return False
def on_open_link_activate(self, widget, kind, text):
helpers.launch_browser_mailer(kind, text)
def on_copy_link_activate(self, widget, text):
clip = gtk.clipboard_get()
clip.set_text(text)
def on_start_chat_activate(self, widget, jid):
gajim.interface.roster.new_chat_from_jid(self.account, jid)
def on_join_group_chat_menuitem_activate(self, widget, jid):
room, server = jid.split('@')
if gajim.interface.windows[self.account].has_key('join_gc'):
instance = gajim.interface.windows[self.account]['join_gc']
instance.xml.get_widget('server_entry').set_text(server)
instance.xml.get_widget('room_entry').set_text(room)
gajim.interface.windows[self.account]['join_gc'].window.present()
else:
try:
gajim.interface.windows[self.account]['join_gc'] = \
dialogs.JoinGroupchatWindow(self.account, server, room)
except RuntimeError:
pass
def on_add_to_roster_activate(self, widget, jid):
dialogs.AddNewContactWindow(self.account, jid)
def make_link_menu(self, event, kind, text):
xml = gtk.glade.XML(GTKGUI_GLADE, 'chat_context_menu', APP)
menu = xml.get_widget('chat_context_menu')
childs = menu.get_children()
if kind == 'url':
childs[0].connect('activate', self.on_copy_link_activate, text)
childs[1].connect('activate', self.on_open_link_activate, kind, text)
childs[2].hide() # copy mail address
childs[3].hide() # open mail composer
childs[4].hide() # jid section seperator
childs[5].hide() # start chat
childs[6].hide() # join group chat
childs[7].hide() # add to roster
else: # It's a mail or a JID
childs[2].connect('activate', self.on_copy_link_activate, text)
childs[3].connect('activate', self.on_open_link_activate, kind, text)
childs[5].connect('activate', self.on_start_chat_activate, text)
childs[6].connect('activate',
self.on_join_group_chat_menuitem_activate, text)
allow_add = False
if gajim.contacts[self.account].has_key(text):
c = gajim.contacts[self.account][text][0]
if _('not in the roster') in c.groups:
allow_add = True
else: # he's not at all in the account contacts
allow_add = True
if allow_add:
childs[7].connect('activate', self.on_add_to_roster_activate, text)
childs[7].show() # show add to roster menuitem
else:
childs[7].hide() # hide add to roster menuitem
childs[0].hide() # copy link location
childs[1].hide() # open link in browser
menu.popup(None, None, None, event.button, event.time)
def hyperlink_handler(self, texttag, widget, event, iter, kind):
if event.type == gtk.gdk.BUTTON_PRESS:
begin_iter = iter.copy()
#we get the begining of the tag
while not begin_iter.begins_tag(texttag):
begin_iter.backward_char()
end_iter = iter.copy()
#we get the end of the tag
while not end_iter.ends_tag(texttag):
end_iter.forward_char()
word = widget.get_buffer().get_text(begin_iter, end_iter).decode(
'utf-8')
if event.button == 3: # right click
self.make_link_menu(event, kind, word)
else:
#we launch the correct application
helpers.launch_browser_mailer(kind, word)
def detect_and_print_special_text(self, otext, jid, other_tags):
textview = self.xmls[jid].get_widget('conversation_textview')
buffer = textview.get_buffer()
start = 0
end = 0
index = 0
# basic: links + mail + formatting is always checked (we like that)
if gajim.config.get('useemoticons'): # search for emoticons & urls
iterator = gajim.interface.emot_and_basic_re.finditer(otext)
else: # search for just urls + mail + formatting
iterator = gajim.interface.basic_pattern_re.finditer(otext)
for match in iterator:
start, end = match.span()
special_text = otext[start:end]
if start != 0:
text_before_special_text = otext[index:start]
end_iter = buffer.get_end_iter()
buffer.insert_with_tags_by_name(end_iter,
text_before_special_text, *other_tags)
index = end # update index
# now print it
self.print_special_text(special_text, other_tags, textview)
return index
def print_special_text(self, special_text, other_tags, textview):
tags = []
use_other_tags = True
show_ascii_formatting_chars=gajim.config.get('show_ascii_formatting_chars')
buffer = textview.get_buffer()
possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS
if possible_emot_ascii_caps in gajim.interface.emoticons.keys():
#it's an emoticon
emot_ascii = possible_emot_ascii_caps
end_iter = buffer.get_end_iter()
anchor = buffer.create_child_anchor(end_iter)
img = gtk.Image()
img.set_from_file(gajim.interface.emoticons[emot_ascii])
img.show()
#add with possible animation
textview.add_child_at_anchor(img, anchor)
elif special_text.startswith('mailto:'):
#it's a mail
tags.append('mail')
use_other_tags = False
elif gajim.interface.sth_at_sth_dot_sth_re.match(special_text):
#it's a mail
tags.append('mail')
use_other_tags = False
elif special_text.startswith('*'): # it's a bold text
tags.append('bold')
if special_text[1] == '/': # it's also italic
tags.append('italic')
if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove */ /*
elif special_text[1] == '_': # it's also underlined
tags.append('underline')
if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove *_ _*
else:
if not show_ascii_formatting_chars:
special_text = special_text[1:-1] # remove * *
elif special_text.startswith('/'): # it's an italic text
tags.append('italic')
if special_text[1] == '*': # it's also bold
tags.append('bold')
if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove /* */
elif special_text[1] == '_': # it's also underlined
tags.append('underline')
if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove /_ _/
else:
if not show_ascii_formatting_chars:
special_text = special_text[1:-1] # remove / /
elif special_text.startswith('_'): # it's an underlined text
tags.append('underline')
if special_text[1] == '*': # it's also bold
tags.append('bold')
if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove _* *_
elif special_text[1] == '/': # it's also italic
tags.append('italic')
if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove _/ /_
else:
if not show_ascii_formatting_chars:
special_text = special_text[1:-1] # remove _ _
else:
#it's a url
tags.append('url')
use_other_tags = False
if len(tags) > 0:
end_iter = buffer.get_end_iter()
all_tags = tags[:]
if use_other_tags:
all_tags += other_tags
buffer.insert_with_tags_by_name(end_iter, special_text, *all_tags)
def scroll_to_end(self, textview):
parent = textview.get_parent()
buffer = textview.get_buffer()
textview.scroll_to_mark(buffer.get_mark('end'), 0, True, 0, 1)
adjustment = parent.get_hadjustment()
adjustment.set_value(0)
return False
def print_empty_line(self, jid):
textview = self.xmls[jid].get_widget('conversation_textview')
buffer = textview.get_buffer()
end_iter = buffer.get_end_iter()
buffer.insert(end_iter, '\n')
def print_conversation_line(self, text, jid, kind, name, tim, def print_conversation_line(self, text, jid, kind, name, tim,
other_tags_for_name = [], other_tags_for_time = [], other_tags_for_name = [], other_tags_for_time = [],
other_tags_for_text = [], count_as_new = True, subject = None): other_tags_for_text = [], count_as_new = True, subject = None):
'''prints 'chat' type messages''' '''prints 'chat' type messages'''
textview = self.xmls[jid].get_widget('conversation_textview') textview = self.conversation_textviews[jid]
buffer = textview.get_buffer()
buffer.begin_user_action()
at_the_end = False
end_iter = buffer.get_end_iter()
end_rect = textview.get_iter_location(end_iter)
visible_rect = textview.get_visible_rect()
if end_rect.y <= (visible_rect.y + visible_rect.height):
at_the_end = True
if buffer.get_char_count() > 0:
buffer.insert(end_iter, '\n')
update_time = True
if kind == 'incoming_queue':
kind = 'incoming'
update_time = False
# print the time stamp
if gajim.config.get('print_time') == 'always':
if not tim:
tim = time.localtime()
before_str = gajim.config.get('before_time')
after_str = gajim.config.get('after_time')
format = before_str + '%H:%M:%S' + after_str
tim_format = time.strftime(format, tim)
buffer.insert_with_tags_by_name(end_iter, tim_format + ' ',
*other_tags_for_time)
elif gajim.config.get('print_time') == 'sometimes':
every_foo_seconds = 60 * gajim.config.get(
'print_ichat_every_foo_minutes')
seconds_passed = time.time() - self.last_time_printout[jid]
if seconds_passed > every_foo_seconds:
self.last_time_printout[jid] = time.time()
end_iter = buffer.get_end_iter()
tim = time.localtime()
tim_format = time.strftime('%H:%M', tim)
buffer.insert_with_tags_by_name(end_iter,
tim_format + '\n',
'time_sometimes')
# scroll to the end of the textview
end_rect = textview.get_iter_location(end_iter)
visible_rect = textview.get_visible_rect()
text_tags = other_tags_for_text[:] # create a new list
if kind == 'status':
text_tags.append(kind)
elif text.startswith('/me ') or text.startswith('/me\n'):
text = '* ' + name + text[3:]
text_tags.append(kind)
if name and len(text_tags) == len(other_tags_for_text):
# not status nor /me
name_tags = other_tags_for_name[:] # create a new list
name_tags.append(kind)
before_str = gajim.config.get('before_nickname')
after_str = gajim.config.get('after_nickname')
format = before_str + name + after_str + ' '
buffer.insert_with_tags_by_name(end_iter, format, *name_tags)
# detect urls formatting and if the user has it on emoticons
index = self.detect_and_print_special_text(text, jid, text_tags)
if subject: # if we have subject, show it too!
subject = _('Subject: %s\n') % subject
end_iter = buffer.get_end_iter()
buffer.insert(end_iter, subject)
# add the rest of text located in the index and after
end_iter = buffer.get_end_iter()
buffer.insert_with_tags_by_name(end_iter, text[index:], *text_tags)
#scroll to the end of the textview
end = False end = False
if at_the_end or kind == 'outgoing': if textview.at_the_end() or kind == 'outgoing':
# we are at the end or we are sending something
end = True end = True
# scroll to the end (via idle in case the scrollbar has appeared) textview.print_conversation_line(text, jid, kind, name, tim,
gobject.idle_add(self.scroll_to_end, textview) other_tags_for_name, other_tags_for_time, other_tags_for_text, subject)
buffer.end_user_action()
if not count_as_new: if not count_as_new:
return return
if kind == 'incoming' and update_time: if kind == 'incoming_queue':
gajim.last_message_time[self.account][jid] = time.time() gajim.last_message_time[self.account][jid] = time.time()
urgent = True urgent = True
if (jid != self.get_active_jid() or \ if (jid != self.get_active_jid() or \
@ -1432,6 +911,5 @@ class Chat:
widget.show_all() widget.show_all()
# make the last message visible, when changing to "full view" # make the last message visible, when changing to "full view"
if not state: if not state:
conversation_textview = \ conv_textview = self.conversation_textviews[jid]
self.xmls[jid].get_widget('conversation_textview') gobject.idle_add(conv_textview.scroll_to_end_iter)
gobject.idle_add(self.scroll_to_end_iter, conversation_textview)

View File

@ -0,0 +1,574 @@
## conversation_textview.py
##
## Gajim Team:
## - Yann Le Boulanger <asterix@lagaule.org>
## - Vincent Hanquez <tab@snarc.org>
## - Nikos Kouremenos <kourem@gmail.com>
##
## Copyright (C) 2003-2005 Gajim Team
##
## 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 gtk
import gtk.glade
import pango
import gobject
import time
import tooltips
import dialogs
try:
import gtkspell
except:
pass
from common import gajim
from common import helpers
from common import i18n
_ = i18n._
APP = i18n.APP
gtk.glade.bindtextdomain(APP, i18n.DIR)
gtk.glade.textdomain(APP)
GTKGUI_GLADE = 'gtkgui.glade'
class ConversationTextview(gtk.TextView):
'''Class for chat/groupchat windows'''
def __init__(self, account):
gtk.TextView.__init__(self)
self.account = account
self.change_cursor = None
font = pango.FontDescription(gajim.config.get('conversation_font'))
self.modify_font(font)
buffer = self.get_buffer()
end_iter = buffer.get_end_iter()
buffer.create_mark('end', end_iter, False)
self.tagIn = buffer.create_tag('incoming')
color = gajim.config.get('inmsgcolor')
self.tagIn.set_property('foreground', color)
self.tagOut = buffer.create_tag('outgoing')
color = gajim.config.get('outmsgcolor')
self.tagOut.set_property('foreground', color)
self.tagStatus = buffer.create_tag('status')
color = gajim.config.get('statusmsgcolor')
self.tagStatus.set_property('foreground', color)
tag = buffer.create_tag('marked')
color = gajim.config.get('markedmsgcolor')
tag.set_property('foreground', color)
tag.set_property('weight', pango.WEIGHT_BOLD)
tag = buffer.create_tag('time_sometimes')
tag.set_property('foreground', '#9e9e9e')
tag.set_property('scale', pango.SCALE_SMALL)
tag.set_property('justification', gtk.JUSTIFY_CENTER)
tag = buffer.create_tag('small')
tag.set_property('scale', pango.SCALE_SMALL)
tag = buffer.create_tag('grey')
tag.set_property('foreground', '#9e9e9e')
tag = buffer.create_tag('url')
tag.set_property('foreground', '#0000ff')
tag.set_property('underline', pango.UNDERLINE_SINGLE)
tag.connect('event', self.hyperlink_handler, 'url')
tag = buffer.create_tag('mail')
tag.set_property('foreground', '#0000ff')
tag.set_property('underline', pango.UNDERLINE_SINGLE)
tag.connect('event', self.hyperlink_handler, 'mail')
tag = buffer.create_tag('bold')
tag.set_property('weight', pango.WEIGHT_BOLD)
tag = buffer.create_tag('italic')
tag.set_property('style', pango.STYLE_ITALIC)
tag = buffer.create_tag('underline')
tag.set_property('underline', pango.UNDERLINE_SINGLE)
buffer.create_tag('focus-out-line', justification = gtk.JUSTIFY_CENTER) # FIXME: make it gtk.JUSTIFY_FILL when GTK+ REALLY supports it
self.set_wrap_mode(gtk.WRAP_WORD)
self.set_editable(False)
self.connect('motion_notify_event', self.on_textview_motion_notify_event)
self.connect('populate_popup', self.on_textview_populate_popup)
self.connect('button_press_event', self.on_textview_button_press_event)
# muc attention states (when we are mentioned in a muc)
# if the room jid is in the list, the room has mentioned us
self.muc_attentions = []
self.line_tooltip = tooltips.BaseTooltip()
def update_tags(self):
self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor'))
self.tagOut.set_property('foreground', gajim.config.get('outmsgcolor'))
self.tagStatus.set_property('foreground',
gajim.config.get('statusmsgcolor'))
def at_the_end(self):
buffer = self.get_buffer()
end_iter = buffer.get_end_iter()
end_rect = self.get_iter_location(end_iter)
visible_rect = self.get_visible_rect()
if end_rect.y <= (visible_rect.y + visible_rect.height):
return True
return False
def scroll_to_end(self):
parent = self.get_parent()
buffer = self.get_buffer()
self.scroll_to_mark(buffer.get_mark('end'), 0, True, 0, 1)
adjustment = parent.get_hadjustment()
adjustment.set_value(0)
return False # when called in an idle_add, just do it once
def bring_scroll_to_end(self, diff_y = 0):
''' scrolls to the end of textview if end is not visible '''
buffer = self.get_buffer()
end_iter = buffer.get_end_iter()
end_rect = self.get_iter_location(end_iter)
visible_rect = self.get_visible_rect()
# scroll only if expected end is not visible
if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
gobject.idle_add(self.scroll_to_end_iter)
def scroll_to_end_iter(self):
buffer = self.get_buffer()
end_iter = buffer.get_end_iter()
self.scroll_to_iter(end_iter, 0, False, 1, 1)
return False # when called in an idle_add, just do it once
def show_line_tooltip(self):
pointer = self.get_pointer()
x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer[0],
pointer[1])
tags = self.get_iter_at_location(x, y).get_tags()
tag_table = self.get_buffer().get_tag_table()
over_line = False
for tag in tags:
if tag == tag_table.lookup('focus-out-line'):
over_line = True
break
if over_line and not self.line_tooltip.win:
# check if the current pointer is still over the line
position = self.window.get_origin()
win = self.get_toplevel()
self.line_tooltip.show_tooltip(_('The text below this ruler is what has been said since the last time you paid attention to this group chat'), (0, 8),
(win.get_screen().get_display().get_pointer()[1],
position[1] + pointer[1]))
def on_textview_motion_notify_event(self, widget, event):
'''change the cursor to a hand when we are over a mail or an url'''
pointer_x, pointer_y, spam = self.window.get_pointer()
x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x,
pointer_y)
tags = self.get_iter_at_location(x, y).get_tags()
if self.change_cursor:
self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
gtk.gdk.Cursor(gtk.gdk.XTERM))
self.change_cursor = None
tag_table = self.get_buffer().get_tag_table()
over_line = False
for tag in tags:
if tag in (tag_table.lookup('url'), tag_table.lookup('mail')):
self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
gtk.gdk.Cursor(gtk.gdk.HAND2))
self.change_cursor = tag
elif tag == tag_table.lookup('focus-out-line'):
over_line = True
if self.line_tooltip.timeout != 0:
# Check if we should hide the line tooltip
if not over_line:
self.line_tooltip.hide_tooltip()
if over_line and not self.line_tooltip.win:
self.line_tooltip.timeout = gobject.timeout_add(500,
self.show_line_tooltip)
self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
self.change_cursor = tag
def clear(self, tv = None):
'''clear text in the textview'''
buffer = self.get_buffer()
start, end = buffer.get_bounds()
buffer.delete(start, end)
def visit_url_from_menuitem(self, widget, link):
'''basically it filters out the widget instance'''
helpers.launch_browser_mailer('url', link)
def on_textview_populate_popup(self, textview, menu):
'''we override the default context menu and we prepend Clear
and if we have sth selected we show a submenu with actions on the phrase
(see on_conversation_textview_button_press_event)'''
item = gtk.SeparatorMenuItem()
menu.prepend(item)
item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
menu.prepend(item)
item.connect('activate', self.clear)
if self.selected_phrase:
s = self.selected_phrase
if len(s) > 25:
s = s[:21] + '...'
item = gtk.MenuItem(_('Actions for "%s"') % s)
menu.prepend(item)
submenu = gtk.Menu()
item.set_submenu(submenu)
always_use_en = gajim.config.get('always_english_wikipedia')
if always_use_en:
link = 'http://en.wikipedia.org/wiki/Special:Search?search=%s'\
% self.selected_phrase
else:
link = 'http://%s.wikipedia.org/wiki/Special:Search?search=%s'\
% (gajim.LANG, self.selected_phrase)
item = gtk.MenuItem(_('Read _Wikipedia Article'))
item.connect('activate', self.visit_url_from_menuitem, link)
submenu.append(item)
item = gtk.MenuItem(_('Look it up in _Dictionary'))
dict_link = gajim.config.get('dictionary_url')
if dict_link == 'WIKTIONARY':
# special link (yeah undocumented but default)
always_use_en = gajim.config.get('always_english_wiktionary')
if always_use_en:
link = 'http://en.wiktionary.org/wiki/Special:Search?search=%s'\
% self.selected_phrase
else:
link = 'http://%s.wiktionary.org/wiki/Special:Search?search=%s'\
% (gajim.LANG, self.selected_phrase)
item.connect('activate', self.visit_url_from_menuitem, link)
else:
if dict_link.find('%s') == -1:
#we must have %s in the url if not WIKTIONARY
item = gtk.MenuItem(_('Dictionary URL is missing an "%s" and it is not WIKTIONARY'))
item.set_property('sensitive', False)
else:
link = dict_link % self.selected_phrase
item.connect('activate', self.visit_url_from_menuitem, link)
submenu.append(item)
search_link = gajim.config.get('search_engine')
if search_link.find('%s') == -1:
#we must have %s in the url
item = gtk.MenuItem(_('Web Search URL is missing an "%s"'))
item.set_property('sensitive', False)
else:
item = gtk.MenuItem(_('Web _Search for it'))
link = search_link % self.selected_phrase
item.connect('activate', self.visit_url_from_menuitem, link)
submenu.append(item)
menu.show_all()
def on_textview_button_press_event(self, widget, event):
# If we clicked on a taged text do NOT open the standard popup menu
# if normal text check if we have sth selected
self.selected_phrase = ''
if event.button != 3: # if not right click
return False
win = self.get_window(gtk.TEXT_WINDOW_TEXT)
x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
int(event.x), int(event.y))
iter = self.get_iter_at_location(x, y)
tags = iter.get_tags()
if tags: # we clicked on sth special (it can be status message too)
for tag in tags:
tag_name = tag.get_property('name')
if 'url' in tag_name or 'mail' in tag_name:
return True # we block normal context menu
# we check if sth was selected and if it was we assign
# selected_phrase variable
# so on_conversation_textview_populate_popup can use it
buffer = self.get_buffer()
return_val = buffer.get_selection_bounds()
if return_val: # if sth was selected when we right-clicked
# get the selected text
start_sel, finish_sel = return_val[0], return_val[1]
self.selected_phrase = buffer.get_text(start_sel, finish_sel).decode('utf-8')
def on_open_link_activate(self, widget, kind, text):
helpers.launch_browser_mailer(kind, text)
def on_copy_link_activate(self, widget, text):
clip = gtk.clipboard_get()
clip.set_text(text)
def on_start_chat_activate(self, widget, jid):
gajim.interface.roster.new_chat_from_jid(self.account, jid)
def on_join_group_chat_menuitem_activate(self, widget, jid):
room, server = jid.split('@')
if gajim.interface.windows[self.account].has_key('join_gc'):
instance = gajim.interface.windows[self.account]['join_gc']
instance.xml.get_widget('server_entry').set_text(server)
instance.xml.get_widget('room_entry').set_text(room)
gajim.interface.windows[self.account]['join_gc'].window.present()
else:
try:
gajim.interface.windows[self.account]['join_gc'] = \
dialogs.JoinGroupchatWindow(self.account, server, room)
except RuntimeError:
pass
def on_add_to_roster_activate(self, widget, jid):
dialogs.AddNewContactWindow(self.account, jid)
def make_link_menu(self, event, kind, text):
xml = gtk.glade.XML(GTKGUI_GLADE, 'chat_context_menu', APP)
menu = xml.get_widget('chat_context_menu')
childs = menu.get_children()
if kind == 'url':
childs[0].connect('activate', self.on_copy_link_activate, text)
childs[1].connect('activate', self.on_open_link_activate, kind, text)
childs[2].hide() # copy mail address
childs[3].hide() # open mail composer
childs[4].hide() # jid section seperator
childs[5].hide() # start chat
childs[6].hide() # join group chat
childs[7].hide() # add to roster
else: # It's a mail or a JID
childs[2].connect('activate', self.on_copy_link_activate, text)
childs[3].connect('activate', self.on_open_link_activate, kind, text)
childs[5].connect('activate', self.on_start_chat_activate, text)
childs[6].connect('activate',
self.on_join_group_chat_menuitem_activate, text)
allow_add = False
if gajim.contacts[self.account].has_key(text):
c = gajim.contacts[self.account][text][0]
if _('not in the roster') in c.groups:
allow_add = True
else: # he's not at all in the account contacts
allow_add = True
if allow_add:
childs[7].connect('activate', self.on_add_to_roster_activate, text)
childs[7].show() # show add to roster menuitem
else:
childs[7].hide() # hide add to roster menuitem
childs[0].hide() # copy link location
childs[1].hide() # open link in browser
menu.popup(None, None, None, event.button, event.time)
def hyperlink_handler(self, texttag, widget, event, iter, kind):
if event.type == gtk.gdk.BUTTON_PRESS:
begin_iter = iter.copy()
# we get the begining of the tag
while not begin_iter.begins_tag(texttag):
begin_iter.backward_char()
end_iter = iter.copy()
# we get the end of the tag
while not end_iter.ends_tag(texttag):
end_iter.forward_char()
word = self.get_buffer().get_text(begin_iter, end_iter).decode('utf-8')
if event.button == 3: # right click
self.make_link_menu(event, kind, word)
else:
# we launch the correct application
helpers.launch_browser_mailer(kind, word)
def detect_and_print_special_text(self, otext, jid, other_tags):
buffer = self.get_buffer()
start = 0
end = 0
index = 0
# basic: links + mail + formatting is always checked (we like that)
if gajim.config.get('useemoticons'): # search for emoticons & urls
iterator = gajim.interface.emot_and_basic_re.finditer(otext)
else: # search for just urls + mail + formatting
iterator = gajim.interface.basic_pattern_re.finditer(otext)
for match in iterator:
start, end = match.span()
special_text = otext[start:end]
if start != 0:
text_before_special_text = otext[index:start]
end_iter = buffer.get_end_iter()
buffer.insert_with_tags_by_name(end_iter,
text_before_special_text, *other_tags)
index = end # update index
# now print it
self.print_special_text(special_text, other_tags)
return index
def print_special_text(self, special_text, other_tags):
tags = []
use_other_tags = True
show_ascii_formatting_chars = \
gajim.config.get('show_ascii_formatting_chars')
buffer = self.get_buffer()
possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS
if possible_emot_ascii_caps in gajim.interface.emoticons.keys():
#it's an emoticon
emot_ascii = possible_emot_ascii_caps
end_iter = buffer.get_end_iter()
anchor = buffer.create_child_anchor(end_iter)
img = gtk.Image()
img.set_from_file(gajim.interface.emoticons[emot_ascii])
img.show()
#add with possible animation
self.add_child_at_anchor(img, anchor)
elif special_text.startswith('mailto:'):
#it's a mail
tags.append('mail')
use_other_tags = False
elif gajim.interface.sth_at_sth_dot_sth_re.match(special_text):
#it's a mail
tags.append('mail')
use_other_tags = False
elif special_text.startswith('*'): # it's a bold text
tags.append('bold')
if special_text[1] == '/': # it's also italic
tags.append('italic')
if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove */ /*
elif special_text[1] == '_': # it's also underlined
tags.append('underline')
if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove *_ _*
else:
if not show_ascii_formatting_chars:
special_text = special_text[1:-1] # remove * *
elif special_text.startswith('/'): # it's an italic text
tags.append('italic')
if special_text[1] == '*': # it's also bold
tags.append('bold')
if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove /* */
elif special_text[1] == '_': # it's also underlined
tags.append('underline')
if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove /_ _/
else:
if not show_ascii_formatting_chars:
special_text = special_text[1:-1] # remove / /
elif special_text.startswith('_'): # it's an underlined text
tags.append('underline')
if special_text[1] == '*': # it's also bold
tags.append('bold')
if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove _* *_
elif special_text[1] == '/': # it's also italic
tags.append('italic')
if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove _/ /_
else:
if not show_ascii_formatting_chars:
special_text = special_text[1:-1] # remove _ _
else:
#it's a url
tags.append('url')
use_other_tags = False
if len(tags) > 0:
end_iter = buffer.get_end_iter()
all_tags = tags[:]
if use_other_tags:
all_tags += other_tags
buffer.insert_with_tags_by_name(end_iter, special_text, *all_tags)
def print_empty_line(self):
buffer = self.get_buffer()
end_iter = buffer.get_end_iter()
buffer.insert(end_iter, '\n')
def print_conversation_line(self, text, jid, kind, name, tim,
other_tags_for_name = [], other_tags_for_time = [],
other_tags_for_text = [], subject = None):
'''prints 'chat' type messages'''
buffer = self.get_buffer()
buffer.begin_user_action()
end_iter = buffer.get_end_iter()
at_the_end = False
if self.at_the_end():
at_the_end = True
if buffer.get_char_count() > 0:
buffer.insert(end_iter, '\n')
if kind == 'incoming_queue':
kind = 'incoming'
# print the time stamp
if gajim.config.get('print_time') == 'always':
if not tim:
tim = time.localtime()
before_str = gajim.config.get('before_time')
after_str = gajim.config.get('after_time')
format = before_str + '%H:%M:%S' + after_str
tim_format = time.strftime(format, tim)
buffer.insert_with_tags_by_name(end_iter, tim_format + ' ',
*other_tags_for_time)
elif gajim.config.get('print_time') == 'sometimes':
every_foo_seconds = 60 * gajim.config.get(
'print_ichat_every_foo_minutes')
seconds_passed = time.time() - self.last_time_printout[jid]
if seconds_passed > every_foo_seconds:
self.last_time_printout[jid] = time.time()
end_iter = buffer.get_end_iter()
tim = time.localtime()
tim_format = time.strftime('%H:%M', tim)
buffer.insert_with_tags_by_name(end_iter, tim_format + '\n',
'time_sometimes')
text_tags = other_tags_for_text[:] # create a new list
if kind == 'status':
text_tags.append(kind)
elif text.startswith('/me ') or text.startswith('/me\n'):
text = '* ' + name + text[3:]
text_tags.append(kind)
if name and len(text_tags) == len(other_tags_for_text):
# not status nor /me
name_tags = other_tags_for_name[:] # create a new list
name_tags.append(kind)
before_str = gajim.config.get('before_nickname')
after_str = gajim.config.get('after_nickname')
format = before_str + name + after_str + ' '
buffer.insert_with_tags_by_name(end_iter, format, *name_tags)
# detect urls formatting and if the user has it on emoticons
index = self.detect_and_print_special_text(text, jid, text_tags)
if subject: # if we have subject, show it too!
subject = _('Subject: %s\n') % subject
end_iter = buffer.get_end_iter()
buffer.insert(end_iter, subject)
# add the rest of text located in the index and after
end_iter = buffer.get_end_iter()
buffer.insert_with_tags_by_name(end_iter, text[index:], *text_tags)
# scroll to the end of the textview
if at_the_end or kind == 'outgoing':
# we are at the end or we are sending something
# scroll to the end (via idle in case the scrollbar has appeared)
gobject.idle_add(self.scroll_to_end)
buffer.end_user_action()

View File

@ -201,7 +201,7 @@ class GroupchatWindow(chat.Chat):
return return
print_focus_out_line = False print_focus_out_line = False
textview = self.xmls[room_jid].get_widget('conversation_textview') textview = self.conversation_textviews[room_jid]
buffer = textview.get_buffer() buffer = textview.get_buffer()
if self.focus_out_end_iter_offset[room_jid] is None: if self.focus_out_end_iter_offset[room_jid] is None:
@ -253,7 +253,7 @@ class GroupchatWindow(chat.Chat):
buffer.end_user_action() buffer.end_user_action()
# scroll to the end (via idle in case the scrollbar has appeared) # scroll to the end (via idle in case the scrollbar has appeared)
gobject.idle_add(self.scroll_to_end, textview) gobject.idle_add(textview.scroll_to_end)
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)
@ -578,8 +578,7 @@ class GroupchatWindow(chat.Chat):
if enter is pressed without the shift key, message (if not empty) is sent if enter is pressed without the shift key, message (if not empty) is sent
and printed in the conversation. Tab does autocomplete in nicknames''' and printed in the conversation. Tab does autocomplete in nicknames'''
room_jid = self.get_active_jid() room_jid = self.get_active_jid()
conversation_textview = self.xmls[room_jid].get_widget( conv_textview = self.conversation_textviews[room_jid]
'conversation_textview')
message_buffer = widget.get_buffer() message_buffer = widget.get_buffer()
start_iter, end_iter = message_buffer.get_bounds() start_iter, end_iter = message_buffer.get_bounds()
message = message_buffer.get_text(start_iter, end_iter, message = message_buffer.get_text(start_iter, end_iter,
@ -667,12 +666,12 @@ class GroupchatWindow(chat.Chat):
if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE DOWN if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE DOWN
self.notebook.emit('key_press_event', event) self.notebook.emit('key_press_event', event)
elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE DOWN elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE DOWN
conversation_textview.emit('key_press_event', event) conv_textview.emit('key_press_event', event)
elif event.keyval == gtk.keysyms.Page_Up: # PAGE UP elif event.keyval == gtk.keysyms.Page_Up: # PAGE UP
if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE UP if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE UP
self.notebook.emit('key_press_event', event) self.notebook.emit('key_press_event', event)
elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE UP elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE UP
conversation_textview.emit('key_press_event', event) conv_textview.emit('key_press_event', event)
elif event.keyval == gtk.keysyms.Return or \ elif event.keyval == gtk.keysyms.Return or \
event.keyval == gtk.keysyms.KP_Enter: # ENTER event.keyval == gtk.keysyms.KP_Enter: # ENTER
if (event.state & gtk.gdk.SHIFT_MASK): if (event.state & gtk.gdk.SHIFT_MASK):
@ -714,8 +713,7 @@ class GroupchatWindow(chat.Chat):
room_jid = self.get_active_jid() room_jid = self.get_active_jid()
message_textview = self.xmls[room_jid].get_widget( message_textview = self.xmls[room_jid].get_widget(
'message_textview') 'message_textview')
conversation_textview = self.xmls[room_jid].get_widget( conv_textview = self.conversation_textviews[room_jid]
'conversation_textview')
message_buffer = message_textview.get_buffer() message_buffer = message_textview.get_buffer()
if message != '' or message != '\n': if message != '' or message != '\n':
self.save_sent_message(room_jid, message) self.save_sent_message(room_jid, message)
@ -726,12 +724,12 @@ class GroupchatWindow(chat.Chat):
command = message_array.pop(0).lower() command = message_array.pop(0).lower()
if command == 'clear': if command == 'clear':
# clear the groupchat window # clear the groupchat window
self.on_clear(None, conversation_textview) conv_textview.clear()
self.on_clear(None, message_textview) self.clear(message_textview)
elif command == 'compact': elif command == 'compact':
# set compact mode # set compact mode
self.set_compact_view(not self.compact_view_current_state) self.set_compact_view(not self.compact_view_current_state)
self.on_clear(None, message_textview) self.clear(message_textview)
elif command == 'nick': elif command == 'nick':
# example: /nick foo # example: /nick foo
if len(message_array): if len(message_array):
@ -1308,8 +1306,7 @@ current room topic.') % command, room_jid)
# FIXME: Find a better indicator that the hpaned has moved. # FIXME: Find a better indicator that the hpaned has moved.
self.list_treeview[room_jid].connect('size-allocate', self.list_treeview[room_jid].connect('size-allocate',
self.on_treeview_size_allocate) self.on_treeview_size_allocate)
conversation_textview = self.xmls[room_jid].get_widget( conv_textview = self.conversation_textviews[room_jid]
'conversation_textview')
self.name_labels[room_jid] = self.xmls[room_jid].get_widget( self.name_labels[room_jid] = self.xmls[room_jid].get_widget(
'banner_name_label') 'banner_name_label')
self.paint_banner(room_jid) self.paint_banner(room_jid)
@ -1353,7 +1350,7 @@ current room topic.') % command, room_jid)
# set an empty subject to show the room_jid # set an empty subject to show the room_jid
self.set_subject(room_jid, '') self.set_subject(room_jid, '')
self.got_disconnected(room_jid) #init some variables self.got_disconnected(room_jid) #init some variables
conversation_textview.grab_focus() conv_textview.grab_focus()
self.childs[room_jid].show_all() self.childs[room_jid].show_all()
def on_message(self, room_jid, nick, msg, tim): def on_message(self, room_jid, nick, msg, tim):

View File

@ -9365,28 +9365,7 @@ topic</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property> <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child> <child>
<widget class="GtkTextView" id="conversation_textview"> <placeholder/>
<property name="border_width">1</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="overwrite">False</property>
<property name="accepts_tab">True</property>
<property name="justification">GTK_JUSTIFY_LEFT</property>
<property name="wrap_mode">GTK_WRAP_WORD</property>
<property name="cursor_visible">False</property>
<property name="pixels_above_lines">0</property>
<property name="pixels_below_lines">0</property>
<property name="pixels_inside_wrap">0</property>
<property name="left_margin">2</property>
<property name="right_margin">2</property>
<property name="indent">0</property>
<property name="text" translatable="yes"></property>
<signal name="motion_notify_event" handler="on_conversation_textview_motion_notify_event" last_modification_time="Sat, 19 Mar 2005 14:59:33 GMT"/>
<signal name="button_press_event" handler="on_conversation_textview_button_press_event" last_modification_time="Sat, 19 Mar 2005 14:59:45 GMT"/>
<signal name="key_press_event" handler="on_conversation_textview_key_press_event" last_modification_time="Sun, 03 Apr 2005 08:53:17 GMT"/>
<signal name="populate_popup" handler="on_conversation_textview_populate_popup" last_modification_time="Wed, 11 May 2005 19:23:10 GMT"/>
</widget>
</child> </child>
</widget> </widget>
<packing> <packing>
@ -10937,28 +10916,7 @@ Status message</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property> <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child> <child>
<widget class="GtkTextView" id="conversation_textview"> <placeholder/>
<property name="border_width">1</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="overwrite">False</property>
<property name="accepts_tab">True</property>
<property name="justification">GTK_JUSTIFY_LEFT</property>
<property name="wrap_mode">GTK_WRAP_WORD</property>
<property name="cursor_visible">False</property>
<property name="pixels_above_lines">0</property>
<property name="pixels_below_lines">0</property>
<property name="pixels_inside_wrap">0</property>
<property name="left_margin">2</property>
<property name="right_margin">2</property>
<property name="indent">0</property>
<property name="text" translatable="yes"></property>
<signal name="motion_notify_event" handler="on_conversation_textview_motion_notify_event" last_modification_time="Thu, 10 Mar 2005 16:07:43 GMT"/>
<signal name="button_press_event" handler="on_conversation_textview_button_press_event" last_modification_time="Thu, 10 Mar 2005 17:54:22 GMT"/>
<signal name="key_press_event" handler="on_conversation_textview_key_press_event" last_modification_time="Thu, 31 Mar 2005 14:50:51 GMT"/>
<signal name="populate_popup" handler="on_conversation_textview_populate_popup" last_modification_time="Wed, 11 May 2005 19:39:04 GMT"/>
</widget>
</child> </child>
</widget> </widget>
<packing> <packing>

View File

@ -576,7 +576,7 @@ class TabbedChatWindow(chat.Chat):
and printed in the conversation''' and printed in the conversation'''
jid = self.get_active_jid() jid = self.get_active_jid()
conversation_textview = self.xmls[jid].get_widget('conversation_textview') conv_textview = self.conversation_textviews[jid]
message_textview = widget message_textview = widget
message_buffer = message_textview.get_buffer() message_buffer = message_textview.get_buffer()
start_iter, end_iter = message_buffer.get_bounds() start_iter, end_iter = message_buffer.get_bounds()
@ -592,12 +592,12 @@ class TabbedChatWindow(chat.Chat):
if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE DOWN if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE DOWN
self.notebook.emit('key_press_event', event) self.notebook.emit('key_press_event', event)
elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE DOWN elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE DOWN
conversation_textview.emit('key_press_event', event) conv_textview.emit('key_press_event', event)
elif event.keyval == gtk.keysyms.Page_Up: # PAGE UP elif event.keyval == gtk.keysyms.Page_Up: # PAGE UP
if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE UP if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE UP
self.notebook.emit('key_press_event', event) self.notebook.emit('key_press_event', event)
elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE UP elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE UP
conversation_textview.emit('key_press_event', event) conv_textview.emit('key_press_event', event)
elif event.keyval == gtk.keysyms.Up: elif event.keyval == gtk.keysyms.Up:
if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+UP if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+UP
self.sent_messages_scroll(jid, 'up', widget.get_buffer()) self.sent_messages_scroll(jid, 'up', widget.get_buffer())
@ -699,7 +699,7 @@ class TabbedChatWindow(chat.Chat):
contact.chatstate = state contact.chatstate = state
if contact.chatstate == 'active': if contact.chatstate == 'active':
self.reset_kbd_mouse_timeout_vars() self.reset_kbd_mouse_timeout_vars()
def send_message(self, message): def send_message(self, message):
'''Send the given message to the active tab''' '''Send the given message to the active tab'''
if not message: if not message:
@ -716,19 +716,19 @@ class TabbedChatWindow(chat.Chat):
(room, nick)).get_response() (room, nick)).get_response()
return return
conversation_textview = self.xmls[jid].get_widget('conversation_textview') conv_textview = self.conversation_textviews[jid]
message_textview = self.xmls[jid].get_widget('message_textview') message_textview = self.xmls[jid].get_widget('message_textview')
message_buffer = message_textview.get_buffer() message_buffer = message_textview.get_buffer()
if message != '' or message != '\n': if message != '' or message != '\n':
self.save_sent_message(jid, message) self.save_sent_message(jid, message)
if message == '/clear': if message == '/clear':
self.on_clear(None, conversation_textview) # clear conversation conv_textview.clear() # clear conversation
self.on_clear(None, message_textview) # clear message textview too self.clear(message_textview) # clear message textview too
return True return True
elif message == '/compact': elif message == '/compact':
self.set_compact_view(not self.compact_view_current_state) self.set_compact_view(not self.compact_view_current_state)
self.on_clear(None, message_textview) self.clear(message_textview)
return True return True
keyID = '' keyID = ''
encrypted = False encrypted = False
@ -938,4 +938,5 @@ class TabbedChatWindow(chat.Chat):
['small'], ['small', 'grey'], ['small', 'grey'], False) ['small'], ['small', 'grey'], ['small', 'grey'], False)
if len(lines): if len(lines):
self.print_empty_line(jid) conv_textview = self.conversation_textviews[jid]
conv_textview.print_empty_line()