Newer
Older

Yann Leboulanger
committed
## conversation_textview.py
##
## Copyright (C) 2005-2006 Yann Leboulanger <asterix@lagaule.org>
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
## Copyright (C) 2005-2006 Travis Shirk <travis@pobox.com>

Yann Leboulanger
committed
##
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify

Yann Leboulanger
committed
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.

Yann Leboulanger
committed
##
## Gajim is distributed in the hope that it will be useful,

Yann Leboulanger
committed
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

Yann Leboulanger
committed
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##

Yann Leboulanger
committed

Yann Leboulanger
committed
import random
from tempfile import gettempdir
from subprocess import Popen
from threading import Timer # for smooth scrolling

Yann Leboulanger
committed

Yann Leboulanger
committed
import gtk
import pango
import gobject
import time
import os

Yann Leboulanger
committed
import tooltips
import dialogs
import Queue

Yann Leboulanger
committed

Yann Leboulanger
committed
from common import gajim
from common import helpers
from calendar import timegm

Yann Leboulanger
committed
from htmltextview import HtmlTextView
from common.exceptions import GajimGeneralException
NOT_SHOWN = 0
ALREADY_RECEIVED = 1
SHOWN = 2
def is_selection_modified(mark):
name = mark.get_name()
if name and name in ('selection_bound', 'insert'):
return True
else:
return False
def has_focus(widget):
return widget.flags() & gtk.HAS_FOCUS == gtk.HAS_FOCUS
class TextViewImage(gtk.Image):
def __init__(self, anchor):
super(TextViewImage, self).__init__()
self.anchor = anchor
self._selected = False
self._disconnect_funcs = []
self.connect('parent-set', self.on_parent_set)
self.connect('expose-event', self.on_expose)
def _get_selected(self):
parent = self.get_parent()
if not parent or not self.anchor: return False
buffer = parent.get_buffer()
position = buffer.get_iter_at_child_anchor(self.anchor)
bounds = buffer.get_selection_bounds()
if bounds and position.in_range(*bounds):
return True
else:
return False
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def get_state(self):
parent = self.get_parent()
if not parent:
return gtk.STATE_NORMAL
if self._selected:
if has_focus(parent):
return gtk.STATE_SELECTED
else:
return gtk.STATE_ACTIVE
else:
return gtk.STATE_NORMAL
def _update_selected(self):
selected = self._get_selected()
if self._selected != selected:
self._selected = selected
self.queue_draw()
def _do_connect(self, widget, signal, callback):
id = widget.connect(signal, callback)
def disconnect():
widget.disconnect(id)
self._disconnect_funcs.append(disconnect)
def _disconnect_signals(self):
for func in self._disconnect_funcs:
func()
self._disconnect_funcs = []
def on_parent_set(self, widget, old_parent):
parent = self.get_parent()
if not parent:
self._disconnect_signals()
return
self._do_connect(parent, 'style-set', self.do_queue_draw)
self._do_connect(parent, 'focus-in-event', self.do_queue_draw)
self._do_connect(parent, 'focus-out-event', self.do_queue_draw)
textbuf = parent.get_buffer()
self._do_connect(textbuf, 'mark-set', self.on_mark_set)
self._do_connect(textbuf, 'mark-deleted', self.on_mark_deleted)
def do_queue_draw(self, *args):
self.queue_draw()
return False
def on_mark_set(self, buf, iterat, mark):
self.on_mark_modified(mark)
return False
def on_mark_deleted(self, buf, mark):
self.on_mark_modified(mark)
return False
def on_mark_modified(self, mark):
if is_selection_modified(mark):
self._update_selected()
def on_expose(self, widget, event):
state = self.get_state()
if state != gtk.STATE_NORMAL:
gc = widget.get_style().base_gc[state]
area = widget.allocation
widget.window.draw_rectangle(gc, True, area.x, area.y,
area.width, area.height)
return False
'''Class for the conversation textview (where user reads already said messages)
for chat/groupchat windows'''
FOCUS_OUT_LINE_PIXBUF = gtk.gdk.pixbuf_new_from_file(os.path.join(
gajim.DATA_DIR, 'pixmaps', 'muc_separator.png'))
XEP0184_WARNING_PIXBUF = gtk.gdk.pixbuf_new_from_file(os.path.join(
# smooth scroll constants
MAX_SCROLL_TIME = 0.4 # seconds
SCROLL_DELAY = 33 # milliseconds
def __init__(self, account, used_in_history_window = False):
'''if used_in_history_window is True, then we do not show
Clear menuitem in context menu'''
self.used_in_history_window = used_in_history_window
# no need to inherit TextView, use it as atrribute is safer
self.tv = HtmlTextView()
self.tv.html_hyperlink_handler = self.html_hyperlink_handler
self.tv.set_border_width(1)
self.tv.set_accepts_tab(True)
self.tv.set_editable(False)
self.tv.set_cursor_visible(False)
self.tv.set_wrap_mode(gtk.WRAP_WORD_CHAR)
self.tv.set_left_margin(2)
self.tv.set_right_margin(2)
self.handlers = {}
self.images = []
self.image_cache = {}
self.xep0184_shown = {}

Yann Leboulanger
committed
# It's True when we scroll in the code, so we can detect scroll from user
self.auto_scrolling = False
id = self.tv.connect('motion_notify_event',
self.on_textview_motion_notify_event)
self.handlers[id] = self.tv
id = self.tv.connect('populate_popup', self.on_textview_populate_popup)
self.handlers[id] = self.tv
id = self.tv.connect('button_press_event',
self.on_textview_button_press_event)
self.on_textview_expose_event)
self.handlers[id] = self.tv

Yann Leboulanger
committed
self.account = account
self.change_cursor = None
self.last_time_printout = 0

Yann Leboulanger
committed
font = pango.FontDescription(gajim.config.get('conversation_font'))
self.tv.modify_font(font)
buffer = self.tv.get_buffer()

Yann Leboulanger
committed
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)

jimpp
committed
colors = gajim.config.get('gc_nicknames_colors')
colors = colors.split(':')
for i,color in enumerate(colors):
tagname = 'gc_nickname_color_' + str(i)

jimpp
committed
tag = buffer.create_tag(tagname)
tag.set_property('foreground', color)

Yann Leboulanger
committed
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', 'darkgrey')

Yann Leboulanger
committed
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)

nkour
committed
tag = buffer.create_tag('restored_message')
color = gajim.config.get('restored_messages_color')
tag.set_property('foreground', color)

Yann Leboulanger
committed
self.tagURL = buffer.create_tag('url')
color = gajim.config.get('urlmsgcolor')
self.tagURL.set_property('foreground', color)
self.tagURL.set_property('underline', pango.UNDERLINE_SINGLE)
id = self.tagURL.connect('event', self.hyperlink_handler, 'url')
self.handlers[id] = self.tagURL

Yann Leboulanger
committed
self.tagMail = buffer.create_tag('mail')
self.tagMail.set_property('foreground', color)
self.tagMail.set_property('underline', pango.UNDERLINE_SINGLE)
id = self.tagMail.connect('event', self.hyperlink_handler, 'mail')
self.handlers[id] = self.tagMail

Yann Leboulanger
committed
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)
tag = buffer.create_tag('xep0184-warning')

Yann Leboulanger
committed

Yann Leboulanger
committed
# One mark at the begining then 2 marks between each lines
size = gajim.config.get('max_conversation_lines')
size = 2 * size - 1
self.marks_queue = Queue.Queue(size)
self.allow_focus_out_line = True

Yann Leboulanger
committed
# holds a mark at the end of --- line
self.focus_out_end_mark = None
self.xep0184_warning_tooltip = tooltips.BaseTooltip()

Yann Leboulanger
committed
self.line_tooltip = tooltips.BaseTooltip()
# use it for hr too
self.tv.focus_out_line_pixbuf = ConversationTextview.FOCUS_OUT_LINE_PIXBUF
self.smooth_id = None

Yann Leboulanger
committed
def del_handlers(self):
for i in self.handlers.keys():
if self.handlers[i].handler_is_connected(i):
self.handlers[i].disconnect(i)
del self.handlers
self.tv.destroy()

Yann Leboulanger
committed
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'))
self.tagURL.set_property('foreground', gajim.config.get('urlmsgcolor'))
self.tagMail.set_property('foreground', gajim.config.get('urlmsgcolor'))

Yann Leboulanger
committed
def at_the_end(self):

Yann Leboulanger
committed
end_iter = buffer.get_end_iter()
end_rect = self.tv.get_iter_location(end_iter)
visible_rect = self.tv.get_visible_rect()

Yann Leboulanger
committed
if end_rect.y <= (visible_rect.y + visible_rect.height):
return True
return False
# Smooth scrolling inspired by Pidgin code
def smooth_scroll(self):
parent = self.tv.get_parent()
if not parent:
return False
vadj = parent.get_vadjustment()
max_val = vadj.upper - vadj.page_size + 1
cur_val = vadj.get_value()
# scroll by 1/3rd of remaining distance
onethird = cur_val + ((max_val - cur_val) / 3.0)

Yann Leboulanger
committed
self.auto_scrolling = True
vadj.set_value(onethird)

Yann Leboulanger
committed
self.auto_scrolling = False
if max_val - onethird < 0.01:
self.smooth_id = None
self.smooth_scroll_timer.cancel()
return False
def smooth_scroll_timeout(self):
gobject.idle_add(self.do_smooth_scroll_timeout)
return
def do_smooth_scroll_timeout(self):
if not self.smooth_id:
# we finished scrolling
return
gobject.source_remove(self.smooth_id)
self.smooth_id = None
parent = self.tv.get_parent()
if parent:
vadj = parent.get_vadjustment()

Yann Leboulanger
committed
self.auto_scrolling = True
vadj.set_value(vadj.upper - vadj.page_size + 1)

Yann Leboulanger
committed
self.auto_scrolling = False
def smooth_scroll_to_end(self):
if None != self.smooth_id: # already scrolling
return False
self.smooth_id = gobject.timeout_add(self.SCROLL_DELAY,
self.smooth_scroll_timer = Timer(self.MAX_SCROLL_TIME,
self.smooth_scroll_timer.start()
return False

Yann Leboulanger
committed
def scroll_to_end(self):
parent = self.tv.get_parent()
buffer = self.tv.get_buffer()
end_mark = buffer.get_mark('end')
if not end_mark:
return False

Yann Leboulanger
committed
self.auto_scrolling = True

Yann Leboulanger
committed
adjustment = parent.get_hadjustment()
adjustment.set_value(0)

Yann Leboulanger
committed
self.auto_scrolling = False

Yann Leboulanger
committed
return False # when called in an idle_add, just do it once
def bring_scroll_to_end(self, diff_y = 0,
use_smooth=gajim.config.get('use_smooth_scrolling')):

Yann Leboulanger
committed
''' scrolls to the end of textview if end is not visible '''

Yann Leboulanger
committed
end_iter = buffer.get_end_iter()
end_rect = self.tv.get_iter_location(end_iter)
visible_rect = self.tv.get_visible_rect()

Yann Leboulanger
committed
# scroll only if expected end is not visible
if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
if use_smooth:
gobject.idle_add(self.smooth_scroll_to_end)
else:
gobject.idle_add(self.scroll_to_end_iter)

Yann Leboulanger
committed
def scroll_to_end_iter(self):

Yann Leboulanger
committed
end_iter = buffer.get_end_iter()
self.tv.scroll_to_iter(end_iter, 0, False, 1, 1)

Yann Leboulanger
committed
return False # when called in an idle_add, just do it once

Yann Leboulanger
committed
def stop_scrolling(self):
if self.smooth_id:
gobject.source_remove(self.smooth_id)
self.smooth_id = None
self.smooth_scroll_timer.cancel()
if self.xep0184_marks.has_key(id):
return
buffer = self.tv.get_buffer()
buffer.begin_user_action()
self.xep0184_marks[id] = buffer.create_mark(None,
buffer.get_end_iter(), left_gravity=True)
self.xep0184_shown[id] = NOT_SHOWN
def show_it():
if self.xep0184_shown[id] == ALREADY_RECEIVED:
return False
end_iter = buffer.get_iter_at_mark(
self.xep0184_marks[id])
buffer.insert(end_iter, ' ')
buffer.insert_pixbuf(end_iter,
ConversationTextview.XEP0184_WARNING_PIXBUF)
before_img_iter = buffer.get_iter_at_mark(
self.xep0184_marks[id])
before_img_iter.forward_char()
post_img_iter = before_img_iter.copy()
post_img_iter.forward_char()
buffer.apply_tag_by_name('xep0184-warning', before_img_iter,
post_img_iter)
self.xep0184_shown[id] = SHOWN
return False
gobject.timeout_add_seconds(2, show_it)
buffer.end_user_action()
def hide_xep0184_warning(self, id):
if self.xep0184_shown[id] == NOT_SHOWN:
self.xep0184_shown[id] = ALREADY_RECEIVED
return
buffer = self.tv.get_buffer()
begin_iter = buffer.get_iter_at_mark(self.xep0184_marks[id])
end_iter = begin_iter.copy()
end_iter.forward_char();
end_iter.forward_char();
buffer.delete(begin_iter, end_iter)
buffer.delete_mark(self.xep0184_marks[id])
buffer.end_user_action()
del self.xep0184_shown[id]
def show_focus_out_line(self):
if not self.allow_focus_out_line:
# if room did not receive focus-in from the last time we added
# --- line then do not readd
return
print_focus_out_line = False
buffer = self.tv.get_buffer()

Yann Leboulanger
committed
if self.focus_out_end_mark is None:
# this happens only first time we focus out on this room
print_focus_out_line = True
else:

Yann Leboulanger
committed
focus_out_end_iter = buffer.get_iter_at_mark(self.focus_out_end_mark)
focus_out_end_iter_offset = focus_out_end_iter.get_offset()
if focus_out_end_iter_offset != buffer.get_end_iter().get_offset():
# this means after last-focus something was printed
# (else end_iter's offset is the same as before)
# only then print ---- line (eg. we avoid printing many following
# ---- lines)
print_focus_out_line = True
if print_focus_out_line and buffer.get_char_count() > 0:
buffer.begin_user_action()
# remove previous focus out line if such focus out line exists

Yann Leboulanger
committed
if self.focus_out_end_mark is not None:
end_iter_for_previous_line = buffer.get_iter_at_mark(
self.focus_out_end_mark)
begin_iter_for_previous_line = end_iter_for_previous_line.copy()
# img_char+1 (the '\n')
begin_iter_for_previous_line.backward_chars(2)
# remove focus out line
buffer.delete(begin_iter_for_previous_line,
end_iter_for_previous_line)

Yann Leboulanger
committed
buffer.delete_mark(self.focus_out_end_mark)
# add the new focus out line
end_iter = buffer.get_end_iter()
buffer.insert(end_iter, '\n')
ConversationTextview.FOCUS_OUT_LINE_PIXBUF)
end_iter = buffer.get_end_iter()
before_img_iter = end_iter.copy()
# one char back (an image also takes one char)
before_img_iter.backward_char()
buffer.apply_tag_by_name('focus-out-line', before_img_iter, end_iter)

Yann Leboulanger
committed
self.allow_focus_out_line = False
# update the iter we hold to make comparison the next time

Yann Leboulanger
committed
self.focus_out_end_mark = buffer.create_mark(None,
buffer.get_end_iter(), left_gravity=True)
buffer.end_user_action()
# scroll to the end (via idle in case the scrollbar has appeared)
gobject.idle_add(self.scroll_to_end)
def show_xep0184_warning_tooltip(self):
pointer = self.tv.get_pointer()
x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
pointer[0], pointer[1])
tags = self.tv.get_iter_at_location(x, y).get_tags()
tag_table = self.tv.get_buffer().get_tag_table()
xep0184_warning = False
for tag in tags:
if tag == tag_table.lookup('xep0184-warning'):
xep0184_warning = True
break
if xep0184_warning and not self.xep0184_warning_tooltip.win:
# check if the current pointer is still over the line
position = self.tv.window.get_origin()
self.xep0184_warning_tooltip.show_tooltip(_('This icon indicates that '
'this message has not yet\nbeen received by the remote end. '
"If this icon stays\nfor a long time, it's likely the message got "
'lost.'), 8, position[1] + pointer[1])

Yann Leboulanger
committed
def show_line_tooltip(self):
x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
pointer[0], pointer[1])
tags = self.tv.get_iter_at_location(x, y).get_tags()
tag_table = self.tv.get_buffer().get_tag_table()

Yann Leboulanger
committed
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.tv.window.get_origin()
self.line_tooltip.show_tooltip(_('Text below this line is what has '
'been said since the last time you paid attention to this group chat'),
8, position[1] + pointer[1])

Yann Leboulanger
committed
def on_textview_expose_event(self, widget, event):
expalloc = event.area
exp_x0 = expalloc.x
exp_y0 = expalloc.y
exp_x1 = exp_x0 + expalloc.width
exp_y1 = exp_y0 + expalloc.height
try:
tryfirst = [self.image_cache[(exp_x0, exp_y0)]]
except KeyError:
tryfirst = []
for image in tryfirst + self.images:
imgalloc = image.allocation
img_x0 = imgalloc.x
img_y0 = imgalloc.y
img_x1 = img_x0 + imgalloc.width
img_y1 = img_y0 + imgalloc.height
if img_x0 <= exp_x0 and img_y0 <= exp_y0 and \
exp_x1 <= img_x1 and exp_y1 <= img_y1:
self.image_cache[(img_x0, img_y0)] = image
widget.propagate_expose(image, event)
return True
return False

Yann Leboulanger
committed
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.tv.window.get_pointer()
x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
pointer_x, pointer_y)
tags = self.tv.get_iter_at_location(x, y).get_tags()

Yann Leboulanger
committed
if self.change_cursor:
self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(

Yann Leboulanger
committed
gtk.gdk.Cursor(gtk.gdk.XTERM))
self.change_cursor = None
tag_table = self.tv.get_buffer().get_tag_table()

Yann Leboulanger
committed
over_line = False

Yann Leboulanger
committed
for tag in tags:
if tag in (tag_table.lookup('url'), tag_table.lookup('mail')):
self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(

Yann Leboulanger
committed
gtk.gdk.Cursor(gtk.gdk.HAND2))
self.change_cursor = tag
elif tag == tag_table.lookup('focus-out-line'):
over_line = True
elif tag == tag_table.lookup('xep0184-warning'):
xep0184_warning = True

Yann Leboulanger
committed
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.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(

Yann Leboulanger
committed
gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
self.change_cursor = tag
if xep0184_warning and not self.xep0184_warning_tooltip.win:
self.xep0184_warning_tooltip.timeout = \
gobject.timeout_add(500,
self.show_xep0184_warning_tooltip)
self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
self.change_cursor = tag

Yann Leboulanger
committed
def clear(self, tv = None):
'''clear text in the textview'''

Yann Leboulanger
committed
start, end = buffer.get_bounds()
buffer.delete(start, end)
size = gajim.config.get('max_conversation_lines')
size = 2 * size - 1
self.marks_queue = Queue.Queue(size)

Yann Leboulanger
committed
self.focus_out_end_mark = None

Yann Leboulanger
committed
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
(only if used_in_history_window is False)
and if we have sth selected we show a submenu with actions on
the phrase (see on_conversation_textview_button_press_event)'''
separator_menuitem_was_added = False
if not self.used_in_history_window:
item = gtk.SeparatorMenuItem()
menu.prepend(item)
separator_menuitem_was_added = True
item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
menu.prepend(item)
id = item.connect('activate', self.clear)
self.handlers[id] = item

Yann Leboulanger
committed
if self.selected_phrase:
if not separator_menuitem_was_added:
item = gtk.SeparatorMenuItem()
menu.prepend(item)
self.selected_phrase = helpers.reduce_chars_newlines(
item = gtk.MenuItem(_('_Actions for "%s"') % self.selected_phrase)

Yann Leboulanger
committed
menu.prepend(item)
submenu = gtk.Menu()
item.set_submenu(submenu)

Yann Leboulanger
committed
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'))
id = item.connect('activate', self.visit_url_from_menuitem, link)
self.handlers[id] = item

Yann Leboulanger
committed
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)
id = item.connect('activate', self.visit_url_from_menuitem, link)
self.handlers[id] = item

Yann Leboulanger
committed
else:
if dict_link.find('%s') == -1:

nkour
committed
# we must have %s in the url if not WIKTIONARY
item = gtk.MenuItem(_(
'Dictionary URL is missing an "%s" and it is not WIKTIONARY'))

Yann Leboulanger
committed
item.set_property('sensitive', False)
else:
link = dict_link % self.selected_phrase
id = item.connect('activate', self.visit_url_from_menuitem,
link)

Yann Leboulanger
committed
submenu.append(item)

Yann Leboulanger
committed
search_link = gajim.config.get('search_engine')
if search_link.find('%s') == -1:

nkour
committed
# we must have %s in the url

Yann Leboulanger
committed
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
id = item.connect('activate', self.visit_url_from_menuitem, link)
self.handlers[id] = item

Yann Leboulanger
committed
submenu.append(item)
id = item.connect('activate', self.visit_url_from_menuitem, link)
self.handlers[id] = item
submenu.append(item)

Yann Leboulanger
committed
menu.show_all()

Yann Leboulanger
committed
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

nkour
committed
self.selected_phrase = '' # do not move belove event button check!

Yann Leboulanger
committed
if event.button != 3: # if not right click
return False
x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,

Yann Leboulanger
committed
int(event.x), int(event.y))
iter = self.tv.get_iter_at_location(x, y)

Yann Leboulanger
committed
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')

Yann Leboulanger
committed
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

Yann Leboulanger
committed
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(

Yann Leboulanger
committed
'utf-8')
elif ord(iter.get_char()) > 31:
# we clicked on a word, do as if it's selected for context menu
start_sel = iter.copy()
if not start_sel.starts_word():
start_sel.backward_word_start()
finish_sel = iter.copy()
if not finish_sel.ends_word():
finish_sel.forward_word_end()
self.selected_phrase = buffer.get_text(start_sel, finish_sel).decode(

Yann Leboulanger
committed
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.new_chat_from_jid(self.account, jid)

Yann Leboulanger
committed
def on_join_group_chat_menuitem_activate(self, widget, room_jid):
if 'join_gc' in gajim.interface.instances[self.account]:
instance = gajim.interface.instances[self.account]['join_gc']
instance.xml.get_widget('room_jid_entry').set_text(room_jid)
gajim.interface.instances[self.account]['join_gc'].window.present()

Yann Leboulanger
committed
else:
try:
gajim.interface.instances[self.account]['join_gc'] = \
dialogs.JoinGroupchatWindow(self.account, room_jid)
except GajimGeneralException:

Yann Leboulanger
committed
pass
def on_add_to_roster_activate(self, widget, jid):
dialogs.AddNewContactWindow(self.account, jid)
def make_link_menu(self, event, kind, text):
xml = gtkgui_helpers.get_glade('chat_context_menu.glade')

Yann Leboulanger
committed
menu = xml.get_widget('chat_context_menu')
childs = menu.get_children()
if kind == 'url':
id = childs[0].connect('activate', self.on_copy_link_activate, text)
self.handlers[id] = childs[0]
id = childs[1].connect('activate', self.on_open_link_activate, kind,
text)

Yann Leboulanger
committed
childs[2].hide() # copy mail address
childs[3].hide() # open mail composer
childs[4].hide() # jid section separator

Yann Leboulanger
committed
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
# load muc icon
join_group_chat_menuitem = xml.get_widget('join_group_chat_menuitem')

Yann Leboulanger
committed
muc_icon = gtkgui_helpers.load_icon('muc_active')
if muc_icon:
join_group_chat_menuitem.set_image(muc_icon)
id = childs[2].connect('activate', self.on_copy_link_activate, text)
self.handlers[id] = childs[2]
id = childs[3].connect('activate', self.on_open_link_activate, kind,
text)
self.handlers[id] = childs[3]
id = childs[5].connect('activate', self.on_start_chat_activate, text)
self.handlers[id] = childs[5]
id = childs[6].connect('activate',

Yann Leboulanger
committed
self.on_join_group_chat_menuitem_activate, text)

Yann Leboulanger
committed
allow_add = False
c = gajim.contacts.get_first_contact_from_jid(self.account, text)
if c and not gajim.contacts.is_pm_from_contact(self.account, c):
if _('Not in Roster') in c.groups:

Yann Leboulanger
committed
allow_add = True

Yann Leboulanger
committed
allow_add = True

Yann Leboulanger
committed
if allow_add:
id = childs[7].connect('activate', self.on_add_to_roster_activate,
text)

Yann Leboulanger
committed
childs[7].show() # show add to roster menuitem
else:
childs[7].hide() # hide add to roster menuitem

Yann Leboulanger
committed
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.tv.get_buffer().get_text(begin_iter, end_iter).decode(
'utf-8')

Yann Leboulanger
committed
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 html_hyperlink_handler(self, texttag, widget, event, iter, kind, href):
if event.type == gtk.gdk.BUTTON_PRESS:
if event.button == 3: # right click
self.make_link_menu(event, kind, href)

Yann Leboulanger
committed
return True
else:
# we launch the correct application
helpers.launch_browser_mailer(kind, href)
def detect_and_print_special_text(self, otext, other_tags):
'''detects special text (emots & links & formatting)
prints normal text before any special text it founts,
then print special text (that happens many times until
last special text is printed) and then returns the index
after *last* special text, so we can print it in
print_conversation_line()'''

Yann Leboulanger
committed
start = 0
end = 0
index = 0
# basic: links + mail + formatting is always checked (we like that)
if gajim.config.get('emoticons_theme'): # search for emoticons & urls

Yann Leboulanger
committed
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()

Yann Leboulanger
committed
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)

Yann Leboulanger
committed

Yann Leboulanger
committed
def latex_to_image(self, str):
result = None
exitcode = 0
# some latex commands are really bad
blacklist = ['\\def', '\\let', '\\futurelet',
'\\newcommand', '\\renewcomment', '\\else', '\\fi', '\\write',
'\\input', '\\include', '\\chardef', '\\catcode', '\\makeatletter',
'\\noexpand', '\\toksdef', '\\every', '\\errhelp', '\\errorstopmode',
'\\scrollmode', '\\nonstopmode', '\\batchmode', '\\read', '\\csname',
'\\newhelp', '\\relax', '\\afterground', '\\afterassignment',
'\\expandafter', '\\noexpand', '\\special', '\\command', '\\loop',
'\\repeat', '\\toks', '\\output', '\\line', '\\mathcode', '\\name',
'\\item', '\\section', '\\mbox', '\\DeclareRobustCommand', '\\[',
'\\]']

Yann Leboulanger
committed
str = str[2:len(str)-2]

Yann Leboulanger
committed
# filter latex code with bad commands
for word in blacklist:
if word in str:
exitcode = 1
break

Yann Leboulanger
committed
if exitcode == 0:
random.seed()
tmpfile = os.path.join(gettempdir(), 'gajimtex_' + random.randint(0,
100).__str__())
# build latex string
texstr = '\\documentclass[12pt]{article}\\usepackage[dvips]{graphicx}'
texstr += '\\usepackage{amsmath}\\usepackage{amssymb}'
texstr += '\\pagestyle{empty}'
texstr += '\\begin{document}\\begin{large}\\begin{gather*}'
texstr += '\\end{gather*}\\end{large}\\end{document}'
file = open(os.path.join(tmpfile + '.tex'), 'w+')
file.write(texstr)
file.flush()
file.close()
p = Popen(['latex', '--interaction=nonstopmode', tmpfile + '.tex'],
cwd=gettempdir())
exitcode = p.wait()

Yann Leboulanger
committed
p = Popen(['dvips', '-E', '-o', tmpfile + '.ps', tmpfile + '.dvi'],
cwd=gettempdir())

Yann Leboulanger
committed
exitcode = p.wait()
if exitcode == 0:
latex_png_dpi = gajim.config.get('latex_png_dpi')
p = Popen(['convert', '-background', 'white', '-flatten', '-density', latex_png_dpi, tmpfile + '.ps', tmpfile + '.png'],
cwd=gettempdir())
exitcode = p.wait()
extensions = ['.tex', '.log', '.aux', '.dvi', '.ps']

Yann Leboulanger
committed
for ext in extensions:
try:
os.remove(tmpfile + ext)
except Exception:
pass

Yann Leboulanger
committed
if exitcode == 0:
result = tmpfile + '.png'

Yann Leboulanger
committed

Yann Leboulanger
committed
def print_special_text(self, special_text, other_tags):
'''is called by detect_and_print_special_text and prints
special text (emots, links, formatting)'''

Yann Leboulanger
committed
tags = []
use_other_tags = True
text_is_valid_uri = False

Yann Leboulanger
committed
show_ascii_formatting_chars = \
gajim.config.get('show_ascii_formatting_chars')

Yann Leboulanger
committed