root/tags/gajim-0.11.4/src/groupchat_control.py

Revision 9090, 70.5 kB (checked in by asterix, 9 months ago)

[Jim] Readd part of [8904] removed in [8938]. Then mix them. So we fix #3495/#3506 and problem when connecting with an already used nick.
Fixes problem when we change nick with dialog to an already used one and then cancel. Fixes #3506.

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