ability to connect to a server that doesn't support roster, and hide the correcpondin...
[gajim.git] / src / groupchat_control.py
blobb7d6c6e807adc609692ffb2811656b28d8fc03ee
1 # -*- coding:utf-8 -*-
2 ## src/groupchat_control.py
3 ##
4 ## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org>
5 ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
6 ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
7 ## Alex Mauer <hawke AT hawkesnest.net>
8 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
9 ## Travis Shirk <travis AT pobox.com>
10 ## Copyright (C) 2007-2008 Julien Pivotto <roidelapluie AT gmail.com>
11 ## Stephan Erb <steve-e AT h3c.de>
12 ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
13 ## Jonathan Schleifer <js-gajim AT webkeks.org>
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 import os
31 import time
32 import locale
33 import gtk
34 import pango
35 import gobject
36 import gtkgui_helpers
37 import gui_menu_builder
38 import message_control
39 import tooltips
40 import dialogs
41 import config
42 import vcard
43 import cell_renderer_image
44 import dataforms_widget
46 from common import gajim
47 from common import helpers
48 from common import dataforms
49 from common import ged
51 from chat_control import ChatControl
52 from chat_control import ChatControlBase
53 from common.exceptions import GajimGeneralException
55 from command_system.implementation.hosts import PrivateChatCommands
56 from command_system.implementation.hosts import GroupChatCommands
58 import logging
59 log = logging.getLogger('gajim.groupchat_control')
61 #(status_image, type, nick, shown_nick)
63 C_IMG, # image to show state (online, new message etc)
64 C_NICK, # contact nickame or ROLE name
65 C_TYPE, # type of the row ('contact' or 'role')
66 C_TEXT, # text shown in the cellrenderer
67 C_AVATAR, # avatar of the contact
68 ) = range(5)
70 def set_renderer_color(treeview, renderer, set_background=True):
71 """
72 Set style for group row, using PRELIGHT system color
73 """
74 if set_background:
75 bgcolor = treeview.style.bg[gtk.STATE_PRELIGHT]
76 renderer.set_property('cell-background-gdk', bgcolor)
77 else:
78 fgcolor = treeview.style.fg[gtk.STATE_PRELIGHT]
79 renderer.set_property('foreground-gdk', fgcolor)
81 def tree_cell_data_func(column, renderer, model, iter_, tv=None):
82 # cell data func is global, because we don't want it to keep
83 # reference to GroupchatControl instance (self)
84 theme = gajim.config.get('roster_theme')
85 # allocate space for avatar only if needed
86 parent_iter = model.iter_parent(iter_)
87 if isinstance(renderer, gtk.CellRendererPixbuf):
88 avatar_position = gajim.config.get('avatar_position_in_roster')
89 if avatar_position == 'right':
90 renderer.set_property('xalign', 1) # align pixbuf to the right
91 else:
92 renderer.set_property('xalign', 0.5)
93 if parent_iter and (model[iter_][C_AVATAR] or avatar_position == \
94 'left'):
95 renderer.set_property('visible', True)
96 renderer.set_property('width', gajim.config.get(
97 'roster_avatar_width'))
98 else:
99 renderer.set_property('visible', False)
100 if parent_iter:
101 bgcolor = gajim.config.get_per('themes', theme, 'contactbgcolor')
102 if bgcolor:
103 renderer.set_property('cell-background', bgcolor)
104 else:
105 renderer.set_property('cell-background', None)
106 if isinstance(renderer, gtk.CellRendererText):
107 # foreground property is only with CellRendererText
108 color = gajim.config.get_per('themes', theme, 'contacttextcolor')
109 if color:
110 renderer.set_property('foreground', color)
111 else:
112 renderer.set_property('foreground', None)
113 renderer.set_property('font',
114 gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont'))
115 else: # it is root (eg. group)
116 bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor')
117 if bgcolor:
118 renderer.set_property('cell-background', bgcolor)
119 else:
120 set_renderer_color(tv, renderer)
121 if isinstance(renderer, gtk.CellRendererText):
122 # foreground property is only with CellRendererText
123 color = gajim.config.get_per('themes', theme, 'grouptextcolor')
124 if color:
125 renderer.set_property('foreground', color)
126 else:
127 set_renderer_color(tv, renderer, False)
128 renderer.set_property('font',
129 gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
132 class PrivateChatControl(ChatControl):
133 TYPE_ID = message_control.TYPE_PM
135 # Set a command host to bound to. Every command given through a private chat
136 # will be processed with this command host.
137 COMMAND_HOST = PrivateChatCommands
139 def __init__(self, parent_win, gc_contact, contact, account, session):
140 room_jid = gc_contact.room_jid
141 room_ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
142 account)
143 if room_jid in gajim.interface.minimized_controls[account]:
144 room_ctrl = gajim.interface.minimized_controls[account][room_jid]
145 if room_ctrl:
146 self.room_name = room_ctrl.name
147 else:
148 self.room_name = room_jid
149 self.gc_contact = gc_contact
150 ChatControl.__init__(self, parent_win, contact, account, session)
151 self.TYPE_ID = 'pm'
152 gajim.ged.register_event_handler('caps-received', ged.GUI1,
153 self._nec_caps_received_pm)
154 gajim.ged.register_event_handler('gc-presence-received', ged.GUI1,
155 self._nec_gc_presence_received)
157 def shutdown(self):
158 super(PrivateChatControl, self).shutdown()
159 gajim.ged.remove_event_handler('caps-received', ged.GUI1,
160 self._nec_caps_received_pm)
161 gajim.ged.remove_event_handler('gc-presence-received', ged.GUI1,
162 self._nec_gc_presence_received)
164 def _nec_caps_received_pm(self, obj):
165 if obj.conn.name != self.account or \
166 obj.fjid != self.gc_contact.get_full_jid():
167 return
168 self.update_contact()
170 def _nec_gc_presence_received(self, obj):
171 if obj.conn.name != self.account:
172 return
173 if obj.fjid != self.full_jid:
174 return
175 if '303' in obj.status_code:
176 self.print_conversation(_('%(nick)s is now known as '
177 '%(new_nick)s') % {'nick': obj.nick, 'new_nick': obj.new_nick},
178 'status')
179 gc_c = gajim.contacts.get_gc_contact(obj.conn.name, obj.room_jid,
180 obj.new_nick)
181 c = gc_c.as_contact()
182 self.gc_contact = gc_c
183 self.contact = c
184 if self.session:
185 # stop e2e
186 if self.session.enable_encryption:
187 thread_id = self.session.thread_id
188 self.session.terminate_e2e()
189 obj.conn.delete_session(obj.fjid, thread_id)
190 self.no_autonegotiation = False
191 self.draw_banner()
192 old_jid = obj.room_jid + '/' + obj.nick
193 new_jid = obj.room_jid + '/' + obj.new_nick
194 gajim.interface.msg_win_mgr.change_key(old_jid, new_jid,
195 obj.conn.name)
196 else:
197 self.contact.show = obj.show
198 self.contact.status = obj.status
199 self.gc_contact.show = obj.show
200 self.gc_contact.status = obj.status
201 uf_show = helpers.get_uf_show(obj.show)
202 self.print_conversation(_('%(nick)s is now %(status)s') % {
203 'nick': obj.nick, 'status': uf_show}, 'status')
204 if obj.status:
205 self.print_conversation(' (', 'status', simple=True)
206 self.print_conversation('%s' % (obj.status), 'status',
207 simple=True)
208 self.print_conversation(')', 'status', simple=True)
209 self.parent_win.redraw_tab(self)
210 self.update_ui()
212 def send_message(self, message, xhtml=None, process_commands=True):
214 Call this method to send the message
216 message = helpers.remove_invalid_xml_chars(message)
217 if not message:
218 return
220 # We need to make sure that we can still send through the room and that
221 # the recipient did not go away
222 contact = gajim.contacts.get_first_contact_from_jid(self.account,
223 self.contact.jid)
224 if not contact:
225 # contact was from pm in MUC
226 room, nick = gajim.get_room_and_nick_from_fjid(self.contact.jid)
227 gc_contact = gajim.contacts.get_gc_contact(self.account, room, nick)
228 if not gc_contact:
229 dialogs.ErrorDialog(
230 _('Sending private message failed'),
231 #in second %s code replaces with nickname
232 _('You are no longer in group chat "%(room)s" or '
233 '"%(nick)s" has left.') % {'room': room, 'nick': nick})
234 return
236 ChatControl.send_message(self, message, xhtml=xhtml,
237 process_commands=process_commands)
239 def update_ui(self):
240 if self.contact.show == 'offline':
241 self.got_disconnected()
242 else:
243 self.got_connected()
244 ChatControl.update_ui(self)
246 def update_contact(self):
247 self.contact = self.gc_contact.as_contact()
249 def begin_e2e_negotiation(self):
250 self.no_autonegotiation = True
252 if not self.session:
253 fjid = self.gc_contact.get_full_jid()
254 new_sess = gajim.connections[self.account].make_new_session(fjid,
255 type_=self.type_id)
256 self.set_session(new_sess)
258 self.session.negotiate_e2e(False)
260 def prepare_context_menu(self, hide_buttonbar_items=False):
262 Set compact view menuitem active state sets active and sensitivity state
263 for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for
264 tranasports) and file_transfer_menuitem and hide()/show() for
265 add_to_roster_menuitem
267 menu = gui_menu_builder.get_contact_menu(self.contact, self.account,
268 use_multiple_contacts=False, show_start_chat=False,
269 show_encryption=True, control=self,
270 show_buttonbar_items=not hide_buttonbar_items,
271 gc_contact=self.gc_contact)
272 return menu
274 class GroupchatControl(ChatControlBase):
275 TYPE_ID = message_control.TYPE_GC
277 # Set a command host to bound to. Every command given through a group chat
278 # will be processed with this command host.
279 COMMAND_HOST = GroupChatCommands
281 def __init__(self, parent_win, contact, acct, is_continued=False):
282 ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
283 'groupchat_control', contact, acct)
285 self.is_continued = is_continued
286 self.is_anonymous = True
288 # Controls the state of autorejoin.
289 # None - autorejoin is neutral.
290 # False - autorejoin is to be prevented (gets reset to initial state in
291 # got_connected()).
292 # int - autorejoin is being active and working (gets reset to initial
293 # state in got_connected()).
294 self.autorejoin = None
296 # Keep error dialog instance to be sure to have only once at a time
297 self.error_dialog = None
299 self.actions_button = self.xml.get_object('muc_window_actions_button')
300 id_ = self.actions_button.connect('clicked',
301 self.on_actions_button_clicked)
302 self.handlers[id_] = self.actions_button
304 widget = self.xml.get_object('change_nick_button')
305 id_ = widget.connect('clicked', self._on_change_nick_menuitem_activate)
306 self.handlers[id_] = widget
308 widget = self.xml.get_object('change_subject_button')
309 id_ = widget.connect('clicked',
310 self._on_change_subject_menuitem_activate)
311 self.handlers[id_] = widget
313 widget = self.xml.get_object('bookmark_button')
314 for bm in gajim.connections[self.account].bookmarks:
315 if bm['jid'] == self.contact.jid:
316 widget.hide()
317 break
318 else:
319 id_ = widget.connect('clicked',
320 self._on_bookmark_room_menuitem_activate)
321 self.handlers[id_] = widget
323 if gtkgui_helpers.gtk_icon_theme.has_icon('bookmark-new'):
324 img = self.xml.get_object('image7')
325 img.set_from_icon_name('bookmark-new', gtk.ICON_SIZE_MENU)
327 widget.show()
329 widget = self.xml.get_object('list_treeview')
330 id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded)
331 self.handlers[id_] = widget
333 id_ = widget.connect('row_collapsed',
334 self.on_list_treeview_row_collapsed)
335 self.handlers[id_] = widget
337 id_ = widget.connect('row_activated',
338 self.on_list_treeview_row_activated)
339 self.handlers[id_] = widget
341 id_ = widget.connect('button_press_event',
342 self.on_list_treeview_button_press_event)
343 self.handlers[id_] = widget
345 id_ = widget.connect('key_press_event',
346 self.on_list_treeview_key_press_event)
347 self.handlers[id_] = widget
349 id_ = widget.connect('motion_notify_event',
350 self.on_list_treeview_motion_notify_event)
351 self.handlers[id_] = widget
353 id_ = widget.connect('leave_notify_event',
354 self.on_list_treeview_leave_notify_event)
355 self.handlers[id_] = widget
357 self.room_jid = self.contact.jid
358 self.nick = contact.name.decode('utf-8')
359 self.new_nick = ''
360 self.name = ''
361 for bm in gajim.connections[self.account].bookmarks:
362 if bm['jid'] == self.room_jid:
363 self.name = bm['name']
364 break
365 if not self.name:
366 self.name = self.room_jid.split('@')[0]
368 compact_view = gajim.config.get('compact_view')
369 self.chat_buttons_set_visible(compact_view)
370 self.widget_set_visible(self.xml.get_object('banner_eventbox'),
371 gajim.config.get('hide_groupchat_banner'))
372 self.widget_set_visible(self.xml.get_object('list_scrolledwindow'),
373 gajim.config.get('hide_groupchat_occupants_list'))
375 self._last_selected_contact = None # None or holds jid, account tuple
377 # muc attention flag (when we are mentioned in a muc)
378 # if True, the room has mentioned us
379 self.attention_flag = False
381 # sorted list of nicks who mentioned us (last at the end)
382 self.attention_list = []
383 self.room_creation = int(time.time()) # Use int to reduce mem usage
384 self.nick_hits = []
385 self.last_key_tabs = False
387 self.subject = ''
389 self.tooltip = tooltips.GCTooltip()
391 # nickname coloring
392 self.gc_count_nicknames_colors = 0
393 self.gc_custom_colors = {}
394 self.number_of_colors = len(gajim.config.get('gc_nicknames_colors').\
395 split(':'))
397 self.name_label = self.xml.get_object('banner_name_label')
398 self.event_box = self.xml.get_object('banner_eventbox')
400 self.list_treeview = self.xml.get_object('list_treeview')
401 selection = self.list_treeview.get_selection()
402 id_ = selection.connect('changed',
403 self.on_list_treeview_selection_changed)
404 self.handlers[id_] = selection
405 id_ = self.list_treeview.connect('style-set',
406 self.on_list_treeview_style_set)
407 self.handlers[id_] = self.list_treeview
408 self.resize_from_another_muc = False
409 # we want to know when the the widget resizes, because that is
410 # an indication that the hpaned has moved...
411 self.hpaned = self.xml.get_object('hpaned')
412 id_ = self.hpaned.connect('notify', self.on_hpaned_notify)
413 self.handlers[id_] = self.hpaned
415 # set the position of the current hpaned
416 hpaned_position = gajim.config.get('gc-hpaned-position')
417 self.hpaned.set_position(hpaned_position)
419 #status_image, shown_nick, type, nickname, avatar
420 self.columns = [gtk.Image, str, str, str, gtk.gdk.Pixbuf]
421 store = gtk.TreeStore(*self.columns)
422 store.set_sort_func(C_NICK, self.tree_compare_iters)
423 store.set_sort_column_id(C_NICK, gtk.SORT_ASCENDING)
424 self.list_treeview.set_model(store)
426 # columns
427 column = gtk.TreeViewColumn()
428 # list of renderers with attributes / properties in the form:
429 # (name, renderer_object, expand?, attribute_name, attribute_value,
430 # cell_data_func, func_arg)
431 self.renderers_list = []
432 # Number of renderers plugins added
433 self.nb_ext_renderers = 0
434 self.renderers_propertys = {}
435 renderer_image = cell_renderer_image.CellRendererImage(0, 0)
436 self.renderers_propertys[renderer_image] = ('width', 26)
437 renderer_text = gtk.CellRendererText()
438 self.renderers_propertys[renderer_text] = ('ellipsize',
439 pango.ELLIPSIZE_END)
441 self.renderers_list += (
442 # status img
443 ('icon', renderer_image, False,
444 'image', C_IMG, tree_cell_data_func, self.list_treeview),
445 # contact name
446 ('name', renderer_text, True,
447 'markup', C_TEXT, tree_cell_data_func, self.list_treeview))
449 # avatar img
450 avater_renderer = ('avatar', gtk.CellRendererPixbuf(),
451 False, 'pixbuf', C_AVATAR,
452 tree_cell_data_func, self.list_treeview)
454 if gajim.config.get('avatar_position_in_roster') == 'right':
455 self.renderers_list.append(avater_renderer)
456 else:
457 self.renderers_list.insert(0, avater_renderer)
459 self.fill_column(column)
460 self.list_treeview.append_column(column)
462 # workaround to avoid gtk arrows to be shown
463 column = gtk.TreeViewColumn() # 2nd COLUMN
464 renderer = gtk.CellRendererPixbuf()
465 column.pack_start(renderer, expand=False)
466 self.list_treeview.append_column(column)
467 column.set_visible(False)
468 self.list_treeview.set_expander_column(column)
470 self.setup_seclabel(self.xml.get_object('label_selector'))
472 self.form_widget = None
474 gajim.ged.register_event_handler('gc-presence-received', ged.GUI1,
475 self._nec_gc_presence_received)
476 gajim.ged.register_event_handler('gc-message-received', ged.GUI1,
477 self._nec_gc_message_received)
478 gajim.ged.register_event_handler('vcard-published', ged.GUI1,
479 self._nec_vcard_published)
480 gajim.ged.register_event_handler('vcard-received', ged.GUI1,
481 self._nec_vcard_received)
482 gajim.ged.register_event_handler('gc-subject-received', ged.GUI1,
483 self._nec_gc_subject_received)
484 gajim.ged.register_event_handler('gc-config-changed-received', ged.GUI1,
485 self._nec_gc_config_changed_received)
486 gajim.ged.register_event_handler('signed-in', ged.GUI1,
487 self._nec_signed_in)
488 gajim.ged.register_event_handler('decrypted-message-received', ged.GUI2,
489 self._nec_decrypted_message_received)
490 gajim.gc_connected[self.account][self.room_jid] = False
491 # disable win, we are not connected yet
492 ChatControlBase.got_disconnected(self)
494 self.update_ui()
495 self.widget.show_all()
497 # PluginSystem: adding GUI extension point for this GroupchatControl
498 # instance object
499 gajim.plugin_manager.gui_extension_point('groupchat_control', self)
501 def fill_column(self, col):
502 for rend in self.renderers_list:
503 col.pack_start(rend[1], expand=rend[2])
504 col.add_attribute(rend[1], rend[3], rend[4])
505 col.set_cell_data_func(rend[1], rend[5], rend[6])
506 # set renderers propertys
507 for renderer in self.renderers_propertys.keys():
508 renderer.set_property(self.renderers_propertys[renderer][0],
509 self.renderers_propertys[renderer][1])
511 def tree_compare_iters(self, model, iter1, iter2):
513 Compare two iters to sort them
515 type1 = model[iter1][C_TYPE]
516 type2 = model[iter2][C_TYPE]
517 if not type1 or not type2:
518 return 0
519 nick1 = model[iter1][C_NICK]
520 nick2 = model[iter2][C_NICK]
521 if not nick1 or not nick2:
522 return 0
523 nick1 = nick1.decode('utf-8')
524 nick2 = nick2.decode('utf-8')
525 if type1 == 'role':
526 return locale.strcoll(nick1, nick2)
527 if type1 == 'contact':
528 gc_contact1 = gajim.contacts.get_gc_contact(self.account,
529 self.room_jid, nick1)
530 if not gc_contact1:
531 return 0
532 if type2 == 'contact':
533 gc_contact2 = gajim.contacts.get_gc_contact(self.account,
534 self.room_jid, nick2)
535 if not gc_contact2:
536 return 0
537 if type1 == 'contact' and type2 == 'contact' and \
538 gajim.config.get('sort_by_show_in_muc'):
539 cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4,
540 'invisible': 5, 'offline': 6, 'error': 7}
541 show1 = cshow[gc_contact1.show]
542 show2 = cshow[gc_contact2.show]
543 if show1 < show2:
544 return -1
545 elif show1 > show2:
546 return 1
547 # We compare names
548 name1 = gc_contact1.get_shown_name()
549 name2 = gc_contact2.get_shown_name()
550 return locale.strcoll(name1.lower(), name2.lower())
552 def on_msg_textview_populate_popup(self, textview, menu):
554 Override the default context menu and we prepend Clear
555 and the ability to insert a nick
557 ChatControlBase.on_msg_textview_populate_popup(self, textview, menu)
558 item = gtk.SeparatorMenuItem()
559 menu.prepend(item)
561 item = gtk.MenuItem(_('Insert Nickname'))
562 menu.prepend(item)
563 submenu = gtk.Menu()
564 item.set_submenu(submenu)
566 for nick in sorted(gajim.contacts.get_nick_list(self.account,
567 self.room_jid)):
568 item = gtk.MenuItem(nick, use_underline=False)
569 submenu.append(item)
570 id_ = item.connect('activate', self.append_nick_in_msg_textview,
571 nick)
572 self.handlers[id_] = item
574 menu.show_all()
576 def resize_occupant_treeview(self, position):
577 self.resize_from_another_muc = True
578 self.hpaned.set_position(position)
579 def reset_flag():
580 self.resize_from_another_muc = False
581 # Reset the flag when everything will be redrawn, and in particular when
582 # on_treeview_size_allocate will have been called.
583 gobject.idle_add(reset_flag)
585 def on_hpaned_notify(self, pane, gparamspec):
587 The MUC treeview has resized. Move the hpaned in all tabs to match
589 if gparamspec.name != 'position':
590 return
591 if self.resize_from_another_muc:
592 # Don't send the event to other MUC
593 return
595 hpaned_position = self.hpaned.get_position()
596 gajim.config.set('gc-hpaned-position', hpaned_position)
597 for account in gajim.gc_connected:
598 for room_jid in [i for i in gajim.gc_connected[account] if \
599 gajim.gc_connected[account][i] and i != self.room_jid]:
600 ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
601 account)
602 if not ctrl and room_jid in \
603 gajim.interface.minimized_controls[account]:
604 ctrl = gajim.interface.minimized_controls[account][room_jid]
605 if ctrl and gajim.config.get('one_message_window') != 'never':
606 ctrl.resize_occupant_treeview(hpaned_position)
608 def iter_contact_rows(self):
610 Iterate over all contact rows in the tree model
612 model = self.list_treeview.get_model()
613 role_iter = model.get_iter_root()
614 while role_iter:
615 contact_iter = model.iter_children(role_iter)
616 while contact_iter:
617 yield model[contact_iter]
618 contact_iter = model.iter_next(contact_iter)
619 role_iter = model.iter_next(role_iter)
621 def on_list_treeview_style_set(self, treeview, style):
623 When style (theme) changes, redraw all contacts
625 # Get the room_jid from treeview
626 for contact in self.iter_contact_rows():
627 nick = contact[C_NICK].decode('utf-8')
628 self.draw_contact(nick)
630 def on_list_treeview_selection_changed(self, selection):
631 model, selected_iter = selection.get_selected()
632 self.draw_contact(self.nick)
633 if self._last_selected_contact is not None:
634 self.draw_contact(self._last_selected_contact)
635 if selected_iter is None:
636 self._last_selected_contact = None
637 return
638 contact = model[selected_iter]
639 nick = contact[C_NICK].decode('utf-8')
640 self._last_selected_contact = nick
641 if contact[C_TYPE] != 'contact':
642 return
643 self.draw_contact(nick, selected=True, focus=True)
645 def get_tab_label(self, chatstate):
647 Markup the label if necessary. Returns a tuple such as: (new_label_str,
648 color) either of which can be None if chatstate is given that means we
649 have HE SENT US a chatstate
652 has_focus = self.parent_win.window.get_property('has-toplevel-focus')
653 current_tab = self.parent_win.get_active_control() == self
654 color_name = None
655 color = None
656 theme = gajim.config.get('roster_theme')
657 if chatstate == 'attention' and (not has_focus or not current_tab):
658 self.attention_flag = True
659 color_name = gajim.config.get_per('themes', theme,
660 'state_muc_directed_msg_color')
661 elif chatstate:
662 if chatstate == 'active' or (current_tab and has_focus):
663 self.attention_flag = False
664 # get active color from gtk
665 color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE]
666 elif chatstate == 'newmsg' and (not has_focus or not current_tab) \
667 and not self.attention_flag:
668 color_name = gajim.config.get_per('themes', theme,
669 'state_muc_msg_color')
670 if color_name:
671 color = gtk.gdk.colormap_get_system().alloc_color(color_name)
673 if self.is_continued:
674 # if this is a continued conversation
675 label_str = self.get_continued_conversation_name()
676 else:
677 label_str = self.name
679 # count waiting highlighted messages
680 unread = ''
681 num_unread = self.get_nb_unread()
682 if num_unread == 1:
683 unread = '*'
684 elif num_unread > 1:
685 unread = '[' + unicode(num_unread) + ']'
686 label_str = unread + label_str
687 return (label_str, color)
689 def get_tab_image(self, count_unread=True):
690 # Set tab image (always 16x16)
691 tab_image = None
692 if gajim.gc_connected[self.account][self.room_jid]:
693 tab_image = gtkgui_helpers.load_icon('muc_active')
694 else:
695 tab_image = gtkgui_helpers.load_icon('muc_inactive')
696 return tab_image
698 def update_ui(self):
699 ChatControlBase.update_ui(self)
700 for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
701 self.draw_contact(nick)
703 def _change_style(self, model, path, iter_):
704 model[iter_][C_NICK] = model[iter_][C_NICK]
706 def change_roster_style(self):
707 model = self.list_treeview.get_model()
708 model.foreach(self._change_style)
710 def repaint_themed_widgets(self):
711 ChatControlBase.repaint_themed_widgets(self)
712 self.change_roster_style()
714 def _update_banner_state_image(self):
715 banner_status_img = self.xml.get_object('gc_banner_status_image')
716 images = gajim.interface.jabber_state_images
717 if self.room_jid in gajim.gc_connected[self.account] and \
718 gajim.gc_connected[self.account][self.room_jid]:
719 image = 'muc_active'
720 else:
721 image = 'muc_inactive'
722 if '32' in images and image in images['32']:
723 muc_icon = images['32'][image]
724 if muc_icon.get_storage_type() != gtk.IMAGE_EMPTY:
725 pix = muc_icon.get_pixbuf()
726 banner_status_img.set_from_pixbuf(pix)
727 return
728 # we need to scale 16x16 to 32x32
729 muc_icon = images['16'][image]
730 pix = muc_icon.get_pixbuf()
731 scaled_pix = pix.scale_simple(32, 32, gtk.gdk.INTERP_BILINEAR)
732 banner_status_img.set_from_pixbuf(scaled_pix)
734 def get_continued_conversation_name(self):
736 Get the name of a continued conversation. Will return Continued
737 Conversation if there isn't any other contact in the room
739 nicks = []
740 for nick in gajim.contacts.get_nick_list(self.account,
741 self.room_jid):
742 if nick != self.nick:
743 nicks.append(nick)
744 if nicks != []:
745 title = ', '
746 title = _('Conversation with ') + title.join(nicks)
747 else:
748 title = _('Continued conversation')
749 return title
751 def draw_banner_text(self):
753 Draw the text in the fat line at the top of the window that houses the
754 room jid, subject
756 self.name_label.set_ellipsize(pango.ELLIPSIZE_END)
757 self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END)
758 font_attrs, font_attrs_small = self.get_font_attrs()
759 if self.is_continued:
760 name = self.get_continued_conversation_name()
761 else:
762 name = self.room_jid
763 text = '<span %s>%s</span>' % (font_attrs, name)
764 self.name_label.set_markup(text)
766 if self.subject:
767 subject = helpers.reduce_chars_newlines(self.subject, max_lines=2)
768 subject = gobject.markup_escape_text(subject)
769 subject_text = self.urlfinder.sub(self.make_href, subject)
770 subject_text = '<span %s>%s</span>' % (font_attrs_small,
771 subject_text)
773 # tooltip must always hold ALL the subject
774 self.event_box.set_tooltip_text(self.subject)
775 self.banner_status_label.set_no_show_all(False)
776 self.banner_status_label.show()
777 else:
778 subject_text = ''
779 self.event_box.set_has_tooltip(False)
780 self.banner_status_label.hide()
781 self.banner_status_label.set_no_show_all(True)
783 self.banner_status_label.set_markup(subject_text)
785 def prepare_context_menu(self, hide_buttonbar_items=False):
787 Set sensitivity state for configure_room
789 xml = gtkgui_helpers.get_gtk_builder('gc_control_popup_menu.ui')
790 menu = xml.get_object('gc_control_popup_menu')
792 bookmark_room_menuitem = xml.get_object('bookmark_room_menuitem')
793 change_nick_menuitem = xml.get_object('change_nick_menuitem')
794 configure_room_menuitem = xml.get_object('configure_room_menuitem')
795 destroy_room_menuitem = xml.get_object('destroy_room_menuitem')
796 change_subject_menuitem = xml.get_object('change_subject_menuitem')
797 history_menuitem = xml.get_object('history_menuitem')
798 minimize_menuitem = xml.get_object('minimize_menuitem')
799 request_voice_menuitem = xml.get_object('request_voice_menuitem')
800 bookmark_separator = xml.get_object('bookmark_separator')
801 separatormenuitem2 = xml.get_object('separatormenuitem2')
802 request_voice_separator = xml.get_object('request_voice_separator')
804 if gtkgui_helpers.gtk_icon_theme.has_icon('bookmark-new'):
805 img = gtk.Image()
806 img.set_from_icon_name('bookmark-new', gtk.ICON_SIZE_MENU)
807 bookmark_room_menuitem.set_image(img)
809 if hide_buttonbar_items:
810 change_nick_menuitem.hide()
811 change_subject_menuitem.hide()
812 bookmark_room_menuitem.hide()
813 history_menuitem.hide()
814 bookmark_separator.hide()
815 separatormenuitem2.hide()
816 else:
817 change_nick_menuitem.show()
818 change_subject_menuitem.show()
819 bookmark_room_menuitem.show()
820 history_menuitem.show()
821 bookmark_separator.show()
822 separatormenuitem2.show()
823 for bm in gajim.connections[self.account].bookmarks:
824 if bm['jid'] == self.room_jid:
825 bookmark_room_menuitem.hide()
826 bookmark_separator.hide()
827 break
829 ag = gtk.accel_groups_from_object(self.parent_win.window)[0]
830 change_nick_menuitem.add_accelerator('activate', ag, gtk.keysyms.n,
831 gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK, gtk.ACCEL_VISIBLE)
832 change_subject_menuitem.add_accelerator('activate', ag,
833 gtk.keysyms.t, gtk.gdk.MOD1_MASK, gtk.ACCEL_VISIBLE)
834 bookmark_room_menuitem.add_accelerator('activate', ag, gtk.keysyms.b,
835 gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
836 history_menuitem.add_accelerator('activate', ag, gtk.keysyms.h,
837 gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
839 if self.contact.jid in gajim.config.get_per('accounts', self.account,
840 'minimized_gc').split(' '):
841 minimize_menuitem.set_active(True)
842 if not gajim.connections[self.account].private_storage_supported:
843 bookmark_room_menuitem.set_sensitive(False)
844 if gajim.gc_connected[self.account][self.room_jid]:
845 c = gajim.contacts.get_gc_contact(self.account, self.room_jid,
846 self.nick)
847 if c.affiliation not in ('owner', 'admin'):
848 configure_room_menuitem.set_sensitive(False)
849 else:
850 configure_room_menuitem.set_sensitive(True)
851 if c.affiliation != 'owner':
852 destroy_room_menuitem.set_sensitive(False)
853 else:
854 destroy_room_menuitem.set_sensitive(True)
855 change_subject_menuitem.set_sensitive(True)
856 change_nick_menuitem.set_sensitive(True)
857 if c.role == 'visitor':
858 request_voice_menuitem.set_sensitive(True)
859 else:
860 request_voice_menuitem.set_sensitive(False)
861 else:
862 # We are not connected to this groupchat, disable unusable menuitems
863 configure_room_menuitem.set_sensitive(False)
864 destroy_room_menuitem.set_sensitive(False)
865 change_subject_menuitem.set_sensitive(False)
866 change_nick_menuitem.set_sensitive(False)
867 request_voice_menuitem.set_sensitive(False)
869 # connect the menuitems to their respective functions
870 id_ = bookmark_room_menuitem.connect('activate',
871 self._on_bookmark_room_menuitem_activate)
872 self.handlers[id_] = bookmark_room_menuitem
874 id_ = change_nick_menuitem.connect('activate',
875 self._on_change_nick_menuitem_activate)
876 self.handlers[id_] = change_nick_menuitem
878 id_ = configure_room_menuitem.connect('activate',
879 self._on_configure_room_menuitem_activate)
880 self.handlers[id_] = configure_room_menuitem
882 id_ = destroy_room_menuitem.connect('activate',
883 self._on_destroy_room_menuitem_activate)
884 self.handlers[id_] = destroy_room_menuitem
886 id_ = change_subject_menuitem.connect('activate',
887 self._on_change_subject_menuitem_activate)
888 self.handlers[id_] = change_subject_menuitem
890 id_ = history_menuitem.connect('activate',
891 self._on_history_menuitem_activate)
892 self.handlers[id_] = history_menuitem
894 id_ = request_voice_menuitem.connect('activate',
895 self._on_request_voice_menuitem_activate)
896 self.handlers[id_] = request_voice_menuitem
898 id_ = minimize_menuitem.connect('toggled',
899 self.on_minimize_menuitem_toggled)
900 self.handlers[id_] = minimize_menuitem
902 menu.connect('selection-done', self.destroy_menu,
903 change_nick_menuitem, change_subject_menuitem,
904 bookmark_room_menuitem, history_menuitem)
905 return menu
907 def destroy_menu(self, menu, change_nick_menuitem, change_subject_menuitem,
908 bookmark_room_menuitem, history_menuitem):
909 # destroy accelerators
910 ag = gtk.accel_groups_from_object(self.parent_win.window)[0]
911 change_nick_menuitem.remove_accelerator(ag, gtk.keysyms.n,
912 gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK)
913 change_subject_menuitem.remove_accelerator(ag, gtk.keysyms.t,
914 gtk.gdk.MOD1_MASK)
915 bookmark_room_menuitem.remove_accelerator(ag, gtk.keysyms.b,
916 gtk.gdk.CONTROL_MASK)
917 history_menuitem.remove_accelerator(ag, gtk.keysyms.h,
918 gtk.gdk.CONTROL_MASK)
919 # destroy menu
920 menu.destroy()
922 def _nec_vcard_published(self, obj):
923 if obj.conn.name != self.account:
924 return
925 show = gajim.SHOW_LIST[obj.conn.connected]
926 status = obj.conn.status
927 obj.conn.send_gc_status(self.nick, self.room_jid, show, status)
929 def _nec_vcard_received(self, obj):
930 if obj.conn.name != self.account:
931 return
932 if obj.jid != self.room_jid:
933 return
934 self.draw_avatar(obj.resource)
936 def _nec_gc_message_received(self, obj):
937 if obj.room_jid != self.room_jid or obj.conn.name != self.account:
938 return
939 if obj.captcha_form:
940 if self.form_widget:
941 self.form_widget.hide()
942 self.form_widget.destroy()
943 self.btn_box.destroy()
944 dataform = dataforms.ExtendForm(node=obj.captcha_form)
945 self.form_widget = dataforms_widget.DataFormWidget(dataform)
947 def on_send_dataform_clicked(widget):
948 if not self.form_widget:
949 return
950 form_node = self.form_widget.data_form.get_purged()
951 form_node.type = 'submit'
952 obj.conn.send_captcha(self.room_jid, form_node)
953 self.form_widget.hide()
954 self.form_widget.destroy()
955 self.btn_box.destroy()
956 self.form_widget = None
957 del self.btn_box
959 self.form_widget.connect('validated', on_send_dataform_clicked)
960 self.form_widget.show_all()
961 vbox = self.xml.get_object('gc_textviews_vbox')
962 vbox.pack_start(self.form_widget, expand=False, fill=False)
964 valid_button = gtk.Button(stock=gtk.STOCK_OK)
965 valid_button.connect('clicked', on_send_dataform_clicked)
966 self.btn_box = gtk.HButtonBox()
967 self.btn_box.set_layout(gtk.BUTTONBOX_END)
968 self.btn_box.pack_start(valid_button)
969 self.btn_box.show_all()
970 vbox.pack_start(self.btn_box, expand=False, fill=False)
971 if self.parent_win:
972 self.parent_win.redraw_tab(self, 'attention')
973 else:
974 self.attention_flag = True
975 if '100' in obj.status_code:
976 # Room is not anonymous
977 self.is_anonymous = False
978 if not obj.nick:
979 # message from server
980 self.print_conversation(obj.msgtxt, tim=obj.timestamp,
981 xhtml=obj.xhtml_msgtxt, displaymarking=obj.displaymarking)
982 else:
983 # message from someone
984 if obj.has_timestamp:
985 # don't print xhtml if it's an old message.
986 # Like that xhtml messages are grayed too.
987 self.print_old_conversation(obj.msgtxt, contact=obj.nick,
988 tim=obj.timestamp, xhtml=None,
989 displaymarking=obj.displaymarking)
990 else:
991 self.print_conversation(obj.msgtxt, contact=obj.nick,
992 tim=obj.timestamp, xhtml=obj.xhtml_msgtxt,
993 displaymarking=obj.displaymarking)
994 obj.needs_highlight = self.needs_visual_notification(obj.msgtxt)
996 def on_private_message(self, nick, msg, tim, xhtml, session, msg_id=None,
997 encrypted=False, displaymarking=None):
998 # Do we have a queue?
999 fjid = self.room_jid + '/' + nick
1000 no_queue = len(gajim.events.get_events(self.account, fjid)) == 0
1002 event = gajim.events.create_event('pm', (msg, '', 'incoming', tim,
1003 encrypted, '', msg_id, xhtml, session, displaymarking))
1004 gajim.events.add_event(self.account, fjid, event)
1006 autopopup = gajim.config.get('autopopup')
1007 autopopupaway = gajim.config.get('autopopupaway')
1008 iter_ = self.get_contact_iter(nick)
1009 path = self.list_treeview.get_model().get_path(iter_)
1010 if not autopopup or (not autopopupaway and \
1011 gajim.connections[self.account].connected > 2):
1012 if no_queue: # We didn't have a queue: we change icons
1013 model = self.list_treeview.get_model()
1014 state_images = \
1015 gajim.interface.roster.get_appropriate_state_images(
1016 self.room_jid, icon_name='event')
1017 image = state_images['event']
1018 model[iter_][C_IMG] = image
1019 if self.parent_win:
1020 self.parent_win.show_title()
1021 self.parent_win.redraw_tab(self)
1022 else:
1023 self._start_private_message(nick)
1024 # Scroll to line
1025 self.list_treeview.expand_row(path[0:1], False)
1026 self.list_treeview.scroll_to_cell(path)
1027 self.list_treeview.set_cursor(path)
1028 contact = gajim.contacts.get_contact_with_highest_priority(
1029 self.account, self.room_jid)
1030 if contact:
1031 gajim.interface.roster.draw_contact(self.room_jid, self.account)
1033 def get_contact_iter(self, nick):
1034 model = self.list_treeview.get_model()
1035 role_iter = model.get_iter_root()
1036 while role_iter:
1037 user_iter = model.iter_children(role_iter)
1038 while user_iter:
1039 if nick == model[user_iter][C_NICK].decode('utf-8'):
1040 return user_iter
1041 else:
1042 user_iter = model.iter_next(user_iter)
1043 role_iter = model.iter_next(role_iter)
1044 return None
1046 def print_old_conversation(self, text, contact='', tim=None, xhtml = None,
1047 displaymarking=None):
1048 if isinstance(text, str):
1049 text = unicode(text, 'utf-8')
1050 if contact:
1051 if contact == self.nick: # it's us
1052 kind = 'outgoing'
1053 else:
1054 kind = 'incoming'
1055 else:
1056 kind = 'status'
1057 if gajim.config.get('restored_messages_small'):
1058 small_attr = ['small']
1059 else:
1060 small_attr = []
1061 ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
1062 small_attr, small_attr + ['restored_message'],
1063 small_attr + ['restored_message'], count_as_new=False, xhtml=xhtml,
1064 displaymarking=displaymarking)
1066 def print_conversation(self, text, contact='', tim=None, xhtml=None,
1067 graphics=True, displaymarking=None):
1069 Print a line in the conversation
1071 If contact is set: it's a message from someone or an info message
1072 (contact = 'info' in such a case).
1073 If contact is not set: it's a message from the server or help.
1075 if isinstance(text, str):
1076 text = unicode(text, 'utf-8')
1077 other_tags_for_name = []
1078 other_tags_for_text = []
1079 if contact:
1080 if contact == self.nick: # it's us
1081 kind = 'outgoing'
1082 elif contact == 'info':
1083 kind = 'info'
1084 contact = None
1085 else:
1086 kind = 'incoming'
1087 # muc-specific chatstate
1088 if self.parent_win:
1089 self.parent_win.redraw_tab(self, 'newmsg')
1090 else:
1091 kind = 'status'
1093 if kind == 'incoming': # it's a message NOT from us
1094 # highlighting and sounds
1095 (highlight, sound) = self.highlighting_for_message(text, tim)
1096 if contact in self.gc_custom_colors:
1097 other_tags_for_name.append('gc_nickname_color_' + \
1098 str(self.gc_custom_colors[contact]))
1099 else:
1100 self.gc_count_nicknames_colors += 1
1101 if self.gc_count_nicknames_colors == self.number_of_colors:
1102 self.gc_count_nicknames_colors = 0
1103 self.gc_custom_colors[contact] = \
1104 self.gc_count_nicknames_colors
1105 other_tags_for_name.append('gc_nickname_color_' + \
1106 str(self.gc_count_nicknames_colors))
1107 if highlight:
1108 # muc-specific chatstate
1109 if self.parent_win:
1110 self.parent_win.redraw_tab(self, 'attention')
1111 else:
1112 self.attention_flag = True
1113 other_tags_for_name.append('bold')
1114 other_tags_for_text.append('marked')
1116 if contact in self.attention_list:
1117 self.attention_list.remove(contact)
1118 elif len(self.attention_list) > 6:
1119 self.attention_list.pop(0) # remove older
1120 self.attention_list.append(contact)
1122 if text.startswith('/me ') or text.startswith('/me\n'):
1123 other_tags_for_text.append('gc_nickname_color_' + \
1124 str(self.gc_custom_colors[contact]))
1126 self.check_and_possibly_add_focus_out_line()
1128 ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
1129 other_tags_for_name, [], other_tags_for_text, xhtml=xhtml,
1130 graphics=graphics, displaymarking=displaymarking)
1132 def get_nb_unread(self):
1133 type_events = ['printed_marked_gc_msg']
1134 if gajim.config.get('notify_on_all_muc_messages'):
1135 type_events.append('printed_gc_msg')
1136 nb = len(gajim.events.get_events(self.account, self.room_jid,
1137 type_events))
1138 nb += self.get_nb_unread_pm()
1139 return nb
1141 def get_nb_unread_pm(self):
1142 nb = 0
1143 for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
1144 nb += len(gajim.events.get_events(self.account, self.room_jid + \
1145 '/' + nick, ['pm']))
1146 return nb
1148 def highlighting_for_message(self, text, tim):
1150 Returns a 2-Tuple. The first says whether or not to highlight the text,
1151 the second, what sound to play
1153 highlight, sound = (None, None)
1155 # Are any of the defined highlighting words in the text?
1156 if self.needs_visual_notification(text):
1157 highlight = True
1158 if gajim.config.get_per('soundevents', 'muc_message_highlight',
1159 'enabled'):
1160 sound = 'highlight'
1162 # Do we play a sound on every muc message?
1163 elif gajim.config.get_per('soundevents', 'muc_message_received', \
1164 'enabled'):
1165 sound = 'received'
1167 # Is it a history message? Don't want sound-floods when we join.
1168 if tim != time.localtime():
1169 sound = None
1171 return (highlight, sound)
1173 def check_and_possibly_add_focus_out_line(self):
1175 Check and possibly add focus out line for room_jid if it needs it and
1176 does not already have it as last event. If it goes to add this line
1177 - remove previous line first
1179 win = gajim.interface.msg_win_mgr.get_window(self.room_jid,
1180 self.account)
1181 if win and self.room_jid == win.get_active_jid() and\
1182 win.window.get_property('has-toplevel-focus') and\
1183 self.parent_win.get_active_control() == self:
1184 # it's the current room and it's the focused window.
1185 # we have full focus (we are reading it!)
1186 return
1188 at_the_end = self.conv_textview.at_the_end()
1189 self.conv_textview.show_focus_out_line(scroll=at_the_end)
1191 def needs_visual_notification(self, text):
1193 Check text to see whether any of the words in (muc_highlight_words and
1194 nick) appear
1196 special_words = gajim.config.get('muc_highlight_words').split(';')
1197 special_words.append(self.nick)
1198 # Strip empties: ''.split(';') == [''] and would highlight everything.
1199 # Also lowercase everything for case insensitive compare.
1200 special_words = [word.lower() for word in special_words if word]
1201 text = text.lower()
1203 for special_word in special_words:
1204 found_here = text.find(special_word)
1205 while(found_here > -1):
1206 end_here = found_here + len(special_word)
1207 if (found_here == 0 or not text[found_here - 1].isalpha()) and \
1208 (end_here == len(text) or not text[end_here].isalpha()):
1209 # It is beginning of text or char before is not alpha AND
1210 # it is end of text or char after is not alpha
1211 return True
1212 # continue searching
1213 start = found_here + 1
1214 found_here = text.find(special_word, start)
1215 return False
1217 def set_subject(self, subject):
1218 self.subject = subject
1219 self.draw_banner_text()
1221 def _nec_gc_subject_received(self, obj):
1222 if obj.conn.name != self.account:
1223 return
1224 if obj.room_jid != self.room_jid:
1225 return
1226 self.set_subject(obj.subject)
1227 text = _('%(nick)s has set the subject to %(subject)s') % {
1228 'nick': obj.nickname, 'subject': obj.subject}
1229 if obj.has_timestamp:
1230 self.print_old_conversation(text)
1231 else:
1232 self.print_conversation(text)
1234 def _nec_gc_config_changed_received(self, obj):
1235 # statuscode is a list
1236 # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
1237 # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes...
1238 # -init
1239 if obj.room_jid != self.room_jid or obj.conn.name != self.account:
1240 return
1242 changes = []
1243 if '100' in obj.status_code:
1244 # Can be a presence (see chg_contact_status in groupchat_control.py)
1245 changes.append(_('Any occupant is allowed to see your full JID'))
1246 self.is_anonymous = False
1247 if '102' in obj.status_code:
1248 changes.append(_('Room now shows unavailable member'))
1249 if '103' in obj.status_code:
1250 changes.append(_('room now does not show unavailable members'))
1251 if '104' in obj.status_code:
1252 changes.append(_('A non-privacy-related room configuration change '
1253 'has occurred'))
1254 if '170' in obj.status_code:
1255 # Can be a presence (see chg_contact_status in groupchat_control.py)
1256 changes.append(_('Room logging is now enabled'))
1257 if '171' in obj.status_code:
1258 changes.append(_('Room logging is now disabled'))
1259 if '172' in obj.status_code:
1260 changes.append(_('Room is now non-anonymous'))
1261 self.is_anonymous = False
1262 if '173' in obj.status_code:
1263 changes.append(_('Room is now semi-anonymous'))
1264 self.is_anonymous = True
1265 if '174' in obj.status_code:
1266 changes.append(_('Room is now fully-anonymous'))
1267 self.is_anonymous = True
1269 for change in changes:
1270 self.print_conversation(change)
1272 def _nec_signed_in(self, obj):
1273 if obj.conn.name != self.account:
1274 return
1275 if self.room_jid in gajim.gc_connected[obj.conn.name] and \
1276 gajim.gc_connected[obj.conn.name][self.room_jid]:
1277 return
1278 password = gajim.gc_passwords.get(self.room_jid, '')
1279 obj.conn.join_gc(self.nick, self.room_jid, password)
1281 def _nec_decrypted_message_received(self, obj):
1282 if obj.conn.name != self.account:
1283 return
1284 if obj.gc_control == self and obj.resource:
1285 # We got a pm from this room
1286 nick = obj.resource
1287 if obj.session.control:
1288 # print if a control is open
1289 obj.session.control.print_conversation(obj.msgtxt,
1290 tim=obj.timestamp, xhtml=obj.xhtml, encrypted=obj.encrypted,
1291 displaymarking=obj.displaymarking)
1292 else:
1293 # otherwise pass it off to the control to be queued
1294 self.on_private_message(nick, obj.msgtxt, obj.timestamp,
1295 obj.xhtml, self.session, msg_id=obj.msg_id,
1296 encrypted=obj.encrypted, displaymarking=obj.displaymarking)
1298 def got_connected(self):
1299 # Make autorejoin stop.
1300 if self.autorejoin:
1301 gobject.source_remove(self.autorejoin)
1302 self.autorejoin = None
1304 gajim.gc_connected[self.account][self.room_jid] = True
1305 ChatControlBase.got_connected(self)
1306 # We don't redraw the whole banner here, because only icon change
1307 self._update_banner_state_image()
1308 if self.parent_win:
1309 self.parent_win.redraw_tab(self)
1310 gobject.idle_add(self.msg_textview.grab_focus)
1312 def got_disconnected(self):
1313 self.list_treeview.get_model().clear()
1314 nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid)
1315 for nick in nick_list:
1316 # Update pm chat window
1317 fjid = self.room_jid + '/' + nick
1318 gc_contact = gajim.contacts.get_gc_contact(self.account,
1319 self.room_jid, nick)
1321 ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.account)
1322 if ctrl:
1323 gc_contact.show = 'offline'
1324 gc_contact.status = ''
1325 ctrl.update_ui()
1326 if ctrl.parent_win:
1327 ctrl.parent_win.redraw_tab(ctrl)
1329 gajim.contacts.remove_gc_contact(self.account, gc_contact)
1330 gajim.gc_connected[self.account][self.room_jid] = False
1331 ChatControlBase.got_disconnected(self)
1332 # Tell connection to note the date we disconnect to avoid duplicate logs
1333 gajim.connections[self.account].gc_got_disconnected(self.room_jid)
1334 # We don't redraw the whole banner here, because only icon change
1335 self._update_banner_state_image()
1336 if self.parent_win:
1337 self.parent_win.redraw_tab(self)
1339 # Autorejoin stuff goes here.
1340 # Notice that we don't need to activate autorejoin if connection is lost
1341 # or in progress.
1342 if self.autorejoin is None and gajim.account_is_connected(self.account):
1343 ar_to = gajim.config.get('muc_autorejoin_timeout')
1344 if ar_to:
1345 self.autorejoin = gobject.timeout_add_seconds(ar_to,
1346 self.rejoin)
1348 def rejoin(self):
1349 if not self.autorejoin:
1350 return False
1351 password = gajim.gc_passwords.get(self.room_jid, '')
1352 gajim.connections[self.account].join_gc(self.nick, self.room_jid,
1353 password)
1354 return True
1356 def draw_roster(self):
1357 self.list_treeview.get_model().clear()
1358 for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
1359 gc_contact = gajim.contacts.get_gc_contact(self.account,
1360 self.room_jid, nick)
1361 self.add_contact_to_roster(nick, gc_contact.show, gc_contact.role,
1362 gc_contact.affiliation, gc_contact.status, gc_contact.jid)
1363 self.draw_all_roles()
1364 # Recalculate column width for ellipsizin
1365 self.list_treeview.columns_autosize()
1367 def on_send_pm(self, widget=None, model=None, iter_=None, nick=None,
1368 msg=None):
1370 Open a chat window and if msg is not None - send private message to a
1371 contact in a room
1373 if nick is None:
1374 nick = model[iter_][C_NICK].decode('utf-8')
1376 ctrl = self._start_private_message(nick)
1377 if ctrl and msg:
1378 ctrl.send_message(msg)
1380 def on_send_file(self, widget, gc_contact):
1382 Send a file to a contact in the room
1384 self._on_send_file(gc_contact)
1386 def draw_contact(self, nick, selected=False, focus=False):
1387 iter_ = self.get_contact_iter(nick)
1388 if not iter_:
1389 return
1390 model = self.list_treeview.get_model()
1391 gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
1392 nick)
1393 state_images = gajim.interface.jabber_state_images['16']
1394 if len(gajim.events.get_events(self.account, self.room_jid + '/' + \
1395 nick)):
1396 image = state_images['event']
1397 else:
1398 image = state_images[gc_contact.show]
1400 name = gobject.markup_escape_text(gc_contact.name)
1402 # Strike name if blocked
1403 fjid = self.room_jid + '/' + nick
1404 if helpers.jid_is_blocked(self.account, fjid):
1405 name = '<span strikethrough="true">%s</span>' % name
1407 status = gc_contact.status
1408 # add status msg, if not empty, under contact name in the treeview
1409 if status and gajim.config.get('show_status_msgs_in_roster'):
1410 status = status.strip()
1411 if status != '':
1412 status = helpers.reduce_chars_newlines(status, max_lines=1)
1413 # escape markup entities and make them small italic and fg color
1414 color = gtkgui_helpers.get_fade_color(self.list_treeview,
1415 selected, focus)
1416 colorstring = "#%04x%04x%04x" % (color.red, color.green,
1417 color.blue)
1418 name += ('\n<span size="small" style="italic" foreground="%s">'
1419 '%s</span>') % (colorstring, gobject.markup_escape_text(
1420 status))
1422 if image.get_storage_type() == gtk.IMAGE_PIXBUF and \
1423 gc_contact.affiliation != 'none' and gajim.config.get(
1424 'show_affiliation_in_groupchat'):
1425 pixbuf1 = image.get_pixbuf().copy()
1426 pixbuf2 = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 4, 4)
1427 if gc_contact.affiliation == 'owner':
1428 pixbuf2.fill(0xff0000ff) # Red
1429 elif gc_contact.affiliation == 'admin':
1430 pixbuf2.fill(0xffb200ff) # Oragne
1431 elif gc_contact.affiliation == 'member':
1432 pixbuf2.fill(0x00ff00ff) # Green
1433 pixbuf2.composite(pixbuf1, 12, 12, pixbuf2.get_property('width'),
1434 pixbuf2.get_property('height'), 0, 0, 1.0, 1.0,
1435 gtk.gdk.INTERP_HYPER, 127)
1436 image = gtk.image_new_from_pixbuf(pixbuf1)
1437 model[iter_][C_IMG] = image
1438 model[iter_][C_TEXT] = name
1440 def draw_avatar(self, nick):
1441 if not gajim.config.get('show_avatars_in_roster'):
1442 return
1443 model = self.list_treeview.get_model()
1444 iter_ = self.get_contact_iter(nick)
1445 if not iter_:
1446 return
1447 fake_jid = self.room_jid + '/' + nick
1448 pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid)
1449 if pixbuf in ('ask', None):
1450 scaled_pixbuf = None
1451 else:
1452 scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster')
1453 model[iter_][C_AVATAR] = scaled_pixbuf
1455 def draw_role(self, role):
1456 role_iter = self.get_role_iter(role)
1457 if not role_iter:
1458 return
1459 model = self.list_treeview.get_model()
1460 role_name = helpers.get_uf_role(role, plural=True)
1461 if gajim.config.get('show_contacts_number'):
1462 nbr_role, nbr_total = gajim.contacts.get_nb_role_total_gc_contacts(
1463 self.account, self.room_jid, role)
1464 role_name += ' (%s/%s)' % (repr(nbr_role), repr(nbr_total))
1465 model[role_iter][C_TEXT] = role_name
1467 def draw_all_roles(self):
1468 for role in ('visitor', 'participant', 'moderator'):
1469 self.draw_role(role)
1471 def _nec_gc_presence_received(self, obj):
1472 if obj.room_jid != self.room_jid or obj.conn.name != self.account:
1473 return
1474 if obj.ptype == 'error':
1475 return
1477 role = obj.role
1478 if not role:
1479 role = 'visitor'
1481 affiliation = obj.affiliation
1482 if not affiliation:
1483 affiliation = 'none'
1485 newly_created = False
1486 nick_jid = obj.nick
1488 # Set to true if role or affiliation have changed
1489 right_changed = False
1491 if obj.real_jid:
1492 # delete ressource
1493 simple_jid = gajim.get_jid_without_resource(obj.real_jid)
1494 nick_jid += ' (%s)' % simple_jid
1496 # status_code
1497 # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-\
1498 # init
1499 if obj.status_code:
1500 if '100' in obj.status_code:
1501 # Can be a message (see handle_event_gc_config_change in
1502 # gajim.py)
1503 self.print_conversation(
1504 _('Any occupant is allowed to see your full JID'))
1505 self.is_anonymous = False
1506 if '170' in obj.status_code:
1507 # Can be a message (see handle_event_gc_config_change in
1508 # gajim.py)
1509 self.print_conversation(_('Room logging is enabled'))
1510 if '201' in obj.status_code:
1511 self.print_conversation(_('A new room has been created'))
1512 if '210' in obj.status_code:
1513 self.print_conversation(\
1514 _('The server has assigned or modified your roomnick'))
1516 if obj.show in ('offline', 'error'):
1517 if obj.status_code:
1518 if '307' in obj.status_code:
1519 if obj.actor is None: # do not print 'kicked by None'
1520 s = _('%(nick)s has been kicked: %(reason)s') % {
1521 'nick': obj.nick, 'reason': obj.reason}
1522 else:
1523 s = _('%(nick)s has been kicked by %(who)s: '
1524 '%(reason)s') % {'nick': obj.nick, 'who': obj.actor,
1525 'reason': obj.reason}
1526 self.print_conversation(s, 'info', graphics=False)
1527 if obj.nick == self.nick and not gajim.config.get(
1528 'muc_autorejoin_on_kick'):
1529 self.autorejoin = False
1530 elif '301' in obj.status_code:
1531 if obj.actor is None: # do not print 'banned by None'
1532 s = _('%(nick)s has been banned: %(reason)s') % {
1533 'nick': obj.nick, 'reason': obj.reason}
1534 else:
1535 s = _('%(nick)s has been banned by %(who)s: '
1536 '%(reason)s') % {'nick': obj.nick, 'who': obj.actor,
1537 'reason': obj.reason}
1538 self.print_conversation(s, 'info', graphics=False)
1539 if obj.nick == self.nick:
1540 self.autorejoin = False
1541 elif '303' in obj.status_code: # Someone changed his or her nick
1542 if obj.new_nick == self.new_nick or obj.nick == self.nick:
1543 # We changed our nick
1544 self.nick = obj.new_nick
1545 self.new_nick = ''
1546 s = _('You are now known as %s') % self.nick
1547 # Stop all E2E sessions
1548 nick_list = gajim.contacts.get_nick_list(self.account,
1549 self.room_jid)
1550 for nick_ in nick_list:
1551 fjid_ = self.room_jid + '/' + nick_
1552 ctrl = gajim.interface.msg_win_mgr.get_control(
1553 fjid_, self.account)
1554 if ctrl and ctrl.session and \
1555 ctrl.session.enable_encryption:
1556 thread_id = ctrl.session.thread_id
1557 ctrl.session.terminate_e2e()
1558 gajim.connections[self.account].delete_session(
1559 fjid_, thread_id)
1560 ctrl.no_autonegotiation = False
1561 else:
1562 s = _('%(nick)s is now known as %(new_nick)s') % {
1563 'nick': obj.nick, 'new_nick': obj.new_nick}
1564 # We add new nick to muc roster here, so we don't see
1565 # that "new_nick has joined the room" when he just changed
1566 # nick.
1567 # add_contact_to_roster will be called a second time
1568 # after that, but that doesn't hurt
1569 self.add_contact_to_roster(obj.new_nick, obj.show, role,
1570 affiliation, obj.status, obj.real_jid)
1571 if obj.nick in self.attention_list:
1572 self.attention_list.remove(obj.nick)
1573 # keep nickname color
1574 if obj.nick in self.gc_custom_colors:
1575 self.gc_custom_colors[obj.new_nick] = \
1576 self.gc_custom_colors[obj.nick]
1577 # rename vcard / avatar
1578 puny_jid = helpers.sanitize_filename(self.room_jid)
1579 puny_nick = helpers.sanitize_filename(obj.nick)
1580 puny_new_nick = helpers.sanitize_filename(obj.new_nick)
1581 old_path = os.path.join(gajim.VCARD_PATH, puny_jid,
1582 puny_nick)
1583 new_path = os.path.join(gajim.VCARD_PATH, puny_jid,
1584 puny_new_nick)
1585 files = {old_path: new_path}
1586 path = os.path.join(gajim.AVATAR_PATH, puny_jid)
1587 # possible extensions
1588 for ext in ('.png', '.jpeg', '_notif_size_bw.png',
1589 '_notif_size_colored.png'):
1590 files[os.path.join(path, puny_nick + ext)] = \
1591 os.path.join(path, puny_new_nick + ext)
1592 for old_file in files:
1593 if os.path.exists(old_file) and old_file != \
1594 files[old_file]:
1595 if os.path.exists(files[old_file]) and \
1596 helpers.windowsify(old_file) != helpers.windowsify(
1597 files[old_file]):
1598 # Windows require this, but os.remove('test')
1599 # will also remove 'TEST'
1600 os.remove(files[old_file])
1601 os.rename(old_file, files[old_file])
1602 self.print_conversation(s, 'info', graphics=False)
1603 elif '321' in obj.status_code:
1604 s = _('%(nick)s has been removed from the room '
1605 '(%(reason)s)') % { 'nick': obj.nick,
1606 'reason': _('affiliation changed') }
1607 self.print_conversation(s, 'info', graphics=False)
1608 elif '322' in obj.status_code:
1609 s = _('%(nick)s has been removed from the room '
1610 '(%(reason)s)') % { 'nick': obj.nick,
1611 'reason': _('room configuration changed to '
1612 'members-only') }
1613 self.print_conversation(s, 'info', graphics=False)
1614 elif '332' in obj.status_code:
1615 s = _('%(nick)s has been removed from the room '
1616 '(%(reason)s)') % {'nick': obj.nick,
1617 'reason': _('system shutdown') }
1618 self.print_conversation(s, 'info', graphics=False)
1619 # Room has been destroyed.
1620 elif 'destroyed' in obj.status_code:
1621 self.autorejoin = False
1622 self.print_conversation(obj.reason, 'info', graphics=False)
1624 if len(gajim.events.get_events(self.account, jid=obj.fjid,
1625 types=['pm'])) == 0:
1626 self.remove_contact(obj.nick)
1627 self.draw_all_roles()
1628 else:
1629 c = gajim.contacts.get_gc_contact(self.account, self.room_jid,
1630 obj.nick)
1631 c.show = obj.show
1632 c.status = obj.status
1633 if obj.nick == self.nick and (not obj.status_code or \
1634 '303' not in obj.status_code): # We became offline
1635 self.got_disconnected()
1636 contact = gajim.contacts.\
1637 get_contact_with_highest_priority(self.account,
1638 self.room_jid)
1639 if contact:
1640 gajim.interface.roster.draw_contact(self.room_jid,
1641 self.account)
1642 if self.parent_win:
1643 self.parent_win.redraw_tab(self)
1644 else:
1645 iter_ = self.get_contact_iter(obj.nick)
1646 if not iter_:
1647 if '210' in obj.status_code:
1648 # Server changed our nick
1649 self.nick = obj.nick
1650 s = _('You are now known as %s') % obj.nick
1651 self.print_conversation(s, 'info', graphics=False)
1652 iter_ = self.add_contact_to_roster(obj.nick, obj.show, role,
1653 affiliation, obj.status, obj.real_jid)
1654 newly_created = True
1655 self.draw_all_roles()
1656 if obj.status_code and '201' in obj.status_code:
1657 # We just created the room
1658 gajim.connections[self.account].request_gc_config(
1659 self.room_jid)
1660 else:
1661 gc_c = gajim.contacts.get_gc_contact(self.account,
1662 self.room_jid, obj.nick)
1663 if not gc_c:
1664 log.error('%s has an iter, but no gc_contact instance' % \
1665 obj.nick)
1666 return
1667 # Re-get vcard if avatar has changed
1668 # We do that here because we may request it to the real JID if
1669 # we knows it. connections.py doesn't know it.
1670 con = gajim.connections[self.account]
1671 if gc_c and gc_c.jid:
1672 real_jid = gc_c.jid
1673 else:
1674 real_jid = obj.fjid
1675 if obj.fjid in obj.conn.vcard_shas:
1676 if obj.avatar_sha != obj.conn.vcard_shas[obj.fjid]:
1677 server = gajim.get_server_from_jid(self.room_jid)
1678 if not server.startswith('irc'):
1679 obj.conn.request_vcard(real_jid, obj.fjid)
1680 else:
1681 cached_vcard = obj.conn.get_cached_vcard(obj.fjid, True)
1682 if cached_vcard and 'PHOTO' in cached_vcard and \
1683 'SHA' in cached_vcard['PHOTO']:
1684 cached_sha = cached_vcard['PHOTO']['SHA']
1685 else:
1686 cached_sha = ''
1687 if cached_sha != obj.avatar_sha:
1688 # avatar has been updated
1689 # sha in mem will be updated later
1690 server = gajim.get_server_from_jid(self.room_jid)
1691 if not server.startswith('irc'):
1692 obj.conn.request_vcard(real_jid, obj.fjid)
1693 else:
1694 # save sha in mem NOW
1695 obj.conn.vcard_shas[obj.fjid] = obj.avatar_sha
1697 actual_affiliation = gc_c.affiliation
1698 if affiliation != actual_affiliation:
1699 if obj.actor:
1700 st = _('** Affiliation of %(nick)s has been set to '
1701 '%(affiliation)s by %(actor)s') % {'nick': nick_jid,
1702 'affiliation': affiliation, 'actor': obj.actor}
1703 else:
1704 st = _('** Affiliation of %(nick)s has been set to '
1705 '%(affiliation)s') % {'nick': nick_jid,
1706 'affiliation': affiliation}
1707 if obj.reason:
1708 st += ' (%s)' % obj.reason
1709 self.print_conversation(st, graphics=False)
1710 right_changed = True
1711 actual_role = self.get_role(obj.nick)
1712 if role != actual_role:
1713 self.remove_contact(obj.nick)
1714 self.add_contact_to_roster(obj.nick, obj.show, role,
1715 affiliation, obj.status, obj.real_jid)
1716 self.draw_role(actual_role)
1717 self.draw_role(role)
1718 if obj.actor:
1719 st = _('** Role of %(nick)s has been set to %(role)s '
1720 'by %(actor)s') % {'nick': nick_jid, 'role': role,
1721 'actor': obj.actor}
1722 else:
1723 st = _('** Role of %(nick)s has been set to '
1724 '%(role)s') % {'nick': nick_jid, 'role': role}
1725 if obj.reason:
1726 st += ' (%s)' % obj.reason
1727 self.print_conversation(st, graphics=False)
1728 right_changed = True
1729 else:
1730 if gc_c.show == obj.show and gc_c.status == obj.status and \
1731 gc_c.affiliation == affiliation: # no change
1732 return
1733 gc_c.show = obj.show
1734 gc_c.affiliation = affiliation
1735 gc_c.status = obj.status
1736 self.draw_contact(obj.nick)
1737 if (time.time() - self.room_creation) > 30 and obj.nick != self.nick \
1738 and (not obj.status_code or '303' not in obj.status_code) and not \
1739 right_changed:
1740 st = ''
1741 print_status = None
1742 for bookmark in gajim.connections[self.account].bookmarks:
1743 if bookmark['jid'] == self.room_jid:
1744 print_status = bookmark.get('print_status', None)
1745 break
1746 if not print_status:
1747 print_status = gajim.config.get('print_status_in_muc')
1748 if obj.show == 'offline':
1749 if obj.nick in self.attention_list:
1750 self.attention_list.remove(obj.nick)
1751 if obj.show == 'offline' and print_status in ('all', 'in_and_out') \
1752 and (not obj.status_code or '307' not in obj.status_code):
1753 st = _('%s has left') % nick_jid
1754 if obj.reason:
1755 st += ' [%s]' % obj.reason
1756 else:
1757 if newly_created and print_status in ('all', 'in_and_out'):
1758 st = _('%s has joined the group chat') % nick_jid
1759 elif print_status == 'all':
1760 st = _('%(nick)s is now %(status)s') % {'nick': nick_jid,
1761 'status': helpers.get_uf_show(obj.show)}
1762 if st:
1763 if obj.status:
1764 st += ' (' + obj.status + ')'
1765 self.print_conversation(st, graphics=False)
1767 def add_contact_to_roster(self, nick, show, role, affiliation, status,
1768 jid=''):
1769 model = self.list_treeview.get_model()
1770 role_name = helpers.get_uf_role(role, plural=True)
1772 resource = ''
1773 if jid:
1774 jids = jid.split('/', 1)
1775 j = jids[0]
1776 if len(jids) > 1:
1777 resource = jids[1]
1778 else:
1779 j = ''
1781 name = nick
1783 role_iter = self.get_role_iter(role)
1784 if not role_iter:
1785 role_iter = model.append(None,
1786 [gajim.interface.jabber_state_images['16']['closed'], role,
1787 'role', role_name, None] + [None] * self.nb_ext_renderers)
1788 self.draw_all_roles()
1789 iter_ = model.append(role_iter, [None, nick, 'contact', name, None] + \
1790 [None] * self.nb_ext_renderers)
1791 if not nick in gajim.contacts.get_nick_list(self.account,
1792 self.room_jid):
1793 gc_contact = gajim.contacts.create_gc_contact(
1794 room_jid=self.room_jid, account=self.account,
1795 name=nick, show=show, status=status, role=role,
1796 affiliation=affiliation, jid=j, resource=resource)
1797 gajim.contacts.add_gc_contact(self.account, gc_contact)
1798 self.draw_contact(nick)
1799 self.draw_avatar(nick)
1800 # Do not ask avatar to irc rooms as irc transports reply with messages
1801 server = gajim.get_server_from_jid(self.room_jid)
1802 if gajim.config.get('ask_avatars_on_startup') and \
1803 not server.startswith('irc'):
1804 fake_jid = self.room_jid + '/' + nick
1805 pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid)
1806 if pixbuf == 'ask':
1807 if j and not self.is_anonymous:
1808 gajim.connections[self.account].request_vcard(j, fake_jid)
1809 else:
1810 gajim.connections[self.account].request_vcard(fake_jid,
1811 fake_jid)
1812 if nick == self.nick: # we became online
1813 self.got_connected()
1814 self.list_treeview.expand_row((model.get_path(role_iter)), False)
1815 if self.is_continued:
1816 self.draw_banner_text()
1817 return iter_
1819 def get_role_iter(self, role):
1820 model = self.list_treeview.get_model()
1821 role_iter = model.get_iter_root()
1822 while role_iter:
1823 role_name = model[role_iter][C_NICK].decode('utf-8')
1824 if role == role_name:
1825 return role_iter
1826 role_iter = model.iter_next(role_iter)
1827 return None
1829 def remove_contact(self, nick):
1831 Remove a user from the contacts_list
1833 model = self.list_treeview.get_model()
1834 iter_ = self.get_contact_iter(nick)
1835 if not iter_:
1836 return
1837 gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
1838 nick)
1839 if gc_contact:
1840 gajim.contacts.remove_gc_contact(self.account, gc_contact)
1841 parent_iter = model.iter_parent(iter_)
1842 model.remove(iter_)
1843 if model.iter_n_children(parent_iter) == 0:
1844 model.remove(parent_iter)
1846 def send_message(self, message, xhtml=None, process_commands=True):
1848 Call this function to send our message
1850 if not message:
1851 return
1853 if process_commands and self.process_as_command(message):
1854 return
1856 message = helpers.remove_invalid_xml_chars(message)
1858 if not message:
1859 return
1861 label = self.get_seclabel()
1862 if message != '' or message != '\n':
1863 self.save_message(message, 'sent')
1865 # Send the message
1866 gajim.connections[self.account].send_gc_message(self.room_jid,
1867 message, xhtml=xhtml, label=label)
1868 self.msg_textview.get_buffer().set_text('')
1869 self.msg_textview.grab_focus()
1871 def get_role(self, nick):
1872 gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
1873 nick)
1874 if gc_contact:
1875 return gc_contact.role
1876 else:
1877 return 'visitor'
1879 def minimizable(self):
1880 if self.contact.jid in gajim.config.get_per('accounts', self.account,
1881 'minimized_gc').split(' '):
1882 return True
1883 return False
1885 def minimize(self, status='offline'):
1886 # Minimize it
1887 win = gajim.interface.msg_win_mgr.get_window(self.contact.jid,
1888 self.account)
1889 ctrl = win.get_control(self.contact.jid, self.account)
1891 ctrl_page = win.notebook.page_num(ctrl.widget)
1892 control = win.notebook.get_nth_page(ctrl_page)
1894 win.notebook.remove_page(ctrl_page)
1895 control.unparent()
1896 ctrl.parent_win = None
1898 gajim.interface.roster.add_groupchat(self.contact.jid, self.account,
1899 status = self.subject)
1901 del win._controls[self.account][self.contact.jid]
1903 def shutdown(self, status='offline'):
1904 # PluginSystem: calling shutdown of super class (ChatControlBase)
1905 # to let it remove it's GUI extension points
1906 super(GroupchatControl, self).shutdown()
1907 # PluginSystem: removing GUI extension points connected with
1908 # GrouphatControl instance object
1909 gajim.plugin_manager.remove_gui_extension_point('groupchat_control',
1910 self)
1912 # Preventing autorejoin from being activated
1913 self.autorejoin = False
1915 gajim.ged.remove_event_handler('gc-presence-received', ged.GUI1,
1916 self._nec_gc_presence_received)
1917 gajim.ged.remove_event_handler('gc-message-received', ged.GUI1,
1918 self._nec_gc_message_received)
1919 gajim.ged.remove_event_handler('vcard-published', ged.GUI1,
1920 self._nec_vcard_published)
1921 gajim.ged.remove_event_handler('vcard-received', ged.GUI1,
1922 self._nec_vcard_received)
1923 gajim.ged.remove_event_handler('gc-subject-received', ged.GUI1,
1924 self._nec_gc_subject_received)
1925 gajim.ged.remove_event_handler('gc-config-changed-received', ged.GUI1,
1926 self._nec_gc_config_changed_received)
1927 gajim.ged.remove_event_handler('signed-in', ged.GUI1,
1928 self._nec_signed_in)
1930 if self.room_jid in gajim.gc_connected[self.account] and \
1931 gajim.gc_connected[self.account][self.room_jid]:
1932 # Tell connection to note the date we disconnect to avoid duplicate
1933 # logs. We do it only when connected because if connection was lost
1934 # there may be new messages since disconnection.
1935 gajim.connections[self.account].gc_got_disconnected(self.room_jid)
1936 gajim.connections[self.account].send_gc_status(self.nick,
1937 self.room_jid, show='offline', status=status)
1938 nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid)
1939 for nick in nick_list:
1940 # Update pm chat window
1941 fjid = self.room_jid + '/' + nick
1942 ctrl = gajim.interface.msg_win_mgr.get_gc_control(fjid,
1943 self.account)
1944 if ctrl:
1945 contact = gajim.contacts.get_gc_contact(self.account,
1946 self.room_jid, nick)
1947 contact.show = 'offline'
1948 contact.status = ''
1949 ctrl.update_ui()
1950 ctrl.parent_win.redraw_tab(ctrl)
1951 for sess in gajim.connections[self.account].get_sessions(fjid):
1952 if sess.control:
1953 sess.control.no_autonegotiation = False
1954 if sess.enable_encryption:
1955 sess.terminate_e2e()
1956 gajim.connections[self.account].delete_session(fjid,
1957 sess.thread_id)
1958 # They can already be removed by the destroy function
1959 if self.room_jid in gajim.contacts.get_gc_list(self.account):
1960 gajim.contacts.remove_room(self.account, self.room_jid)
1961 del gajim.gc_connected[self.account][self.room_jid]
1962 # Save hpaned position
1963 gajim.config.set('gc-hpaned-position', self.hpaned.get_position())
1964 # remove all register handlers on wigets, created by self.xml
1965 # to prevent circular references among objects
1966 for i in self.handlers.keys():
1967 if self.handlers[i].handler_is_connected(i):
1968 self.handlers[i].disconnect(i)
1969 del self.handlers[i]
1970 # Remove unread events from systray
1971 gajim.events.remove_events(self.account, self.room_jid)
1973 def safe_shutdown(self):
1974 if self.minimizable():
1975 return True
1976 includes = gajim.config.get('confirm_close_muc_rooms').split(' ')
1977 excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ')
1978 # whether to ask for comfirmation before closing muc
1979 if (gajim.config.get('confirm_close_muc') or self.room_jid in includes)\
1980 and gajim.gc_connected[self.account][self.room_jid] and self.room_jid \
1981 not in excludes:
1982 return False
1983 return True
1985 def allow_shutdown(self, method, on_yes, on_no, on_minimize):
1986 if self.minimizable():
1987 on_minimize(self)
1988 return
1989 if method == self.parent_win.CLOSE_ESC:
1990 iter_ = self.list_treeview.get_selection().get_selected()[1]
1991 if iter_:
1992 self.list_treeview.get_selection().unselect_all()
1993 on_no(self)
1994 return
1995 includes = gajim.config.get('confirm_close_muc_rooms').split(' ')
1996 excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ')
1997 # whether to ask for comfirmation before closing muc
1998 if (gajim.config.get('confirm_close_muc') or self.room_jid in includes)\
1999 and gajim.gc_connected[self.account][self.room_jid] and self.room_jid \
2000 not in excludes:
2002 def on_ok(clicked):
2003 if clicked:
2004 # user does not want to be asked again
2005 gajim.config.set('confirm_close_muc', False)
2006 on_yes(self)
2008 def on_cancel(clicked):
2009 if clicked:
2010 # user does not want to be asked again
2011 gajim.config.set('confirm_close_muc', False)
2012 on_no(self)
2014 pritext = _('Are you sure you want to leave group chat "%s"?')\
2015 % self.name
2016 sectext = _('If you close this window, you will be disconnected '
2017 'from this group chat.')
2019 dialogs.ConfirmationDialogCheck(pritext, sectext,
2020 _('_Do not ask me again'), on_response_ok=on_ok,
2021 on_response_cancel=on_cancel)
2022 return
2024 on_yes(self)
2026 def set_control_active(self, state):
2027 self.conv_textview.allow_focus_out_line = True
2028 self.attention_flag = False
2029 ChatControlBase.set_control_active(self, state)
2030 if not state:
2031 # add the focus-out line to the tab we are leaving
2032 self.check_and_possibly_add_focus_out_line()
2033 # Sending active to undo unread state
2034 self.parent_win.redraw_tab(self, 'active')
2036 def get_specific_unread(self):
2037 # returns the number of the number of unread msgs
2038 # for room_jid & number of unread private msgs with each contact
2039 # that we have
2040 nb = 0
2041 for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
2042 fjid = self.room_jid + '/' + nick
2043 nb += len(gajim.events.get_events(self.account, fjid))
2044 # gc can only have messages as event
2045 return nb
2047 def _on_change_subject_menuitem_activate(self, widget):
2048 def on_ok(subject):
2049 # Note, we don't update self.subject since we don't know whether it
2050 # will work yet
2051 gajim.connections[self.account].send_gc_subject(self.room_jid,
2052 subject)
2054 dialogs.InputTextDialog(_('Changing Subject'),
2055 _('Please specify the new subject:'), input_str=self.subject,
2056 ok_handler=on_ok)
2058 def _on_change_nick_menuitem_activate(self, widget):
2059 if 'change_nick_dialog' in gajim.interface.instances:
2060 gajim.interface.instances['change_nick_dialog'].present()
2061 else:
2062 title = _('Changing Nickname')
2063 prompt = _('Please specify the new nickname you want to use:')
2064 gajim.interface.instances['change_nick_dialog'] = \
2065 dialogs.ChangeNickDialog(self.account, self.room_jid, title,
2066 prompt, change_nick=True)
2068 def _on_configure_room_menuitem_activate(self, widget):
2069 c = gajim.contacts.get_gc_contact(self.account, self.room_jid,
2070 self.nick)
2071 if c.affiliation == 'owner':
2072 gajim.connections[self.account].request_gc_config(self.room_jid)
2073 elif c.affiliation == 'admin':
2074 if self.room_jid not in gajim.interface.instances[self.account][
2075 'gc_config']:
2076 gajim.interface.instances[self.account]['gc_config'][
2077 self.room_jid] = config.GroupchatConfigWindow(self.account,
2078 self.room_jid)
2080 def _on_destroy_room_menuitem_activate(self, widget):
2081 def on_ok(reason, jid):
2082 if jid:
2083 # Test jid
2084 try:
2085 jid = helpers.parse_jid(jid)
2086 except Exception:
2087 dialogs.ErrorDialog(_('Invalid group chat Jabber ID'),
2088 _('The group chat Jabber ID has not allowed characters.'))
2089 return
2090 gajim.connections[self.account].destroy_gc_room(self.room_jid,
2091 reason, jid)
2093 # Ask for a reason
2094 dialogs.DoubleInputDialog(_('Destroying %s') % self.room_jid,
2095 _('You are going to definitively destroy this room.\n'
2096 'You may specify a reason below:'),
2097 _('You may also enter an alternate venue:'), ok_handler=on_ok)
2099 def _on_bookmark_room_menuitem_activate(self, widget):
2101 Bookmark the room, without autojoin and not minimized
2103 password = gajim.gc_passwords.get(self.room_jid, '')
2104 gajim.interface.add_gc_bookmark(self.account, self.name, self.room_jid,\
2105 '0', '0', password, self.nick)
2107 def _on_request_voice_menuitem_activate(self, widget):
2109 Request voice in the current room
2111 gajim.connections[self.account].request_voice(self.room_jid)
2113 def _on_drag_data_received(self, widget, context, x, y, selection,
2114 target_type, timestamp):
2115 # Invite contact to groupchat
2116 treeview = gajim.interface.roster.tree
2117 model = treeview.get_model()
2118 if not selection.data or target_type == 80:
2119 # target_type = 80 means a file is dropped
2120 return
2121 data = selection.data
2122 path = treeview.get_selection().get_selected_rows()[1][0]
2123 iter_ = model.get_iter(path)
2124 type_ = model[iter_][2]
2125 if type_ != 'contact': # source is not a contact
2126 return
2127 contact_jid = data.decode('utf-8')
2128 gajim.connections[self.account].send_invite(self.room_jid, contact_jid)
2130 def handle_message_textview_mykey_press(self, widget, event_keyval,
2131 event_keymod):
2132 # NOTE: handles mykeypress which is custom signal connected to this
2133 # CB in new_room(). for this singal see message_textview.py
2135 if not widget.get_sensitive():
2136 # Textview is not sensitive, don't handle keypress
2137 return
2138 # construct event instance from binding
2139 event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
2140 event.keyval = event_keyval
2141 event.state = event_keymod
2142 event.time = 0 # assign current time
2144 message_buffer = widget.get_buffer()
2145 start_iter, end_iter = message_buffer.get_bounds()
2147 if event.keyval == gtk.keysyms.Tab: # TAB
2148 cursor_position = message_buffer.get_insert()
2149 end_iter = message_buffer.get_iter_at_mark(cursor_position)
2150 text = message_buffer.get_text(start_iter, end_iter, False).decode(
2151 'utf-8')
2153 splitted_text = text.split()
2155 # HACK: Not the best soltution.
2156 if (text.startswith(self.COMMAND_PREFIX) and not
2157 text.startswith(self.COMMAND_PREFIX * 2) and \
2158 len(splitted_text) == 1):
2159 return super(GroupchatControl, self).\
2160 handle_message_textview_mykey_press(widget, event_keyval,
2161 event_keymod)
2163 # nick completion
2164 # check if tab is pressed with empty message
2165 if len(splitted_text): # if there are any words
2166 begin = splitted_text[-1] # last word we typed
2167 else:
2168 begin = ''
2170 gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char')
2171 with_refer_to_nick_char = False
2172 after_nick_len = 1 # the space that is printed after we type [Tab]
2174 # first part of this if : works fine even if refer_to_nick_char
2175 if gc_refer_to_nick_char and text.endswith(
2176 gc_refer_to_nick_char + ' '):
2177 with_refer_to_nick_char = True
2178 after_nick_len = len(gc_refer_to_nick_char + ' ')
2179 if len(self.nick_hits) and self.last_key_tabs and \
2180 text[:-after_nick_len].endswith(self.nick_hits[0]):
2181 # we should cycle
2182 # Previous nick in list may had a space inside, so we check text
2183 # and not splitted_text and store it into 'begin' var
2184 self.nick_hits.append(self.nick_hits[0])
2185 begin = self.nick_hits.pop(0)
2186 else:
2187 self.nick_hits = [] # clear the hit list
2188 list_nick = gajim.contacts.get_nick_list(self.account,
2189 self.room_jid)
2190 list_nick.sort(key=unicode.lower) # case-insensitive sort
2191 if begin == '':
2192 # empty message, show lasts nicks that highlighted us first
2193 for nick in self.attention_list:
2194 if nick in list_nick:
2195 list_nick.remove(nick)
2196 list_nick.insert(0, nick)
2198 list_nick.remove(self.nick) # Skip self
2199 for nick in list_nick:
2200 if nick.lower().startswith(begin.lower()):
2201 # the word is the begining of a nick
2202 self.nick_hits.append(nick)
2203 if len(self.nick_hits):
2204 if len(splitted_text) < 2 or with_refer_to_nick_char:
2205 # This is the 1st word of the line or no word or we are cycling
2206 # at the beginning, possibly with a space in one nick
2207 add = gc_refer_to_nick_char + ' '
2208 else:
2209 add = ' '
2210 start_iter = end_iter.copy()
2211 if self.last_key_tabs and with_refer_to_nick_char or (text and \
2212 text[-1] == ' '):
2213 # have to accomodate for the added space from last
2214 # completion
2215 # gc_refer_to_nick_char may be more than one char!
2216 start_iter.backward_chars(len(begin) + len(add))
2217 elif self.last_key_tabs and not gajim.config.get(
2218 'shell_like_completion'):
2219 # have to accomodate for the added space from last
2220 # completion
2221 start_iter.backward_chars(len(begin) + \
2222 len(gc_refer_to_nick_char))
2223 else:
2224 start_iter.backward_chars(len(begin))
2226 message_buffer.delete(start_iter, end_iter)
2227 # get a shell-like completion
2228 # if there's more than one nick for this completion, complete
2229 # only the part that all these nicks have in common
2230 if gajim.config.get('shell_like_completion') and \
2231 len(self.nick_hits) > 1:
2232 end = False
2233 completion = ''
2234 add = "" # if nick is not complete, don't add anything
2235 while not end and len(completion) < len(self.nick_hits[0]):
2236 completion = self.nick_hits[0][:len(completion)+1]
2237 for nick in self.nick_hits:
2238 if completion.lower() not in nick.lower():
2239 end = True
2240 completion = completion[:-1]
2241 break
2242 # if the current nick matches a COMPLETE existing nick,
2243 # and if the user tab TWICE, complete that nick (with the
2244 # "add")
2245 if self.last_key_tabs:
2246 for nick in self.nick_hits:
2247 if nick == completion:
2248 # The user seems to want this nick, so
2249 # complete it as if it were the only nick
2250 # available
2251 add = gc_refer_to_nick_char + ' '
2252 else:
2253 completion = self.nick_hits[0]
2254 message_buffer.insert_at_cursor(completion + add)
2255 self.last_key_tabs = True
2256 return True
2257 self.last_key_tabs = False
2259 def on_list_treeview_key_press_event(self, widget, event):
2260 if event.keyval == gtk.keysyms.Escape:
2261 selection = widget.get_selection()
2262 iter_ = selection.get_selected()[1]
2263 if iter_:
2264 widget.get_selection().unselect_all()
2265 return True
2267 def on_list_treeview_row_expanded(self, widget, iter_, path):
2269 When a row is expanded: change the icon of the arrow
2271 model = widget.get_model()
2272 image = gajim.interface.jabber_state_images['16']['opened']
2273 model[iter_][C_IMG] = image
2275 def on_list_treeview_row_collapsed(self, widget, iter_, path):
2277 When a row is collapsed: change the icon of the arrow
2279 model = widget.get_model()
2280 image = gajim.interface.jabber_state_images['16']['closed']
2281 model[iter_][C_IMG] = image
2283 def kick(self, widget, nick):
2285 Kick a user
2287 def on_ok(reason):
2288 gajim.connections[self.account].gc_set_role(self.room_jid, nick,
2289 'none', reason)
2291 # ask for reason
2292 dialogs.InputDialog(_('Kicking %s') % nick,
2293 _('You may specify a reason below:'), ok_handler=on_ok)
2295 def mk_menu(self, event, iter_):
2297 Make contact's popup menu
2299 model = self.list_treeview.get_model()
2300 nick = model[iter_][C_NICK].decode('utf-8')
2301 c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
2302 fjid = self.room_jid + '/' + nick
2303 jid = c.jid
2304 target_affiliation = c.affiliation
2305 target_role = c.role
2307 # looking for user's affiliation and role
2308 user_nick = self.nick
2309 user_affiliation = gajim.contacts.get_gc_contact(self.account,
2310 self.room_jid, user_nick).affiliation
2311 user_role = self.get_role(user_nick)
2313 # making menu from gtk builder
2314 xml = gtkgui_helpers.get_gtk_builder('gc_occupants_menu.ui')
2316 # these conditions were taken from JEP 0045
2317 item = xml.get_object('kick_menuitem')
2318 if user_role != 'moderator' or \
2319 (user_affiliation == 'admin' and target_affiliation == 'owner') or \
2320 (user_affiliation == 'member' and target_affiliation in ('admin',
2321 'owner')) or (user_affiliation == 'none' and target_affiliation != \
2322 'none'):
2323 item.set_sensitive(False)
2324 id_ = item.connect('activate', self.kick, nick)
2325 self.handlers[id_] = item
2327 item = xml.get_object('voice_checkmenuitem')
2328 item.set_active(target_role != 'visitor')
2329 if user_role != 'moderator' or \
2330 user_affiliation == 'none' or \
2331 (user_affiliation=='member' and target_affiliation!='none') or \
2332 target_affiliation in ('admin', 'owner'):
2333 item.set_sensitive(False)
2334 id_ = item.connect('activate', self.on_voice_checkmenuitem_activate,
2335 nick)
2336 self.handlers[id_] = item
2338 item = xml.get_object('moderator_checkmenuitem')
2339 item.set_active(target_role == 'moderator')
2340 if not user_affiliation in ('admin', 'owner') or \
2341 target_affiliation in ('admin', 'owner'):
2342 item.set_sensitive(False)
2343 id_ = item.connect('activate', self.on_moderator_checkmenuitem_activate,
2344 nick)
2345 self.handlers[id_] = item
2347 item = xml.get_object('ban_menuitem')
2348 if not user_affiliation in ('admin', 'owner') or \
2349 (target_affiliation in ('admin', 'owner') and\
2350 user_affiliation != 'owner'):
2351 item.set_sensitive(False)
2352 id_ = item.connect('activate', self.ban, jid)
2353 self.handlers[id_] = item
2355 item = xml.get_object('member_checkmenuitem')
2356 item.set_active(target_affiliation != 'none')
2357 if not user_affiliation in ('admin', 'owner') or \
2358 (user_affiliation != 'owner' and target_affiliation in ('admin',
2359 'owner')):
2360 item.set_sensitive(False)
2361 id_ = item.connect('activate', self.on_member_checkmenuitem_activate,
2362 jid)
2363 self.handlers[id_] = item
2365 item = xml.get_object('admin_checkmenuitem')
2366 item.set_active(target_affiliation in ('admin', 'owner'))
2367 if not user_affiliation == 'owner':
2368 item.set_sensitive(False)
2369 id_ = item.connect('activate', self.on_admin_checkmenuitem_activate,
2370 jid)
2371 self.handlers[id_] = item
2373 item = xml.get_object('owner_checkmenuitem')
2374 item.set_active(target_affiliation == 'owner')
2375 if not user_affiliation == 'owner':
2376 item.set_sensitive(False)
2377 id_ = item.connect('activate', self.on_owner_checkmenuitem_activate,
2378 jid)
2379 self.handlers[id_] = item
2381 item = xml.get_object('invite_menuitem')
2382 muc_icon = gtkgui_helpers.load_icon('muc_active')
2383 if muc_icon:
2384 item.set_image(muc_icon)
2385 if c.jid and c.name != self.nick:
2386 gui_menu_builder.build_invite_submenu(item, ((c, self.account),),
2387 ignore_rooms=[self.room_jid])
2388 else:
2389 item.set_sensitive(False)
2391 item = xml.get_object('information_menuitem')
2392 id_ = item.connect('activate', self.on_info, nick)
2393 self.handlers[id_] = item
2395 item = xml.get_object('history_menuitem')
2396 id_ = item.connect('activate', self.on_history, nick)
2397 self.handlers[id_] = item
2399 item = xml.get_object('add_to_roster_menuitem')
2400 our_jid = gajim.get_jid_from_account(self.account)
2401 if not jid or jid == our_jid or not gajim.connections[self.account].\
2402 roster_supported:
2403 item.set_sensitive(False)
2404 else:
2405 id_ = item.connect('activate', self.on_add_to_roster, jid)
2406 self.handlers[id_] = item
2408 item = xml.get_object('block_menuitem')
2409 item2 = xml.get_object('unblock_menuitem')
2410 if helpers.jid_is_blocked(self.account, fjid):
2411 item.set_no_show_all(True)
2412 item.hide()
2413 id_ = item2.connect('activate', self.on_unblock, nick)
2414 self.handlers[id_] = item2
2415 else:
2416 id_ = item.connect('activate', self.on_block, nick)
2417 self.handlers[id_] = item
2418 item2.set_no_show_all(True)
2419 item2.hide()
2421 item = xml.get_object('send_private_message_menuitem')
2422 id_ = item.connect('activate', self.on_send_pm, model, iter_)
2423 self.handlers[id_] = item
2425 item = xml.get_object('send_file_menuitem')
2426 # add a special img for send file menuitem
2427 path_to_upload_img = gtkgui_helpers.get_icon_path('gajim-upload')
2428 img = gtk.Image()
2429 img.set_from_file(path_to_upload_img)
2430 item.set_image(img)
2432 if not c.resource:
2433 item.set_sensitive(False)
2434 else:
2435 id_ = item.connect('activate', self.on_send_file, c)
2436 self.handlers[id_] = item
2438 # show the popup now!
2439 menu = xml.get_object('gc_occupants_menu')
2440 menu.show_all()
2441 menu.popup(None, None, None, event.button, event.time)
2443 def _start_private_message(self, nick):
2444 gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
2445 nick_jid = gc_c.get_full_jid()
2447 ctrl = gajim.interface.msg_win_mgr.get_control(nick_jid, self.account)
2448 if not ctrl:
2449 ctrl = gajim.interface.new_private_chat(gc_c, self.account)
2451 if ctrl:
2452 ctrl.parent_win.set_active_tab(ctrl)
2454 return ctrl
2456 def on_row_activated(self, widget, path):
2458 When an iter is activated (dubblick or single click if gnome is set this
2461 model = widget.get_model()
2462 if len(path) == 1: # It's a group
2463 if (widget.row_expanded(path)):
2464 widget.collapse_row(path)
2465 else:
2466 widget.expand_row(path, False)
2467 else: # We want to send a private message
2468 nick = model[path][C_NICK].decode('utf-8')
2469 self._start_private_message(nick)
2471 def on_list_treeview_row_activated(self, widget, path, col=0):
2473 When an iter is double clicked: open the chat window
2475 if not gajim.single_click:
2476 self.on_row_activated(widget, path)
2478 def on_list_treeview_button_press_event(self, widget, event):
2480 Popup user's group's or agent menu
2482 # hide tooltip, no matter the button is pressed
2483 self.tooltip.hide_tooltip()
2484 try:
2485 pos = widget.get_path_at_pos(int(event.x), int(event.y))
2486 path, x = pos[0], pos[2]
2487 except TypeError:
2488 widget.get_selection().unselect_all()
2489 return
2490 if event.button == 3: # right click
2491 widget.get_selection().select_path(path)
2492 model = widget.get_model()
2493 iter_ = model.get_iter(path)
2494 if len(path) == 2:
2495 self.mk_menu(event, iter_)
2496 return True
2498 elif event.button == 2: # middle click
2499 widget.get_selection().select_path(path)
2500 model = widget.get_model()
2501 iter_ = model.get_iter(path)
2502 if len(path) == 2:
2503 nick = model[iter_][C_NICK].decode('utf-8')
2504 self._start_private_message(nick)
2505 return True
2507 elif event.button == 1: # left click
2508 if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK:
2509 self.on_row_activated(widget, path)
2510 return True
2511 else:
2512 model = widget.get_model()
2513 iter_ = model.get_iter(path)
2514 nick = model[iter_][C_NICK].decode('utf-8')
2515 if not nick in gajim.contacts.get_nick_list(self.account,
2516 self.room_jid):
2517 # it's a group
2518 if x < 27:
2519 if (widget.row_expanded(path)):
2520 widget.collapse_row(path)
2521 else:
2522 widget.expand_row(path, False)
2523 elif event.state & gtk.gdk.SHIFT_MASK:
2524 self.append_nick_in_msg_textview(self.msg_textview, nick)
2525 self.msg_textview.grab_focus()
2526 return True
2528 def append_nick_in_msg_textview(self, widget, nick):
2529 message_buffer = self.msg_textview.get_buffer()
2530 start_iter, end_iter = message_buffer.get_bounds()
2531 cursor_position = message_buffer.get_insert()
2532 end_iter = message_buffer.get_iter_at_mark(cursor_position)
2533 text = message_buffer.get_text(start_iter, end_iter, False)
2534 start = ''
2535 if text: # Cursor is not at first position
2536 if not text[-1] in (' ', '\n', '\t'):
2537 start = ' '
2538 add = ' '
2539 else:
2540 gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char')
2541 add = gc_refer_to_nick_char + ' '
2542 message_buffer.insert_at_cursor(start + nick + add)
2544 def on_list_treeview_motion_notify_event(self, widget, event):
2545 model = widget.get_model()
2546 props = widget.get_path_at_pos(int(event.x), int(event.y))
2547 if self.tooltip.timeout > 0:
2548 if not props or self.tooltip.id != props[0]:
2549 self.tooltip.hide_tooltip()
2550 if props:
2551 [row, col, x, y] = props
2552 iter_ = None
2553 try:
2554 iter_ = model.get_iter(row)
2555 except Exception:
2556 self.tooltip.hide_tooltip()
2557 return
2558 typ = model[iter_][C_TYPE].decode('utf-8')
2559 if typ == 'contact':
2560 account = self.account
2562 if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
2563 self.tooltip.id = row
2564 nick = model[iter_][C_NICK].decode('utf-8')
2565 self.tooltip.timeout = gobject.timeout_add(500,
2566 self.show_tooltip, gajim.contacts.get_gc_contact(
2567 account, self.room_jid, nick))
2569 def on_list_treeview_leave_notify_event(self, widget, event):
2570 props = widget.get_path_at_pos(int(event.x), int(event.y))
2571 if self.tooltip.timeout > 0:
2572 if not props or self.tooltip.id == props[0]:
2573 self.tooltip.hide_tooltip()
2575 def show_tooltip(self, contact):
2576 if not self.list_treeview.window:
2577 # control has been destroyed since tooltip was requested
2578 return
2579 pointer = self.list_treeview.get_pointer()
2580 props = self.list_treeview.get_path_at_pos(pointer[0], pointer[1])
2581 # check if the current pointer is at the same path
2582 # as it was before setting the timeout
2583 if props and self.tooltip.id == props[0]:
2584 rect = self.list_treeview.get_cell_area(props[0], props[1])
2585 position = self.list_treeview.window.get_origin()
2586 self.tooltip.show_tooltip(contact, rect.height,
2587 position[1] + rect.y)
2588 else:
2589 self.tooltip.hide_tooltip()
2591 def grant_voice(self, widget, nick):
2593 Grant voice privilege to a user
2595 gajim.connections[self.account].gc_set_role(self.room_jid, nick,
2596 'participant')
2598 def revoke_voice(self, widget, nick):
2600 Revoke voice privilege to a user
2602 gajim.connections[self.account].gc_set_role(self.room_jid, nick,
2603 'visitor')
2605 def grant_moderator(self, widget, nick):
2607 Grant moderator privilege to a user
2609 gajim.connections[self.account].gc_set_role(self.room_jid, nick,
2610 'moderator')
2612 def revoke_moderator(self, widget, nick):
2614 Revoke moderator privilege to a user
2616 gajim.connections[self.account].gc_set_role(self.room_jid, nick,
2617 'participant')
2619 def ban(self, widget, jid):
2621 Ban a user
2623 def on_ok(reason):
2624 gajim.connections[self.account].gc_set_affiliation(self.room_jid,
2625 jid, 'outcast', reason)
2627 # to ban we know the real jid. so jid is not fakejid
2628 nick = gajim.get_nick_from_jid(jid)
2629 # ask for reason
2630 dialogs.InputDialog(_('Banning %s') % nick,
2631 _('You may specify a reason below:'), ok_handler=on_ok)
2633 def grant_membership(self, widget, jid):
2635 Grant membership privilege to a user
2637 gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
2638 'member')
2640 def revoke_membership(self, widget, jid):
2642 Revoke membership privilege to a user
2644 gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
2645 'none')
2647 def grant_admin(self, widget, jid):
2649 Grant administrative privilege to a user
2651 gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
2652 'admin')
2654 def revoke_admin(self, widget, jid):
2656 Revoke administrative privilege to a user
2658 gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
2659 'member')
2661 def grant_owner(self, widget, jid):
2663 Grant owner privilege to a user
2665 gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
2666 'owner')
2668 def revoke_owner(self, widget, jid):
2670 Revoke owner privilege to a user
2672 gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
2673 'admin')
2675 def on_info(self, widget, nick):
2677 Call vcard_information_window class to display user's information
2679 gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
2680 nick)
2681 contact = gc_contact.as_contact()
2682 if contact.jid in gajim.interface.instances[self.account]['infos']:
2683 gajim.interface.instances[self.account]['infos'][contact.jid].\
2684 window.present()
2685 else:
2686 gajim.interface.instances[self.account]['infos'][contact.jid] = \
2687 vcard.VcardWindow(contact, self.account, gc_contact)
2689 def on_history(self, widget, nick):
2690 jid = gajim.construct_fjid(self.room_jid, nick)
2691 self._on_history_menuitem_activate(widget=widget, jid=jid)
2693 def on_add_to_roster(self, widget, jid):
2694 dialogs.AddNewContactWindow(self.account, jid)
2696 def on_block(self, widget, nick):
2697 fjid = self.room_jid + '/' + nick
2698 connection = gajim.connections[self.account]
2699 if fjid in connection.blocked_contacts:
2700 return
2701 new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny',
2702 'value' : fjid, 'child': [u'message', u'iq', u'presence-out']}
2703 connection.blocked_list.append(new_rule)
2704 connection.blocked_contacts.append(fjid)
2705 self.draw_contact(nick)
2706 connection.set_privacy_list('block', connection.blocked_list)
2707 if len(connection.blocked_list) == 1:
2708 connection.set_active_list('block')
2709 connection.set_default_list('block')
2710 connection.get_privacy_list('block')
2712 def on_unblock(self, widget, nick):
2713 fjid = self.room_jid + '/' + nick
2714 connection = gajim.connections[self.account]
2715 connection.new_blocked_list = []
2716 # needed for draw_contact:
2717 if fjid in connection.blocked_contacts:
2718 connection.blocked_contacts.remove(fjid)
2719 self.draw_contact(nick)
2720 for rule in connection.blocked_list:
2721 if rule['action'] != 'deny' or rule['type'] != 'jid' \
2722 or rule['value'] != fjid:
2723 connection.new_blocked_list.append(rule)
2725 connection.set_privacy_list('block', connection.new_blocked_list)
2726 connection.get_privacy_list('block')
2727 if len(connection.new_blocked_list) == 0:
2728 connection.blocked_list = []
2729 connection.blocked_contacts = []
2730 connection.blocked_groups = []
2731 connection.set_default_list('')
2732 connection.set_active_list('')
2733 connection.del_privacy_list('block')
2734 if 'privay_list_block' in gajim.interface.instances[self.account]:
2735 del gajim.interface.instances[self.account]\
2736 ['privay_list_block']
2738 def on_voice_checkmenuitem_activate(self, widget, nick):
2739 if widget.get_active():
2740 self.grant_voice(widget, nick)
2741 else:
2742 self.revoke_voice(widget, nick)
2744 def on_moderator_checkmenuitem_activate(self, widget, nick):
2745 if widget.get_active():
2746 self.grant_moderator(widget, nick)
2747 else:
2748 self.revoke_moderator(widget, nick)
2750 def on_member_checkmenuitem_activate(self, widget, jid):
2751 if widget.get_active():
2752 self.grant_membership(widget, jid)
2753 else:
2754 self.revoke_membership(widget, jid)
2756 def on_admin_checkmenuitem_activate(self, widget, jid):
2757 if widget.get_active():
2758 self.grant_admin(widget, jid)
2759 else:
2760 self.revoke_admin(widget, jid)
2762 def on_owner_checkmenuitem_activate(self, widget, jid):
2763 if widget.get_active():
2764 self.grant_owner(widget, jid)
2765 else:
2766 self.revoke_owner(widget, jid)