1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 import os
31 import time
32 import gtk
33 import pango
34 import gobject
35 import gtkgui_helpers
36 import gui_menu_builder
37 import message_control
38 import dialogs
39 import history_window
40 import notify
41 import re
42
43 from common import gajim
44 from common import helpers
45 from common import exceptions
46 from message_control import MessageControl
47 from conversation_textview import ConversationTextview
48 from message_textview import MessageTextView
49 from common.contacts import GC_Contact
50 from common.logger import constants
51 from common.pep import MOODS, ACTIVITIES
52 from common.xmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
53 from common.xmpp.protocol import NS_RECEIPTS, NS_ESESSION
54 from common.xmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_JINGLE_ICE_UDP
55
56 from command_system.implementation.middleware import ChatCommandProcessor
57 from command_system.implementation.middleware import CommandTools
58 from command_system.implementation.hosts import ChatCommands
59
60
61
62 import command_system.implementation.standard
63
64 try:
65 import gtkspell
66 HAS_GTK_SPELL = True
67 except ImportError:
68 HAS_GTK_SPELL = False
69
70
71
72
73
74
75 langs = {_('English'): 'en', _('Belarusian'): 'be', _('Bulgarian'): 'bg', _('Breton'): 'br', _('Czech'): 'cs', _('German'): 'de', _('Greek'): 'el', _('British'): 'en_GB', _('Esperanto'): 'eo', _('Spanish'): 'es', _('Basque'): 'eu', _('French'): 'fr', _('Croatian'): 'hr', _('Italian'): 'it', _('Norwegian (b)'): 'nb', _('Dutch'): 'nl', _('Norwegian'): 'no', _('Polish'): 'pl', _('Portuguese'): 'pt', _('Brazilian Portuguese'): 'pt_BR', _('Russian'): 'ru', _('Serbian'): 'sr', _('Slovak'): 'sk', _('Swedish'): 'sv', _('Chinese (Ch)'): 'zh_CN'}
76
77 if gajim.config.get('use_speller') and HAS_GTK_SPELL:
78
79
80 tv = gtk.TextView()
81 spell = gtkspell.Spell(tv)
82 for lang in dict(langs):
83 try:
84 spell.set_language(langs[lang])
85 except OSError:
86 del langs[lang]
87 if spell:
88 spell.detach()
89 del tv
90
91
93 """
94 A base class containing a banner, ConversationTextview, MessageTextView
95 """
96
97 keymap = gtk.gdk.keymap_get_default()
98 try:
99 keycode_c = keymap.get_entries_for_keyval(gtk.keysyms.c)[0][0]
100 except TypeError:
101 keycode_c = 54
102 try:
103 keycode_ins = keymap.get_entries_for_keyval(gtk.keysyms.Insert)[0][0]
104 except TypeError:
105 keycode_ins = 118
107 url_color = gajim.config.get('urlmsgcolor')
108 url = match.group()
109 if not '://' in url:
110 url = 'http://' + url
111 return '<a href="%s"><span color="%s">%s</span></a>' % (url,
112 url_color, match.group())
113
115 """
116 Get pango font attributes for banner from theme settings
117 """
118 theme = gajim.config.get('roster_theme')
119 bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
120 bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs')
121
122 if bannerfont:
123 font = pango.FontDescription(bannerfont)
124 else:
125 font = pango.FontDescription('Normal')
126 if bannerfontattrs:
127
128 if 'B' in bannerfontattrs:
129 font.set_weight(pango.WEIGHT_HEAVY)
130 if 'I' in bannerfontattrs:
131 font.set_style(pango.STYLE_ITALIC)
132
133 font_attrs = 'font_desc="%s"' % font.to_string()
134
135
136 if font.get_size() == 0:
137 font_attrs = '%s size="x-large"' % font_attrs
138 font.set_weight(pango.WEIGHT_NORMAL)
139 font_attrs_small = 'font_desc="%s" size="small"' % font.to_string()
140 return (font_attrs, font_attrs_small)
141
149
160
162 """
163 Derived types SHOULD implement this
164 """
165 pass
166
168 """
169 Derived types SHOULD implement this
170 """
171 self.draw_banner()
172
179
181 """
182 Derived types MAY implement this
183 """
184 pass
185
186 - def handle_message_textview_mykey_press(self, widget, event_keyval,
187 event_keymod):
188 """
189 Derives types SHOULD implement this, rather than connection to the even
190 itself
191 """
192 event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
193 event.keyval = event_keyval
194 event.state = event_keymod
195 event.time = 0
196
197 _buffer = widget.get_buffer()
198 start, end = _buffer.get_bounds()
199
200 if event.keyval -- gtk.keysyms.Tab:
201 position = _buffer.get_insert()
202 end = _buffer.get_iter_at_mark(position)
203
204 text = _buffer.get_text(start, end, False)
205 text = text.decode('utf8')
206
207 splitted = text.split()
208
209 if (text.startswith(self.COMMAND_PREFIX) and not
210 text.startswith(self.COMMAND_PREFIX * 2) and len(splitted) == 1):
211
212 text = splitted[0]
213 bare = text.lstrip(self.COMMAND_PREFIX)
214
215 if len(text) == 1:
216 self.command_hits = []
217 for command in self.list_commands():
218 for name in command.names:
219 self.command_hits.append(name)
220 else:
221 if (self.last_key_tabs and self.command_hits and
222 self.command_hits[0].startswith(bare)):
223 self.command_hits.append(self.command_hits.pop(0))
224 else:
225 self.command_hits = []
226 for command in self.list_commands():
227 for name in command.names:
228 if name.startswith(bare):
229 self.command_hits.append(name)
230
231 if self.command_hits:
232 _buffer.delete(start, end)
233 _buffer.insert_at_cursor(self.COMMAND_PREFIX + self.command_hits[0] + ' ')
234 self.last_key_tabs = True
235
236 return True
237
238 self.last_key_tabs = False
239
242
244 self.seclabel_combo = combo
245 self.seclabel_combo.hide()
246 self.seclabel_combo.set_no_show_all(True)
247 lb = gtk.ListStore(str)
248 self.seclabel_combo.set_model(lb)
249 cell = gtk.CellRendererText()
250 cell.set_property('xpad', 5)
251 self.seclabel_combo.pack_start(cell, True)
252
253 self.seclabel_combo.add_attribute(cell, 'text', 0)
254 if gajim.connections[self.account].seclabel_supported:
255 gajim.connections[self.account].seclabel_catalogue(self.contact.jid, self.on_seclabels_ready)
256
258 lb = self.seclabel_combo.get_model()
259 lb.clear()
260 for label in gajim.connections[self.account].seclabel_catalogues[self.contact.jid][2]:
261 lb.append([label])
262 self.seclabel_combo.set_active(0)
263 self.seclabel_combo.set_no_show_all(False)
264 self.seclabel_combo.show_all()
265
266 - def __init__(self, type_id, parent_win, widget_name, contact, acct,
267 resource=None):
268
269
270 self.space_pressed = True
271
272 if resource is None:
273
274
275 c = gajim.contacts.get_contact_with_highest_priority(
276 acct, contact.jid)
277 if c and not isinstance(c, GC_Contact):
278 contact = c
279
280 MessageControl.__init__(self, type_id, parent_win, widget_name,
281 contact, acct, resource=resource)
282
283 widget = self.xml.get_object('history_button')
284 id_ = widget.connect('clicked', self._on_history_menuitem_activate)
285 self.handlers[id_] = widget
286
287
288 widget = self.xml.get_object('emoticons_button')
289 id_ = widget.connect('clicked', self.on_emoticons_button_clicked)
290 self.handlers[id_] = widget
291
292
293 widget = self.xml.get_object('banner_eventbox')
294 id_ = widget.connect('button-press-event',
295 self._on_banner_eventbox_button_press_event)
296 self.handlers[id_] = widget
297
298 self.urlfinder = re.compile(
299 r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]")
300
301 self.banner_status_label = self.xml.get_object('banner_label')
302 id_ = self.banner_status_label.connect('populate_popup',
303 self.on_banner_label_populate_popup)
304 self.handlers[id_] = self.banner_status_label
305
306
307 self.TARGET_TYPE_URI_LIST = 80
308 self.dnd_list = [ ( 'text/uri-list', 0, self.TARGET_TYPE_URI_LIST ),
309 ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_APP, 0)]
310 id_ = self.widget.connect('drag_data_received',
311 self._on_drag_data_received)
312 self.handlers[id_] = self.widget
313 self.widget.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
314 gtk.DEST_DEFAULT_HIGHLIGHT |
315 gtk.DEST_DEFAULT_DROP,
316 self.dnd_list, gtk.gdk.ACTION_COPY)
317
318
319 self.conv_textview = ConversationTextview(self.account)
320 id_ = self.conv_textview.connect('quote', self.on_quote)
321 self.handlers[id_] = self.conv_textview.tv
322 id_ = self.conv_textview.tv.connect('key_press_event',
323 self._conv_textview_key_press_event)
324 self.handlers[id_] = self.conv_textview.tv
325
326 self.drag_entered = False
327 id_ = self.conv_textview.tv.connect('drag_data_received',
328 self._on_drag_data_received)
329 self.handlers[id_] = self.conv_textview.tv
330 id_ = self.conv_textview.tv.connect('drag_motion', self._on_drag_motion)
331 self.handlers[id_] = self.conv_textview.tv
332 id_ = self.conv_textview.tv.connect('drag_leave', self._on_drag_leave)
333 self.handlers[id_] = self.conv_textview.tv
334 self.conv_textview.tv.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
335 gtk.DEST_DEFAULT_HIGHLIGHT |
336 gtk.DEST_DEFAULT_DROP,
337 self.dnd_list, gtk.gdk.ACTION_COPY)
338
339 self.conv_scrolledwindow = self.xml.get_object(
340 'conversation_scrolledwindow')
341 self.conv_scrolledwindow.add(self.conv_textview.tv)
342 widget = self.conv_scrolledwindow.get_vadjustment()
343 id_ = widget.connect('value-changed',
344 self.on_conversation_vadjustment_value_changed)
345 self.handlers[id_] = widget
346 id_ = widget.connect('changed',
347 self.on_conversation_vadjustment_changed)
348 self.handlers[id_] = widget
349 self.scroll_to_end_id = None
350 self.was_at_the_end = True
351
352
353 self.msg_scrolledwindow = self.xml.get_object('message_scrolledwindow')
354 self.msg_textview = MessageTextView()
355 id_ = self.msg_textview.connect('mykeypress',
356 self._on_message_textview_mykeypress_event)
357 self.handlers[id_] = self.msg_textview
358 self.msg_scrolledwindow.add(self.msg_textview)
359 id_ = self.msg_textview.connect('key_press_event',
360 self._on_message_textview_key_press_event)
361 self.handlers[id_] = self.msg_textview
362 id_ = self.msg_textview.connect('size-request', self.size_request)
363 self.handlers[id_] = self.msg_textview
364 id_ = self.msg_textview.connect('populate_popup',
365 self.on_msg_textview_populate_popup)
366 self.handlers[id_] = self.msg_textview
367
368 id_ = self.msg_textview.connect('drag_data_received',
369 self._on_drag_data_received)
370 self.handlers[id_] = self.msg_textview
371 self.msg_textview.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
372 gtk.DEST_DEFAULT_HIGHLIGHT,
373 self.dnd_list, gtk.gdk.ACTION_COPY)
374
375 self.update_font()
376
377
378 widget = self.xml.get_object('send_button')
379 id_ = widget.connect('clicked', self._on_send_button_clicked)
380 self.handlers[id_] = widget
381
382 widget = self.xml.get_object('formattings_button')
383 id_ = widget.connect('clicked', self.on_formattings_button_clicked)
384 self.handlers[id_] = widget
385
386
387 self.sent_history = []
388 self.sent_history_pos = 0
389 self.orig_msg = None
390
391
392
393
394 img = self.xml.get_object('emoticons_button_image')
395 img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
396 'smile.png'))
397 self.toggle_emoticons()
398
399
400 if gajim.config.get('use_speller') and HAS_GTK_SPELL:
401 self.set_speller()
402 self.conv_textview.tv.show()
403 self._paint_banner()
404
405
406 self.user_nick = None
407
408 self.smooth = True
409 self.msg_textview.grab_focus()
410
411 self.command_hits = []
412 self.last_key_tabs = False
413
414
415
416 gajim.plugin_manager.gui_extension_point('chat_control_base', self)
417
418
419
420 CommandTools.__init__(self)
421
440
442 """
443 Override the default context menu and add our own menutiems
444 """
445 item = gtk.SeparatorMenuItem()
446 menu.prepend(item)
447
448 menu2 = self.prepare_context_menu()
449 i = 0
450 for item in menu2:
451 menu2.remove(item)
452 menu.prepend(item)
453 menu.reorder_child(item, i)
454 i += 1
455 menu.show_all()
456
462
464 """
465 Override the default context menu and we prepend an option to switch
466 languages
467 """
468 def _on_select_dictionary(widget, lang):
469 per_type = 'contacts'
470 if self.type_id == message_control.TYPE_GC:
471 per_type = 'rooms'
472 if not gajim.config.get_per(per_type, self.contact.jid):
473 gajim.config.add_per(per_type, self.contact.jid)
474 gajim.config.set_per(per_type, self.contact.jid, 'speller_language',
475 lang)
476 spell = gtkspell.get_from_text_view(self.msg_textview)
477 self.msg_textview.lang = lang
478 spell.set_language(lang)
479 widget.set_active(True)
480
481 item = gtk.ImageMenuItem(gtk.STOCK_UNDO)
482 menu.prepend(item)
483 id_ = item.connect('activate', self.msg_textview.undo)
484 self.handlers[id_] = item
485
486 item = gtk.SeparatorMenuItem()
487 menu.prepend(item)
488
489 item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
490 menu.prepend(item)
491 id_ = item.connect('activate', self.msg_textview.clear)
492 self.handlers[id_] = item
493
494 if gajim.config.get('use_speller') and HAS_GTK_SPELL:
495 item = gtk.MenuItem(_('Spelling language'))
496 menu.prepend(item)
497 submenu = gtk.Menu()
498 item.set_submenu(submenu)
499 for lang in sorted(langs):
500 item = gtk.CheckMenuItem(lang)
501 if langs[lang] == self.msg_textview.lang:
502 item.set_active(True)
503 submenu.append(item)
504 id_ = item.connect('activate', _on_select_dictionary, langs[lang])
505 self.handlers[id_] = item
506
507 menu.show_all()
508
510 text = '>' + text.replace('\n', '\n>') + '\n'
511 message_buffer = self.msg_textview.get_buffer()
512 message_buffer.insert_at_cursor(text)
513
514
521
538
540 """
541 Repaint banner with theme color
542 """
543 theme = gajim.config.get('roster_theme')
544 bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
545 textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
546
547
548 banner_eventbox = self.xml.get_object('banner_eventbox')
549 banner_name_label = self.xml.get_object('banner_name_label')
550 self.disconnect_style_event(banner_name_label)
551 self.disconnect_style_event(self.banner_status_label)
552 if bgcolor:
553 banner_eventbox.modify_bg(gtk.STATE_NORMAL,
554 gtk.gdk.color_parse(bgcolor))
555 default_bg = False
556 else:
557 default_bg = True
558 if textcolor:
559 banner_name_label.modify_fg(gtk.STATE_NORMAL,
560 gtk.gdk.color_parse(textcolor))
561 self.banner_status_label.modify_fg(gtk.STATE_NORMAL,
562 gtk.gdk.color_parse(textcolor))
563 default_fg = False
564 else:
565 default_fg = True
566 if default_bg or default_fg:
567 self._on_style_set_event(banner_name_label, None, default_fg,
568 default_bg)
569 if self.banner_status_label.flags() & gtk.REALIZED:
570
571 self._on_style_set_event(self.banner_status_label, None, default_fg,
572 default_bg)
573
575
576 for id_ in self.handlers.keys():
577 if self.handlers[id_] == widget:
578 widget.disconnect(id_)
579 del self.handlers[id_]
580 break
581
587
589 """
590 Set style of widget from style class *.Frame.Eventbox
591 opts[0] == True -> set fg color
592 opts[1] == True -> set bg color
593 """
594 banner_eventbox = self.xml.get_object('banner_eventbox')
595 self.disconnect_style_event(widget)
596 if opts[1]:
597 bg_color = widget.style.bg[gtk.STATE_SELECTED]
598 banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color)
599 if opts[0]:
600 fg_color = widget.style.fg[gtk.STATE_SELECTED]
601 widget.modify_fg(gtk.STATE_NORMAL, fg_color)
602 self.connect_style_event(widget, opts[0], opts[1])
603
604 - def _conv_textview_key_press_event(self, widget, event):
605
606 keymap = gtk.gdk.keymap_get_default()
607 keycode = keymap.get_entries_for_keyval(event.keyval)[0][0]
608 if (event.state & gtk.gdk.CONTROL_MASK and keycode in (self.keycode_c,
609 self.keycode_ins)) or (event.state & gtk.gdk.SHIFT_MASK and \
610 event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.Page_Up)):
611 return False
612 self.parent_win.notebook.emit('key_press_event', event)
613 return True
614
616 if not gajim.config.get('emoticons_theme'):
617 return
618 def set_emoticons_menu_position(w, msg_tv = self.msg_textview):
619 window = msg_tv.get_window(gtk.TEXT_WINDOW_WIDGET)
620
621 origin = window.get_origin()
622 size = window.get_size()
623 buf = msg_tv.get_buffer()
624
625 cursor = msg_tv.get_iter_location(buf.get_iter_at_mark(
626 buf.get_insert()))
627 cursor = msg_tv.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT,
628 cursor.x, cursor.y)
629 x = origin[0] + cursor[0]
630 y = origin[1] + size[1]
631 menu_height = gajim.interface.emoticons_menu.size_request()[1]
632
633
634
635
636
637
638 if y + menu_height > gtk.gdk.screen_height():
639
640 y -= menu_height + (msg_tv.allocation.height / buf.get_line_count())
641
642
643 return (x, y, True)
644 gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
645 gajim.interface.emoticons_menu.popup(None, None,
646 set_emoticons_menu_position, 1, 0)
647
649 if event.keyval == gtk.keysyms.space:
650 self.space_pressed = True
651
652 elif (self.space_pressed or self.msg_textview.undo_pressed) and \
653 event.keyval not in (gtk.keysyms.Control_L, gtk.keysyms.Control_R) and \
654 not (event.keyval == gtk.keysyms.z and event.state & gtk.gdk.CONTROL_MASK):
655
656
657
658 _buffer = widget.get_buffer()
659 start_iter, end_iter = _buffer.get_bounds()
660 self.msg_textview.save_undo(_buffer.get_text(start_iter, end_iter))
661 self.space_pressed = False
662
663
664 if self.widget_name == 'groupchat_control':
665 if event.keyval not in (gtk.keysyms.ISO_Left_Tab, gtk.keysyms.Tab):
666 self.last_key_tabs = False
667 if event.state & gtk.gdk.SHIFT_MASK:
668
669 if event.state & gtk.gdk.CONTROL_MASK and \
670 event.keyval == gtk.keysyms.ISO_Left_Tab:
671 self.parent_win.move_to_next_unread_tab(False)
672 return True
673
674 elif event.keyval == gtk.keysyms.Page_Down or \
675 event.keyval == gtk.keysyms.Page_Up:
676 self.conv_textview.tv.emit('key_press_event', event)
677 return True
678 elif event.state & gtk.gdk.CONTROL_MASK:
679 if event.keyval == gtk.keysyms.Tab:
680 self.parent_win.move_to_next_unread_tab(True)
681 return True
682 return False
683
684 - def _on_message_textview_mykeypress_event(self, widget, event_keyval,
685 event_keymod):
686 """
687 When a key is pressed: if enter is pressed without the shift key, message
688 (if not empty) is sent and printed in the conversation
689 """
690
691
692 message_textview = widget
693 message_buffer = message_textview.get_buffer()
694 start_iter, end_iter = message_buffer.get_bounds()
695 message = message_buffer.get_text(start_iter, end_iter, False).decode(
696 'utf-8')
697 xhtml = self.msg_textview.get_xhtml()
698
699
700 event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
701 event.keyval = event_keyval
702 event.state = event_keymod
703 event.time = 0
704
705 if event.keyval == gtk.keysyms.Up:
706 if event.state & gtk.gdk.CONTROL_MASK:
707 self.sent_messages_scroll('up', widget.get_buffer())
708 elif event.keyval == gtk.keysyms.Down:
709 if event.state & gtk.gdk.CONTROL_MASK:
710 self.sent_messages_scroll('down', widget.get_buffer())
711 elif event.keyval == gtk.keysyms.Return or \
712 event.keyval == gtk.keysyms.KP_Enter:
713
714
715
716 if gajim.config.get('send_on_ctrl_enter'):
717
718
719
720 if event.state == 0:
721 end_iter = message_buffer.get_end_iter()
722 message_buffer.insert_at_cursor('\n')
723 send_message = False
724 elif event.state & gtk.gdk.CONTROL_MASK:
725 send_message = True
726 else:
727 if event.state & gtk.gdk.CONTROL_MASK:
728 end_iter = message_buffer.get_end_iter()
729 message_buffer.insert_at_cursor('\n')
730 send_message = False
731 else:
732 send_message = True
733
734 if gajim.connections[self.account].connected < 2 and send_message:
735
736 dialogs.ErrorDialog(_('A connection is not available'),
737 _('Your message can not be sent until you are connected.'))
738 send_message = False
739
740 if send_message:
741 self.send_message(message, xhtml=xhtml)
742 elif event.keyval == gtk.keysyms.z:
743 if event.state & gtk.gdk.CONTROL_MASK:
744 self.msg_textview.undo()
745 else:
746
747 self.handle_message_textview_mykey_press(widget, event_keyval,
748 event_keymod)
749
752 """
753 Derived types SHOULD implement this
754 """
755 pass
756
758
759 self.drag_entered = False
760 self.conv_textview.tv.set_editable(False)
761
763
764 if not self.drag_entered:
765
766 self.drag_entered_conv = True
767 self.conv_textview.tv.set_editable(True)
768
770 label = None
771 if self.seclabel_combo is not None:
772 idx = self.seclabel_combo.get_active()
773 if idx != -1:
774 cat = gajim.connections[self.account].seclabel_catalogues[self.contact.jid]
775 lname = cat[2][idx]
776 label = cat[1][lname]
777 return label
778
779 - def send_message(self, message, keyID='', type_='chat', chatstate=None,
780 msg_id=None, composing_xep=None, resource=None, xhtml=None,
781 callback=None, callback_args=[], process_commands=True):
782 """
783 Send the given message to the active tab. Doesn't return None if error
784 """
785 if not message or message == '\n':
786 return None
787
788 if process_commands and self.process_as_command(message):
789 return
790
791 label = self.get_seclabel()
792 MessageControl.send_message(self, message, keyID, type_=type_,
793 chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep,
794 resource=resource, user_nick=self.user_nick, xhtml=xhtml,
795 label=label,
796 callback=callback, callback_args=callback_args)
797
798
799 self.save_sent_message(message)
800
801
802 self.user_nick = None
803
804
805 message_buffer = self.msg_textview.get_buffer()
806 message_buffer.set_text('')
807
809
810 size = len(self.sent_history)
811
812 max_size = gajim.config.get('key_up_lines')
813 if size >= max_size:
814 for i in xrange(0, size - 1):
815 self.sent_history[i] = self.sent_history[i + 1]
816 self.sent_history[max_size - 1] = message
817
818
819 self.sent_history_pos = max_size
820 else:
821 self.sent_history.append(message)
822 self.sent_history_pos = size + 1
823 self.orig_msg = None
824
825 - def print_conversation_line(self, text, kind, name, tim,
826 other_tags_for_name=[], other_tags_for_time=[],
827 other_tags_for_text=[], count_as_new=True, subject=None,
828 old_kind=None, xhtml=None, simple=False, xep0184_id=None,
829 graphics=True, displaymarking=None):
830 """
831 Print 'chat' type messages
832 """
833 jid = self.contact.jid
834 full_jid = self.get_full_jid()
835 textview = self.conv_textview
836 end = False
837 if self.was_at_the_end or kind == 'outgoing':
838 end = True
839 textview.print_conversation_line(text, jid, kind, name, tim,
840 other_tags_for_name, other_tags_for_time, other_tags_for_text,
841 subject, old_kind, xhtml, simple=simple, graphics=graphics,
842 displaymarking=displaymarking)
843
844 if xep0184_id is not None:
845 textview.show_xep0184_warning(xep0184_id)
846
847 if not count_as_new:
848 return
849 if kind == 'incoming':
850 if not self.type_id == message_control.TYPE_GC or \
851 gajim.config.get('notify_on_all_muc_messages') or \
852 'marked' in other_tags_for_text:
853
854
855
856 gajim.last_message_time[self.account][full_jid] = time.time()
857
858 if kind in ('incoming', 'incoming_queue', 'error'):
859 gc_message = False
860 if self.type_id == message_control.TYPE_GC:
861 gc_message = True
862
863 if ((self.parent_win and (not self.parent_win.get_active_control() or \
864 self != self.parent_win.get_active_control() or \
865 not self.parent_win.is_active() or not end)) or \
866 (gc_message and \
867 jid in gajim.interface.minimized_controls[self.account])) and \
868 kind in ('incoming', 'incoming_queue', 'error'):
869
870
871 if gc_message:
872 if 'marked' in other_tags_for_text:
873 type_ = 'printed_marked_gc_msg'
874 else:
875 type_ = 'printed_gc_msg'
876 event = 'gc_message_received'
877 else:
878 type_ = 'printed_' + self.type_id
879 event = 'message_received'
880 show_in_roster = notify.get_show_in_roster(event,
881 self.account, self.contact, self.session)
882 show_in_systray = notify.get_show_in_systray(event,
883 self.account, self.contact, type_)
884
885 event = gajim.events.create_event(type_, (self,),
886 show_in_roster = show_in_roster,
887 show_in_systray = show_in_systray)
888 gajim.events.add_event(self.account, full_jid, event)
889
890 if show_in_roster:
891 gajim.interface.roster.draw_contact(self.contact.jid,
892 self.account)
893
894 if not self.parent_win:
895 return
896
897 if (not self.parent_win.get_active_control() or \
898 self != self.parent_win.get_active_control() or \
899 not self.parent_win.is_active() or not end) and \
900 kind in ('incoming', 'incoming_queue', 'error'):
901 self.parent_win.redraw_tab(self)
902 if not self.parent_win.is_active():
903 self.parent_win.show_title(True, self)
904 else:
905 self.parent_win.show_title(False, self)
906
908 """
909 Hide show emoticons_button and make sure emoticons_menu is always there
910 when needed
911 """
912 emoticons_button = self.xml.get_object('emoticons_button')
913 if gajim.config.get('emoticons_theme'):
914 emoticons_button.show()
915 emoticons_button.set_no_show_all(False)
916 else:
917 emoticons_button.hide()
918 emoticons_button.set_no_show_all(True)
919
921 buffer_ = self.msg_textview.get_buffer()
922 if buffer_.get_char_count():
923 buffer_.insert_at_cursor(' %s ' % str_)
924 else:
925 buffer_.insert_at_cursor('%s ' % str_)
926 self.msg_textview.grab_focus()
927
934
985
987 color_dialog = gtk.ColorSelectionDialog('Select a color')
988 color_dialog.connect('response', self.msg_textview.color_set,
989 color_dialog.colorsel)
990 color_dialog.show_all()
991
993 font_dialog = gtk.FontSelectionDialog('Select a font')
994 font_dialog.connect('response', self.msg_textview.font_set,
995 font_dialog.fontsel)
996 font_dialog.show_all()
997
998
1007
1009 font = pango.FontDescription(gajim.config.get('conversation_font'))
1010 self.conv_textview.tv.modify_font(font)
1011 self.msg_textview.modify_font(font)
1012
1015
1017 buffer_ = tv.get_buffer()
1018 start, end = buffer_.get_bounds()
1019 buffer_.delete(start, end)
1020
1022 """
1023 When history menuitem is pressed: call history window
1024 """
1025 if not jid:
1026 jid = self.contact.jid
1027
1028 if 'logs' in gajim.interface.instances:
1029 gajim.interface.instances['logs'].window.present()
1030 gajim.interface.instances['logs'].open_history(jid, self.account)
1031 else:
1032 gajim.interface.instances['logs'] = \
1033 history_window.HistoryWindow(jid, self.account)
1034
1042 if self.TYPE_ID == message_control.TYPE_PM:
1043 gc_contact = self.gc_contact
1044 if gc_contact:
1045
1046 gc_control = gajim.interface.msg_win_mgr.get_gc_control(
1047 gc_contact.room_jid, self.account)
1048 self_contact = gajim.contacts.get_gc_contact(self.account,
1049 gc_control.room_jid, gc_control.nick)
1050 if gc_control.is_anonymous and gc_contact.affiliation not in ['admin',
1051 'owner'] and self_contact.affiliation in ['admin', 'owner']:
1052 contact = gajim.contacts.get_contact(self.account, gc_contact.jid)
1053 if not contact or contact.sub not in ('both', 'to'):
1054 prim_text = _('Really send file?')
1055 sec_text = _('If you send a file to %s, he/she will know your '
1056 'real Jabber ID.') % gc_contact.name
1057 dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text,
1058 on_response_ok = (_on_ok, gc_contact))
1059 dialog.popup()
1060 return
1061 _on_ok(gc_contact)
1062 return
1063 _on_ok(self.contact)
1064
1066 """
1067 When a grouchat is minimized, unparent the tab, put it in roster etc
1068 """
1069 old_value = False
1070 minimized_gc = gajim.config.get_per('accounts', self.account,
1071 'minimized_gc').split()
1072 if self.contact.jid in minimized_gc:
1073 old_value = True
1074 minimize = widget.get_active()
1075 if minimize and not self.contact.jid in minimized_gc:
1076 minimized_gc.append(self.contact.jid)
1077 if not minimize and self.contact.jid in minimized_gc:
1078 minimized_gc.remove(self.contact.jid)
1079 if old_value != minimize:
1080 gajim.config.set_per('accounts', self.account, 'minimized_gc',
1081 ' '.join(minimized_gc))
1082
1095
1096
1112
1119
1121 """
1122 When message_textview changes its size: if the new height will enlarge
1123 the window, enable the scrollbar automatic policy. Also enable scrollbar
1124 automatic policy for horizontal scrollbar if message we have in
1125 message_textview is too big
1126 """
1127 if msg_textview.window is None:
1128 return
1129
1130 min_height = self.conv_scrolledwindow.get_property('height-request')
1131 conversation_height = self.conv_textview.tv.window.get_size()[1]
1132 message_height = msg_textview.window.get_size()[1]
1133 message_width = msg_textview.window.get_size()[0]
1134
1135 if conversation_height < 2:
1136 return
1137
1138 if conversation_height < min_height:
1139 min_height = conversation_height
1140
1141
1142
1143
1144
1145
1146 diff_y = message_height - requisition.height
1147 if diff_y != 0:
1148 if conversation_height + diff_y < min_height:
1149 if message_height + conversation_height - min_height > min_height:
1150 policy = self.msg_scrolledwindow.get_property(
1151 'vscrollbar-policy')
1152
1153 if policy != gtk.POLICY_AUTOMATIC:
1154 self.msg_scrolledwindow.set_property('vscrollbar-policy',
1155 gtk.POLICY_AUTOMATIC)
1156 self.msg_scrolledwindow.set_property('height-request',
1157 message_height + conversation_height - min_height)
1158 self.bring_scroll_to_end(msg_textview)
1159 else:
1160 self.msg_scrolledwindow.set_property('vscrollbar-policy',
1161 gtk.POLICY_NEVER)
1162 self.msg_scrolledwindow.set_property('height-request', -1)
1163 self.conv_textview.bring_scroll_to_end(diff_y - 18, False)
1164 else:
1165 self.conv_textview.bring_scroll_to_end(diff_y - 18, self.smooth)
1166 self.smooth = True
1167
1168
1169 if requisition.width > message_width:
1170 self.msg_scrolledwindow.set_property('hscrollbar-policy',
1171 gtk.POLICY_AUTOMATIC)
1172 else:
1173 self.msg_scrolledwindow.set_property('hscrollbar-policy',
1174 gtk.POLICY_NEVER)
1175
1176 return True
1177
1179
1180
1181 if self.was_at_the_end:
1182 self.conv_textview.bring_scroll_to_end(-18)
1183 self.was_at_the_end = (adjustment.upper - adjustment.value - adjustment.page_size) < 18
1184
1217
1245
1271
1273 p = 0.4
1274 mask = 0
1275 color.red = int((color.red * p) + (mask * (1 - p)))
1276 color.green = int((color.green * p) + (mask * (1 - p)))
1277 color.blue = int((color.blue * p) + (mask * (1 - p)))
1278 return color
1279
1293
1300
1302 self.msg_textview.set_sensitive(True)
1303 self.msg_textview.set_editable(True)
1304
1305
1307 self.msg_textview.set_sensitive(False)
1308 self.msg_textview.set_editable(False)
1309 self.conv_textview.tv.grab_focus()
1310
1311 self.no_autonegotiation = False
1312
1313
1314
1316 """
1317 A control for standard 1-1 chat
1318 """
1319 (
1320 JINGLE_STATE_NULL,
1321 JINGLE_STATE_CONNECTING,
1322 JINGLE_STATE_CONNECTION_RECEIVED,
1323 JINGLE_STATE_CONNECTED,
1324 JINGLE_STATE_ERROR
1325 ) = range(5)
1326
1327 TYPE_ID = message_control.TYPE_CHAT
1328 old_msg_kind = None
1329
1330
1331
1332 COMMAND_HOST = ChatCommands
1333
1334 - def __init__(self, parent_win, contact, acct, session, resource = None):
1335 ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
1336 'chat_control', contact, acct, resource)
1337
1338 self.gpg_is_active = False
1339
1340
1341 self.actions_button = self.xml.get_object('message_window_actions_button')
1342 id_ = self.actions_button.connect('clicked',
1343 self.on_actions_button_clicked)
1344 self.handlers[id_] = self.actions_button
1345
1346 self._formattings_button = self.xml.get_object('formattings_button')
1347
1348 self._add_to_roster_button = self.xml.get_object(
1349 'add_to_roster_button')
1350 id_ = self._add_to_roster_button.connect('clicked',
1351 self._on_add_to_roster_menuitem_activate)
1352 self.handlers[id_] = self._add_to_roster_button
1353
1354 self._audio_button = self.xml.get_object('audio_togglebutton')
1355 id_ = self._audio_button.connect('toggled', self.on_audio_button_toggled)
1356 self.handlers[id_] = self._audio_button
1357
1358 gtkgui_helpers.add_image_to_button(self._audio_button,
1359 'gajim-mic_inactive')
1360
1361 self._video_button = self.xml.get_object('video_togglebutton')
1362 id_ = self._video_button.connect('toggled', self.on_video_button_toggled)
1363 self.handlers[id_] = self._video_button
1364
1365 gtkgui_helpers.add_image_to_button(self._video_button,
1366 'gajim-cam_inactive')
1367
1368 self._send_file_button = self.xml.get_object('send_file_button')
1369
1370 path_to_upload_img = gtkgui_helpers.get_icon_path('gajim-upload')
1371 img = gtk.Image()
1372 img.set_from_file(path_to_upload_img)
1373 self._send_file_button.set_image(img)
1374 id_ = self._send_file_button.connect('clicked',
1375 self._on_send_file_menuitem_activate)
1376 self.handlers[id_] = self._send_file_button
1377
1378 self._convert_to_gc_button = self.xml.get_object(
1379 'convert_to_gc_button')
1380 id_ = self._convert_to_gc_button.connect('clicked',
1381 self._on_convert_to_gc_menuitem_activate)
1382 self.handlers[id_] = self._convert_to_gc_button
1383
1384 contact_information_button = self.xml.get_object(
1385 'contact_information_button')
1386 id_ = contact_information_button.connect('clicked',
1387 self._on_contact_information_menuitem_activate)
1388 self.handlers[id_] = contact_information_button
1389
1390 compact_view = gajim.config.get('compact_view')
1391 self.chat_buttons_set_visible(compact_view)
1392 self.widget_set_visible(self.xml.get_object('banner_eventbox'),
1393 gajim.config.get('hide_chat_banner'))
1394
1395 self.authentication_button = self.xml.get_object(
1396 'authentication_button')
1397 id_ = self.authentication_button.connect('clicked',
1398 self._on_authentication_button_clicked)
1399 self.handlers[id_] = self.authentication_button
1400
1401
1402 self.lock_image = self.xml.get_object('lock_image')
1403
1404
1405 img = self.xml.get_object('convert_to_gc_button_image')
1406 img.set_from_pixbuf(gtkgui_helpers.load_icon(
1407 'muc_active').get_pixbuf())
1408
1409 self._audio_banner_image = self.xml.get_object('audio_banner_image')
1410 self._video_banner_image = self.xml.get_object('video_banner_image')
1411 self.audio_sid = None
1412 self.audio_state = self.JINGLE_STATE_NULL
1413 self.audio_available = False
1414 self.video_sid = None
1415 self.video_state = self.JINGLE_STATE_NULL
1416 self.video_available = False
1417
1418 self.update_toolbar()
1419
1420 self._pep_images = {}
1421 self._pep_images['mood'] = self.xml.get_object('mood_image')
1422 self._pep_images['activity'] = self.xml.get_object('activity_image')
1423 self._pep_images['tune'] = self.xml.get_object('tune_image')
1424 self._pep_images['location'] = self.xml.get_object('location_image')
1425 self.update_all_pep_types()
1426
1427
1428
1429
1430 self.show_bigger_avatar_timeout_id = None
1431 self.bigger_avatar_window = None
1432 self.show_avatar()
1433
1434
1435 self.reset_kbd_mouse_timeout_vars()
1436 self._schedule_activity_timers()
1437
1438
1439 id_ = self.parent_win.window.connect('motion-notify-event',
1440 self._on_window_motion_notify)
1441 self.handlers[id_] = self.parent_win.window
1442 message_tv_buffer = self.msg_textview.get_buffer()
1443 id_ = message_tv_buffer.connect('changed',
1444 self._on_message_tv_buffer_changed)
1445 self.handlers[id_] = message_tv_buffer
1446
1447 widget = self.xml.get_object('avatar_eventbox')
1448 widget.set_property('height-request', gajim.config.get(
1449 'chat_avatar_height'))
1450 id_ = widget.connect('enter-notify-event',
1451 self.on_avatar_eventbox_enter_notify_event)
1452 self.handlers[id_] = widget
1453
1454 id_ = widget.connect('leave-notify-event',
1455 self.on_avatar_eventbox_leave_notify_event)
1456 self.handlers[id_] = widget
1457
1458 id_ = widget.connect('button-press-event',
1459 self.on_avatar_eventbox_button_press_event)
1460 self.handlers[id_] = widget
1461
1462 widget = self.xml.get_object('location_eventbox')
1463 id_ = widget.connect('button-release-event',
1464 self.on_location_eventbox_button_release_event)
1465 self.handlers[id_] = widget
1466
1467 for key in ('1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'):
1468 widget = self.xml.get_object(key + '_button')
1469 id_ = widget.connect('pressed', self.on_num_button_pressed, key)
1470 self.handlers[id_] = widget
1471 id_ = widget.connect('released', self.on_num_button_released)
1472 self.handlers[id_] = widget
1473
1474 self.dtmf_window = self.xml.get_object('dtmf_window')
1475 id_ = self.dtmf_window.connect('focus-out-event',
1476 self.on_dtmf_window_focus_out_event)
1477 self.handlers[id_] = self.dtmf_window
1478
1479 widget = self.xml.get_object('dtmf_button')
1480 id_ = widget.connect('clicked', self.on_dtmf_button_clicked)
1481 self.handlers[id_] = widget
1482
1483 widget = self.xml.get_object('mic_hscale')
1484 id_ = widget.connect('value_changed', self.on_mic_hscale_value_changed)
1485 self.handlers[id_] = widget
1486
1487 widget = self.xml.get_object('sound_hscale')
1488 id_ = widget.connect('value_changed', self.on_sound_hscale_value_changed)
1489 self.handlers[id_] = widget
1490
1491 if not session:
1492
1493
1494 if not resource:
1495 resource = contact.resource
1496 session = gajim.connections[self.account].find_controlless_session(
1497 self.contact.jid, resource)
1498
1499 self.setup_seclabel(self.xml.get_object('label_selector'))
1500 if session:
1501 session.control = self
1502 self.session = session
1503
1504 if session.enable_encryption:
1505 self.print_esession_details()
1506
1507
1508 self.no_autonegotiation = False
1509 e2e_is_active = self.session and self.session.enable_encryption
1510 gpg_pref = gajim.config.get_per('contacts', contact.jid,
1511 'gpg_enabled')
1512
1513
1514 if not e2e_is_active and gpg_pref and \
1515 gajim.config.get_per('accounts', self.account, 'keyid') and \
1516 gajim.connections[self.account].USE_GPG:
1517 self.gpg_is_active = True
1518 gajim.encrypted_chats[self.account].append(contact.jid)
1519 msg = _('GPG encryption enabled')
1520 ChatControlBase.print_conversation_line(self, msg,
1521 'status', '', None)
1522
1523 if self.session:
1524 self.session.loggable = gajim.config.get_per('accounts',
1525 self.account, 'log_encrypted_sessions')
1526
1527 self._show_lock_image(self.gpg_is_active, 'GPG', self.gpg_is_active,
1528 self.session and self.session.is_loggable(), True)
1529
1530 self.update_ui()
1531
1532 self.restore_conversation()
1533 self.msg_textview.grab_focus()
1534
1585
1587 for pep_type in self._pep_images:
1588 self.update_pep(pep_type)
1589
1607
1609 if jingle_type not in ('audio', 'video'):
1610 return
1611 banner_image = getattr(self, '_' + jingle_type + '_banner_image')
1612 state = getattr(self, jingle_type + '_state')
1613 if state == self.JINGLE_STATE_NULL:
1614 banner_image.hide()
1615 else:
1616 banner_image.show()
1617 if state == self.JINGLE_STATE_CONNECTING:
1618 banner_image.set_from_stock(
1619 gtk.STOCK_CONVERT, 1)
1620 elif state == self.JINGLE_STATE_CONNECTION_RECEIVED:
1621 banner_image.set_from_stock(
1622 gtk.STOCK_NETWORK, 1)
1623 elif state == self.JINGLE_STATE_CONNECTED:
1624 banner_image.set_from_stock(
1625 gtk.STOCK_CONNECT, 1)
1626 elif state == self.JINGLE_STATE_ERROR:
1627 banner_image.set_from_stock(
1628 gtk.STOCK_DIALOG_WARNING, 1)
1629 self.update_toolbar()
1630
1632 self._update_jingle('audio')
1633 hbox = self.xml.get_object('audio_buttons_hbox')
1634 if self.audio_state == self.JINGLE_STATE_CONNECTED:
1635
1636 input_vol = gajim.config.get('audio_input_volume')
1637 output_vol = gajim.config.get('audio_output_volume')
1638 input_vol = max(min(input_vol, 100), 0)
1639 output_vol = max(min(output_vol, 100), 0)
1640 self.xml.get_object('mic_hscale').set_value(input_vol)
1641 self.xml.get_object('sound_hscale').set_value(output_vol)
1642
1643 hbox.set_no_show_all(False)
1644 hbox.show_all()
1645 elif not self.audio_sid:
1646 hbox.set_no_show_all(True)
1647 hbox.hide()
1648
1651
1664
1670
1671
1673 if jingle_type not in ('audio', 'video'):
1674 return
1675 if state in ('connecting', 'connected', 'stop', 'error') and reason:
1676 str = _('%(type)s state : %(state)s, reason: %(reason)s') % {
1677 'type': jingle_type.capitalize(), 'state': state, 'reason': reason}
1678 self.print_conversation(str, 'info')
1679
1680 states = {'connecting': self.JINGLE_STATE_CONNECTING,
1681 'connection_received': self.JINGLE_STATE_CONNECTION_RECEIVED,
1682 'connected': self.JINGLE_STATE_CONNECTED,
1683 'stop': self.JINGLE_STATE_NULL,
1684 'error': self.JINGLE_STATE_ERROR}
1685
1686 jingle_state = states[state]
1687 if getattr(self, jingle_type + '_state') == jingle_state or state == 'error':
1688 return
1689
1690 if state == 'stop' and getattr(self, jingle_type + '_sid') not in (None, sid):
1691 return
1692
1693 setattr(self, jingle_type + '_state', jingle_state)
1694
1695 if jingle_state == self.JINGLE_STATE_NULL:
1696 setattr(self, jingle_type + '_sid', None)
1697 if state in ('connection_received', 'connecting'):
1698 setattr(self, jingle_type + '_sid', sid)
1699
1700 getattr(self, '_' + jingle_type + '_button').set_active(jingle_state != self.JINGLE_STATE_NULL)
1701
1702 getattr(self, 'update_' + jingle_type)()
1703
1706
1709
1711 session = gajim.connections[self.account].get_jingle_session(
1712 self.contact.get_full_jid(), self.audio_sid)
1713 return session.get_content('audio')
1714
1717
1720
1723
1725 self.dtmf_window.hide()
1726
1731
1732
1737
1739 """
1740 Enter the eventbox area so we under conditions add a timeout to show a
1741 bigger avatar after 0.5 sec
1742 """
1743 jid = self.contact.jid
1744 avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
1745 if avatar_pixbuf in ('ask', None):
1746 return
1747 avatar_w = avatar_pixbuf.get_width()
1748 avatar_h = avatar_pixbuf.get_height()
1749
1750 scaled_buf = self.xml.get_object('avatar_image').get_pixbuf()
1751 scaled_buf_w = scaled_buf.get_width()
1752 scaled_buf_h = scaled_buf.get_height()
1753
1754
1755 if avatar_w > scaled_buf_w or avatar_h > scaled_buf_h:
1756
1757 if self.show_bigger_avatar_timeout_id is not None:
1758 gobject.source_remove(self.show_bigger_avatar_timeout_id)
1759 self.show_bigger_avatar_timeout_id = gobject.timeout_add(500,
1760 self.show_bigger_avatar, widget)
1761
1763 """
1764 Left the eventbox area that holds the avatar img
1765 """
1766
1767 if self.show_bigger_avatar_timeout_id is not None:
1768 gobject.source_remove(self.show_bigger_avatar_timeout_id)
1769 self.show_bigger_avatar_timeout_id = None
1770
1789
1798
1800 """
1801 It gets called no matter if it is the active window or not
1802 """
1803 if self.parent_win.get_active_jid() == self.contact.jid:
1804
1805 self.mouse_over_in_last_5_secs = True
1806 self.mouse_over_in_last_30_secs = True
1807
1813
1818
1820 contact = gajim.contacts.get_contact_with_highest_priority(self.account,
1821 self.contact.jid)
1822 if not contact or self.resource:
1823
1824 contact = self.contact
1825 show = contact.show
1826 jid = contact.jid
1827
1828
1829 img_32 = gajim.interface.roster.get_appropriate_state_images(jid,
1830 size = '32', icon_name = show)
1831 img_16 = gajim.interface.roster.get_appropriate_state_images(jid,
1832 icon_name = show)
1833 if show in img_32 and img_32[show].get_pixbuf():
1834
1835 banner_image = img_32[show]
1836 use_size_32 = True
1837 else:
1838 banner_image = img_16[show]
1839 use_size_32 = False
1840
1841 banner_status_img = self.xml.get_object('banner_status_image')
1842 if banner_image.get_storage_type() == gtk.IMAGE_ANIMATION:
1843 banner_status_img.set_from_animation(banner_image.get_animation())
1844 else:
1845 pix = banner_image.get_pixbuf()
1846 if pix is not None:
1847 if use_size_32:
1848 banner_status_img.set_from_pixbuf(pix)
1849 else:
1850 scaled_pix = pix.scale_simple(32, 32,
1851 gtk.gdk.INTERP_BILINEAR)
1852 banner_status_img.set_from_pixbuf(scaled_pix)
1853
1854 - def draw_banner_text(self):
1855 """
1856 Draw the text in the fat line at the top of the window that houses the
1857 name, jid
1858 """
1859 contact = self.contact
1860 jid = contact.jid
1861
1862 banner_name_label = self.xml.get_object('banner_name_label')
1863
1864 name = contact.get_shown_name()
1865 if self.resource:
1866 name += '/' + self.resource
1867 if self.TYPE_ID == message_control.TYPE_PM:
1868 name = _('%(nickname)s from group chat %(room_name)s') %\
1869 {'nickname': name, 'room_name': self.room_name}
1870 name = gobject.markup_escape_text(name)
1871
1872
1873
1874
1875 acct_info = ''
1876 for account in gajim.contacts.get_accounts():
1877 if account == self.account:
1878 continue
1879 if acct_info:
1880 break
1881 for jid in gajim.contacts.get_jid_list(account):
1882 other_contact_ = \
1883 gajim.contacts.get_first_contact_from_jid(account, jid)
1884 if other_contact_.get_shown_name() == self.contact.get_shown_name():
1885 acct_info = ' (%s)' % \
1886 gobject.markup_escape_text(self.account)
1887 break
1888
1889 status = contact.status
1890 if status is not None:
1891 banner_name_label.set_ellipsize(pango.ELLIPSIZE_END)
1892 self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END)
1893 status_reduced = helpers.reduce_chars_newlines(status, max_lines = 1)
1894 status_escaped = gobject.markup_escape_text(status_reduced)
1895
1896 font_attrs, font_attrs_small = self.get_font_attrs()
1897 st = gajim.config.get('displayed_chat_state_notifications')
1898 cs = contact.chatstate
1899 if cs and st in ('composing_only', 'all'):
1900 if contact.show == 'offline':
1901 chatstate = ''
1902 elif contact.composing_xep == 'XEP-0085':
1903 if st == 'all' or cs == 'composing':
1904 chatstate = helpers.get_uf_chatstate(cs)
1905 else:
1906 chatstate = ''
1907 elif contact.composing_xep == 'XEP-0022':
1908 if cs in ('composing', 'paused'):
1909
1910 chatstate = helpers.get_uf_chatstate(cs)
1911 else:
1912 chatstate = ''
1913 else:
1914
1915 chatstate = helpers.get_uf_chatstate(cs)
1916
1917 label_text = '<span %s>%s</span><span %s>%s %s</span>' \
1918 % (font_attrs, name, font_attrs_small,
1919 acct_info, chatstate)
1920 if acct_info:
1921 acct_info = ' ' + acct_info
1922 label_tooltip = '%s%s %s' % (name, acct_info, chatstate)
1923 else:
1924
1925 label_text = '<span %s>%s</span><span %s>%s</span>' % \
1926 (font_attrs, name, font_attrs_small, acct_info)
1927 if acct_info:
1928 acct_info = ' ' + acct_info
1929 label_tooltip = '%s%s' % (name, acct_info)
1930
1931 if status_escaped:
1932 status_text = self.urlfinder.sub(self.make_href, status_escaped)
1933 status_text = '<span %s>%s</span>' % (font_attrs_small, status_escaped)
1934 self.banner_status_label.set_tooltip_text(status)
1935 self.banner_status_label.set_no_show_all(False)
1936 self.banner_status_label.show()
1937 else:
1938 status_text = ''
1939 self.banner_status_label.hide()
1940 self.banner_status_label.set_no_show_all(True)
1941
1942 self.banner_status_label.set_markup(status_text)
1943
1944 banner_name_label.set_markup(label_text)
1945 banner_name_label.set_tooltip_text(label_tooltip)
1946
1947 - def close_jingle_content(self, jingle_type):
1948 sid = getattr(self, jingle_type + '_sid')
1949 if not sid:
1950 return
1951 setattr(self, jingle_type + '_sid', None)
1952 setattr(self, jingle_type + '_state', self.JINGLE_STATE_NULL)
1953 session = gajim.connections[self.account].get_jingle_session(
1954 self.contact.get_full_jid(), sid)
1955 if session:
1956 content = session.get_content(jingle_type)
1957 if content:
1958 session.remove_content(content.creator, content.name)
1959 getattr(self, '_' + jingle_type + '_button').set_active(False)
1960 getattr(self, 'update_' + jingle_type)()
1961
1978
1981
1984
1986 if not self.gpg_is_active and not self.contact.keyID:
1987 dialogs.ErrorDialog(_('No GPG key assigned'),
1988 _('No GPG key is assigned to this contact. So you cannot '
1989 'encrypt messages with GPG.'))
1990 return
1991 ec = gajim.encrypted_chats[self.account]
1992 if self.gpg_is_active:
1993
1994 ec.remove(self.contact.jid)
1995 self.gpg_is_active = False
1996 loggable = False
1997 msg = _('GPG encryption disabled')
1998 ChatControlBase.print_conversation_line(self, msg,
1999 'status', '', None)
2000 if self.session:
2001 self.session.loggable = True
2002
2003 else:
2004
2005 ec.append(self.contact.jid)
2006 self.gpg_is_active = True
2007 msg = _('GPG encryption enabled')
2008 ChatControlBase.print_conversation_line(self, msg,
2009 'status', '', None)
2010
2011 loggable = gajim.config.get_per('accounts', self.account,
2012 'log_encrypted_sessions')
2013
2014 if self.session:
2015 self.session.loggable = loggable
2016
2017 loggable = self.session.is_loggable()
2018 else:
2019 loggable = loggable and gajim.config.should_log(self.account,
2020 self.contact.jid)
2021
2022 if loggable:
2023 msg = _('Session WILL be logged')
2024 else:
2025 msg = _('Session WILL NOT be logged')
2026
2027 ChatControlBase.print_conversation_line(self, msg,
2028 'status', '', None)
2029
2030 gajim.config.set_per('contacts', self.contact.jid,
2031 'gpg_enabled', self.gpg_is_active)
2032
2033 self._show_lock_image(self.gpg_is_active, 'GPG',
2034 self.gpg_is_active, loggable, True)
2035
2036 - def _show_lock_image(self, visible, enc_type = '', enc_enabled = False,
2037 chat_logged = False, authenticated = False):
2038 """
2039 Set lock icon visibility and create tooltip
2040 """
2041
2042 status_string = enc_enabled and _('is') or _('is NOT')
2043
2044 logged_string = chat_logged and _('will') or _('will NOT')
2045
2046 if authenticated:
2047
2048 authenticated_string = _('and authenticated')
2049 img_path = gtkgui_helpers.get_icon_path('gajim-security_high')
2050 else:
2051
2052 authenticated_string = _('and NOT authenticated')
2053 img_path = gtkgui_helpers.get_icon_path('gajim-security_low')
2054 self.lock_image.set_from_file(img_path)
2055
2056
2057
2058
2059 tooltip = _('%(type)s encryption %(status)s active %(authenticated)s.\n'
2060 'Your chat session %(logged)s be logged.') % {'type': enc_type,
2061 'status': status_string, 'authenticated': authenticated_string,
2062 'logged': logged_string}
2063
2064 self.authentication_button.set_tooltip_text(tooltip)
2065 self.widget_set_visible(self.authentication_button, not visible)
2066 self.lock_image.set_sensitive(enc_enabled)
2067
2073
2074 - def send_message(self, message, keyID='', chatstate=None, xhtml=None,
2075 process_commands=True):
2076 """
2077 Send a message to contact
2078 """
2079 if message in ('', None, '\n'):
2080 return None
2081
2082
2083 self.reset_kbd_mouse_timeout_vars()
2084
2085 contact = self.contact
2086
2087 encrypted = bool(self.session) and self.session.enable_encryption
2088
2089 keyID = ''
2090 if self.gpg_is_active:
2091 keyID = contact.keyID
2092 encrypted = True
2093 if not keyID:
2094 keyID = 'UNKNOWN'
2095
2096 chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \
2097 'disabled'
2098 composing_xep = contact.composing_xep
2099 chatstate_to_send = None
2100 if chatstates_on and contact is not None:
2101 if composing_xep is None:
2102
2103
2104
2105
2106
2107
2108 if contact.our_chatstate:
2109
2110 composing_xep = 'asked_once'
2111
2112 chatstate_to_send = 'active'
2113 contact.our_chatstate = 'ask'
2114
2115
2116 elif composing_xep is not False:
2117
2118 chatstate_to_send = 'active'
2119 contact.our_chatstate = 'active'
2120
2121 gobject.source_remove(self.possible_paused_timeout_id)
2122 gobject.source_remove(self.possible_inactive_timeout_id)
2123 self._schedule_activity_timers()
2124
2125 def _on_sent(id_, contact, message, encrypted, xhtml, label):
2126 if contact.supports(NS_RECEIPTS) and gajim.config.get_per('accounts',
2127 self.account, 'request_receipt'):
2128 xep0184_id = id_
2129 else:
2130 xep0184_id = None
2131 if label:
2132 displaymarking = label.getTag('displaymarking')
2133 else:
2134 displaymarking = None
2135 self.print_conversation(message, self.contact.jid, encrypted=encrypted,
2136 xep0184_id=xep0184_id, xhtml=xhtml, displaymarking=displaymarking)
2137
2138 ChatControlBase.send_message(self, message, keyID, type_='chat',
2139 chatstate=chatstate_to_send, composing_xep=composing_xep,
2140 xhtml=xhtml, callback=_on_sent,
2141 callback_args=[contact, message, encrypted, xhtml, self.get_seclabel()],
2142 process_commands=process_commands)
2143
2145 """
2146 Did we move mouse of that window or write something in message textview
2147 in the last 5 seconds? If yes - we go active for mouse, composing for
2148 kbd. If not - we go paused if we were previously composing
2149 """
2150 contact = self.contact
2151 jid = contact.jid
2152 current_state = contact.our_chatstate
2153 if current_state is False:
2154 return False
2155
2156 message_buffer = self.msg_textview.get_buffer()
2157 if self.kbd_activity_in_last_5_secs and message_buffer.get_char_count():
2158
2159 self.send_chatstate('composing')
2160 elif self.mouse_over_in_last_5_secs and\
2161 jid == self.parent_win.get_active_jid():
2162 self.send_chatstate('active')
2163 else:
2164 if current_state == 'composing':
2165 self.send_chatstate('paused')
2166
2167
2168
2169 self.reset_kbd_mouse_timeout_vars()
2170 return True
2171
2173 """
2174 Did we move mouse over that window or wrote something in message textview
2175 in the last 30 seconds? if yes - we go active. If no - we go inactive
2176 """
2177 contact = self.contact
2178
2179 current_state = contact.our_chatstate
2180 if current_state is False:
2181 return False
2182
2183 if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs:
2184 return True
2185
2186 if not self.mouse_over_in_last_30_secs or \
2187 self.kbd_activity_in_last_30_secs:
2188 self.send_chatstate('inactive', contact)
2189
2190
2191
2192 self.reset_kbd_mouse_timeout_vars()
2193 return True
2194
2196 self.kbd_activity_in_last_5_secs = False
2197 self.mouse_over_in_last_5_secs = False
2198 self.mouse_over_in_last_30_secs = False
2199 self.kbd_activity_in_last_30_secs = False
2200
2204
2206 """
2207 Print esession settings to textview
2208 """
2209 e2e_is_active = bool(self.session) and self.session.enable_encryption
2210 if e2e_is_active:
2211 msg = _('This session is encrypted')
2212
2213 if self.session.is_loggable():
2214 msg += _(' and WILL be logged')
2215 else:
2216 msg += _(' and WILL NOT be logged')
2217
2218 ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
2219
2220 if not self.session.verified_identity:
2221 ChatControlBase.print_conversation_line(self, _("Remote contact's identity not verified. Click the shield button for more details."), 'status', '', None)
2222 else:
2223 msg = _('E2E encryption disabled')
2224 ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
2225
2226 self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \
2227 self.session.is_loggable(), self.session and self.session.verified_identity)
2228
2229 - def print_conversation(self, text, frm='', tim=None, encrypted=False,
2230 subject=None, xhtml=None, simple=False, xep0184_id=None,
2231 displaymarking=None):
2232 """
2233 Print a line in the conversation
2234
2235 If frm is set to status: it's a status message.
2236 if frm is set to error: it's an error message. The difference between
2237 status and error is mainly that with error, msg count as a new message
2238 (in systray and in control).
2239 If frm is set to info: it's a information message.
2240 If frm is set to print_queue: it is incomming from queue.
2241 If frm is set to another value: it's an outgoing message.
2242 If frm is not set: it's an incomming message.
2243 """
2244 contact = self.contact
2245
2246 if frm == 'status':
2247 if not gajim.config.get('print_status_in_chats'):
2248 return
2249 kind = 'status'
2250 name = ''
2251 elif frm == 'error':
2252 kind = 'error'
2253 name = ''
2254 elif frm == 'info':
2255 kind = 'info'
2256 name = ''
2257 else:
2258 if self.session and self.session.enable_encryption:
2259
2260 if not encrypted:
2261 msg = _('The following message was NOT encrypted')
2262 ChatControlBase.print_conversation_line(self, msg, 'status', '',
2263 tim)
2264 else:
2265
2266 if encrypted and not self.gpg_is_active:
2267 msg = _('The following message was encrypted')
2268 ChatControlBase.print_conversation_line(self, msg, 'status', '',
2269 tim)
2270
2271 if encrypted == 'xep27':
2272 self._toggle_gpg()
2273 elif not encrypted and self.gpg_is_active:
2274 msg = _('The following message was NOT encrypted')
2275 ChatControlBase.print_conversation_line(self, msg, 'status', '',
2276 tim)
2277 if not frm:
2278 kind = 'incoming'
2279 name = contact.get_shown_name()
2280 elif frm == 'print_queue':
2281 kind = 'incoming_queue'
2282 name = contact.get_shown_name()
2283 else:
2284 kind = 'outgoing'
2285 name = gajim.nicks[self.account]
2286 if not xhtml and not (encrypted and self.gpg_is_active) and \
2287 gajim.config.get('rst_formatting_outgoing_messages'):
2288 from common.rst_xhtml_generator import create_xhtml
2289 xhtml = create_xhtml(text)
2290 if xhtml:
2291 xhtml = '<body xmlns="%s">%s</body>' % (NS_XHTML, xhtml)
2292 ChatControlBase.print_conversation_line(self, text, kind, name, tim,
2293 subject=subject, old_kind=self.old_msg_kind, xhtml=xhtml,
2294 simple=simple, xep0184_id=xep0184_id, displaymarking=displaymarking)
2295 if text.startswith('/me ') or text.startswith('/me\n'):
2296 self.old_msg_kind = None
2297 else:
2298 self.old_msg_kind = kind
2299
2301 unread = ''
2302 if self.resource:
2303 jid = self.contact.get_full_jid()
2304 else:
2305 jid = self.contact.jid
2306 num_unread = len(gajim.events.get_events(self.account, jid,
2307 ['printed_' + self.type_id, self.type_id]))
2308 if num_unread == 1 and not gajim.config.get('show_unread_tab_icon'):
2309 unread = '*'
2310 elif num_unread > 1:
2311 unread = '[' + unicode(num_unread) + ']'
2312
2313
2314 theme = gajim.config.get('roster_theme')
2315 color = None
2316 if not chatstate:
2317 chatstate = self.contact.chatstate
2318 if chatstate is not None:
2319 if chatstate == 'composing':
2320 color = gajim.config.get_per('themes', theme,
2321 'state_composing_color')
2322 elif chatstate == 'inactive':
2323 color = gajim.config.get_per('themes', theme,
2324 'state_inactive_color')
2325 elif chatstate == 'gone':
2326 color = gajim.config.get_per('themes', theme,
2327 'state_gone_color')
2328 elif chatstate == 'paused':
2329 color = gajim.config.get_per('themes', theme,
2330 'state_paused_color')
2331 if color:
2332
2333 color = gtk.gdk.colormap_get_system().alloc_color(color)
2334
2335
2336 if chatstate in ('inactive', 'gone') and\
2337 self.parent_win.get_active_control() != self:
2338 color = self.lighten_color(color)
2339 else:
2340 color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE]
2341
2342
2343 name = self.contact.get_shown_name()
2344 if self.resource:
2345 name += '/' + self.resource
2346 label_str = gobject.markup_escape_text(name)
2347 if num_unread:
2348 label_str = '<b>' + unread + label_str + '</b>'
2349 return (label_str, color)
2350
2379
2381 """
2382 Set compact view menuitem active state sets active and sensitivity state
2383 for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for
2384 tranasports) and file_transfer_menuitem and hide()/show() for
2385 add_to_roster_menuitem
2386 """
2387 menu = gui_menu_builder.get_contact_menu(self.contact, self.account,
2388 use_multiple_contacts=False, show_start_chat=False,
2389 show_encryption=True, control=self,
2390 show_buttonbar_items=not hide_buttonbar_items)
2391 return menu
2392
2394 """
2395 Send OUR chatstate as STANDLONE chat state message (eg. no body)
2396 to contact only if new chatstate is different from the previous one
2397 if jid is not specified, send to active tab
2398 """
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410 chatstate_setting = gajim.config.get('outgoing_chat_state_notifications')
2411 if chatstate_setting == 'disabled':
2412 return
2413 elif chatstate_setting == 'composing_only' and state != 'active' and\
2414 state != 'composing':
2415 return
2416
2417 if contact is None:
2418 contact = self.parent_win.get_active_contact()
2419 if contact is None:
2420
2421
2422 return
2423
2424
2425 if contact.show == 'offline':
2426 return
2427
2428 if contact.composing_xep is False:
2429 return
2430
2431
2432
2433 if contact.our_chatstate == state:
2434 return
2435
2436 if contact.composing_xep is None:
2437
2438
2439
2440
2441
2442
2443 return
2444
2445 if contact.our_chatstate == 'ask':
2446 return
2447
2448
2449
2450 if contact.composing_xep == 'XEP-0022' and \
2451 contact.our_chatstate in ('paused', 'active', 'inactive') and \
2452 state is not 'composing':
2453 contact.our_chatstate = 'active'
2454 self.reset_kbd_mouse_timeout_vars()
2455 return
2456
2457
2458 if state == 'paused' and not contact.our_chatstate == 'composing':
2459
2460 MessageControl.send_message(self, None, chatstate = 'active')
2461 contact.our_chatstate = 'active'
2462 self.reset_kbd_mouse_timeout_vars()
2463
2464
2465 elif contact.our_chatstate == 'inactive' and state == 'composing':
2466
2467 MessageControl.send_message(self, None, chatstate = 'active')
2468 contact.our_chatstate = 'active'
2469 self.reset_kbd_mouse_timeout_vars()
2470
2471 MessageControl.send_message(self, None, chatstate = state,
2472 msg_id = contact.msg_id, composing_xep = contact.composing_xep)
2473 contact.our_chatstate = state
2474 if contact.our_chatstate == 'active':
2475 self.reset_kbd_mouse_timeout_vars()
2476
2478
2479
2480 super(ChatControl, self).shutdown()
2481
2482
2483 gajim.plugin_manager.remove_gui_extension_point('chat_control', self)
2484
2485 self.send_chatstate('gone', self.contact)
2486 self.contact.chatstate = None
2487 self.contact.our_chatstate = None
2488
2489 for jingle_type in ('audio', 'video'):
2490 self.close_jingle_content(jingle_type)
2491
2492
2493 if self.session:
2494 self.session.control = None
2495
2496
2497 gobject.source_remove(self.possible_paused_timeout_id)
2498 gobject.source_remove(self.possible_inactive_timeout_id)
2499
2500 if self.bigger_avatar_window:
2501 self.bigger_avatar_window.destroy()
2502
2503 gajim.events.remove_events(self.account, self.get_full_jid(),
2504 types = ['printed_' + self.type_id, self.type_id])
2505
2506 key = (self.contact.jid, self.account)
2507 roster = gajim.interface.roster
2508 if key in roster.contacts_to_be_removed.keys() and \
2509 not roster.contact_has_pending_roster_events(self.contact, self.account):
2510 backend = roster.contacts_to_be_removed[key]['backend']
2511 del roster.contacts_to_be_removed[key]
2512 roster.remove_contact(self.contact.jid, self.account, force=True,
2513 backend=backend)
2514
2515
2516 for i in self.handlers.keys():
2517 if self.handlers[i].handler_is_connected(i):
2518 self.handlers[i].disconnect(i)
2519 del self.handlers[i]
2520 self.conv_textview.del_handlers()
2521 if gajim.config.get('use_speller') and HAS_GTK_SPELL:
2522 spell_obj = gtkspell.get_from_text_view(self.msg_textview)
2523 if spell_obj:
2524 spell_obj.detach()
2525 self.msg_textview.destroy()
2526
2529
2532
2539
2540 def on_cancel():
2541 on_no(self)
2542
2543 dialogs.ConfirmationDialog(
2544
2545 _('You just received a new message from "%s"') % self.contact.jid,
2546 _('If you close this tab and you have history disabled, '\
2547 'this message will be lost.'), on_response_ok=on_ok,
2548 on_response_cancel=on_cancel)
2549 return
2550 on_yes(self)
2551
2553 """
2554 Handle incoming chatstate that jid SENT TO us
2555 """
2556 self.draw_banner_text()
2557
2558 self.parent_win.redraw_tab(self, self.contact.chatstate)
2559
2574
2604
2642
2663
2665 jid = self.contact.jid
2666
2667 if gajim.jid_is_transport(jid):
2668 return
2669
2670
2671 restore_how_many = gajim.config.get('restore_lines')
2672 if restore_how_many <= 0:
2673 return
2674 timeout = gajim.config.get('restore_timeout')
2675
2676
2677
2678 pending_how_many = len(gajim.events.get_events(self.account, jid,
2679 ['chat', 'pm']))
2680 if self.resource:
2681 pending_how_many += len(gajim.events.get_events(self.account,
2682 self.contact.get_full_jid(), ['chat', 'pm']))
2683
2684 try:
2685 rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many,
2686 pending_how_many, timeout, self.account)
2687 except exceptions.DatabaseMalformed:
2688 import common.logger
2689 dialogs.ErrorDialog(_('Database Error'),
2690 _('The database file (%s) cannot be read. Try to repair it or remove it (all history will be lost).') % common.logger.LOG_DB_PATH)
2691 rows = []
2692 local_old_kind = None
2693 for row in rows:
2694 if not row[2]:
2695 continue
2696 if row[1] in (constants.KIND_CHAT_MSG_SENT,
2697 constants.KIND_SINGLE_MSG_SENT):
2698 kind = 'outgoing'
2699 name = gajim.nicks[self.account]
2700 elif row[1] in (constants.KIND_SINGLE_MSG_RECV,
2701 constants.KIND_CHAT_MSG_RECV):
2702 kind = 'incoming'
2703 name = self.contact.get_shown_name()
2704 elif row[1] == constants.KIND_ERROR:
2705 kind = 'status'
2706 name = self.contact.get_shown_name()
2707
2708 tim = time.localtime(float(row[0]))
2709
2710 if gajim.config.get('restored_messages_small'):
2711 small_attr = ['small']
2712 else:
2713 small_attr = []
2714 ChatControlBase.print_conversation_line(self, row[2], kind, name, tim,
2715 small_attr,
2716 small_attr + ['restored_message'],
2717 small_attr + ['restored_message'],
2718 False, old_kind = local_old_kind)
2719 if row[2].startswith('/me ') or row[2].startswith('/me\n'):
2720 local_old_kind = None
2721 else:
2722 local_old_kind = kind
2723 if len(rows):
2724 self.conv_textview.print_empty_line()
2725
2727 """
2728 Read queue and print messages containted in it
2729 """
2730 jid = self.contact.jid
2731 jid_with_resource = jid
2732 if self.resource:
2733 jid_with_resource += '/' + self.resource
2734 events = gajim.events.get_events(self.account, jid_with_resource)
2735
2736
2737 message_ids = []
2738 for event in events:
2739 if event.type_ != self.type_id:
2740 continue
2741 data = event.parameters
2742 kind = data[2]
2743 if kind == 'error':
2744 kind = 'info'
2745 else:
2746 kind = 'print_queue'
2747 dm = None
2748 if len(data) > 10:
2749 dm = data[10]
2750 self.print_conversation(data[0], kind, tim = data[3],
2751 encrypted = data[4], subject = data[1], xhtml = data[7],
2752 displaymarking=dm)
2753 if len(data) > 6 and isinstance(data[6], int):
2754 message_ids.append(data[6])
2755
2756 if len(data) > 8:
2757 self.set_session(data[8])
2758 if message_ids:
2759 gajim.logger.set_read_messages(message_ids)
2760 gajim.events.remove_events(self.account, jid_with_resource,
2761 types = [self.type_id])
2762
2763 typ = 'chat'
2764
2765
2766
2767 room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
2768 control = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
2769 self.account)
2770 if control and control.type_id == message_control.TYPE_GC:
2771 control.update_ui()
2772 control.parent_win.show_title()
2773 typ = 'pm'
2774
2775 self.redraw_after_event_removed(jid)
2776 if (self.contact.show in ('offline', 'error')):
2777 show_offline = gajim.config.get('showoffline')
2778 show_transports = gajim.config.get('show_transports_group')
2779 if (not show_transports and gajim.jid_is_transport(jid)) or \
2780 (not show_offline and typ == 'chat' and \
2781 len(gajim.contacts.get_contacts(self.account, jid)) < 2):
2782 gajim.interface.roster.remove_to_be_removed(self.contact.jid,
2783 self.account)
2784 elif typ == 'pm':
2785 control.remove_contact(nick)
2786
2788 """
2789 Resize the avatar, if needed, so it has at max half the screen size and
2790 shows it
2791 """
2792 if not small_avatar.window:
2793
2794 return
2795 avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(
2796 self.contact.jid)
2797 if avatar_pixbuf in ('ask', None):
2798 return
2799
2800
2801
2802
2803
2804 image = self.xml.get_object('avatar_image')
2805 pixbuf = image.get_pixbuf()
2806 pixbuf.fill(0xffffff00L)
2807 image.queue_draw()
2808
2809 screen_w = gtk.gdk.screen_width()
2810 screen_h = gtk.gdk.screen_height()
2811 avatar_w = avatar_pixbuf.get_width()
2812 avatar_h = avatar_pixbuf.get_height()
2813 half_scr_w = screen_w / 2
2814 half_scr_h = screen_h / 2
2815 if avatar_w > half_scr_w:
2816 avatar_w = half_scr_w
2817 if avatar_h > half_scr_h:
2818 avatar_h = half_scr_h
2819 window = gtk.Window(gtk.WINDOW_POPUP)
2820 self.bigger_avatar_window = window
2821 pixmap, mask = avatar_pixbuf.render_pixmap_and_mask()
2822 window.set_size_request(avatar_w, avatar_h)
2823
2824
2825
2826 window.set_events(gtk.gdk.POINTER_MOTION_MASK)
2827 window.set_app_paintable(True)
2828 window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP)
2829
2830 window.realize()
2831 window.window.set_back_pixmap(pixmap, False)
2832 window.window.shape_combine_mask(mask, 0, 0)
2833
2834
2835 x0, y0 = small_avatar.window.get_origin()
2836 x0 += small_avatar.allocation.x
2837 y0 += small_avatar.allocation.y
2838 center_x= x0 + (small_avatar.allocation.width / 2)
2839 center_y = y0 + (small_avatar.allocation.height / 2)
2840 pos_x, pos_y = center_x - (avatar_w / 2), center_y - (avatar_h / 2)
2841 window.move(pos_x, pos_y)
2842
2843 invisible_cursor = gtkgui_helpers.get_invisible_cursor()
2844 window.window.set_cursor(invisible_cursor)
2845
2846
2847 window.connect('leave_notify_event',
2848 self._on_window_avatar_leave_notify_event)
2849 window.connect('motion-notify-event',
2850 self._on_window_motion_notify_event)
2851
2852 window.show_all()
2853
2855 """
2856 Just left the popup window that holds avatar
2857 """
2858 self.bigger_avatar_window.destroy()
2859 self.bigger_avatar_window = None
2860
2861 self.show_avatar()
2862
2864 """
2865 Just moved the mouse so show the cursor
2866 """
2867 cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
2868 self.bigger_avatar_window.window.set_cursor(cursor)
2869
2872
2875
2878
2881
2887
2903
2913
2924
2939