2 ## src/conversation_textview.py
4 ## Copyright (C) 2005 Norman Rasmussen <norman AT rasmussen.co.za>
5 ## Copyright (C) 2005-2006 Alex Mauer <hawke AT hawkesnest.net>
6 ## Travis Shirk <travis AT pobox.com>
7 ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
8 ## Copyright (C) 2005-2008 Yann Leboulanger <asterix AT lagaule.org>
9 ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
10 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
11 ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
12 ## Julien Pivotto <roidelapluie AT gmail.com>
13 ## Stephan Erb <steve-e AT h3c.de>
15 ## This file is part of Gajim.
17 ## Gajim is free software; you can redistribute it and/or modify
18 ## it under the terms of the GNU General Public License as published
19 ## by the Free Software Foundation; version 3 only.
21 ## Gajim is distributed in the hope that it will be useful,
22 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
23 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 ## GNU General Public License for more details.
26 ## You should have received a copy of the GNU General Public License
27 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
30 from threading
import Timer
# for smooth scrolling
43 from common
import gajim
44 from common
import helpers
45 from common
import latex
46 from calendar
import timegm
47 from common
.fuzzyclock
import FuzzyClock
49 from htmltextview
import HtmlTextView
50 from common
.exceptions
import GajimGeneralException
51 from common
.exceptions
import LatexError
57 def is_selection_modified(mark
):
58 name
= mark
.get_name()
59 if name
and name
in ('selection_bound', 'insert'):
64 def has_focus(widget
):
65 return widget
.flags() & gtk
.HAS_FOCUS
== gtk
.HAS_FOCUS
67 class TextViewImage(gtk
.Image
):
69 def __init__(self
, anchor
, text
):
70 super(TextViewImage
, self
).__init
__()
72 self
._selected
= False
73 self
._disconnect
_funcs
= []
74 self
.connect('parent-set', self
.on_parent_set
)
75 self
.connect('expose-event', self
.on_expose
)
76 self
.set_tooltip_text(text
)
77 self
.anchor
.set_data('plaintext', text
)
79 def _get_selected(self
):
80 parent
= self
.get_parent()
81 if not parent
or not self
.anchor
: return False
82 buffer_
= parent
.get_buffer()
83 position
= buffer_
.get_iter_at_child_anchor(self
.anchor
)
84 bounds
= buffer_
.get_selection_bounds()
85 if bounds
and position
.in_range(*bounds
):
91 parent
= self
.get_parent()
93 return gtk
.STATE_NORMAL
96 return gtk
.STATE_SELECTED
98 return gtk
.STATE_ACTIVE
100 return gtk
.STATE_NORMAL
102 def _update_selected(self
):
103 selected
= self
._get
_selected
()
104 if self
._selected
!= selected
:
105 self
._selected
= selected
108 def _do_connect(self
, widget
, signal
, callback
):
109 id_
= widget
.connect(signal
, callback
)
111 widget
.disconnect(id_
)
112 self
._disconnect
_funcs
.append(disconnect
)
114 def _disconnect_signals(self
):
115 for func
in self
._disconnect
_funcs
:
117 self
._disconnect
_funcs
= []
119 def on_parent_set(self
, widget
, old_parent
):
120 parent
= self
.get_parent()
122 self
._disconnect
_signals
()
125 self
._do
_connect
(parent
, 'style-set', self
.do_queue_draw
)
126 self
._do
_connect
(parent
, 'focus-in-event', self
.do_queue_draw
)
127 self
._do
_connect
(parent
, 'focus-out-event', self
.do_queue_draw
)
129 textbuf
= parent
.get_buffer()
130 self
._do
_connect
(textbuf
, 'mark-set', self
.on_mark_set
)
131 self
._do
_connect
(textbuf
, 'mark-deleted', self
.on_mark_deleted
)
133 def do_queue_draw(self
, *args
):
137 def on_mark_set(self
, buf
, iterat
, mark
):
138 self
.on_mark_modified(mark
)
141 def on_mark_deleted(self
, buf
, mark
):
142 self
.on_mark_modified(mark
)
145 def on_mark_modified(self
, mark
):
146 if is_selection_modified(mark
):
147 self
._update
_selected
()
149 def on_expose(self
, widget
, event
):
150 state
= self
.get_state()
151 if state
!= gtk
.STATE_NORMAL
:
152 gc
= widget
.get_style().base_gc
[state
]
153 area
= widget
.allocation
154 widget
.window
.draw_rectangle(gc
, True, area
.x
, area
.y
,
155 area
.width
, area
.height
)
159 class ConversationTextview(gobject
.GObject
):
160 '''Class for the conversation textview (where user reads already said
161 messages) for chat/groupchat windows'''
163 quote
= (gobject
.SIGNAL_RUN_LAST | gobject
.SIGNAL_ACTION
,
169 FOCUS_OUT_LINE_PIXBUF
= gtk
.gdk
.pixbuf_new_from_file(os
.path
.join(
170 gajim
.DATA_DIR
, 'pixmaps', 'muc_separator.png'))
171 XEP0184_WARNING_PIXBUF
= gtk
.gdk
.pixbuf_new_from_file(os
.path
.join(
172 gajim
.DATA_DIR
, 'pixmaps', 'receipt_missing.png'))
174 # smooth scroll constants
175 MAX_SCROLL_TIME
= 0.4 # seconds
176 SCROLL_DELAY
= 33 # milliseconds
178 def __init__(self
, account
, used_in_history_window
= False):
179 '''if used_in_history_window is True, then we do not show
180 Clear menuitem in context menu'''
181 gobject
.GObject
.__init
__(self
)
182 self
.used_in_history_window
= used_in_history_window
184 self
.fc
= FuzzyClock()
187 # no need to inherit TextView, use it as atrribute is safer
188 self
.tv
= HtmlTextView()
189 self
.tv
.html_hyperlink_handler
= self
.html_hyperlink_handler
192 self
.tv
.set_border_width(1)
193 self
.tv
.set_accepts_tab(True)
194 self
.tv
.set_editable(False)
195 self
.tv
.set_cursor_visible(False)
196 self
.tv
.set_wrap_mode(gtk
.WRAP_WORD_CHAR
)
197 self
.tv
.set_left_margin(2)
198 self
.tv
.set_right_margin(2)
201 self
.image_cache
= {}
202 self
.xep0184_marks
= {}
203 self
.xep0184_shown
= {}
205 # It's True when we scroll in the code, so we can detect scroll from user
206 self
.auto_scrolling
= False
209 id_
= self
.tv
.connect('motion_notify_event',
210 self
.on_textview_motion_notify_event
)
211 self
.handlers
[id_
] = self
.tv
212 id_
= self
.tv
.connect('populate_popup', self
.on_textview_populate_popup
)
213 self
.handlers
[id_
] = self
.tv
214 id_
= self
.tv
.connect('button_press_event',
215 self
.on_textview_button_press_event
)
216 self
.handlers
[id_
] = self
.tv
218 id_
= self
.tv
.connect('expose-event',
219 self
.on_textview_expose_event
)
220 self
.handlers
[id_
] = self
.tv
223 self
.account
= account
224 self
.change_cursor
= False
225 self
.last_time_printout
= 0
227 font
= pango
.FontDescription(gajim
.config
.get('conversation_font'))
228 self
.tv
.modify_font(font
)
229 buffer_
= self
.tv
.get_buffer()
230 end_iter
= buffer_
.get_end_iter()
231 buffer_
.create_mark('end', end_iter
, False)
233 self
.tagIn
= buffer_
.create_tag('incoming')
234 color
= gajim
.config
.get('inmsgcolor')
235 font
= pango
.FontDescription(gajim
.config
.get('inmsgfont'))
236 self
.tagIn
.set_property('foreground', color
)
237 self
.tagIn
.set_property('font-desc', font
)
239 self
.tagOut
= buffer_
.create_tag('outgoing')
240 color
= gajim
.config
.get('outmsgcolor')
241 font
= pango
.FontDescription(gajim
.config
.get('outmsgfont'))
242 self
.tagOut
.set_property('foreground', color
)
243 self
.tagOut
.set_property('font-desc', font
)
245 self
.tagStatus
= buffer_
.create_tag('status')
246 color
= gajim
.config
.get('statusmsgcolor')
247 font
= pango
.FontDescription(gajim
.config
.get('satusmsgfont'))
248 self
.tagStatus
.set_property('foreground', color
)
249 self
.tagStatus
.set_property('font-desc', font
)
251 self
.tagInText
= buffer_
.create_tag('incomingtxt')
252 color
= gajim
.config
.get('inmsgtxtcolor')
253 font
= pango
.FontDescription(gajim
.config
.get('inmsgtxtfont'))
255 self
.tagInText
.set_property('foreground', color
)
256 self
.tagInText
.set_property('font-desc', font
)
258 self
.tagOutText
= buffer_
.create_tag('outgoingtxt')
259 color
= gajim
.config
.get('outmsgtxtcolor')
261 font
= pango
.FontDescription(gajim
.config
.get('outmsgtxtfont'))
262 self
.tagOutText
.set_property('foreground', color
)
263 self
.tagOutText
.set_property('font-desc', font
)
265 colors
= gajim
.config
.get('gc_nicknames_colors')
266 colors
= colors
.split(':')
267 for i
,color
in enumerate(colors
):
268 tagname
= 'gc_nickname_color_' + str(i
)
269 tag
= buffer_
.create_tag(tagname
)
270 tag
.set_property('foreground', color
)
272 tag
= buffer_
.create_tag('marked')
273 color
= gajim
.config
.get('markedmsgcolor')
274 tag
.set_property('foreground', color
)
275 tag
.set_property('weight', pango
.WEIGHT_BOLD
)
277 tag
= buffer_
.create_tag('time_sometimes')
278 tag
.set_property('foreground', 'darkgrey')
279 tag
.set_property('scale', pango
.SCALE_SMALL
)
280 tag
.set_property('justification', gtk
.JUSTIFY_CENTER
)
282 tag
= buffer_
.create_tag('small')
283 tag
.set_property('scale', pango
.SCALE_SMALL
)
285 tag
= buffer_
.create_tag('restored_message')
286 color
= gajim
.config
.get('restored_messages_color')
287 tag
.set_property('foreground', color
)
289 self
.tagURL
= buffer_
.create_tag('url')
290 color
= gajim
.config
.get('urlmsgcolor')
291 self
.tagURL
.set_property('foreground', color
)
292 self
.tagURL
.set_property('underline', pango
.UNDERLINE_SINGLE
)
293 id_
= self
.tagURL
.connect('event', self
.hyperlink_handler
, 'url')
294 self
.handlers
[id_
] = self
.tagURL
296 self
.tagMail
= buffer_
.create_tag('mail')
297 self
.tagMail
.set_property('foreground', color
)
298 self
.tagMail
.set_property('underline', pango
.UNDERLINE_SINGLE
)
299 id_
= self
.tagMail
.connect('event', self
.hyperlink_handler
, 'mail')
300 self
.handlers
[id_
] = self
.tagMail
302 self
.tagXMPP
= buffer_
.create_tag('xmpp')
303 self
.tagXMPP
.set_property('foreground', color
)
304 self
.tagXMPP
.set_property('underline', pango
.UNDERLINE_SINGLE
)
305 id_
= self
.tagXMPP
.connect('event', self
.hyperlink_handler
, 'xmpp')
306 self
.handlers
[id_
] = self
.tagXMPP
308 self
.tagSthAtSth
= buffer_
.create_tag('sth_at_sth')
309 self
.tagSthAtSth
.set_property('foreground', color
)
310 self
.tagSthAtSth
.set_property('underline', pango
.UNDERLINE_SINGLE
)
311 id_
= self
.tagSthAtSth
.connect('event', self
.hyperlink_handler
,
313 self
.handlers
[id_
] = self
.tagSthAtSth
315 tag
= buffer_
.create_tag('bold')
316 tag
.set_property('weight', pango
.WEIGHT_BOLD
)
318 tag
= buffer_
.create_tag('italic')
319 tag
.set_property('style', pango
.STYLE_ITALIC
)
321 tag
= buffer_
.create_tag('underline')
322 tag
.set_property('underline', pango
.UNDERLINE_SINGLE
)
324 buffer_
.create_tag('focus-out-line', justification
= gtk
.JUSTIFY_CENTER
)
326 tag
= buffer_
.create_tag('xep0184-warning')
328 # One mark at the begining then 2 marks between each lines
329 size
= gajim
.config
.get('max_conversation_lines')
331 self
.marks_queue
= Queue
.Queue(size
)
333 self
.allow_focus_out_line
= True
334 # holds a mark at the end of --- line
335 self
.focus_out_end_mark
= None
337 self
.xep0184_warning_tooltip
= tooltips
.BaseTooltip()
339 self
.line_tooltip
= tooltips
.BaseTooltip()
341 self
.tv
.focus_out_line_pixbuf
= ConversationTextview
.FOCUS_OUT_LINE_PIXBUF
342 self
.smooth_id
= None
344 def del_handlers(self
):
345 for i
in self
.handlers
.keys():
346 if self
.handlers
[i
].handler_is_connected(i
):
347 self
.handlers
[i
].disconnect(i
)
351 # self.line_tooltip.destroy()
353 def update_tags(self
):
354 self
.tagIn
.set_property('foreground', gajim
.config
.get('inmsgcolor'))
355 self
.tagOut
.set_property('foreground', gajim
.config
.get('outmsgcolor'))
356 self
.tagStatus
.set_property('foreground',
357 gajim
.config
.get('statusmsgcolor'))
358 self
.tagURL
.set_property('foreground', gajim
.config
.get('urlmsgcolor'))
359 self
.tagMail
.set_property('foreground', gajim
.config
.get('urlmsgcolor'))
361 def at_the_end(self
):
362 buffer_
= self
.tv
.get_buffer()
363 end_iter
= buffer_
.get_end_iter()
364 end_rect
= self
.tv
.get_iter_location(end_iter
)
365 visible_rect
= self
.tv
.get_visible_rect()
366 if end_rect
.y
<= (visible_rect
.y
+ visible_rect
.height
):
370 # Smooth scrolling inspired by Pidgin code
371 def smooth_scroll(self
):
372 parent
= self
.tv
.get_parent()
375 vadj
= parent
.get_vadjustment()
376 max_val
= vadj
.upper
- vadj
.page_size
+ 1
377 cur_val
= vadj
.get_value()
378 # scroll by 1/3rd of remaining distance
379 onethird
= cur_val
+ ((max_val
- cur_val
) / 3.0)
380 self
.auto_scrolling
= True
381 vadj
.set_value(onethird
)
382 self
.auto_scrolling
= False
383 if max_val
- onethird
< 0.01:
384 self
.smooth_id
= None
385 self
.smooth_scroll_timer
.cancel()
389 def smooth_scroll_timeout(self
):
390 gobject
.idle_add(self
.do_smooth_scroll_timeout
)
393 def do_smooth_scroll_timeout(self
):
394 if not self
.smooth_id
:
395 # we finished scrolling
397 gobject
.source_remove(self
.smooth_id
)
398 self
.smooth_id
= None
399 parent
= self
.tv
.get_parent()
401 vadj
= parent
.get_vadjustment()
402 self
.auto_scrolling
= True
403 vadj
.set_value(vadj
.upper
- vadj
.page_size
+ 1)
404 self
.auto_scrolling
= False
406 def smooth_scroll_to_end(self
):
407 if None != self
.smooth_id
: # already scrolling
409 self
.smooth_id
= gobject
.timeout_add(self
.SCROLL_DELAY
,
411 self
.smooth_scroll_timer
= Timer(self
.MAX_SCROLL_TIME
,
412 self
.smooth_scroll_timeout
)
413 self
.smooth_scroll_timer
.start()
416 def scroll_to_end(self
):
417 parent
= self
.tv
.get_parent()
418 buffer_
= self
.tv
.get_buffer()
419 end_mark
= buffer_
.get_mark('end')
422 self
.auto_scrolling
= True
423 self
.tv
.scroll_to_mark(end_mark
, 0, True, 0, 1)
424 adjustment
= parent
.get_hadjustment()
425 adjustment
.set_value(0)
426 self
.auto_scrolling
= False
427 return False # when called in an idle_add, just do it once
429 def bring_scroll_to_end(self
, diff_y
= 0,
430 use_smooth
=gajim
.config
.get('use_smooth_scrolling')):
431 ''' scrolls to the end of textview if end is not visible '''
432 buffer_
= self
.tv
.get_buffer()
433 end_iter
= buffer_
.get_end_iter()
434 end_rect
= self
.tv
.get_iter_location(end_iter
)
435 visible_rect
= self
.tv
.get_visible_rect()
436 # scroll only if expected end is not visible
437 if end_rect
.y
>= (visible_rect
.y
+ visible_rect
.height
+ diff_y
):
439 gobject
.idle_add(self
.smooth_scroll_to_end
)
441 gobject
.idle_add(self
.scroll_to_end_iter
)
443 def scroll_to_end_iter(self
):
444 buffer_
= self
.tv
.get_buffer()
445 end_iter
= buffer_
.get_end_iter()
448 self
.tv
.scroll_to_iter(end_iter
, 0, False, 1, 1)
449 return False # when called in an idle_add, just do it once
451 def stop_scrolling(self
):
453 gobject
.source_remove(self
.smooth_id
)
454 self
.smooth_id
= None
455 self
.smooth_scroll_timer
.cancel()
457 def show_xep0184_warning(self
, id_
):
458 if id_
in self
.xep0184_marks
:
461 buffer_
= self
.tv
.get_buffer()
462 buffer_
.begin_user_action()
464 self
.xep0184_marks
[id_
] = buffer_
.create_mark(None,
465 buffer_
.get_end_iter(), left_gravity
=True)
466 self
.xep0184_shown
[id_
] = NOT_SHOWN
469 if (not id_
in self
.xep0184_shown
) or \
470 self
.xep0184_shown
[id_
] == ALREADY_RECEIVED
:
473 end_iter
= buffer_
.get_iter_at_mark(self
.xep0184_marks
[id_
])
474 buffer_
.insert(end_iter
, ' ')
475 anchor
= buffer_
.create_child_anchor(end_iter
)
476 img
= TextViewImage(anchor
, '')
477 img
.set_from_pixbuf(ConversationTextview
.XEP0184_WARNING_PIXBUF
)
479 self
.tv
.add_child_at_anchor(img
, anchor
)
480 before_img_iter
= buffer_
.get_iter_at_mark(self
.xep0184_marks
[id_
])
481 before_img_iter
.forward_char()
482 post_img_iter
= before_img_iter
.copy()
483 post_img_iter
.forward_char()
484 buffer_
.apply_tag_by_name('xep0184-warning', before_img_iter
,
487 self
.xep0184_shown
[id_
] = SHOWN
489 gobject
.timeout_add_seconds(3, show_it
)
491 buffer_
.end_user_action()
493 def hide_xep0184_warning(self
, id_
):
494 if id_
not in self
.xep0184_marks
:
497 if self
.xep0184_shown
[id_
] == NOT_SHOWN
:
498 self
.xep0184_shown
[id_
] = ALREADY_RECEIVED
501 buffer_
= self
.tv
.get_buffer()
502 buffer_
.begin_user_action()
504 begin_iter
= buffer_
.get_iter_at_mark(self
.xep0184_marks
[id_
])
506 end_iter
= begin_iter
.copy()
507 # XXX: Is there a nicer way?
508 end_iter
.forward_char()
509 end_iter
.forward_char()
511 buffer_
.delete(begin_iter
, end_iter
)
512 buffer_
.delete_mark(self
.xep0184_marks
[id_
])
514 buffer_
.end_user_action()
516 del self
.xep0184_marks
[id_
]
517 del self
.xep0184_shown
[id_
]
519 def show_focus_out_line(self
):
520 if not self
.allow_focus_out_line
:
521 # if room did not receive focus-in from the last time we added
522 # --- line then do not readd
525 print_focus_out_line
= False
526 buffer_
= self
.tv
.get_buffer()
528 if self
.focus_out_end_mark
is None:
529 # this happens only first time we focus out on this room
530 print_focus_out_line
= True
533 focus_out_end_iter
= buffer_
.get_iter_at_mark(self
.focus_out_end_mark
)
534 focus_out_end_iter_offset
= focus_out_end_iter
.get_offset()
535 if focus_out_end_iter_offset
!= buffer_
.get_end_iter().get_offset():
536 # this means after last-focus something was printed
537 # (else end_iter's offset is the same as before)
538 # only then print ---- line (eg. we avoid printing many following
540 print_focus_out_line
= True
542 if print_focus_out_line
and buffer_
.get_char_count() > 0:
543 buffer_
.begin_user_action()
545 # remove previous focus out line if such focus out line exists
546 if self
.focus_out_end_mark
is not None:
547 end_iter_for_previous_line
= buffer_
.get_iter_at_mark(
548 self
.focus_out_end_mark
)
549 begin_iter_for_previous_line
= end_iter_for_previous_line
.copy()
550 # img_char+1 (the '\n')
551 begin_iter_for_previous_line
.backward_chars(2)
553 # remove focus out line
554 buffer_
.delete(begin_iter_for_previous_line
,
555 end_iter_for_previous_line
)
556 buffer_
.delete_mark(self
.focus_out_end_mark
)
558 # add the new focus out line
559 end_iter
= buffer_
.get_end_iter()
560 buffer_
.insert(end_iter
, '\n')
561 buffer_
.insert_pixbuf(end_iter
,
562 ConversationTextview
.FOCUS_OUT_LINE_PIXBUF
)
564 end_iter
= buffer_
.get_end_iter()
565 before_img_iter
= end_iter
.copy()
566 # one char back (an image also takes one char)
567 before_img_iter
.backward_char()
568 buffer_
.apply_tag_by_name('focus-out-line', before_img_iter
, end_iter
)
570 self
.allow_focus_out_line
= False
572 # update the iter we hold to make comparison the next time
573 self
.focus_out_end_mark
= buffer_
.create_mark(None,
574 buffer_
.get_end_iter(), left_gravity
=True)
576 buffer_
.end_user_action()
578 # scroll to the end (via idle in case the scrollbar has appeared)
579 gobject
.idle_add(self
.scroll_to_end
)
581 def show_xep0184_warning_tooltip(self
):
582 pointer
= self
.tv
.get_pointer()
583 x
, y
= self
.tv
.window_to_buffer_coords(gtk
.TEXT_WINDOW_TEXT
,
584 pointer
[0], pointer
[1])
585 tags
= self
.tv
.get_iter_at_location(x
, y
).get_tags()
586 tag_table
= self
.tv
.get_buffer().get_tag_table()
587 xep0184_warning
= False
589 if tag
== tag_table
.lookup('xep0184-warning'):
590 xep0184_warning
= True
592 if xep0184_warning
and not self
.xep0184_warning_tooltip
.win
:
593 # check if the current pointer is still over the line
594 position
= self
.tv
.window
.get_origin()
595 self
.xep0184_warning_tooltip
.show_tooltip(_('This icon indicates that '
596 'this message has not yet\nbeen received by the remote end. '
597 "If this icon stays\nfor a long time, it's likely the message got "
598 'lost.'), 8, position
[1] + pointer
[1])
600 def show_line_tooltip(self
):
601 pointer
= self
.tv
.get_pointer()
602 x
, y
= self
.tv
.window_to_buffer_coords(gtk
.TEXT_WINDOW_TEXT
,
603 pointer
[0], pointer
[1])
604 tags
= self
.tv
.get_iter_at_location(x
, y
).get_tags()
605 tag_table
= self
.tv
.get_buffer().get_tag_table()
608 if tag
== tag_table
.lookup('focus-out-line'):
611 if over_line
and not self
.line_tooltip
.win
:
612 # check if the current pointer is still over the line
613 position
= self
.tv
.window
.get_origin()
614 self
.line_tooltip
.show_tooltip(_('Text below this line is what has '
615 'been said since the\nlast time you paid attention to this group '
616 'chat'), 8, position
[1] + pointer
[1])
618 def on_textview_expose_event(self
, widget
, event
):
619 expalloc
= event
.area
622 exp_x1
= exp_x0
+ expalloc
.width
623 exp_y1
= exp_y0
+ expalloc
.height
626 tryfirst
= [self
.image_cache
[(exp_x0
, exp_y0
)]]
630 for image
in tryfirst
+ self
.images
:
631 imgalloc
= image
.allocation
634 img_x1
= img_x0
+ imgalloc
.width
635 img_y1
= img_y0
+ imgalloc
.height
637 if img_x0
<= exp_x0
and img_y0
<= exp_y0
and \
638 exp_x1
<= img_x1
and exp_y1
<= img_y1
:
639 self
.image_cache
[(img_x0
, img_y0
)] = image
640 widget
.propagate_expose(image
, event
)
644 def on_textview_motion_notify_event(self
, widget
, event
):
645 '''change the cursor to a hand when we are over a mail or an
647 pointer_x
, pointer_y
= self
.tv
.window
.get_pointer()[0:2]
648 x
, y
= self
.tv
.window_to_buffer_coords(gtk
.TEXT_WINDOW_TEXT
,
649 pointer_x
, pointer_y
)
650 tags
= self
.tv
.get_iter_at_location(x
, y
).get_tags()
651 if self
.change_cursor
:
652 self
.tv
.get_window(gtk
.TEXT_WINDOW_TEXT
).set_cursor(
653 gtk
.gdk
.Cursor(gtk
.gdk
.XTERM
))
654 self
.change_cursor
= False
655 tag_table
= self
.tv
.get_buffer().get_tag_table()
657 xep0184_warning
= False
659 if tag
in (tag_table
.lookup('url'), tag_table
.lookup('mail'), \
660 tag_table
.lookup('xmpp'), tag_table
.lookup('sth_at_sth')):
661 self
.tv
.get_window(gtk
.TEXT_WINDOW_TEXT
).set_cursor(
662 gtk
.gdk
.Cursor(gtk
.gdk
.HAND2
))
663 self
.change_cursor
= True
664 elif tag
== tag_table
.lookup('focus-out-line'):
666 elif tag
== tag_table
.lookup('xep0184-warning'):
667 xep0184_warning
= True
669 if self
.line_tooltip
.timeout
!= 0:
670 # Check if we should hide the line tooltip
672 self
.line_tooltip
.hide_tooltip()
673 if self
.xep0184_warning_tooltip
.timeout
!= 0:
674 # Check if we should hide the XEP-184 warning tooltip
675 if not xep0184_warning
:
676 self
.xep0184_warning_tooltip
.hide_tooltip()
677 if over_line
and not self
.line_tooltip
.win
:
678 self
.line_tooltip
.timeout
= gobject
.timeout_add(500,
679 self
.show_line_tooltip
)
680 self
.tv
.get_window(gtk
.TEXT_WINDOW_TEXT
).set_cursor(
681 gtk
.gdk
.Cursor(gtk
.gdk
.LEFT_PTR
))
682 self
.change_cursor
= True
683 if xep0184_warning
and not self
.xep0184_warning_tooltip
.win
:
684 self
.xep0184_warning_tooltip
.timeout
= gobject
.timeout_add(500,
685 self
.show_xep0184_warning_tooltip
)
686 self
.tv
.get_window(gtk
.TEXT_WINDOW_TEXT
).set_cursor(
687 gtk
.gdk
.Cursor(gtk
.gdk
.LEFT_PTR
))
688 self
.change_cursor
= True
690 def clear(self
, tv
= None):
691 '''clear text in the textview'''
692 buffer_
= self
.tv
.get_buffer()
693 start
, end
= buffer_
.get_bounds()
694 buffer_
.delete(start
, end
)
695 size
= gajim
.config
.get('max_conversation_lines')
697 self
.marks_queue
= Queue
.Queue(size
)
698 self
.focus_out_end_mark
= None
700 def visit_url_from_menuitem(self
, widget
, link
):
701 '''basically it filters out the widget instance'''
702 helpers
.launch_browser_mailer('url', link
)
704 def on_textview_populate_popup(self
, textview
, menu
):
705 '''we override the default context menu and we prepend Clear
706 (only if used_in_history_window is False)
707 and if we have sth selected we show a submenu with actions on
708 the phrase (see on_conversation_textview_button_press_event)'''
710 separator_menuitem_was_added
= False
711 if not self
.used_in_history_window
:
712 item
= gtk
.SeparatorMenuItem()
714 separator_menuitem_was_added
= True
716 item
= gtk
.ImageMenuItem(gtk
.STOCK_CLEAR
)
718 id_
= item
.connect('activate', self
.clear
)
719 self
.handlers
[id_
] = item
721 if self
.selected_phrase
:
722 if not separator_menuitem_was_added
:
723 item
= gtk
.SeparatorMenuItem()
726 if not self
.used_in_history_window
:
727 item
= gtk
.MenuItem(_('_Quote'))
728 id_
= item
.connect('activate', self
.on_quote
)
729 self
.handlers
[id_
] = item
732 _selected_phrase
= helpers
.reduce_chars_newlines(
733 self
.selected_phrase
, 25, 2)
734 item
= gtk
.MenuItem(_('_Actions for "%s"') % _selected_phrase
)
737 item
.set_submenu(submenu
)
739 always_use_en
= gajim
.config
.get('always_english_wikipedia')
741 link
= 'http://en.wikipedia.org/wiki/Special:Search?search=%s'\
742 % self
.selected_phrase
744 link
= 'http://%s.wikipedia.org/wiki/Special:Search?search=%s'\
745 % (gajim
.LANG
, self
.selected_phrase
)
746 item
= gtk
.MenuItem(_('Read _Wikipedia Article'))
747 id_
= item
.connect('activate', self
.visit_url_from_menuitem
, link
)
748 self
.handlers
[id_
] = item
751 item
= gtk
.MenuItem(_('Look it up in _Dictionary'))
752 dict_link
= gajim
.config
.get('dictionary_url')
753 if dict_link
== 'WIKTIONARY':
754 # special link (yeah undocumented but default)
755 always_use_en
= gajim
.config
.get('always_english_wiktionary')
757 link
= 'http://en.wiktionary.org/wiki/Special:Search?search=%s'\
758 % self
.selected_phrase
760 link
= 'http://%s.wiktionary.org/wiki/Special:Search?search=%s'\
761 % (gajim
.LANG
, self
.selected_phrase
)
762 id_
= item
.connect('activate', self
.visit_url_from_menuitem
, link
)
763 self
.handlers
[id_
] = item
765 if dict_link
.find('%s') == -1:
766 # we must have %s in the url if not WIKTIONARY
767 item
= gtk
.MenuItem(_(
768 'Dictionary URL is missing an "%s" and it is not WIKTIONARY'))
769 item
.set_property('sensitive', False)
771 link
= dict_link
% self
.selected_phrase
772 id_
= item
.connect('activate', self
.visit_url_from_menuitem
,
774 self
.handlers
[id_
] = item
778 search_link
= gajim
.config
.get('search_engine')
779 if search_link
.find('%s') == -1:
780 # we must have %s in the url
781 item
= gtk
.MenuItem(_('Web Search URL is missing an "%s"'))
782 item
.set_property('sensitive', False)
784 item
= gtk
.MenuItem(_('Web _Search for it'))
785 link
= search_link
% self
.selected_phrase
786 id_
= item
.connect('activate', self
.visit_url_from_menuitem
, link
)
787 self
.handlers
[id_
] = item
790 item
= gtk
.MenuItem(_('Open as _Link'))
791 id_
= item
.connect('activate', self
.visit_url_from_menuitem
, link
)
792 self
.handlers
[id_
] = item
797 def on_quote(self
, widget
):
798 self
.emit('quote', self
.selected_phrase
)
800 def on_textview_button_press_event(self
, widget
, event
):
801 # If we clicked on a taged text do NOT open the standard popup menu
802 # if normal text check if we have sth selected
803 self
.selected_phrase
= '' # do not move belove event button check!
805 if event
.button
!= 3: # if not right click
808 x
, y
= self
.tv
.window_to_buffer_coords(gtk
.TEXT_WINDOW_TEXT
,
809 int(event
.x
), int(event
.y
))
810 iter_
= self
.tv
.get_iter_at_location(x
, y
)
811 tags
= iter_
.get_tags()
814 if tags
: # we clicked on sth special (it can be status message too)
816 tag_name
= tag
.get_property('name')
817 if tag_name
in ('url', 'mail', 'xmpp', 'sth_at_sth'):
818 return True # we block normal context menu
820 # we check if sth was selected and if it was we assign
821 # selected_phrase variable
822 # so on_conversation_textview_populate_popup can use it
823 buffer_
= self
.tv
.get_buffer()
824 return_val
= buffer_
.get_selection_bounds()
825 if return_val
: # if sth was selected when we right-clicked
826 # get the selected text
827 start_sel
, finish_sel
= return_val
[0], return_val
[1]
828 self
.selected_phrase
= buffer_
.get_text(start_sel
, finish_sel
).decode(
830 elif ord(iter_
.get_char()) > 31:
831 # we clicked on a word, do as if it's selected for context menu
832 start_sel
= iter_
.copy()
833 if not start_sel
.starts_word():
834 start_sel
.backward_word_start()
835 finish_sel
= iter_
.copy()
836 if not finish_sel
.ends_word():
837 finish_sel
.forward_word_end()
838 self
.selected_phrase
= buffer_
.get_text(start_sel
, finish_sel
).decode(
841 def on_open_link_activate(self
, widget
, kind
, text
):
842 helpers
.launch_browser_mailer(kind
, text
)
844 def on_copy_link_activate(self
, widget
, text
):
845 clip
= gtk
.clipboard_get()
848 def on_start_chat_activate(self
, widget
, jid
):
849 gajim
.interface
.new_chat_from_jid(self
.account
, jid
)
851 def on_join_group_chat_menuitem_activate(self
, widget
, room_jid
):
852 if 'join_gc' in gajim
.interface
.instances
[self
.account
]:
853 instance
= gajim
.interface
.instances
[self
.account
]['join_gc']
854 instance
.xml
.get_widget('room_jid_entry').set_text(room_jid
)
855 gajim
.interface
.instances
[self
.account
]['join_gc'].window
.present()
858 dialogs
.JoinGroupchatWindow(account
=None, room_jid
=room_jid
)
859 except GajimGeneralException
:
862 def on_add_to_roster_activate(self
, widget
, jid
):
863 dialogs
.AddNewContactWindow(self
.account
, jid
)
865 def make_link_menu(self
, event
, kind
, text
):
866 xml
= gtkgui_helpers
.get_glade('chat_context_menu.glade')
867 menu
= xml
.get_widget('chat_context_menu')
868 childs
= menu
.get_children()
870 id_
= childs
[0].connect('activate', self
.on_copy_link_activate
, text
)
871 self
.handlers
[id_
] = childs
[0]
872 id_
= childs
[1].connect('activate', self
.on_open_link_activate
, kind
,
874 self
.handlers
[id_
] = childs
[1]
875 childs
[2].hide() # copy mail address
876 childs
[3].hide() # open mail composer
877 childs
[4].hide() # jid section separator
878 childs
[5].hide() # start chat
879 childs
[6].hide() # join group chat
880 childs
[7].hide() # add to roster
881 else: # It's a mail or a JID
883 join_group_chat_menuitem
= xml
.get_widget('join_group_chat_menuitem')
884 muc_icon
= gtkgui_helpers
.load_icon('muc_active')
886 join_group_chat_menuitem
.set_image(muc_icon
)
889 if text
.startswith('xmpp:'):
891 id_
= childs
[2].connect('activate', self
.on_copy_link_activate
, text
)
892 self
.handlers
[id_
] = childs
[2]
893 id_
= childs
[3].connect('activate', self
.on_open_link_activate
, kind
,
895 self
.handlers
[id_
] = childs
[3]
896 id_
= childs
[5].connect('activate', self
.on_start_chat_activate
, text
)
897 self
.handlers
[id_
] = childs
[5]
898 id_
= childs
[6].connect('activate',
899 self
.on_join_group_chat_menuitem_activate
, text
)
900 self
.handlers
[id_
] = childs
[6]
904 c
= gajim
.contacts
.get_first_contact_from_jid(self
.account
, text
)
905 if c
and not gajim
.contacts
.is_pm_from_contact(self
.account
, c
):
906 if _('Not in Roster') in c
.groups
:
908 else: # he or she's not at all in the account contacts
912 id_
= childs
[7].connect('activate', self
.on_add_to_roster_activate
,
914 self
.handlers
[id_
] = childs
[7]
915 childs
[7].show() # show add to roster menuitem
917 childs
[7].hide() # hide add to roster menuitem
920 childs
[2].hide() # copy mail address
921 childs
[3].hide() # open mail composer
922 childs
[4].hide() # jid section separator
924 childs
[4].hide() # jid section separator
925 childs
[5].hide() # start chat
926 childs
[6].hide() # join group chat
927 childs
[7].hide() # add to roster
929 childs
[0].hide() # copy link location
930 childs
[1].hide() # open link in browser
932 menu
.popup(None, None, None, event
.button
, event
.time
)
934 def hyperlink_handler(self
, texttag
, widget
, event
, iter_
, kind
):
935 if event
.type == gtk
.gdk
.BUTTON_PRESS
:
936 begin_iter
= iter_
.copy()
937 # we get the begining of the tag
938 while not begin_iter
.begins_tag(texttag
):
939 begin_iter
.backward_char()
940 end_iter
= iter_
.copy()
941 # we get the end of the tag
942 while not end_iter
.ends_tag(texttag
):
943 end_iter
.forward_char()
944 word
= self
.tv
.get_buffer().get_text(begin_iter
, end_iter
).decode(
946 if event
.button
== 3: # right click
947 self
.make_link_menu(event
, kind
, word
)
949 # we launch the correct application
953 (jid
, action
) = word
.split('?')
955 self
.on_join_group_chat_menuitem_activate(None, jid
)
957 self
.on_start_chat_activate(None, jid
)
959 self
.on_start_chat_activate(None, word
)
961 helpers
.launch_browser_mailer(kind
, word
)
963 def html_hyperlink_handler(self
, texttag
, widget
, event
, iter_
, kind
, href
):
964 if event
.type == gtk
.gdk
.BUTTON_PRESS
:
965 if event
.button
== 3: # right click
966 self
.make_link_menu(event
, kind
, href
)
969 # we launch the correct application
970 helpers
.launch_browser_mailer(kind
, href
)
973 def detect_and_print_special_text(self
, otext
, other_tags
, graphics
=True):
974 '''detects special text (emots & links & formatting)
975 prints normal text before any special text it founts,
976 then print special text (that happens many times until
977 last special text is printed) and then returns the index
978 after *last* special text, so we can print it in
979 print_conversation_line()'''
981 buffer_
= self
.tv
.get_buffer()
983 insert_tags_func
= buffer_
.insert_with_tags_by_name
984 # detect_and_print_special_text() is also used by
985 # HtmlHandler.handle_specials() and there tags is gtk.TextTag objects,
987 if other_tags
and isinstance(other_tags
[0], gtk
.TextTag
):
988 insert_tags_func
= buffer_
.insert_with_tags
992 # Too many special elements (emoticons, LaTeX formulas, etc)
993 # may cause Gajim to freeze (see #5129).
994 # We impose an arbitrary limit of 100 specials per message.
997 # basic: links + mail + formatting is always checked (we like that)
998 if gajim
.config
.get('emoticons_theme') and graphics
:
999 # search for emoticons & urls
1000 iterator
= gajim
.interface
.emot_and_basic_re
.finditer(otext
)
1001 else: # search for just urls + mail + formatting
1002 iterator
= gajim
.interface
.basic_pattern_re
.finditer(otext
)
1003 for match
in iterator
:
1004 start
, end
= match
.span()
1005 special_text
= otext
[start
:end
]
1007 text_before_special_text
= otext
[index
:start
]
1008 end_iter
= buffer_
.get_end_iter()
1009 # we insert normal text
1010 insert_tags_func(end_iter
, text_before_special_text
, *other_tags
)
1011 index
= end
# update index
1014 self
.print_special_text(special_text
, other_tags
, graphics
=graphics
)
1016 if specials_limit
<= 0:
1019 # add the rest of text located in the index and after
1020 end_iter
= buffer_
.get_end_iter()
1021 insert_tags_func(end_iter
, otext
[index
:], *other_tags
)
1023 return buffer_
.get_end_iter()
1025 def print_special_text(self
, special_text
, other_tags
, graphics
=True):
1026 '''is called by detect_and_print_special_text and prints
1027 special text (emots, links, formatting)'''
1029 use_other_tags
= True
1030 text_is_valid_uri
= False
1031 show_ascii_formatting_chars
= \
1032 gajim
.config
.get('show_ascii_formatting_chars')
1033 buffer_
= self
.tv
.get_buffer()
1035 # Check if we accept this as an uri
1036 schemes
= gajim
.config
.get('uri_schemes').split()
1037 for scheme
in schemes
:
1038 if special_text
.startswith(scheme
+ ':'):
1039 text_is_valid_uri
= True
1041 possible_emot_ascii_caps
= special_text
.upper() # emoticons keys are CAPS
1042 if gajim
.config
.get('emoticons_theme') and \
1043 possible_emot_ascii_caps
in gajim
.interface
.emoticons
.keys() and graphics
:
1045 emot_ascii
= possible_emot_ascii_caps
1046 end_iter
= buffer_
.get_end_iter()
1047 anchor
= buffer_
.create_child_anchor(end_iter
)
1048 img
= TextViewImage(anchor
, special_text
)
1049 animations
= gajim
.interface
.emoticons_animations
1050 if not emot_ascii
in animations
:
1051 animations
[emot_ascii
] = gtk
.gdk
.PixbufAnimation(
1052 gajim
.interface
.emoticons
[emot_ascii
])
1053 img
.set_from_animation(animations
[emot_ascii
])
1055 self
.images
.append(img
)
1056 # add with possible animation
1057 self
.tv
.add_child_at_anchor(img
, anchor
)
1058 elif special_text
.startswith('www.') or \
1059 special_text
.startswith('ftp.') or \
1062 use_other_tags
= False
1063 elif special_text
.startswith('mailto:'):
1065 use_other_tags
= False
1066 elif special_text
.startswith('xmpp:'):
1068 use_other_tags
= False
1069 elif gajim
.interface
.sth_at_sth_dot_sth_re
.match(special_text
):
1070 # it's a JID or mail
1071 tags
.append('sth_at_sth')
1072 use_other_tags
= False
1073 elif special_text
.startswith('*'): # it's a bold text
1075 if special_text
[1] == '/' and special_text
[-2] == '/' and\
1076 len(special_text
) > 4: # it's also italic
1077 tags
.append('italic')
1078 if not show_ascii_formatting_chars
:
1079 special_text
= special_text
[2:-2] # remove */ /*
1080 elif special_text
[1] == '_' and special_text
[-2] == '_' and \
1081 len(special_text
) > 4: # it's also underlined
1082 tags
.append('underline')
1083 if not show_ascii_formatting_chars
:
1084 special_text
= special_text
[2:-2] # remove *_ _*
1086 if not show_ascii_formatting_chars
:
1087 special_text
= special_text
[1:-1] # remove * *
1088 elif special_text
.startswith('/'): # it's an italic text
1089 tags
.append('italic')
1090 if special_text
[1] == '*' and special_text
[-2] == '*' and \
1091 len(special_text
) > 4: # it's also bold
1093 if not show_ascii_formatting_chars
:
1094 special_text
= special_text
[2:-2] # remove /* */
1095 elif special_text
[1] == '_' and special_text
[-2] == '_' and \
1096 len(special_text
) > 4: # it's also underlined
1097 tags
.append('underline')
1098 if not show_ascii_formatting_chars
:
1099 special_text
= special_text
[2:-2] # remove /_ _/
1101 if not show_ascii_formatting_chars
:
1102 special_text
= special_text
[1:-1] # remove / /
1103 elif special_text
.startswith('_'): # it's an underlined text
1104 tags
.append('underline')
1105 if special_text
[1] == '*' and special_text
[-2] == '*' and \
1106 len(special_text
) > 4: # it's also bold
1108 if not show_ascii_formatting_chars
:
1109 special_text
= special_text
[2:-2] # remove _* *_
1110 elif special_text
[1] == '/' and special_text
[-2] == '/' and \
1111 len(special_text
) > 4: # it's also italic
1112 tags
.append('italic')
1113 if not show_ascii_formatting_chars
:
1114 special_text
= special_text
[2:-2] # remove _/ /_
1116 if not show_ascii_formatting_chars
:
1117 special_text
= special_text
[1:-1] # remove _ _
1118 elif gajim
.HAVE_LATEX
and special_text
.startswith('$$') and \
1119 special_text
.endswith('$$') and graphics
:
1121 imagepath
= latex
.latex_to_image(special_text
[2:-2])
1122 except LatexError
, e
:
1123 # print the error after the line has been written
1124 gobject
.idle_add(self
.print_conversation_line
, str(e
), '', 'info',
1127 end_iter
= buffer_
.get_end_iter()
1128 if imagepath
is not None:
1129 anchor
= buffer_
.create_child_anchor(end_iter
)
1130 img
= TextViewImage(anchor
, special_text
)
1131 img
.set_from_file(imagepath
)
1134 self
.tv
.add_child_at_anchor(img
, anchor
)
1137 os
.remove(imagepath
)
1141 buffer_
.insert(end_iter
, special_text
)
1142 use_other_tags
= False
1144 # It's nothing special
1146 end_iter
= buffer_
.get_end_iter()
1147 insert_tags_func
= buffer_
.insert_with_tags_by_name
1148 if other_tags
and isinstance(other_tags
[0], gtk
.TextTag
):
1149 insert_tags_func
= buffer_
.insert_with_tags
1151 insert_tags_func(end_iter
, special_text
, *other_tags
)
1154 end_iter
= buffer_
.get_end_iter()
1157 all_tags
+= other_tags
1158 # convert all names to TextTag
1159 ttt
= buffer_
.get_tag_table()
1160 all_tags
= [(ttt
.lookup(t
) if isinstance(t
, str) else t
) for t
in all_tags
]
1161 buffer_
.insert_with_tags(end_iter
, special_text
, *all_tags
)
1163 def print_empty_line(self
):
1164 buffer_
= self
.tv
.get_buffer()
1165 end_iter
= buffer_
.get_end_iter()
1166 buffer_
.insert_with_tags_by_name(end_iter
, '\n', 'eol')
1168 def print_conversation_line(self
, text
, jid
, kind
, name
, tim
,
1169 other_tags_for_name
=[], other_tags_for_time
=[], other_tags_for_text
=[],
1170 subject
=None, old_kind
=None, xhtml
=None, simple
=False, graphics
=True):
1171 '''prints 'chat' type messages'''
1172 buffer_
= self
.tv
.get_buffer()
1173 buffer_
.begin_user_action()
1174 if self
.marks_queue
.full():
1175 # remove oldest line
1176 m1
= self
.marks_queue
.get()
1177 m2
= self
.marks_queue
.get()
1178 i1
= buffer_
.get_iter_at_mark(m1
)
1179 i2
= buffer_
.get_iter_at_mark(m2
)
1180 buffer_
.delete(i1
, i2
)
1181 buffer_
.delete_mark(m1
)
1182 end_iter
= buffer_
.get_end_iter()
1183 end_offset
= end_iter
.get_offset()
1184 at_the_end
= self
.at_the_end()
1185 move_selection
= False
1186 if buffer_
.get_has_selection() and buffer_
.get_selection_bounds()[1].\
1187 get_offset() == end_offset
:
1188 move_selection
= True
1190 # Create one mark and add it to queue once if it's the first line
1191 # else twice (one for end bound, one for start bound)
1193 if buffer_
.get_char_count() > 0:
1195 buffer_
.insert_with_tags_by_name(end_iter
, '\n', 'eol')
1197 sel_start
, sel_end
= buffer_
.get_selection_bounds()
1198 sel_end
.backward_char()
1199 buffer_
.select_range(sel_start
, sel_end
)
1200 mark
= buffer_
.create_mark(None, end_iter
, left_gravity
=True)
1201 self
.marks_queue
.put(mark
)
1203 mark
= buffer_
.create_mark(None, end_iter
, left_gravity
=True)
1204 self
.marks_queue
.put(mark
)
1205 if kind
== 'incoming_queue':
1207 if old_kind
== 'incoming_queue':
1208 old_kind
= 'incoming'
1209 # print the time stamp
1211 # We don't have tim for outgoing messages...
1212 tim
= time
.localtime()
1213 current_print_time
= gajim
.config
.get('print_time')
1214 if current_print_time
== 'always' and kind
!= 'info' and not simple
:
1215 timestamp_str
= self
.get_time_to_show(tim
)
1216 timestamp
= time
.strftime(timestamp_str
, tim
)
1217 buffer_
.insert_with_tags_by_name(end_iter
, timestamp
,
1218 *other_tags_for_time
)
1219 elif current_print_time
== 'sometimes' and kind
!= 'info' and not simple
:
1220 every_foo_seconds
= 60 * gajim
.config
.get(
1221 'print_ichat_every_foo_minutes')
1222 seconds_passed
= time
.mktime(tim
) - self
.last_time_printout
1223 if seconds_passed
> every_foo_seconds
:
1224 self
.last_time_printout
= time
.mktime(tim
)
1225 end_iter
= buffer_
.get_end_iter()
1226 if gajim
.config
.get('print_time_fuzzy') > 0:
1227 ft
= self
.fc
.fuzzy_time(gajim
.config
.get('print_time_fuzzy'), tim
)
1228 tim_format
= ft
.decode(locale
.getpreferredencoding())
1230 tim_format
= self
.get_time_to_show(tim
)
1231 buffer_
.insert_with_tags_by_name(end_iter
, tim_format
+ '\n',
1233 # kind = info, we print things as if it was a status: same color, ...
1234 if kind
in ('error', 'info'):
1236 other_text_tag
= self
.detect_other_text_tag(text
, kind
)
1237 text_tags
= other_tags_for_text
[:] # create a new list
1239 # note that color of /me may be overwritten in gc_control
1240 text_tags
.append(other_text_tag
)
1241 else: # not status nor /me
1242 if gajim
.config
.get('chat_merge_consecutive_nickname'):
1243 if kind
!= old_kind
:
1244 self
.print_name(name
, kind
, other_tags_for_name
)
1246 self
.print_real_text(gajim
.config
.get(
1247 'chat_merge_consecutive_nickname_indent'))
1249 self
.print_name(name
, kind
, other_tags_for_name
)
1250 if kind
== 'incoming':
1251 text_tags
.append('incomingtxt')
1252 elif kind
== 'outgoing':
1253 text_tags
.append('outgoingtxt')
1254 self
.print_subject(subject
)
1255 self
.print_real_text(text
, text_tags
, name
, xhtml
, graphics
=graphics
)
1257 # scroll to the end of the textview
1258 if at_the_end
or kind
== 'outgoing':
1259 # we are at the end or we are sending something
1260 # scroll to the end (via idle in case the scrollbar has appeared)
1261 if gajim
.config
.get('use_smooth_scrolling'):
1262 gobject
.idle_add(self
.smooth_scroll_to_end
)
1264 gobject
.idle_add(self
.scroll_to_end
)
1266 buffer_
.end_user_action()
1268 def get_time_to_show(self
, tim
):
1269 '''Get the time, with the day before if needed and return it.
1270 It DOESN'T format a fuzzy time'''
1272 # get difference in days since epoch (86400 = 24*3600)
1273 # number of days since epoch for current time (in GMT) -
1274 # number of days since epoch for message (in GMT)
1275 diff_day
= int(timegm(time
.localtime())) / 86400 -\
1276 int(timegm(tim
)) / 86400
1280 day_str
= _('Yesterday')
1283 # %i is day in year (1-365), %d (1-31) we want %i
1284 day_str
= _('%i days ago') % diff_day
1286 format
+= day_str
+ ' '
1287 timestamp_str
= gajim
.config
.get('time_stamp')
1288 timestamp_str
= helpers
.from_one_line(timestamp_str
)
1289 format
+= timestamp_str
1290 tim_format
= time
.strftime(format
, tim
)
1291 if locale
.getpreferredencoding() != 'KOI8-R':
1292 # if tim_format comes as unicode because of day_str.
1293 # we convert it to the encoding that we want (and that is utf-8)
1294 tim_format
= helpers
.ensure_utf8_string(tim_format
)
1297 def detect_other_text_tag(self
, text
, kind
):
1298 if kind
== 'status':
1300 elif text
.startswith('/me ') or text
.startswith('/me\n'):
1303 def print_name(self
, name
, kind
, other_tags_for_name
):
1305 buffer_
= self
.tv
.get_buffer()
1306 end_iter
= buffer_
.get_end_iter()
1307 name_tags
= other_tags_for_name
[:] # create a new list
1308 name_tags
.append(kind
)
1309 before_str
= gajim
.config
.get('before_nickname')
1310 before_str
= helpers
.from_one_line(before_str
)
1311 after_str
= gajim
.config
.get('after_nickname')
1312 after_str
= helpers
.from_one_line(after_str
)
1313 format
= before_str
+ name
+ after_str
+ ' '
1314 buffer_
.insert_with_tags_by_name(end_iter
, format
, *name_tags
)
1316 def print_subject(self
, subject
):
1317 if subject
: # if we have subject, show it too!
1318 subject
= _('Subject: %s\n') % subject
1319 buffer_
= self
.tv
.get_buffer()
1320 end_iter
= buffer_
.get_end_iter()
1321 buffer_
.insert(end_iter
, subject
)
1322 self
.print_empty_line()
1324 def print_real_text(self
, text
, text_tags
=[], name
=None, xhtml
=None,
1326 '''this adds normal and special text. call this to add text'''
1329 if name
and (text
.startswith('/me ') or text
.startswith('/me\n')):
1330 xhtml
= xhtml
.replace('/me', '<i>* %s</i>' % (name
,), 1)
1331 self
.tv
.display_html(xhtml
.encode('utf-8'), self
)
1333 except Exception, e
:
1334 gajim
.log
.debug('Error processing xhtml' + str(e
))
1335 gajim
.log
.debug('with |' + xhtml
+ '|')
1337 # /me is replaced by name if name is given
1338 if name
and (text
.startswith('/me ') or text
.startswith('/me\n')):
1339 text
= '* ' + name
+ text
[3:]
1340 text_tags
.append('italic')
1341 # detect urls formatting and if the user has it on emoticons
1342 self
.detect_and_print_special_text(text
, text_tags
, graphics
=graphics
)