root/branches/gajim_0.11/src/conversation_textview.py

Revision 7984, 29.7 kB (checked in by asterix, 19 months ago)

mrege diff from trunk

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                        else:
536                                # we launch the correct application
537                                helpers.launch_browser_mailer(kind, href)
538
539
540        def detect_and_print_special_text(self, otext, other_tags):
541                '''detects special text (emots & links & formatting)
542                prints normal text before any special text it founts,
543                then print special text (that happens many times until
544                last special text is printed) and then returns the index
545                after *last* special text, so we can print it in
546                print_conversation_line()'''
547
548                buffer = self.tv.get_buffer()
549
550                start = 0
551                end = 0
552                index = 0
553
554                # basic: links + mail + formatting is always checked (we like that)
555                if gajim.config.get('emoticons_theme'): # search for emoticons & urls
556                        iterator = gajim.interface.emot_and_basic_re.finditer(otext)
557                else: # search for just urls + mail + formatting
558                        iterator = gajim.interface.basic_pattern_re.finditer(otext)
559                for match in iterator:
560                        start, end = match.span()
561                        special_text = otext[start:end]
562                        if start != 0:
563                                text_before_special_text = otext[index:start]
564                                end_iter = buffer.get_end_iter()
565                                # we insert normal text
566                                buffer.insert_with_tags_by_name(end_iter,
567                                        text_before_special_text, *other_tags)
568                        index = end # update index
569
570                        # now print it
571                        self.print_special_text(special_text, other_tags)
572
573                return index # the position after *last* special text
574
575        def print_special_text(self, special_text, other_tags):
576                '''is called by detect_and_print_special_text and prints
577                special text (emots, links, formatting)'''
578                tags = []
579                use_other_tags = True
580                show_ascii_formatting_chars = \
581                        gajim.config.get('show_ascii_formatting_chars')
582                buffer = self.tv.get_buffer()
583
584                possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS
585                if gajim.config.get('emoticons_theme') and \
586                possible_emot_ascii_caps in gajim.interface.emoticons.keys():
587                        # it's an emoticon
588                        emot_ascii = possible_emot_ascii_caps
589                        end_iter = buffer.get_end_iter()
590                        anchor = buffer.create_child_anchor(end_iter)
591                        img = gtk.Image()
592                        animations = gajim.interface.emoticons_animations
593                        if not emot_ascii in animations:
594                                animations[emot_ascii] = gtk.gdk.PixbufAnimation(
595                                        gajim.interface.emoticons[emot_ascii])
596                        img.set_from_animation(animations[emot_ascii])
597                        img.show()
598                        # add with possible animation
599                        self.tv.add_child_at_anchor(img, anchor)
600                #FIXME: one day, somehow sync with regexp in gajim.py
601                elif special_text.startswith('http://') or \
602                        special_text.startswith('www.') or \
603                        special_text.startswith('ftp://') or \
604                        special_text.startswith('ftp.') or \
605                        special_text.startswith('https://') or \
606                        special_text.startswith('gopher://') or \
607                        special_text.startswith('news://') or \
608                        special_text.startswith('ed2k://') or \
609                        special_text.startswith('irc://') or \
610                        special_text.startswith('sip:') or \
611                        special_text.startswith('magnet:'):
612                        # it's a url
613                        tags.append('url')
614                        use_other_tags = False
615                elif special_text.startswith('mailto:') or \
616                gajim.interface.sth_at_sth_dot_sth_re.match(special_text):
617                        # it's a mail
618                        tags.append('mail')
619                        use_other_tags = False
620                elif special_text.startswith('*'): # it's a bold text
621                        tags.append('bold')
622                        if special_text[1] == '/' and special_text[-2] == '/' and len(special_text) > 4: # it's also italic
623                                tags.append('italic')
624                                if not show_ascii_formatting_chars:
625                                        special_text = special_text[2:-2] # remove */ /*
626                        elif special_text[1] == '_' and special_text[-2] == '_' and len(special_text) > 4: # it's also underlined
627                                tags.append('underline')
628                                if not show_ascii_formatting_chars:
629                                        special_text = special_text[2:-2] # remove *_ _*
630                        else:
631                                if not show_ascii_formatting_chars:
632                                        special_text = special_text[1:-1] # remove * *
633                elif special_text.startswith('/'): # it's an italic text
634                        tags.append('italic')
635                        if special_text[1] == '*' and special_text[-2] == '*' and len(special_text) > 4: # it's also bold
636                                tags.append('bold')
637                                if not show_ascii_formatting_chars:
638                                        special_text = special_text[2:-2] # remove /* */
639                        elif special_text[1] == '_' and special_text[-2] == '_' and len(special_text) > 4: # it's also underlined
640                                tags.append('underline')
641                                if not show_ascii_formatting_chars:
642                                        special_text = special_text[2:-2] # remove /_ _/
643                        else:
644                                if not show_ascii_formatting_chars:
645                                        special_text = special_text[1:-1] # remove / /
646                elif special_text.startswith('_'): # it's an underlined text
647                        tags.append('underline')
648                        if special_text[1] == '*' and special_text[-2] == '*' and len(special_text) > 4: # it's also bold
649                                tags.append('bold')
650                                if not show_ascii_formatting_chars:
651                                        special_text = special_text[2:-2] # remove _* *_
652                        elif special_text[1] == '/' and special_text[-2] == '/' and len(special_text) > 4: # it's also italic
653                                tags.append('italic')
654                                if not show_ascii_formatting_chars:
655                                        special_text = special_text[2:-2] # remove _/ /_
656                        else:
657                                if not show_ascii_formatting_chars:
658                                        special_text = special_text[1:-1] # remove _ _
659                else:
660                        #it's a url
661                        tags.append('url')
662                        use_other_tags = False
663
664                if len(tags) > 0:
665                        end_iter = buffer.get_end_iter()
666                        all_tags = tags[:]
667                        if use_other_tags:
668                                all_tags += other_tags
669                        buffer.insert_with_tags_by_name(end_iter, special_text, *all_tags)
670
671        def print_empty_line(self):
672                buffer = self.tv.get_buffer()
673                end_iter = buffer.get_end_iter()
674                buffer.insert_with_tags_by_name(end_iter, '\n', 'eol')
675
676        def print_conversation_line(self, text, jid, kind, name, tim,
677        other_tags_for_name = [], other_tags_for_time = [], other_tags_for_text = [],
678        subject = None, old_kind = None, xhtml = None):
679                '''prints 'chat' type messages'''
680                buffer = self.tv.get_buffer()
681                buffer.begin_user_action()
682                end_iter = buffer.get_end_iter()
683                at_the_end = False
684                if self.at_the_end():
685                        at_the_end = True
686
687                if buffer.get_char_count() > 0:
688                        buffer.insert_with_tags_by_name(end_iter, '\n', 'eol')
689                if kind == 'incoming_queue':
690                        kind = 'incoming'
691                if old_kind == 'incoming_queue':
692                        old_kind = 'incoming'
693                # print the time stamp
694                if not tim:
695                        # We don't have tim for outgoing messages...
696                        tim = time.localtime()
697                current_print_time = gajim.config.get('print_time')
698                if current_print_time == 'always' and kind != 'info':
699                        timestamp_str = self.get_time_to_show(tim)
700                        timestamp = time.strftime(timestamp_str, tim)
701                        buffer.insert_with_tags_by_name(end_iter, timestamp,
702                                *other_tags_for_time)
703                elif current_print_time == 'sometimes' and kind != 'info':
704                        every_foo_seconds = 60 * gajim.config.get(
705                                'print_ichat_every_foo_minutes')
706                        seconds_passed = time.mktime(tim) - self.last_time_printout
707                        if seconds_passed > every_foo_seconds:
708                                self.last_time_printout = time.mktime(tim)
709                                end_iter = buffer.get_end_iter()
710                                if gajim.config.get('print_time_fuzzy') > 0:
711                                        fc = FuzzyClock()
712                                        fc.setTime(time.strftime('%H:%M', tim))
713                                        ft = fc.getFuzzyTime(gajim.config.get('print_time_fuzzy'))
714                                        tim_format = ft.decode(locale.getpreferredencoding())
715                                else:
716                                        tim_format = self.get_time_to_show(tim)
717                                buffer.insert_with_tags_by_name(end_iter, tim_format + '\n',
718                                        'time_sometimes')
719                # kind = info, we print things as if it was a status: same color, ...
720                if kind == 'info':
721                        kind = 'status'
722                other_text_tag = self.detect_other_text_tag(text, kind)
723                text_tags = other_tags_for_text[:] # create a new list
724                if other_text_tag:
725                        # note that color of /me may be overwritten in gc_control
726                        text_tags.append(other_text_tag)
727                else: # not status nor /me
728                        if gajim.config.get(
729                                'chat_merge_consecutive_nickname'):
730                                if kind != old_kind:
731                                        self.print_name(name, kind, other_tags_for_name)
732                                else:
733                                        self.print_real_text(gajim.config.get(
734                                                'chat_merge_consecutive_nickname_indent'))
735                        else:
736                                self.print_name(name, kind, other_tags_for_name)
737                self.print_subject(subject)
738                self.print_real_text(text, text_tags, name, xhtml)
739
740                # scroll to the end of the textview
741                if at_the_end or kind == 'outgoing':
742                        # we are at the end or we are sending something
743                        # scroll to the end (via idle in case the scrollbar has appeared)
744                        gobject.idle_add(self.scroll_to_end)
745
746                buffer.end_user_action()
747
748        def get_time_to_show(self, tim):
749                '''Get the time, with the day before if needed and return it.
750                It DOESN'T format a fuzzy time'''
751                format = ''
752                # get difference in days since epoch (86400 = 24*3600)
753                # number of days since epoch for current time (in GMT) -
754                # number of days since epoch for message (in GMT)
755                diff_day = int(timegm(time.localtime())) / 86400 -\
756                        int(timegm(tim)) / 86400
757                if diff_day == 0:
758                        day_str = ''
759                elif diff_day == 1:
760                        day_str = _('Yesterday')
761                else:
762                        #the number is >= 2
763                        # %i is day in year (1-365), %d (1-31) we want %i
764                        day_str = _('%i days ago') % diff_day
765                if day_str:
766                        format += day_str + ' '
767                timestamp_str = gajim.config.get('time_stamp')
768                timestamp_str = helpers.from_one_line(timestamp_str)
769                format += timestamp_str
770                tim_format = time.strftime(format, tim)
771