2 ## src/groupchat_control.py
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/>.
37 import gui_menu_builder
38 import message_control
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
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
70 def set_renderer_color(treeview
, renderer
, set_background
=True):
72 Set style for group row, using PRELIGHT system color
75 bgcolor
= treeview
.style
.bg
[gtk
.STATE_PRELIGHT
]
76 renderer
.set_property('cell-background-gdk', bgcolor
)
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
92 renderer
.set_property('xalign', 0.5)
93 if parent_iter
and (model
[iter_
][C_AVATAR
] or avatar_position
== \
95 renderer
.set_property('visible', True)
96 renderer
.set_property('width', gajim
.config
.get(
97 'roster_avatar_width'))
99 renderer
.set_property('visible', False)
101 bgcolor
= gajim
.config
.get_per('themes', theme
, 'contactbgcolor')
103 renderer
.set_property('cell-background', bgcolor
)
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')
110 renderer
.set_property('foreground', color
)
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')
118 renderer
.set_property('cell-background', bgcolor
)
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')
125 renderer
.set_property('foreground', color
)
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
,
143 if room_jid
in gajim
.interface
.minimized_controls
[account
]:
144 room_ctrl
= gajim
.interface
.minimized_controls
[account
][room_jid
]
146 self
.room_name
= room_ctrl
.name
148 self
.room_name
= room_jid
149 self
.gc_contact
= gc_contact
150 ChatControl
.__init
__(self
, parent_win
, contact
, account
, session
)
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
)
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():
168 self
.update_contact()
170 def _nec_gc_presence_received(self
, obj
):
171 if obj
.conn
.name
!= self
.account
:
173 if obj
.fjid
!= self
.full_jid
:
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
},
179 gc_c
= gajim
.contacts
.get_gc_contact(obj
.conn
.name
, obj
.room_jid
,
181 c
= gc_c
.as_contact()
182 self
.gc_contact
= gc_c
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
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
,
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')
205 self
.print_conversation(' (', 'status', simple
=True)
206 self
.print_conversation('%s' % (obj
.status
), 'status',
208 self
.print_conversation(')', 'status', simple
=True)
209 self
.parent_win
.redraw_tab(self
)
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
)
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
,
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
)
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
})
236 ChatControl
.send_message(self
, message
, xhtml
=xhtml
,
237 process_commands
=process_commands
)
240 if self
.contact
.show
== 'offline':
241 self
.got_disconnected()
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
253 fjid
= self
.gc_contact
.get_full_jid()
254 new_sess
= gajim
.connections
[self
.account
].make_new_session(fjid
,
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
)
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
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
:
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
)
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')
361 for bm
in gajim
.connections
[self
.account
].bookmarks
:
362 if bm
['jid'] == self
.room_jid
:
363 self
.name
= bm
['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
385 self
.last_key_tabs
= False
389 self
.tooltip
= tooltips
.GCTooltip()
392 self
.gc_count_nicknames_colors
= 0
393 self
.gc_custom_colors
= {}
394 self
.number_of_colors
= len(gajim
.config
.get('gc_nicknames_colors').\
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
)
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',
441 self
.renderers_list
+= (
443 ('icon', renderer_image
, False,
444 'image', C_IMG
, tree_cell_data_func
, self
.list_treeview
),
446 ('name', renderer_text
, True,
447 'markup', C_TEXT
, tree_cell_data_func
, self
.list_treeview
))
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
)
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
,
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
)
495 self
.widget
.show_all()
497 # PluginSystem: adding GUI extension point for this GroupchatControl
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
:
519 nick1
= model
[iter1
][C_NICK
]
520 nick2
= model
[iter2
][C_NICK
]
521 if not nick1
or not nick2
:
523 nick1
= nick1
.decode('utf-8')
524 nick2
= nick2
.decode('utf-8')
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
)
532 if type2
== 'contact':
533 gc_contact2
= gajim
.contacts
.get_gc_contact(self
.account
,
534 self
.room_jid
, nick2
)
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
]
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()
561 item
= gtk
.MenuItem(_('Insert Nickname'))
564 item
.set_submenu(submenu
)
566 for nick
in sorted(gajim
.contacts
.get_nick_list(self
.account
,
568 item
= gtk
.MenuItem(nick
, use_underline
=False)
570 id_
= item
.connect('activate', self
.append_nick_in_msg_textview
,
572 self
.handlers
[id_
] = item
576 def resize_occupant_treeview(self
, position
):
577 self
.resize_from_another_muc
= True
578 self
.hpaned
.set_position(position
)
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':
591 if self
.resize_from_another_muc
:
592 # Don't send the event to other MUC
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
,
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()
615 contact_iter
= model
.iter_children(role_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
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':
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
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')
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')
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()
677 label_str
= self
.name
679 # count waiting highlighted messages
681 num_unread
= self
.get_nb_unread()
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)
692 if gajim
.gc_connected
[self
.account
][self
.room_jid
]:
693 tab_image
= gtkgui_helpers
.load_icon('muc_active')
695 tab_image
= gtkgui_helpers
.load_icon('muc_inactive')
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
]:
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
)
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
740 for nick
in gajim
.contacts
.get_nick_list(self
.account
,
742 if nick
!= self
.nick
:
746 title
= _('Conversation with ') + title
.join(nicks
)
748 title
= _('Continued conversation')
751 def draw_banner_text(self
):
753 Draw the text in the fat line at the top of the window that houses the
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()
763 text
= '<span %s>%s</span>' % (font_attrs
, name
)
764 self
.name_label
.set_markup(text
)
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
,
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()
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'):
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()
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()
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
,
847 if c
.affiliation
not in ('owner', 'admin'):
848 configure_room_menuitem
.set_sensitive(False)
850 configure_room_menuitem
.set_sensitive(True)
851 if c
.affiliation
!= 'owner':
852 destroy_room_menuitem
.set_sensitive(False)
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)
860 request_voice_menuitem
.set_sensitive(False)
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
)
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
,
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
)
922 def _nec_vcard_published(self
, obj
):
923 if obj
.conn
.name
!= self
.account
:
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
:
932 if obj
.jid
!= self
.room_jid
:
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
:
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
:
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
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)
972 self
.parent_win
.redraw_tab(self
, 'attention')
974 self
.attention_flag
= True
975 if '100' in obj
.status_code
:
976 # Room is not anonymous
977 self
.is_anonymous
= False
979 # message from server
980 self
.print_conversation(obj
.msgtxt
, tim
=obj
.timestamp
,
981 xhtml
=obj
.xhtml_msgtxt
, displaymarking
=obj
.displaymarking
)
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
)
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()
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
1020 self
.parent_win
.show_title()
1021 self
.parent_win
.redraw_tab(self
)
1023 self
._start
_private
_message
(nick
)
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
)
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()
1037 user_iter
= model
.iter_children(role_iter
)
1039 if nick
== model
[user_iter
][C_NICK
].decode('utf-8'):
1042 user_iter
= model
.iter_next(user_iter
)
1043 role_iter
= model
.iter_next(role_iter
)
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')
1051 if contact
== self
.nick
: # it's us
1057 if gajim
.config
.get('restored_messages_small'):
1058 small_attr
= ['small']
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
= []
1080 if contact
== self
.nick
: # it's us
1082 elif contact
== 'info':
1087 # muc-specific chatstate
1089 self
.parent_win
.redraw_tab(self
, 'newmsg')
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
]))
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
))
1108 # muc-specific chatstate
1110 self
.parent_win
.redraw_tab(self
, 'attention')
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
,
1138 nb
+= self
.get_nb_unread_pm()
1141 def get_nb_unread_pm(self
):
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']))
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
):
1158 if gajim
.config
.get_per('soundevents', 'muc_message_highlight',
1162 # Do we play a sound on every muc message?
1163 elif gajim
.config
.get_per('soundevents', 'muc_message_received', \
1167 # Is it a history message? Don't want sound-floods when we join.
1168 if tim
!= time
.localtime():
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
,
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!)
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
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
]
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
1212 # continue searching
1213 start
= found_here
+ 1
1214 found_here
= text
.find(special_word
, start
)
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
:
1224 if obj
.room_jid
!= self
.room_jid
:
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
)
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...
1239 if obj
.room_jid
!= self
.room_jid
or obj
.conn
.name
!= self
.account
:
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 '
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
:
1275 if self
.room_jid
in gajim
.gc_connected
[obj
.conn
.name
] and \
1276 gajim
.gc_connected
[obj
.conn
.name
][self
.room_jid
]:
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
:
1284 if obj
.gc_control
== self
and obj
.resource
:
1285 # We got a pm from this room
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
)
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.
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
()
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
)
1323 gc_contact
.show
= 'offline'
1324 gc_contact
.status
= ''
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
()
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
1342 if self
.autorejoin
is None and gajim
.account_is_connected(self
.account
):
1343 ar_to
= gajim
.config
.get('muc_autorejoin_timeout')
1345 self
.autorejoin
= gobject
.timeout_add_seconds(ar_to
,
1349 if not self
.autorejoin
:
1351 password
= gajim
.gc_passwords
.get(self
.room_jid
, '')
1352 gajim
.connections
[self
.account
].join_gc(self
.nick
, self
.room_jid
,
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,
1370 Open a chat window and if msg is not None - send private message to a
1374 nick
= model
[iter_
][C_NICK
].decode('utf-8')
1376 ctrl
= self
._start
_private
_message
(nick
)
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
)
1390 model
= self
.list_treeview
.get_model()
1391 gc_contact
= gajim
.contacts
.get_gc_contact(self
.account
, self
.room_jid
,
1393 state_images
= gajim
.interface
.jabber_state_images
['16']
1394 if len(gajim
.events
.get_events(self
.account
, self
.room_jid
+ '/' + \
1396 image
= state_images
['event']
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()
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
,
1416 colorstring
= "#%04x%04x%04x" % (color
.red
, color
.green
,
1418 name
+= ('\n<span size="small" style="italic" foreground="%s">'
1419 '%s</span>') % (colorstring
, gobject
.markup_escape_text(
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'):
1443 model
= self
.list_treeview
.get_model()
1444 iter_
= self
.get_contact_iter(nick
)
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
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
)
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
:
1474 if obj
.ptype
== 'error':
1481 affiliation
= obj
.affiliation
1483 affiliation
= 'none'
1485 newly_created
= False
1488 # Set to true if role or affiliation have changed
1489 right_changed
= False
1493 simple_jid
= gajim
.get_jid_without_resource(obj
.real_jid
)
1494 nick_jid
+= ' (%s)' % simple_jid
1497 # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-\
1500 if '100' in obj
.status_code
:
1501 # Can be a message (see handle_event_gc_config_change in
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
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'):
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
}
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
}
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
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
,
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(
1560 ctrl
.no_autonegotiation
= False
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
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
,
1583 new_path
= os
.path
.join(gajim
.VCARD_PATH
, puny_jid
,
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
!= \
1595 if os
.path
.exists(files
[old_file
]) and \
1596 helpers
.windowsify(old_file
) != helpers
.windowsify(
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 '
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()
1629 c
= gajim
.contacts
.get_gc_contact(self
.account
, self
.room_jid
,
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
,
1640 gajim
.interface
.roster
.draw_contact(self
.room_jid
,
1643 self
.parent_win
.redraw_tab(self
)
1645 iter_
= self
.get_contact_iter(obj
.nick
)
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(
1661 gc_c
= gajim
.contacts
.get_gc_contact(self
.account
,
1662 self
.room_jid
, obj
.nick
)
1664 log
.error('%s has an iter, but no gc_contact instance' % \
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
:
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
)
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']
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
)
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
:
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
}
1704 st
= _('** Affiliation of %(nick)s has been set to '
1705 '%(affiliation)s') % {'nick': nick_jid
,
1706 'affiliation': affiliation
}
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
)
1719 st
= _('** Role of %(nick)s has been set to %(role)s '
1720 'by %(actor)s') % {'nick': nick_jid
, 'role': role
,
1723 st
= _('** Role of %(nick)s has been set to '
1724 '%(role)s') % {'nick': nick_jid
, 'role': role
}
1726 st
+= ' (%s)' % obj
.reason
1727 self
.print_conversation(st
, graphics
=False)
1728 right_changed
= True
1730 if gc_c
.show
== obj
.show
and gc_c
.status
== obj
.status
and \
1731 gc_c
.affiliation
== affiliation
: # no change
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 \
1742 for bookmark
in gajim
.connections
[self
.account
].bookmarks
:
1743 if bookmark
['jid'] == self
.room_jid
:
1744 print_status
= bookmark
.get('print_status', None)
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
1755 st
+= ' [%s]' % obj
.reason
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
)}
1764 st
+= ' (' + obj
.status
+ ')'
1765 self
.print_conversation(st
, graphics
=False)
1767 def add_contact_to_roster(self
, nick
, show
, role
, affiliation
, status
,
1769 model
= self
.list_treeview
.get_model()
1770 role_name
= helpers
.get_uf_role(role
, plural
=True)
1774 jids
= jid
.split('/', 1)
1783 role_iter
= self
.get_role_iter(role
)
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
,
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
)
1807 if j
and not self
.is_anonymous
:
1808 gajim
.connections
[self
.account
].request_vcard(j
, fake_jid
)
1810 gajim
.connections
[self
.account
].request_vcard(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()
1819 def get_role_iter(self
, role
):
1820 model
= self
.list_treeview
.get_model()
1821 role_iter
= model
.get_iter_root()
1823 role_name
= model
[role_iter
][C_NICK
].decode('utf-8')
1824 if role
== role_name
:
1826 role_iter
= model
.iter_next(role_iter
)
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
)
1837 gc_contact
= gajim
.contacts
.get_gc_contact(self
.account
, self
.room_jid
,
1840 gajim
.contacts
.remove_gc_contact(self
.account
, gc_contact
)
1841 parent_iter
= model
.iter_parent(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
1853 if process_commands
and self
.process_as_command(message
):
1856 message
= helpers
.remove_invalid_xml_chars(message
)
1861 label
= self
.get_seclabel()
1862 if message
!= '' or message
!= '\n':
1863 self
.save_message(message
, 'sent')
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
,
1875 return gc_contact
.role
1879 def minimizable(self
):
1880 if self
.contact
.jid
in gajim
.config
.get_per('accounts', self
.account
,
1881 'minimized_gc').split(' '):
1885 def minimize(self
, status
='offline'):
1887 win
= gajim
.interface
.msg_win_mgr
.get_window(self
.contact
.jid
,
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
)
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',
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
,
1945 contact
= gajim
.contacts
.get_gc_contact(self
.account
,
1946 self
.room_jid
, nick
)
1947 contact
.show
= 'offline'
1950 ctrl
.parent_win
.redraw_tab(ctrl
)
1951 for sess
in gajim
.connections
[self
.account
].get_sessions(fjid
):
1953 sess
.control
.no_autonegotiation
= False
1954 if sess
.enable_encryption
:
1955 sess
.terminate_e2e()
1956 gajim
.connections
[self
.account
].delete_session(fjid
,
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():
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 \
1985 def allow_shutdown(self
, method
, on_yes
, on_no
, on_minimize
):
1986 if self
.minimizable():
1989 if method
== self
.parent_win
.CLOSE_ESC
:
1990 iter_
= self
.list_treeview
.get_selection().get_selected()[1]
1992 self
.list_treeview
.get_selection().unselect_all()
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 \
2004 # user does not want to be asked again
2005 gajim
.config
.set('confirm_close_muc', False)
2008 def on_cancel(clicked
):
2010 # user does not want to be asked again
2011 gajim
.config
.set('confirm_close_muc', False)
2014 pritext
= _('Are you sure you want to leave group chat "%s"?')\
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
)
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
)
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
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
2047 def _on_change_subject_menuitem_activate(self
, widget
):
2049 # Note, we don't update self.subject since we don't know whether it
2051 gajim
.connections
[self
.account
].send_gc_subject(self
.room_jid
,
2054 dialogs
.InputTextDialog(_('Changing Subject'),
2055 _('Please specify the new subject:'), input_str
=self
.subject
,
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()
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
,
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
][
2076 gajim
.interface
.instances
[self
.account
]['gc_config'][
2077 self
.room_jid
] = config
.GroupchatConfigWindow(self
.account
,
2080 def _on_destroy_room_menuitem_activate(self
, widget
):
2081 def on_ok(reason
, jid
):
2085 jid
= helpers
.parse_jid(jid
)
2087 dialogs
.ErrorDialog(_('Invalid group chat Jabber ID'),
2088 _('The group chat Jabber ID has not allowed characters.'))
2090 gajim
.connections
[self
.account
].destroy_gc_room(self
.room_jid
,
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
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
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
,
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
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(
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
,
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
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]):
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)
2187 self
.nick_hits
= [] # clear the hit list
2188 list_nick
= gajim
.contacts
.get_nick_list(self
.account
,
2190 list_nick
.sort(key
=unicode.lower
) # case-insensitive sort
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
+ ' '
2210 start_iter
= end_iter
.copy()
2211 if self
.last_key_tabs
and with_refer_to_nick_char
or (text
and \
2213 # have to accomodate for the added space from last
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
2221 start_iter
.backward_chars(len(begin
) + \
2222 len(gc_refer_to_nick_char
))
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:
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():
2240 completion
= completion
[:-1]
2242 # if the current nick matches a COMPLETE existing nick,
2243 # and if the user tab TWICE, complete that nick (with the
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
2251 add
= gc_refer_to_nick_char
+ ' '
2253 completion
= self
.nick_hits
[0]
2254 message_buffer
.insert_at_cursor(completion
+ add
)
2255 self
.last_key_tabs
= 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]
2264 widget
.get_selection().unselect_all()
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
):
2288 gajim
.connections
[self
.account
].gc_set_role(self
.room_jid
, nick
,
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
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
!= \
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
,
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
,
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',
2360 item
.set_sensitive(False)
2361 id_
= item
.connect('activate', self
.on_member_checkmenuitem_activate
,
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
,
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
,
2379 self
.handlers
[id_
] = item
2381 item
= xml
.get_object('invite_menuitem')
2382 muc_icon
= gtkgui_helpers
.load_icon('muc_active')
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
])
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
].\
2403 item
.set_sensitive(False)
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)
2413 id_
= item2
.connect('activate', self
.on_unblock
, nick
)
2414 self
.handlers
[id_
] = item2
2416 id_
= item
.connect('activate', self
.on_block
, nick
)
2417 self
.handlers
[id_
] = item
2418 item2
.set_no_show_all(True)
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')
2429 img
.set_from_file(path_to_upload_img
)
2433 item
.set_sensitive(False)
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')
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
)
2449 ctrl
= gajim
.interface
.new_private_chat(gc_c
, self
.account
)
2452 ctrl
.parent_win
.set_active_tab(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
)
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()
2485 pos
= widget
.get_path_at_pos(int(event
.x
), int(event
.y
))
2486 path
, x
= pos
[0], pos
[2]
2488 widget
.get_selection().unselect_all()
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
)
2495 self
.mk_menu(event
, iter_
)
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
)
2503 nick
= model
[iter_
][C_NICK
].decode('utf-8')
2504 self
._start
_private
_message
(nick
)
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
)
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
,
2519 if (widget
.row_expanded(path
)):
2520 widget
.collapse_row(path
)
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()
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)
2535 if text
: # Cursor is not at first position
2536 if not text
[-1] in (' ', '\n', '\t'):
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()
2551 [row
, col
, x
, y
] = props
2554 iter_
= model
.get_iter(row
)
2556 self
.tooltip
.hide_tooltip()
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
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
)
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
,
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
,
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
,
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
,
2619 def ban(self
, widget
, jid
):
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
)
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
,
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
,
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
,
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
,
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
,
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
,
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
,
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
].\
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
:
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
)
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
)
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
)
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
)
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
)
2766 self
.revoke_owner(widget
, jid
)