root/tags/gajim-0.11.4/src/conversation_textview.py

Revision 8742, 29.7 kB (checked in by asterix, 13 months ago)

don't show both normal and hyperlink menu when we right click on a link. fixes #
3430

Line 
1##      conversation_textview.py
2##
3## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
4## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
5## Copyright (C) 2005-2006 Travis Shirk <travis@pobox.com>
6##
7## This program is free software; you can redistribute it and/or modify
8## it under the terms of the GNU General Public License as published
9## by the Free Software Foundation; version 2 only.
10##
11## This program is distributed in the hope that it will be useful,
12## but WITHOUT ANY WARRANTY; without even the implied warranty of
13## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14## GNU General Public License for more details.
15##
16
17import gtk
18import pango
19import gobject
20import time
21import os
22import tooltips
23import dialogs
24import locale
25
26import gtkgui_helpers
27from common import gajim
28from common import helpers
29from calendar import timegm
30from common.fuzzyclock import FuzzyClock
31
32from htmltextview import HtmlTextView
33from common.exceptions import GajimGeneralException
34
35class ConversationTextview:
36        '''Class for the conversation textview (where user reads already said messages)
37        for chat/groupchat windows'''
38       
39        path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png')
40        FOCUS_OUT_LINE_PIXBUF = gtk.gdk.pixbuf_new_from_file(path_to_file)
41
42        def __init__(self, account, used_in_history_window = False):
43                '''if used_in_history_window is True, then we do not show
44                Clear menuitem in context menu'''
45                self.used_in_history_window = used_in_history_window
46               
47                # no need to inherit TextView, use it as atrribute is safer
48                self.tv = HtmlTextView()
49                self.tv.html_hyperlink_handler = self.html_hyperlink_handler
50
51                # set properties
52                self.tv.set_border_width(1)
53                self.tv.set_accepts_tab(True)
54                self.tv.set_editable(False)
55                self.tv.set_cursor_visible(False)
56                self.tv.set_wrap_mode(gtk.WRAP_WORD_CHAR)
57                self.tv.set_left_margin(2)
58                self.tv.set_right_margin(2)
59                self.handlers = {}
60
61                # connect signals
62                id = self.tv.connect('motion_notify_event',
63                        self.on_textview_motion_notify_event)
64                self.handlers[id] = self.tv
65                id = self.tv.connect('populate_popup', self.on_textview_populate_popup)
66                self.handlers[id] = self.tv
67                id = self.tv.connect('button_press_event',
68                        self.on_textview_button_press_event)
69                self.handlers[id] = self.tv
70
71                self.account = account
72                self.change_cursor = None
73                self.last_time_printout = 0
74
75                font = pango.FontDescription(gajim.config.get('conversation_font'))
76                self.tv.modify_font(font)
77                buffer = self.tv.get_buffer()
78                end_iter = buffer.get_end_iter()
79                buffer.create_mark('end', end_iter, False)
80
81                self.tagIn = buffer.create_tag('incoming')
82                color = gajim.config.get('inmsgcolor')
83                self.tagIn.set_property('foreground', color)
84                self.tagOut = buffer.create_tag('outgoing')
85                color = gajim.config.get('outmsgcolor')
86                self.tagOut.set_property('foreground', color)
87                self.tagStatus = buffer.create_tag('status')
88                color = gajim.config.get('statusmsgcolor')
89                self.tagStatus.set_property('foreground', color)
90
91                colors = gajim.config.get('gc_nicknames_colors')
92                colors = colors.split(':')
93                for color in xrange(len(colors)):
94                        tagname = 'gc_nickname_color_' + str(color)
95                        tag = buffer.create_tag(tagname)
96                        color = colors[color]
97                        tag.set_property('foreground', color)
98
99                tag = buffer.create_tag('marked')
100                color = gajim.config.get('markedmsgcolor')
101                tag.set_property('foreground', color)
102                tag.set_property('weight', pango.WEIGHT_BOLD)
103
104                tag = buffer.create_tag('time_sometimes')
105                tag.set_property('foreground', 'darkgrey')
106                tag.set_property('scale', pango.SCALE_SMALL)
107                tag.set_property('justification', gtk.JUSTIFY_CENTER)
108
109                tag = buffer.create_tag('small')
110                tag.set_property('scale', pango.SCALE_SMALL)
111
112                tag = buffer.create_tag('restored_message')
113                color = gajim.config.get('restored_messages_color')
114                tag.set_property('foreground', color)
115
116                self.tagURL = buffer.create_tag('url')
117                color = gajim.config.get('urlmsgcolor')
118                self.tagURL.set_property('foreground', color)
119                self.tagURL.set_property('underline', pango.UNDERLINE_SINGLE)
120                id = self.tagURL.connect('event', self.hyperlink_handler, 'url')
121                self.handlers[id] = self.tagURL
122
123                self.tagMail = buffer.create_tag('mail')
124                self.tagMail.set_property('foreground', color)
125                self.tagMail.set_property('underline', pango.UNDERLINE_SINGLE)
126                id = self.tagMail.connect('event', self.hyperlink_handler, 'mail')
127                self.handlers[id] = self.tagMail
128
129                tag = buffer.create_tag('bold')
130                tag.set_property('weight', pango.WEIGHT_BOLD)
131
132                tag = buffer.create_tag('italic')
133                tag.set_property('style', pango.STYLE_ITALIC)
134
135                tag = buffer.create_tag('underline')
136                tag.set_property('underline', pango.UNDERLINE_SINGLE)
137
138                buffer.create_tag('focus-out-line', justification = gtk.JUSTIFY_CENTER)
139
140                self.allow_focus_out_line = True
141                # holds the iter's offset which points to the end of --- line
142                self.focus_out_end_iter_offset = None
143
144                self.line_tooltip = tooltips.BaseTooltip()
145                # use it for hr too
146                self.tv.focus_out_line_pixbuf = ConversationTextview.FOCUS_OUT_LINE_PIXBUF
147
148        def del_handlers(self):
149                for i in self.handlers.keys():
150                        if self.handlers[i].handler_is_connected(i):
151                                self.handlers[i].disconnect(i)
152                del self.handlers
153                self.tv.destroy()
154                #FIXME:
155                # self.line_tooltip.destroy()
156       
157        def update_tags(self):
158                self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor'))
159                self.tagOut.set_property('foreground', gajim.config.get('outmsgcolor'))
160                self.tagStatus.set_property('foreground',
161                        gajim.config.get('statusmsgcolor'))
162                self.tagURL.set_property('foreground', gajim.config.get('urlmsgcolor'))
163                self.tagMail.set_property('foreground', gajim.config.get('urlmsgcolor'))
164
165        def at_the_end(self):
166                buffer = self.tv.get_buffer()
167                end_iter = buffer.get_end_iter()
168                end_rect = self.tv.get_iter_location(end_iter)
169                visible_rect = self.tv.get_visible_rect()
170                if end_rect.y <= (visible_rect.y + visible_rect.height):
171                        return True
172                return False
173
174        def scroll_to_end(self):
175                parent = self.tv.get_parent()
176                buffer = self.tv.get_buffer()
177                end_mark = buffer.get_mark('end')
178                if not end_mark:
179                        return False
180                self.tv.scroll_to_mark(end_mark, 0, True, 0, 1)
181                adjustment = parent.get_hadjustment()
182                adjustment.set_value(0)
183                return False # when called in an idle_add, just do it once
184
185        def bring_scroll_to_end(self, diff_y = 0):
186                ''' scrolls to the end of textview if end is not visible '''
187                buffer = self.tv.get_buffer()
188                end_iter = buffer.get_end_iter()
189                end_rect = self.tv.get_iter_location(end_iter)
190                visible_rect = self.tv.get_visible_rect()
191                # scroll only if expected end is not visible
192                if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
193                        gobject.idle_add(self.scroll_to_end_iter)
194
195        def scroll_to_end_iter(self):
196                buffer = self.tv.get_buffer()
197                end_iter = buffer.get_end_iter()
198                if not end_iter:
199                        return False
200                self.tv.scroll_to_iter(end_iter, 0, False, 1, 1)
201                return False # when called in an idle_add, just do it once
202
203        def show_focus_out_line(self):
204                if not self.allow_focus_out_line:
205                        # if room did not receive focus-in from the last time we added
206                        # --- line then do not readd
207                        return
208
209                print_focus_out_line = False
210                buffer = self.tv.get_buffer()
211
212                if self.focus_out_end_iter_offset is None:
213                        # this happens only first time we focus out on this room
214                        print_focus_out_line = True
215
216                else:
217                        if self.focus_out_end_iter_offset != buffer.get_end_iter().\
218                        get_offset():
219                                # this means after last-focus something was printed
220                                # (else end_iter's offset is the same as before)
221                                # only then print ---- line (eg. we avoid printing many following
222                                # ---- lines)
223                                print_focus_out_line = True
224
225                if print_focus_out_line and buffer.get_char_count() > 0:
226                        buffer.begin_user_action()
227
228                        # remove previous focus out line if such focus out line exists
229                        if self.focus_out_end_iter_offset is not None:
230                                end_iter_for_previous_line = buffer.get_iter_at_offset(
231                                        self.focus_out_end_iter_offset)
232                                begin_iter_for_previous_line = end_iter_for_previous_line.copy()
233                                # img_char+1 (the '\n')
234                                begin_iter_for_previous_line.backward_chars(2)
235
236                                # remove focus out line
237                                buffer.delete(begin_iter_for_previous_line,
238                                        end_iter_for_previous_line)
239
240                        # add the new focus out line
241                        end_iter = buffer.get_end_iter()
242                        buffer.insert(end_iter, '\n')
243                        buffer.insert_pixbuf(end_iter,
244                                ConversationTextview.FOCUS_OUT_LINE_PIXBUF)
245
246                        end_iter = buffer.get_end_iter()
247                        before_img_iter = end_iter.copy()
248                        before_img_iter.backward_char() # one char back (an image also takes one char)
249                        buffer.apply_tag_by_name('focus-out-line', before_img_iter, end_iter)
250
251                        self.allow_focus_out_line = False
252
253                        # update the iter we hold to make comparison the next time
254                        self.focus_out_end_iter_offset = buffer.get_end_iter().get_offset()
255
256                        buffer.end_user_action()
257
258                        # scroll to the end (via idle in case the scrollbar has appeared)
259                        gobject.idle_add(self.scroll_to_end)
260
261        def show_line_tooltip(self):
262                pointer = self.tv.get_pointer()
263                x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer[0],
264                        pointer[1])
265                tags = self.tv.get_iter_at_location(x, y).get_tags()
266                tag_table = self.tv.get_buffer().get_tag_table()
267                over_line = False
268                for tag in tags:
269                        if tag == tag_table.lookup('focus-out-line'):
270                                over_line = True
271                                break
272                if over_line and not self.line_tooltip.win:
273                        # check if the current pointer is still over the line
274                        position = self.tv.window.get_origin()
275                        self.line_tooltip.show_tooltip(_('Text below this line is what has '
276                        'been said since the last time you paid attention to this group chat'), 8, position[1] + pointer[1])
277
278        def on_textview_motion_notify_event(self, widget, event):
279                '''change the cursor to a hand when we are over a mail or an url'''
280                pointer_x, pointer_y, spam = self.tv.window.get_pointer()
281                x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x,
282                        pointer_y)
283                tags = self.tv.get_iter_at_location(x, y).get_tags()
284                if self.change_cursor:
285                        self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
286                                gtk.gdk.Cursor(gtk.gdk.XTERM))
287                        self.change_cursor = None
288                tag_table = self.tv.get_buffer().get_tag_table()
289                over_line = False
290                for tag in tags:
291                        if tag in (tag_table.lookup('url'), tag_table.lookup('mail')):
292                                self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
293                                        gtk.gdk.Cursor(gtk.gdk.HAND2))
294                                self.change_cursor = tag
295                        elif tag == tag_table.lookup('focus-out-line'):
296                                over_line = True
297
298                if self.line_tooltip.timeout != 0:
299                        # Check if we should hide the line tooltip
300                        if not over_line:
301                                self.line_tooltip.hide_tooltip()
302                if over_line and not self.line_tooltip.win:
303                        self.line_tooltip.timeout = gobject.timeout_add(500,
304                                self.show_line_tooltip)
305                        self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
306                                gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
307                        self.change_cursor = tag
308
309        def clear(self, tv = None):
310                '''clear text in the textview'''
311                buffer = self.tv.get_buffer()
312                start, end = buffer.get_bounds()
313                buffer.delete(start, end)
314                self.focus_out_end_iter_offset = None
315
316        def visit_url_from_menuitem(self, widget, link):
317                '''basically it filters out the widget instance'''
318                helpers.launch_browser_mailer('url', link)
319
320        def on_textview_populate_popup(self, textview, menu):
321                '''we override the default context menu and we prepend Clear
322                (only if used_in_history_window is False)
323                and if we have sth selected we show a submenu with actions on the phrase
324                (see on_conversation_textview_button_press_event)'''
325
326                separator_menuitem_was_added = False
327                if not self.used_in_history_window:
328                        item = gtk.SeparatorMenuItem()
329                        menu.prepend(item)
330                        separator_menuitem_was_added = True
331
332                        item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
333                        menu.prepend(item)
334                        id = item.connect('activate', self.clear)
335                        self.handlers[id] = item
336
337                if self.selected_phrase:
338                        if not separator_menuitem_was_added:
339                                item = gtk.SeparatorMenuItem()
340                                menu.prepend(item)
341
342                        self.selected_phrase = helpers.reduce_chars_newlines(
343                                self.selected_phrase, 25, 2)
344                        item = gtk.MenuItem(_('_Actions for "%s"') % self.selected_phrase)
345                        menu.prepend(item)
346                        submenu = gtk.Menu()
347                        item.set_submenu(submenu)
348
349                        always_use_en = gajim.config.get('always_english_wikipedia')
350                        if always_use_en:
351                                link = 'http://en.wikipedia.org/wiki/Special:Search?search=%s'\
352                                        % self.selected_phrase
353                        else:
354                                link = 'http://%s.wikipedia.org/wiki/Special:Search?search=%s'\
355                                        % (gajim.LANG, self.selected_phrase)
356                        item = gtk.MenuItem(_('Read _Wikipedia Article'))
357                        id = item.connect('activate', self.visit_url_from_menuitem, link)
358                        self.handlers[id] = item
359                        submenu.append(item)
360
361                        item = gtk.MenuItem(_('Look it up in _Dictionary'))
362                        dict_link = gajim.config.get('dictionary_url')
363                        if dict_link == 'WIKTIONARY':
364                                # special link (yeah undocumented but default)
365                                always_use_en = gajim.config.get('always_english_wiktionary')
366                                if always_use_en:
367                                        link = 'http://en.wiktionary.org/wiki/Special:Search?search=%s'\
368                                                % self.selected_phrase
369                                else:
370                                        link = 'http://%s.wiktionary.org/wiki/Special:Search?search=%s'\
371                                                % (gajim.LANG, self.selected_phrase)
372                                id = item.connect('activate', self.visit_url_from_menuitem, link)
373                                self.handlers[id] = item
374                        else:
375                                if dict_link.find('%s') == -1:
376                                        # we must have %s in the url if not WIKTIONARY
377                                        item = gtk.MenuItem(_('Dictionary URL is missing an "%s" and it is not WIKTIONARY'))
378                                        item.set_property('sensitive', False)
379                                else:
380                                        link = dict_link % self.selected_phrase
381                                        id = item.connect('activate', self.visit_url_from_menuitem,
382                                                link)
383                                        self.handlers[id] = item
384                        submenu.append(item)
385
386
387                        search_link = gajim.config.get('search_engine')
388                        if search_link.find('%s') == -1:
389                                # we must have %s in the url
390                                item = gtk.MenuItem(_('Web Search URL is missing an "%s"'))
391                                item.set_property('sensitive', False)
392                        else:
393                                item = gtk.MenuItem(_('Web _Search for it'))
394                                link =  search_link % self.selected_phrase
395                                id = item.connect('activate', self.visit_url_from_menuitem, link)
396                                self.handlers[id] = item
397                        submenu.append(item)
398                       
399                        item = gtk.MenuItem(_('Open as _Link'))
400                        id = item.connect('activate', self.visit_url_from_menuitem, link)
401                        self.handlers[id] = item
402                        submenu.append(item)
403
404                menu.show_all()
405
406        def on_textview_button_press_event(self, widget, event):
407                # If we clicked on a taged text do NOT open the standard popup menu
408                # if normal text check if we have sth selected
409                self.selected_phrase = '' # do not move belove event button check!
410
411                if event.button != 3: # if not right click
412                        return False
413
414                x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
415                        int(event.x), int(event.y))
416                iter = self.tv.get_iter_at_location(x, y)
417                tags = iter.get_tags()
418
419
420                if tags: # we clicked on sth special (it can be status message too)
421                        for tag in tags:
422                                tag_name = tag.get_property('name')
423                                if tag_name in ('url', 'mail'):
424                                        return True # we block normal context menu
425
426                # we check if sth was selected and if it was we assign
427                # selected_phrase variable
428                # so on_conversation_textview_populate_popup can use it
429                buffer = self.tv.get_buffer()
430                return_val = buffer.get_selection_bounds()
431                if return_val: # if sth was selected when we right-clicked
432                        # get the selected text
433                        start_sel, finish_sel = return_val[0], return_val[1]
434                        self.selected_phrase = buffer.get_text(start_sel, finish_sel).decode('utf-8')
435
436        def on_open_link_activate(self, widget, kind, text):
437                helpers.launch_browser_mailer(kind, text)
438
439        def on_copy_link_activate(self, widget, text):
440                clip = gtk.clipboard_get()
441                clip.set_text(text)
442
443        def on_start_chat_activate(self, widget, jid):
444                gajim.interface.roster.new_chat_from_jid(self.account, jid)
445
446        def on_join_group_chat_menuitem_activate(self, widget, room_jid):
447                if 'join_gc' in gajim.interface.instances[self.account]:
448                        instance = gajim.interface.instances[self.account]['join_gc']
449                        instance.xml.get_widget('room_jid_entry').set_text(room_jid)
450                        gajim.interface.instances[self.account]['join_gc'].window.present()
451                else:
452                        try:
453                                gajim.interface.instances[self.account]['join_gc'] = \
454                                dialogs.JoinGroupchatWindow(self.account, room_jid)
455                        except GajimGeneralException:
456                                pass
457
458        def on_add_to_roster_activate(self, widget, jid):
459                dialogs.AddNewContactWindow(self.account, jid)
460
461        def make_link_menu(self, event, kind, text):
462                xml = gtkgui_helpers.get_glade('chat_context_menu.glade')
463                menu = xml.get_widget('chat_context_menu')
464                childs = menu.get_children()
465                if kind == 'url':
466                        id = childs[0].connect('activate', self.on_copy_link_activate, text)
467                        self.handlers[id] = childs[0]
468                        id = childs[1].connect('activate', self.on_open_link_activate, kind, text)
469                        self.handlers[id] = childs[1]
470                        childs[2].hide() # copy mail address
471                        childs[3].hide() # open mail composer
472                        childs[4].hide() # jid section separator
473                        childs[5].hide() # start chat
474                        childs[6].hide() # join group chat
475                        childs[7].hide() # add to roster
476                else: # It's a mail or a JID
477                        # load muc icon
478                        join_group_chat_menuitem = xml.get_widget('join_group_chat_menuitem')
479                        muc_icon = gajim.interface.roster.load_icon('muc_active')
480                        if muc_icon:
481                                join_group_chat_menuitem.set_image(muc_icon)
482
483                        text = text.lower()
484                        id = childs[2].connect('activate', self.on_copy_link_activate, text)
485                        self.handlers[id] = childs[2]
486                        id = childs[3].connect('activate', self.on_open_link_activate, kind, text)
487                        self.handlers[id] = childs[3]
488                        id = childs[5].connect('activate', self.on_start_chat_activate, text)
489                        self.handlers[id] = childs[5]
490                        id = childs[6].connect('activate',
491                                self.on_join_group_chat_menuitem_activate, text)
492                        self.handlers[id] = childs[6]
493
494                        allow_add = False
495                        c = gajim.contacts.get_first_contact_from_jid(self.account, text)
496                        if c and not gajim.contacts.is_pm_from_contact(self.account, c):
497                                if _('Not in Roster') in c.groups:
498                                        allow_add = True
499                        else: # he or she's not at all in the account contacts
500                                allow_add = True
501
502                        if allow_add:
503                                id = childs[7].connect('activate', self.on_add_to_roster_activate, text)
504                                self.handlers[id] = childs[7]
505                                childs[7].show() # show add to roster menuitem
506                        else:
507                                childs[7].hide() # hide add to roster menuitem
508
509                        childs[0].hide() # copy link location
510                        childs[1].hide() # open link in browser
511
512                menu.popup(None, None, None, event.button, event.time)
513
514        def hyperlink_handler(self, texttag, widget, event, iter, kind):
515                if event.type == gtk.gdk.BUTTON_PRESS:
516                        begin_iter = iter.copy()
517                        # we get the begining of the tag
518                        while not begin_iter.begins_tag(texttag):
519                                begin_iter.backward_char()
520                        end_iter = iter.copy()
521                        # we get the end of the tag
522                        while not end_iter.ends_tag(texttag):
523                                end_iter.forward_char()
524                        word = self.tv.get_buffer().get_text(begin_iter, end_iter).decode('utf-8')
525                        if event.button == 3: # right click
526                                self.make_link_menu(event, kind, word)
527                        else:
528                                # we launch the correct application
529                                helpers.launch_browser_mailer(kind, word)
530
531        def html_hyperlink_handler(self, texttag, widget, event, iter, kind, href):
532                if event.type == gtk.gdk.BUTTON_PRESS:
533                        if event.button == 3: # right click
534                                self.make_link_menu(event, kind, href)
535                                return True
536                        else:
537                                # we launch the correct application
538                                helpers.launch_browser_mailer(kind, href)
539
540
541        def detect_and_print_special_text(self, otext, other_tags):
542                '''detects special text (emots & links & formatting)
543                prints normal text before any special text it founts,
544                then print special text (that happens many times until
545                last special text is printed) and then returns the index
546                after *last* special text, so we can print it in
547                print_conversation_line()'''
548
549                buffer = self.tv.get_buffer()
550
551                start = 0
552                end = 0
553                index = 0
554
555                # basic: links + mail + formatting is always checked (we like that)
556                if gajim.config.get('emoticons_theme'): # search for emoticons & urls
557                        iterator = gajim.interface.emot_and_basic_re.finditer(otext)
558                else: # search for just urls + mail + formatting
559                        iterator = gajim.interface.basic_pattern_re.finditer(otext)
560                for match in iterator:
561                        start, end = match.span()
562                        special_text = otext[start:end]
563                        if start != 0:
564                                text_before_special_text = otext[index:start]
565                                end_iter = buffer.get_end_iter()
566                                # we insert normal text
567                                buffer.insert_with_tags_by_name(end_iter,
568                                        text_before_special_text, *other_tags)
569                        index = end # update index
570
571                        # now print it
572                        self.print_special_text(special_text, other_tags)
573
574                return index # the position after *last* special text
575
576        def print_special_text(self, special_text, other_tags):
577                '''is called by detect_and_print_special_text and prints
578                special text (emots, links, formatting)'''
579                tags = []
580                use_other_tags = True
581                show_ascii_formatting_chars = \
582                        gajim.config.get('show_ascii_formatting_chars')
583                buffer = self.tv.get_buffer()
584
585                possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS
586                if gajim.config.get('emoticons_theme') and \
587                possible_emot_ascii_caps in gajim.interface.emoticons.keys():
588                        # it's an emoticon
589                        emot_ascii = possible_emot_ascii_caps
590                        end_iter = buffer.get_end_iter()
591                        anchor = buffer.create_child_anchor(end_iter)
592                        img = gtk.Image()
593                        animations = gajim.interface.emoticons_animations
594                        if not emot_ascii in animations:
595                                animations[emot_ascii] = gtk.gdk.PixbufAnimation(
596                                        gajim.interface.emoticons[emot_ascii])
597                        img.set_from_animation(animations[emot_ascii])
598                        img.show()
599                        # add with possible animation
600                        self.tv.add_child_at_anchor(img, anchor)
601                #FIXME: one day, somehow sync with regexp in gajim.py
602                elif special_text.startswith('http://') or \
603                        special_text.startswith('www.') or \
604                        special_text.startswith('ftp://') or \
605                        special_text.startswith('ftp.') or \
606                        special_text.startswith('https://') or \
607                        special_text.startswith('gopher://') or \
608                        special_text.startswith('news://') or \
609                        special_text.startswith('ed2k://') or \
610                        special_text.startswith('irc://') or \
611                        special_text.startswith('sip:') or \
612                        special_text.startswith('magnet:'):
613                        # it's a url
614                        tags.append('url')
615                        use_other_tags = False
616                elif special_text.startswith('mailto:') or \
617                gajim.interface.sth_at_sth_dot_sth_re.match(special_text):
618                        # it's a mail
619                        tags.append('mail')
620                        use_other_tags = False
621                elif special_text.startswith('*'): # it's a bold text
622                        tags.append('bold')
623                        if special_text[1] == '/' and special_text[-2] == '/' and len(special_text) > 4: # it's also italic
624                                tags.append('italic')
625                                if not show_ascii_formatting_chars:
626                                        special_text = special_text[2:-2] # remove */ /*
627                        elif special_text[1] == '_' and special_text[-2] == '_' and len(special_text) > 4: # it's also underlined
628                                tags.append('underline')
629                                if not show_ascii_formatting_chars:
630                                        special_text = special_text[2:-2] # remove *_ _*
631                        else:
632                                if not show_ascii_formatting_chars:
633                                        special_text = special_text[1:-1] # remove * *
634                elif special_text.startswith('/'): # it's an italic text
635                        tags.append('italic')
636                        if special_text[1] == '*' and special_text[-2] == '*' and len(special_text) > 4: # it's also bold
637                                tags.append('bold')
638                                if not show_ascii_formatting_chars:
639                                        special_text = special_text[2:-2] # remove /* */
640                        elif special_text[1] == '_' and special_text[-2] == '_' and len(special_text) > 4: # it's also underlined
641                                tags.append('underline')
642                                if not show_ascii_formatting_chars:
643                                        special_text = special_text[2:-2] # remove /_ _/
644                        else:
645                                if not show_ascii_formatting_chars:
646                                        special_text = special_text[1:-1] # remove / /
647                elif special_text.startswith('_'): # it's an underlined text
648                        tags.append('underline')
649                        if special_text[1] == '*' and special_text[-2] == '*' and len(special_text) > 4: # it's also bold
650                                tags.append('bold')
651                                if not show_ascii_formatting_chars:
652                                        special_text = special_text[2:-2] # remove _* *_
653                        elif special_text[1] == '/' and special_text[-2] == '/' and len(special_text) > 4: # it's also italic
654                                tags.append('italic')
655                                if not show_ascii_formatting_chars:
656                                        special_text = special_text[2:-2] # remove _/ /_
657                        else:
658                                if not show_ascii_formatting_chars:
659                                        special_text = special_text[1:-1] # remove _ _
660                else:
661                        #it's a url
662                        tags.append('url')
663                        use_other_tags = False
664
665                if len(tags) > 0:
666                        end_iter = buffer.get_end_iter()
667                        all_tags = tags[:]
668                        if use_other_tags:
669                                all_tags += other_tags
670                        buffer.insert_with_tags_by_name(end_iter, special_text, *all_tags)
671
672        def print_empty_line(self):
673                buffer = self.tv.get_buffer()
674                end_iter = buffer.get_end_iter()
675                buffer.insert_with_tags_by_name(end_iter, '\n', 'eol')
676
677        def print_conversation_line(self, text, jid, kind, name, tim,
678        other_tags_for_name = [], other_tags_for_time = [], other_tags_for_text = [],
679        subject = None, old_kind = None, xhtml = None):
680                '''prints 'chat' type messages'''
681                buffer = self.tv.get_buffer()
682                buffer.begin_user_action()
683                end_iter = buffer.get_end_iter()
684                at_the_end = False
685                if self.at_the_end():
686                        at_the_end = True
687
688                if buffer.get_char_count() > 0:
689                        buffer.insert_with_tags_by_name(end_iter, '\n', 'eol')
690                if kind == 'incoming_queue':
691                        kind = 'incoming'
692                if old_kind == 'incoming_queue':
693                        old_kind = 'incoming'
694                # print the time stamp
695                if not tim:
696                        # We don't have tim for outgoing messages...
697                        tim = time.localtime()
698                current_print_time = gajim.config.get('print_time')
699                if current_print_time == 'always' and kind != 'info':
700                        timestamp_str = self.get_time_to_show(tim)
701                        timestamp = time.strftime(timestamp_str, tim)
702                        buffer.insert_with_tags_by_name(end_iter, timestamp,
703                                *other_tags_for_time)
704                elif current_print_time == 'sometimes' and kind != 'info':
705                        every_foo_seconds = 60 * gajim.config.get(
706                                'print_ichat_every_foo_minutes')
707                        seconds_passed = time.mktime(tim) - self.last_time_printout
708                        if seconds_passed > every_foo_seconds:
709                                self.last_time_printout = time.mktime(tim)
710                                end_iter = buffer.get_end_iter()
711                                if gajim.config.get('print_time_fuzzy') > 0:
712                                        fc = FuzzyClock()
713                                        fc.setTime(time.strftime('%H:%M', tim))
714                                        ft = fc.getFuzzyTime(gajim.config.get('print_time_fuzzy'))
715                                        tim_format = ft.decode(locale.getpreferredencoding())
716                                else:
717                                        tim_format = self.get_time_to_show(tim)
718                                buffer.insert_with_tags_by_name(end_iter, tim_format + '\n',
719                                        'time_sometimes')
720                # kind = info, we print things as if it was a status: same color, ...
721                if kind == 'info':
722                        kind = 'status'
723                other_text_tag = self.detect_other_text_tag(text, kind)
724                text_tags = other_tags_for_text[:] # create a new list
725                if other_text_tag:
726                        # note that color of /me may be overwritten in gc_control
727                        text_tags.append(other_text_tag)
728                else: # not status nor /me
729                        if gajim.config.get(
730                                'chat_merge_consecutive_nickname'):
731                                if kind != old_kind:
732                                        self.print_name(name, kind, other_tags_for_name)
733                                else:
734                                        self.print_real_text(gajim.config.get(
735                                                'chat_merge_consecutive_nickname_indent'))
736                        else:
737                                self.print_name(name, kind, other_tags_for_name)
738                self.print_subject(subject)
739                self.print_real_text(text, text_tags, name, xhtml)
740
741                # scroll to the end of the textview
742                if at_the_end or kind == 'outgoing':
743                        # we are at the end or we are sending something
744                        # scroll to the end (via idle in case the scrollbar has appeared)
745                        gobject.idle_add(self.scroll_to_end)
746
747                buffer.end_user_action()
748
749        def get_time_to_show(self, tim):
750                '''Get the time, with the day before if needed and return it.
751                It DOESN'T format a fuzzy time'''
752                format = ''
753                # get difference in days since epoch (86400 = 24*3600)
754                # number of days since epoch for current time (in GMT) -
755                # number of days since epoch for message (in GMT)
756                diff_day = int(timegm(time.localtime())) / 86400 -\
757                        int(timegm(tim)) / 86400
758                if diff_day == 0:
759                        day_str = ''
760                elif diff_day == 1:
761                        day_str = _('Yesterday')
762                else:
763                        #the number is >= 2
764                        # %i is day in year (1-365), %d (1-31) we want %i
765                        day_str = _('%i days ago') % diff_day
766                if day_str:
767                        format += day_str + ' '
768                timestamp_str = gajim.config.get('time_stamp')
769                timestamp_str = helpers.from_one_line(timestamp_str)
770                format += timestamp_str