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

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

mrege diff from trunk

<
Line 
1##      groupchat_control.py
2##
3## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
4##                         Vincent Hanquez <tab@snarc.org>
5## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
6##                    Vincent Hanquez <tab@snarc.org>
7##                    Nikos Kouremenos <kourem@gmail.com>
8##                    Dimitur Kirov <dkirov@gmail.com>
9##                    Travis Shirk <travis@pobox.com>
10##                    Norman Rasmussen <norman@rasmussen.co.za>
11## Copyright (C) 2006 Travis Shirk <travis@pobox.com>
12##
13## This program is free software; you can redistribute it and/or modify
14## it under the terms of the GNU General Public License as published
15## by the Free Software Foundation; version 2 only.
16##
17## This program is distributed in the hope that it will be useful,
18## but WITHOUT ANY WARRANTY; without even the implied warranty of
19## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20## GNU General Public License for more details.
21##
22
23import os
24import time
25import gtk
26import pango
27import gobject
28import gtkgui_helpers
29import message_control
30import tooltips
31import dialogs
32import config
33import vcard
34import cell_renderer_image
35
36from common import gajim
37from common import helpers
38
39from chat_control import ChatControl
40from chat_control import ChatControlBase
41from conversation_textview import ConversationTextview
42from common.exceptions import GajimGeneralException
43
44#(status_image, type, nick, shown_nick)
45(
46C_IMG, # image to show state (online, new message etc)
47C_NICK, # contact nickame or ROLE name
48C_TYPE, # type of the row ('contact' or 'role')
49C_TEXT, # text shown in the cellrenderer
50C_AVATAR, # avatar of the contact
51) = range(5)
52       
53def set_renderer_color(treeview, renderer, set_background = True):
54        '''set style for group row, using PRELIGHT system color'''
55        if set_background:
56                bgcolor = treeview.style.bg[gtk.STATE_PRELIGHT]
57                renderer.set_property('cell-background-gdk', bgcolor)
58        else:
59                fgcolor = treeview.style.fg[gtk.STATE_PRELIGHT]
60                renderer.set_property('foreground-gdk', fgcolor)
61
62def tree_cell_data_func(column, renderer, model, iter, tv=None):
63        # cell data func is global, because we don't want it to keep
64        # reference to GroupchatControl instance (self)
65        theme = gajim.config.get('roster_theme')
66        if model.iter_parent(iter):
67                bgcolor = gajim.config.get_per('themes', theme, 'contactbgcolor')
68                if bgcolor:
69                        renderer.set_property('cell-background', bgcolor)
70                else:
71                        renderer.set_property('cell-background', None)
72                if isinstance(renderer, gtk.CellRendererText):
73                        # foreground property is only with CellRendererText
74                        color = gajim.config.get_per('themes', theme, 'contacttextcolor')
75                        if color:
76                                renderer.set_property('foreground', color)
77                        else:
78                                renderer.set_property('foreground', None)
79                        renderer.set_property('font',
80                                gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont'))
81        else: # it is root (eg. group)
82                bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor')
83                if bgcolor:
84                        renderer.set_property('cell-background', bgcolor)
85                else:
86                        set_renderer_color(tv, renderer)
87                if isinstance(renderer, gtk.CellRendererText):
88                        # foreground property is only with CellRendererText
89                        color = gajim.config.get_per('themes', theme, 'grouptextcolor')
90                        if color:
91                                renderer.set_property('foreground', color)
92                        else:
93                                set_renderer_color(tv, renderer, False)
94                        renderer.set_property('font',
95                                gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
96
97class PrivateChatControl(ChatControl):
98        TYPE_ID = message_control.TYPE_PM
99
100        def __init__(self, parent_win, contact, acct):
101                room_jid = contact.jid.split('/')[0]
102                room_ctrl = gajim.interface.msg_win_mgr.get_control(room_jid, acct)
103                self.room_name = room_ctrl.name
104                ChatControl.__init__(self, parent_win, contact, acct)
105                self.TYPE_ID = 'pm'
106
107        def send_message(self, message):
108                '''call this function to send our message'''
109                if not message:
110                        return
111
112                # We need to make sure that we can still send through the room and that
113                # the recipient did not go away
114                contact = gajim.contacts.get_first_contact_from_jid(self.account,
115                        self.contact.jid)
116                if contact is None:
117                        # contact was from pm in MUC
118                        room, nick = gajim.get_room_and_nick_from_fjid(self.contact.jid)
119                        gc_contact = gajim.contacts.get_gc_contact(self.account, room, nick)
120                        if not gc_contact:
121                                dialogs.ErrorDialog(
122                                        _('Sending private message failed'),
123                                        #in second %s code replaces with nickname
124                                        _('You are no longer in group chat "%s" or "%s" has left.') % \
125                                        (room, nick))
126                                return
127
128                ChatControl.send_message(self, message)
129       
130        def update_ui(self):
131                if self.contact.show == 'offline':
132                        self.got_disconnected()
133                else:
134                        self.got_connected()
135                ChatControl.update_ui(self)
136
137
138class GroupchatControl(ChatControlBase):
139        TYPE_ID = message_control.TYPE_GC
140        # alphanum sorted
141        MUC_CMDS = ['ban', 'chat', 'query', 'clear', 'close', 'compact',
142                'help', 'invite', 'join', 'kick', 'leave', 'me', 'msg', 'nick',
143                'part', 'names', 'say', 'topic']
144
145        def __init__(self, parent_win, contact, acct):
146                ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
147                                        'muc_child_vbox', contact, acct);
148
149                widget = self.xml.get_widget('muc_window_actions_button')
150                id = widget.connect('clicked', self.on_actions_button_clicked)
151                self.handlers[id] = widget
152
153                widget = self.xml.get_widget('list_treeview')
154                id = widget.connect('row_expanded', self.on_list_treeview_row_expanded)
155                self.handlers[id] = widget
156
157                id = widget.connect('row_collapsed',
158                        self.on_list_treeview_row_collapsed)
159                self.handlers[id] = widget
160
161                id = widget.connect('row_activated',
162                        self.on_list_treeview_row_activated)
163                self.handlers[id] = widget
164
165                id = widget.connect('button_press_event',
166                        self.on_list_treeview_button_press_event)
167                self.handlers[id] = widget
168
169                id = widget.connect('key_press_event',
170                        self.on_list_treeview_key_press_event)
171                self.handlers[id] = widget
172
173                id = widget.connect('motion_notify_event',
174                        self.on_list_treeview_motion_notify_event)
175                self.handlers[id] = widget
176
177                id = widget.connect('leave_notify_event',
178                        self.on_list_treeview_leave_notify_event)
179                self.handlers[id] = widget
180
181                self.room_jid = self.contact.jid
182                self.nick = contact.name
183                self.name = self.room_jid.split('@')[0]
184
185                hide_chat_buttons_always = gajim.config.get(
186                        'always_hide_groupchat_buttons')
187                self.chat_buttons_set_visible(hide_chat_buttons_always)
188                self.widget_set_visible(self.xml.get_widget('banner_eventbox'),
189                        gajim.config.get('hide_groupchat_banner'))
190                self.widget_set_visible(self.xml.get_widget('list_scrolledwindow'),
191                        gajim.config.get('hide_groupchat_occupants_list'))
192
193                self._last_selected_contact = None # None or holds jid, account tuple
194
195                # muc attention flag (when we are mentioned in a muc)
196                # if True, the room has mentioned us
197                self.attention_flag = False
198                self.room_creation = int(time.time()) # Use int to reduce mem usage
199                self.nick_hits = []
200                self.cmd_hits = []
201                self.last_key_tabs = False
202
203                self.subject = ''
204                self.subject_tooltip = gtk.Tooltips()
205
206                self.tooltip = tooltips.GCTooltip()
207
208                # connect the menuitems to their respective functions
209                xm = gtkgui_helpers.get_glade('gc_control_popup_menu.glade')
210
211                widget = xm.get_widget('bookmark_room_menuitem')
212                id = widget.connect('activate',
213                        self._on_bookmark_room_menuitem_activate)
214                self.handlers[id] = widget
215
216                widget = xm.get_widget('change_nick_menuitem')
217                id = widget.connect('activate', self._on_change_nick_menuitem_activate)
218                self.handlers[id] = widget
219
220                widget = xm.get_widget('configure_room_menuitem')
221                id = widget.connect('activate',
222                        self._on_configure_room_menuitem_activate)
223                self.handlers[id] = widget
224
225                widget = xm.get_widget('change_subject_menuitem')
226                id = widget.connect('activate',
227                        self._on_change_subject_menuitem_activate)
228                self.handlers[id] = widget
229
230                widget = xm.get_widget('compact_view_menuitem')
231                id = widget.connect('activate', self._on_compact_view_menuitem_activate)
232                self.handlers[id] = widget
233
234                widget = xm.get_widget('history_menuitem')
235                id = widget.connect('activate', self._on_history_menuitem_activate)
236                self.handlers[id] = widget
237
238                self.gc_popup_menu = xm.get_widget('gc_control_popup_menu')
239
240                self.name_label = self.xml.get_widget('banner_name_label')
241                self.event_box = self.xml.get_widget('banner_eventbox')
242
243                # set the position of the current hpaned
244                self.hpaned_position = gajim.config.get('gc-hpaned-position')
245                self.hpaned = self.xml.get_widget('hpaned')
246                self.hpaned.set_position(self.hpaned_position)
247
248                self.list_treeview = self.xml.get_widget('list_treeview')
249                selection = self.list_treeview.get_selection()
250                id = selection.connect('changed',
251                                self.on_list_treeview_selection_changed)
252                self.handlers[id] = selection
253                id = self.list_treeview.connect('style-set',
254                        self.on_list_treeview_style_set)
255                self.handlers[id] = self.list_treeview
256                # we want to know when the the widget resizes, because that is
257                # an indication that the hpaned has moved...
258                # FIXME: Find a better indicator that the hpaned has moved.
259                id = self.list_treeview.connect('size-allocate',
260                        self.on_treeview_size_allocate)
261                self.handlers[id] = self.list_treeview
262                #status_image, shown_nick, type, nickname, avatar
263                store = gtk.TreeStore(gtk.Image, str, str, str, gtk.gdk.Pixbuf)
264                store.set_sort_column_id(C_TEXT, gtk.SORT_ASCENDING)
265                self.list_treeview.set_model(store)
266
267                # columns
268
269                # this col has 3 cells:
270                # first one img, second one text, third is sec pixbuf
271                column = gtk.TreeViewColumn()
272
273                renderer_pixbuf = gtk.CellRendererPixbuf() # avatar image
274                column.pack_start(renderer_pixbuf, expand = False)
275                column.add_attribute(renderer_pixbuf, 'pixbuf', C_AVATAR)
276                column.set_cell_data_func(renderer_pixbuf, tree_cell_data_func,
277                        self.list_treeview)
278                renderer_pixbuf.set_property('xalign', 1) # align pixbuf to the right
279
280                renderer_image = cell_renderer_image.CellRendererImage(0, 0) # status img
281                column.pack_start(renderer_image, expand = False)
282                column.add_attribute(renderer_image, 'image', C_IMG)
283                column.set_cell_data_func(renderer_image, tree_cell_data_func,
284                        self.list_treeview)
285
286                renderer_text = gtk.CellRendererText() # nickname
287                column.pack_start(renderer_text, expand = True)
288                column.add_attribute(renderer_text, 'markup', C_TEXT)
289                column.set_cell_data_func(renderer_text, tree_cell_data_func,
290                        self.list_treeview)
291
292                self.list_treeview.append_column(column)
293
294                # workaround to avoid gtk arrows to be shown
295                column = gtk.TreeViewColumn() # 2nd COLUMN
296                renderer = gtk.CellRendererPixbuf()
297                column.pack_start(renderer, expand = False)
298                self.list_treeview.append_column(column)
299                column.set_visible(False)
300                self.list_treeview.set_expander_column(column)
301
302                gajim.gc_connected[self.account][self.room_jid] = False
303                # disable win, we are not connected yet
304                ChatControlBase.got_disconnected(self)
305
306                self.update_ui()
307                self.conv_textview.tv.grab_focus()
308                self.widget.show_all()
309
310        def on_msg_textview_populate_popup(self, textview, menu):
311                '''we override the default context menu and we prepend Clear
312                and the ability to insert a nick'''
313                ChatControlBase.on_msg_textview_populate_popup(self, textview, menu)
314                item = gtk.SeparatorMenuItem()
315                menu.prepend(item)
316
317                item = gtk.MenuItem(_('Insert Nickname'))
318                menu.prepend(item)
319                submenu = gtk.Menu()
320                item.set_submenu(submenu)
321
322                for nick in sorted(gajim.contacts.get_nick_list(self.account,
323                self.room_jid)):
324                        item = gtk.MenuItem(nick, use_underline = False)
325                        submenu.append(item)
326                        id = item.connect('activate', self.append_nick_in_msg_textview, nick)
327                        self.handlers[id] = item
328
329                menu.show_all()
330
331        def on_treeview_size_allocate(self, widget, allocation):
332                '''The MUC treeview has resized. Move the hpaned in all tabs to match'''
333                self.hpaned_position = self.hpaned.get_position()
334                self.hpaned.set_position(self.hpaned_position)
335
336        def iter_contact_rows(self):
337                '''iterate over all contact rows in the tree model'''
338                model = self.list_treeview.get_model()
339                role_iter = model.get_iter_root()
340                while role_iter:
341                        contact_iter = model.iter_children(role_iter)
342                        while contact_iter:
343                                yield model[contact_iter]
344                                contact_iter = model.iter_next(contact_iter)
345                        role_iter = model.iter_next(role_iter)
346
347        def on_list_treeview_style_set(self, treeview, style):
348                '''When style (theme) changes, redraw all contacts'''
349                # Get the room_jid from treeview
350                for contact in self.iter_contact_rows():
351                        nick = contact[C_NICK].decode('utf-8')
352                        self.draw_contact(nick)
353
354        def on_list_treeview_selection_changed(self, selection):
355                model, selected_iter = selection.get_selected()
356                self.draw_contact(self.nick)
357                if self._last_selected_contact is not None:
358                        self.draw_contact(self._last_selected_contact)
359                if selected_iter is None:
360                        self._last_selected_contact = None
361                        return
362                contact = model[selected_iter]
363                nick = contact[C_NICK].decode('utf-8')
364                self._last_selected_contact = nick
365                if contact[C_TYPE] != 'contact':
366                        return
367                self.draw_contact(nick, selected=True, focus=True)
368
369        def get_tab_label(self, chatstate):
370                '''Markup the label if necessary. Returns a tuple such as:
371                (new_label_str, color)
372                either of which can be None
373                if chatstate is given that means we have HE SENT US a chatstate'''
374
375                has_focus = self.parent_win.window.get_property('has-toplevel-focus')
376                current_tab = self.parent_win.get_active_control() == self
377                color_name = None
378                color = None
379                theme = gajim.config.get('roster_theme')
380                if chatstate == 'attention' and (not has_focus or not current_tab):
381                        self.attention_flag = True
382                        color_name = gajim.config.get_per('themes', theme,
383                                                        'state_muc_directed_msg_color')
384                elif chatstate:
385                        if chatstate == 'active' or (current_tab and has_focus):
386                                self.attention_flag = False
387                                # get active color from gtk
388                                color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE]
389                        elif chatstate == 'newmsg' and (not has_focus or not current_tab) and\
390                                        not self.attention_flag:
391                                color_name = gajim.config.get_per('themes', theme,
392                                        'state_muc_msg_color')
393                if color_name:
394                        color = gtk.gdk.colormap_get_system().alloc_color(color_name)
395                       
396                label_str = self.name
397               
398                # count waiting highlighted messages
399                unread = ''
400                num_unread = self.get_nb_unread()
401                if num_unread == 1:
402                        unread = '*'
403                elif num_unread > 1:
404                        unread = '[' + unicode(num_unread) + ']'
405                label_str = unread + label_str
406                return (label_str, color)
407
408        def get_tab_image(self):
409                # Set tab image (always 16x16)
410                tab_image = None
411                if gajim.gc_connected[self.account][self.room_jid]:
412                        tab_image = gajim.interface.roster.load_icon('muc_active')
413                else:
414                        tab_image = gajim.interface.roster.load_icon('muc_inactive')
415                return tab_image
416
417        def update_ui(self):
418                ChatControlBase.update_ui(self)
419                for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
420                        self.draw_contact(nick)
421
422        def _change_style(self, model, path, iter):
423                model[iter][C_NICK] = model[iter][C_NICK]
424
425        def change_roster_style(self):
426                model = self.list_treeview.get_model()
427                model.foreach(self._change_style)
428
429        def repaint_themed_widgets(self):
430                ChatControlBase.repaint_themed_widgets(self)
431                self.change_roster_style()
432
433        def _update_banner_state_image(self):
434                banner_status_img = self.xml.get_widget('gc_banner_status_image')
435                images = gajim.interface.roster.jabber_state_images
436                if gajim.gc_connected[self.account].has_key(self.room_jid) and \
437                gajim.gc_connected[self.account][self.room_jid]:
438                        image = 'muc_active'
439                else:
440                        image = 'muc_inactive'
441                if images.has_key('32') and images['32'].has_key(image):
442                        muc_icon = images['32'][image]
443                        if muc_icon.get_storage_type() != gtk.IMAGE_EMPTY:
444                                pix = muc_icon.get_pixbuf()
445                                banner_status_img.set_from_pixbuf(pix)
446                                return
447                # we need to scale 16x16 to 32x32
448                muc_icon = images['16'][image]
449                pix = muc_icon.get_pixbuf()
450                scaled_pix = pix.scale_simple(32, 32, gtk.gdk.INTERP_BILINEAR)
451                banner_status_img.set_from_pixbuf(scaled_pix)
452
453        def draw_banner_text(self):
454                '''Draw the text in the fat line at the top of the window that
455                houses the room jid, subject.
456                '''
457                self.name_label.set_ellipsize(pango.ELLIPSIZE_END)
458                font_attrs, font_attrs_small = self.get_font_attrs()
459                text = '<span %s>%s</span>' % (font_attrs, self.room_jid)
460                if self.subject:
461                        subject = helpers.reduce_chars_newlines(self.subject, max_lines = 2)
462                        subject = gtkgui_helpers.escape_for_pango_markup(subject)
463                        text += '\n<span %s>%s</span>' % (font_attrs_small, subject)
464
465                        # tooltip must always hold ALL the subject
466                        self.subject_tooltip.set_tip(self.event_box, self.subject)
467
468                self.name_label.set_markup(text)
469       
470        def prepare_context_menu(self):
471                '''sets compact view menuitem active state
472                sets sensitivity state for configure_room'''
473                menu = self.gc_popup_menu
474                childs = menu.get_children()
475                # hide chat buttons
476                childs[5].set_active(self.hide_chat_buttons_current)
477                if gajim.gc_connected[self.account][self.room_jid]:
478                        c = gajim.contacts.get_gc_contact(self.account, self.room_jid,
479                                self.nick)
480                        if c.affiliation not in ('owner', 'admin'):
481                                childs[1].set_sensitive(False)
482                else:
483                        # We are not connected to this groupchat, disable unusable menuitems
484                        childs[1].set_sensitive(False)
485                        childs[2].set_sensitive(False)
486                        childs[3].set_sensitive(False)
487                return menu
488
489        def on_message(self, nick, msg, tim, has_timestamp = False, xhtml = None):
490                if not nick:
491                        # message from server
492                        self.print_conversation(msg, tim = tim, xhtml = xhtml)
493                else:
494                        # message from someone
495                        if has_timestamp:
496                                self.print_old_conversation(msg, nick, tim, xhtml)
497                        else:
498                                self.print_conversation(msg, nick, tim, xhtml)
499
500        def on_private_message(self, nick, msg, tim, xhtml):
501                # Do we have a queue?
502                fjid = self.room_jid + '/' + nick
503                no_queue = len(gajim.events.get_events(self.account, fjid)) == 0
504
505                # We print if window is opened
506                pm_control = gajim.interface.msg_win_mgr.get_control(fjid, self.account)
507                if pm_control:
508                        pm_control.print_conversation(msg, tim = tim, xhtml = xhtml)
509                        return
510
511                event = gajim.events.create_event('pm', (msg, '', 'incoming', tim,
512                        False, '', None, xhtml))
513                gajim.events.add_event(self.account, fjid, event)
514
515                autopopup = gajim.config.get('autopopup')
516                autopopupaway = gajim.config.get('autopopupaway')
517                iter = self.get_contact_iter(nick)
518                path = self.list_treeview.get_model().get_path(iter)
519                if not autopopup or (not autopopupaway and \
520                gajim.connections[self.account].connected > 2):
521                        if no_queue: # We didn't have a queue: we change icons
522                                model = self.list_treeview.get_model()
523                                state_images =\
524                                        gajim.interface.roster.get_appropriate_state_images(
525                                                self.room_jid, icon_name = 'message')
526                                image = state_images['message']
527                                model[iter][C_IMG] = image
528                        self.parent_win.show_title()
529                        self.parent_win.redraw_tab(self)
530                else:
531                        self._start_private_message(nick)
532                # Scroll to line
533                self.list_treeview.expand_row(path[0:1], False)
534                self.list_treeview.scroll_to_cell(path)
535                self.list_treeview.set_cursor(path)
536
537        def get_contact_iter(self, nick):
538                model = self.list_treeview.get_model()
539                fin = False
540                role_iter = model.get_iter_root()
541                if not role_iter:
542                        return None
543                while not fin:
544                        fin2 = False
545                        user_iter = model.iter_children(role_iter)
546                        if not user_iter:
547                                fin2 = True
548                        while not fin2:
549                                if nick == model[user_iter][C_NICK].decode('utf-8'):
550                                        return user_iter
551                                user_iter = model.iter_next(user_iter)
552                                if not user_iter:
553                                        fin2 = True
554                        role_iter = model.iter_next(role_iter)
555                        if not role_iter:
556                                fin = True
557                return None
558
559        gc_count_nicknames_colors = 0
560        gc_custom_colors = {} 
561
562        def print_old_conversation(self, text, contact = '', tim = None,
563        xhtml = None):
564                if isinstance(text, str):
565                        text = unicode(text, 'utf-8')
566                if contact:
567                        if contact == self.nick: # it's us
568                                kind = 'outgoing'
569                        else:
570                                kind = 'incoming'
571                else:
572                        kind = 'status'
573                if gajim.config.get('restored_messages_small'):
574                        small_attr = ['small']
575                else:
576                        small_attr = []
577                ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
578                        small_attr, small_attr + ['restored_message'],
579                        small_attr + ['restored_message'], xhtml = xhtml)
580
581        def print_conversation(self, text, contact = '', tim = None, xhtml = None):
582                '''Print a line in the conversation:
583                if contact is set: it's a message from someone or an info message (contact
584                = 'info' in such a case)
585                if contact is not set: it's a message from the server or help'''
586                if isinstance(text, str):
587                        text = unicode(text, 'utf-8')
588                other_tags_for_name = []
589                other_tags_for_text = []
590                if contact:
591                        if contact == self.nick: # it's us
592                                kind = 'outgoing'
593                        elif contact == 'info':
594                                kind = 'info'
595                                contact = None
596                        else:
597                                kind = 'incoming'
598                                # muc-specific chatstate
599                                self.parent_win.redraw_tab(self, 'newmsg')
600                else:
601                        kind = 'status'
602
603                if kind == 'incoming': # it's a message NOT from us
604                        # highlighting and sounds
605                        (highlight, sound) = self.highlighting_for_message(text, tim)
606                        if self.gc_custom_colors.has_key(contact):
607                                other_tags_for_name.append('gc_nickname_color_' + \
608                                        str(self.gc_custom_colors[contact]))
609                        else:
610                                self.gc_count_nicknames_colors += 1
611                                number_of_colors = len(gajim.config.get('gc_nicknames_colors').\
612                                        split(':'))
613                                if self.gc_count_nicknames_colors == number_of_colors:
614                                        self.gc_count_nicknames_colors = 0                             
615                                self.gc_custom_colors[contact] = \
616                                        self.gc_count_nicknames_colors
617                                other_tags_for_name.append('gc_nickname_color_' + \
618                                        str(self.gc_count_nicknames_colors))
619                        if highlight:
620                                # muc-specific chatstate
621                                self.parent_win.redraw_tab(self, 'attention')
622                                other_tags_for_name.append('bold')
623                                other_tags_for_text.append('marked')
624                        if sound == 'received':
625                                helpers.play_sound('muc_message_received')
626                        elif sound == 'highlight':
627                                helpers.play_sound('muc_message_highlight')
628                        if text.startswith('/me ') or text.startswith('/me\n'):
629                                other_tags_for_text.append('gc_nickname_color_' + \
630                                        str(self.gc_custom_colors[contact]))
631
632                        self.check_and_possibly_add_focus_out_line()
633
634                ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
635                        other_tags_for_name, [], other_tags_for_text, xhtml = xhtml)
636
637        def get_nb_unread(self):
638                nb = len(gajim.events.get_events(self.account, self.room_jid,
639                        ['printed_gc_msg']))
640                nb += self.get_nb_unread_pm()
641                return nb
642
643        def get_nb_unread_pm(self):
644                nb = 0
645                for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
646                        nb += len(gajim.events.get_events(self.account, self.room_jid + '/' + \
647                                nick, ['pm']))
648                return nb
649
650        def highlighting_for_message(self, text, tim):
651                '''Returns a 2-Tuple. The first says whether or not to highlight the
652                text, the second, what sound to play.'''
653                highlight, sound = (None, None)
654
655                # Do we play a sound on every muc message?
656                if gajim.config.get_per('soundevents', 'muc_message_received', 'enabled'):
657                        sound = 'received'
658
659                # Are any of the defined highlighting words in the text?
660                if self.needs_visual_notification(text):
661                        highlight = True
662                        if gajim.config.get_per('soundevents', 'muc_message_highlight',
663                                                                        'enabled'):
664                                sound = 'highlight'
665
666                # Is it a history message? Don't want sound-floods when we join.
667                if tim != time.localtime():
668                        sound = None
669
670                return (highlight, sound)
671
672        def check_and_possibly_add_focus_out_line(self):
673                '''checks and possibly adds focus out line for room_jid if it needs it
674                and does not already have it as last event. If it goes to add this line
675                it removes previous line first'''
676
677                win = gajim.interface.msg_win_mgr.get_window(self.room_jid, self.account)
678                if self.room_jid == win.get_active_jid() and\
679                win.window.get_property('has-toplevel-focus') and\
680                self.parent_win.get_active_control() == self:
681                        # it's the current room and it's the focused window.
682                        # we have full focus (we are reading it!)
683                        return
684
685                self.conv_textview.show_focus_out_line()
686
687        def needs_visual_notification(self, text):
688                '''checks text to see whether any of the words in (muc_highlight_words
689                and nick) appear.'''
690
691                special_words = gajim.config.get('muc_highlight_words').split(';')
692                special_words.append(self.nick)
693                # Strip empties: ''.split(';') == [''] and would highlight everything.
694                # Also lowercase everything for case insensitive compare.
695                special_words = [word.lower() for word in special_words if word]
696                text = text.lower()
697
698                text_splitted = text.split()
699                for word in text_splitted: # get each word of the text
700                        for special_word in special_words:
701                                if word.startswith(special_word):
702                                        # get char after the word that highlight us
703                                        char_position = len(special_word)
704                                        refer_to_nick_char = \
705                                                word[char_position:char_position+1]
706                                        if (refer_to_nick_char != ''):
707                                                refer_to_nick_char_code = ord(refer_to_nick_char)
708                                                if ((refer_to_nick_char_code < 65 or \
709                                                refer_to_nick_char_code > 123) or \
710                                                (refer_to_nick_char_code < 97 and \
711                                                refer_to_nick_char_code > 90)):
712                                                        return True
713                                                else:
714                                                        # This is A->Z or a->z, we can be sure our nick is the
715                                                        # beginning of a real word, do not highlight. Note that we
716                                                        # can probably do a better detection of non-punctuation
717                                                        # characters
718                                                        return False
719                                        else: # Special word == word, no char after in word
720                                                return True
721                return False
722
723        def set_subject(self, subject):
724                self.subject = subject
725                self.draw_banner_text()
726
727        def got_connected(self):
728                gajim.gc_connected[self.account][self.room_jid] = True
729                ChatControlBase.got_connected(self)
730                # We don't redraw the whole banner here, because only icon change
731                self._update_banner_state_image()
732
733        def got_disconnected(self):
734                self.list_treeview.get_model().clear()
735                nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid)
736                for nick in nick_list:
737                        # Update pm chat window
738                        fjid = self.room_jid + '/' + nick
739                        ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.account)
740                        gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
741                                nick)
742                        if ctrl:
743                                gc_contact.show = 'offline'
744                                gc_contact.status = ''
745                                ctrl.update_ui()
746                                ctrl.parent_win.redraw_tab(ctrl)
747                        gajim.contacts.remove_gc_contact(self.account, gc_contact)
748                gajim.gc_connected[self.account][self.room_jid] = False
749                ChatControlBase.got_disconnected(self)
750                # We don't redraw the whole banner here, because only icon change
751                self._update_banner_state_image()
752
753        def draw_roster(self):
754                self.list_treeview.get_model().clear()
755                for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
756                        gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
757                                nick)
758                        self.add_contact_to_roster(nick, gc_contact.show, gc_contact.role,
759                                                gc_contact.affiliation, gc_contact.status,
760                                                gc_contact.jid)
761
762        def on_send_pm(self, widget = None, model = None, iter = None, nick = None,
763        msg = None):
764                '''opens a chat window and msg is not None sends private message to a
765                contact in a room'''
766                if nick is None:
767                        nick = model[iter][C_NICK].decode('utf-8')
768                fjid = gajim.construct_fjid(self.room_jid, nick) # 'fake' jid
769
770                self._start_private_message(nick)
771                if msg:
772                        gajim.interface.msg_win_mgr.get_control(fjid, self.account).\
773                                send_message(msg)
774
775        def draw_contact(self, nick, selected=False, focus=False):
776                iter = self.get_contact_iter(nick)
777                if not iter:
778                        return
779                model = self.list_treeview.get_model()
780                gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
781                        nick)
782                state_images = gajim.interface.roster.jabber_state_images['16']
783                if len(gajim.events.get_events(self.account, self.room_jid + '/' + nick)):
784                        image = state_images['message']
785                else:
786                        image = state_images[gc_contact.show]
787
788                name = gtkgui_helpers.escape_for_pango_markup(gc_contact.name)
789                status = gc_contact.status
790                # add status msg, if not empty, under contact name in the treeview
791                if status and gajim.config.get('show_status_msgs_in_roster'):
792                        status = status.strip()
793                        if status != '':
794                                status = helpers.reduce_chars_newlines(status, max_lines = 1)
795                                # escape markup entities and make them small italic and fg color
796                                color = gtkgui_helpers._get_fade_color(self.list_treeview,
797                                        selected, focus)
798                                colorstring = "#%04x%04x%04x" % (color.red, color.green, color.blue)
799                                name += '\n' '<span size="small" style="italic" foreground="%s">%s</span>'\
800                                        % (colorstring, gtkgui_helpers.escape_for_pango_markup(status))
801
802                model[iter][C_IMG] = image
803                model[iter][C_TEXT] = name
804
805        def draw_avatar(self, nick):
806                model = self.list_treeview.get_model()
807                iter = self.get_contact_iter(nick)
808                if not iter: