root/branches/gajim_0.8.2/src/groupchat_window.py

Revision 3444, 45.2 kB (checked in by nk, 3 years ago)

patch of hakwe

Line 
1## groupchat_window.py
2##
3## Gajim Team:
4## - Yann Le Boulanger <asterix@lagaule.org>
5## - Vincent Hanquez <tab@snarc.org>
6## - Nikos Kouremenos <kourem@gmail.com>
7##
8## Copyright (C) 2003-2005 Gajim Team
9##
10## This program is free software; you can redistribute it and/or modify
11## it under the terms of the GNU General Public License as published
12## by the Free Software Foundation; version 2 only.
13##
14## This program is distributed in the hope that it will be useful,
15## but WITHOUT ANY WARRANTY; without even the implied warranty of
16## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17## GNU General Public License for more details.
18##
19
20import gtk
21import gtk.glade
22import pango
23import gobject
24import time
25import dialogs
26import chat
27import cell_renderer_image
28import gtkgui_helpers
29import history_window
30import tooltips
31
32from gajim import Contact
33from common import gajim
34from common import helpers
35from gettext import ngettext
36from common import i18n
37
38_ = i18n._
39Q_ = i18n.Q_
40APP = i18n.APP
41gtk.glade.bindtextdomain(APP, i18n.DIR)
42gtk.glade.textdomain(APP)
43
44GTKGUI_GLADE = 'gtkgui.glade'
45
46class GroupchatWindow(chat.Chat):
47        """Class for Groupchat window"""
48        def __init__(self, room_jid, nick, plugin, account):
49                chat.Chat.__init__(self, plugin, account, 'groupchat_window')
50               
51                # alphanum sorted
52                self.muc_cmds = ['ban', 'chat', 'clear', 'close', 'compact', 'kick', 
53                        'leave', 'me', 'msg', 'nick', 'part', 'topic']
54               
55                self.nicks = {} # our nick for each groupchat we are in
56                self.list_treeview = {}
57                self.subjects = {}
58                self.name_labels = {}
59                self.subject_tooltip = {}
60                self.room_creation = {}
61                self.nick_hits = {} # possible candidates for nick completion
62                self.cmd_hits = {} # possible candidates for command completion
63                self.last_key_tabs = {}
64                self.hpaneds = {} # used for auto positioning
65                self.hpaned_position = gajim.config.get('gc-hpaned-position')
66                self.gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char')
67                self.new_room(room_jid, nick)
68                self.show_title()
69                self.tooltip = tooltips.GCTooltip(plugin)
70               
71               
72                # NOTE: if it not a window event, connect in new_room function
73                signal_dict = {
74'on_groupchat_window_destroy': self.on_groupchat_window_destroy,
75'on_groupchat_window_delete_event': self.on_groupchat_window_delete_event,
76'on_groupchat_window_focus_in_event': self.on_groupchat_window_focus_in_event,
77'on_groupchat_window_focus_out_event': self.on_groupchat_window_focus_out_event,
78'on_chat_notebook_key_press_event': self.on_chat_notebook_key_press_event,
79         }
80
81                self.xml.signal_autoconnect(signal_dict)
82
83
84                #FIXME: 0.9 implement you lost focus of MUC room here (Psi has a <hr/>)
85                # DO NOT CONNECT ABOVE but in glade..
86                #'on_chat_notebook_switch_page'
87                #'on_groupchat_popup_menu_destroy'
88
89
90
91                # get size and position from config
92                if gajim.config.get('saveposition'):
93                        gtkgui_helpers.move_window(self.window, gajim.config.get('gc-x-position'),
94                                gajim.config.get('gc-y-position'))
95                        gtkgui_helpers.resize_window(self.window, gajim.config.get('gc-width'),
96                                        gajim.config.get('gc-height'))
97                self.window.show_all()
98
99        def save_var(self, room_jid):
100                if not room_jid in self.nicks:
101                        return {}
102                return {
103                        'nick': self.nicks[room_jid],
104                        'model': self.list_treeview[room_jid].get_model(),
105                        'subject': self.subjects[room_jid],
106                        'contacts': gajim.gc_contacts[self.account][room_jid],
107                        'connected': gajim.gc_connected[self.account][room_jid],
108                }
109               
110        def load_var(self, room_jid, var):
111                if not self.xmls.has_key(room_jid):
112                        return
113                self.list_treeview[room_jid].set_model(var['model'])
114                self.list_treeview[room_jid].expand_all()
115                self.set_subject(room_jid, var['subject'])
116                self.subjects[room_jid] = var['subject']
117                gajim.gc_contacts[self.account][room_jid] = var['contacts']
118                gajim.gc_connected[self.account][room_jid] = var['connected']
119                if gajim.gc_connected[self.account][room_jid]:
120                        self.got_connected(room_jid)
121
122        def on_groupchat_window_delete_event(self, widget, event):
123                """close window"""
124                for room_jid in self.xmls:
125                        if time.time() - gajim.last_message_time[self.account][room_jid] < 2:
126                                dialog = dialogs.ConfirmationDialog(
127        _('You just received a new message in room "%s"') % room_jid.split('@')[0],
128        _('If you close this window and you have history disabled, this message will be lost.')
129                                        )
130                                if dialog.get_response() != gtk.RESPONSE_OK:
131                                        dialog.destroy()
132                                        return True # stop the propagation of the delete event
133               
134                # whether to ask for comfirmation before closing muc
135                if gajim.config.get('confirm_close_muc'):
136                        names = []
137                        for room_jid in self.xmls:
138                                if gajim.gc_connected[self.account][room_jid]:
139                                        names.append(gajim.get_nick_from_jid(room_jid))
140
141                        if len(names): # if one or more rooms connected
142                                pritext = i18n.ngettext(
143                                        'Are you sure you want to leave room "%s"?',
144                                        'Are you sure you want to leave rooms "%s"?',
145                                        len(names), names[0], ', '.join(names))
146                       
147                                sectext = i18n.ngettext(
148                        'If you close this window, you will be disconnected from this room.',
149                        'If you close this window, you will be disconnected from these rooms.',
150                                        len(names))
151                       
152                                dialog = dialogs.ConfirmationDialogCheck(pritext, sectext,
153                                        _('Do not ask me again') )
154                       
155                                if dialog.get_response() != gtk.RESPONSE_OK:
156                                        return True  # stop propagation of the delete event
157                       
158                                if dialog.is_checked():
159                                        gajim.config.set('confirm_close_muc', False)
160                                        dialog.destroy()
161
162                for room_jid in self.xmls:
163                        if gajim.gc_connected[self.account][room_jid]:
164                                gajim.connections[self.account].send_gc_status(self.nicks[room_jid],
165                                        room_jid, 'offline', 'offline')
166
167                if gajim.config.get('saveposition'):
168                        # save window position and size
169                        gajim.config.set('gc-hpaned-position', self.hpaned_position)
170                        x, y = self.window.get_position()
171                        gajim.config.set('gc-x-position', x)
172                        gajim.config.set('gc-y-position', y)
173                        width, height = self.window.get_size()
174                        gajim.config.set('gc-width', width)
175                        gajim.config.set('gc-height', height)
176       
177        def on_groupchat_window_destroy(self, widget):
178                chat.Chat.on_window_destroy(self, widget, 'gc')
179                for room_jid in self.xmls:
180                        del gajim.gc_contacts[self.account][room_jid]
181                        del gajim.gc_connected[self.account][room_jid]
182
183        def on_groupchat_window_focus_in_event(self, widget, event):
184                '''When window gets focus'''
185                chat.Chat.on_chat_window_focus_in_event(self, widget, event)
186
187        def on_groupchat_window_focus_out_event(self, widget, event):
188                '''When window loses focus'''
189                #chat.Chat.on_chat_window_focus_out_event(self, widget, event)
190                #FIXME: merge with on_tabbed_chat_window_focus_out_event in chat.py
191                #do the you were here in MUC conversation thing
192                pass
193
194        def on_chat_notebook_key_press_event(self, widget, event):
195                chat.Chat.on_chat_notebook_key_press_event(self, widget, event)
196       
197        def on_chat_notebook_switch_page(self, notebook, page, page_num):
198                new_child = notebook.get_nth_page(page_num)
199                new_jid = ''
200                for room_jid in self.xmls:
201                        if self.childs[room_jid] == new_child: 
202                                new_jid = room_jid
203                                break
204                subject = self.subjects[new_jid]
205
206                subject = gtkgui_helpers.escape_for_pango_markup(subject)
207                new_jid = gtkgui_helpers.escape_for_pango_markup(new_jid)
208
209                name_label = self.name_labels[new_jid]
210                name_label.set_markup('<span weight="heavy" size="x-large">%s</span>\n%s' % (new_jid, subject))
211                event_box = name_label.get_parent()
212                if subject == '':
213                        subject = _('This room has no subject')
214                self.subject_tooltip[new_jid].set_tip(event_box, subject)
215                chat.Chat.on_chat_notebook_switch_page(self, notebook, page, page_num)
216
217        def get_role_iter(self, room_jid, role):
218                model = self.list_treeview[room_jid].get_model()
219                fin = False
220                iter = model.get_iter_root()
221                if not iter:
222                        return None
223                while not fin:
224                        role_name = model.get_value(iter, 1).decode('utf-8')
225                        if role == role_name:
226                                return iter
227                        iter = model.iter_next(iter)
228                        if not iter:
229                                fin = True
230                return None
231
232        def get_contact_iter(self, room_jid, nick):
233                model = self.list_treeview[room_jid].get_model()
234                fin = False
235                role_iter = model.get_iter_root()
236                if not role_iter:
237                        return None
238                while not fin:
239                        fin2 = False
240                        user_iter = model.iter_children(role_iter)
241                        if not user_iter:
242                                fin2 = True
243                        while not fin2:
244                                if nick == model.get_value(user_iter, 1).decode('utf-8'):
245                                        return user_iter
246                                user_iter = model.iter_next(user_iter)
247                                if not user_iter:
248                                        fin2 = True
249                        role_iter = model.iter_next(role_iter)
250                        if not role_iter:
251                                fin = True
252                return None
253
254        def get_nick_list(self, room_jid):
255                '''get nicks of contacts in a room'''
256                return gajim.gc_contacts[self.account][room_jid].keys()
257
258        def remove_contact(self, room_jid, nick):
259                """Remove a user from the contacts_list"""
260                model = self.list_treeview[room_jid].get_model()
261                iter = self.get_contact_iter(room_jid, nick)
262                if not iter:
263                        return
264                if gajim.gc_contacts[self.account][room_jid].has_key(nick):
265                        del gajim.gc_contacts[self.account][room_jid][nick]
266                parent_iter = model.iter_parent(iter)
267                model.remove(iter)
268                if model.iter_n_children(parent_iter) == 0:
269                        model.remove(parent_iter)
270
271        def escape(self, s):
272                return s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
273       
274        def add_contact_to_roster(self, room_jid, nick, show, role, jid, affiliation):
275                model = self.list_treeview[room_jid].get_model()
276                image = self.plugin.roster.jabber_state_images[show]
277                resource = ''
278                role_name = helpers.get_uf_role(role, plural = True)
279
280                if jid:
281                        jids = jid.split('/', 1)
282                        j = jids[0]
283                        if len(jids) > 1:
284                                resource = jids[1]
285                else:
286                        j = ''
287                role_iter = self.get_role_iter(room_jid, role)
288                if not role_iter:
289                        role_iter = model.append(None,
290                                (self.plugin.roster.jabber_state_images['closed'], role,
291                                '<b>%s</b>' % role_name))
292                iter = model.append(role_iter, (image, nick, self.escape(nick)))
293                gajim.gc_contacts[self.account][room_jid][nick] = \
294                        Contact(jid = j, name = nick, show = show, resource = resource,
295                        role = role, affiliation = affiliation)
296                if nick == self.nicks[room_jid]: # we became online
297                        self.got_connected(room_jid)
298                self.list_treeview[room_jid].expand_row((model.get_path(role_iter)),
299                        False)
300                return iter
301       
302        def get_role(self, room_jid, nick):
303                if gajim.gc_contacts[self.account][room_jid].has_key(nick):
304                        return gajim.gc_contacts[self.account][room_jid][nick].role
305                else:
306                        return 'visitor'
307
308        def update_state_images(self):
309                roster = self.plugin.roster
310                for room_jid in self.list_treeview:
311                        model = self.list_treeview[room_jid].get_model()
312                        role_iter = model.get_iter_root()
313                        if not role_iter:
314                                continue
315                        while role_iter:
316                                user_iter = model.iter_children(role_iter)
317                                if not user_iter:
318                                        continue
319                                while user_iter:
320                                        nick = model.get_value(user_iter, 1).decode('utf-8')
321                                        show = gajim.gc_contacts[self.account][room_jid][nick].show
322                                        state_images = roster.get_appropriate_state_images(room_jid)
323                                        image = state_images[show] #FIXME: always Jabber why?
324                                        model.set_value(user_iter, 0, image)
325                                        user_iter = model.iter_next(user_iter)
326                                role_iter = model.iter_next(role_iter)
327
328        def chg_contact_status(self, room_jid, nick, show, status, role, affiliation,
329                jid, reason, actor, statusCode, new_nick, account):
330                """When a user changes his status"""
331                if show == 'invisible':
332                        return
333                if not role:
334                        role = 'visitor'
335                model = self.list_treeview[room_jid].get_model()
336                if show in ('offline', 'error'):
337                        if statusCode == '307':
338                                self.print_conversation(_('%s has been kicked by %s: %s') % (nick,
339                                        actor, reason), room_jid)
340                                        #FIXME: this produced foo has been kciked by JID: reason
341                                        #Should we show the JID to everyone? the same for ban
342                                        #I propose we use nick
343                        elif statusCode == '301':
344                                self.print_conversation(_('%s has been banned by %s: %s') % (nick,
345                                        actor, reason), room_jid)
346                        elif statusCode == '303': # Someone changed his nick
347                                self.print_conversation(_('%s is now known as %s') % (nick,
348                                        new_nick), room_jid)
349                                if nick == self.nicks[room_jid]: # We changed our nick
350                                        self.nicks[room_jid] = new_nick
351                        self.remove_contact(room_jid, nick)
352                        if nick == self.nicks[room_jid] and statusCode != '303': # We became offline
353                                self.got_disconnected(room_jid)
354                else:
355                        iter = self.get_contact_iter(room_jid, nick)
356                        if not iter:
357                                iter = self.add_contact_to_roster(room_jid, nick, show, role, jid,
358                                        affiliation)
359                        else:
360                                actual_role = self.get_role(room_jid, nick)
361                                if role != actual_role:
362                                        self.remove_contact(room_jid, nick)
363                                        self.add_contact_to_roster(room_jid, nick, show, role, jid,
364                                                affiliation)
365                                else:
366                                        c = gajim.gc_contacts[self.account][room_jid][nick]
367                                        if c.show == show and c.status == status and \
368                                                c.affiliation == affiliation: #no change
369                                                return
370                                        c.show = show
371                                        c.affiliation = affiliation
372                                        roster = self.plugin.roster
373                                        state_images = roster.get_appropriate_state_images(jid)
374                                        image = state_images[show]
375                                        model.set_value(iter, 0, image)
376                if (time.time() - self.room_creation[room_jid]) > 30 and \
377                                nick != self.nicks[room_jid] and statusCode != '303':
378                        if show == 'offline':
379                                st = _('%s has left') % nick
380                        else:
381                                st = _('%s is now %s') % (nick, helpers.get_uf_show(show))
382                        if status:
383                                st += ' (' + status + ')'
384                        self.print_conversation(st, room_jid)
385
386       
387        def set_subject(self, room_jid, subject):
388                self.subjects[room_jid] = subject
389                name_label = self.name_labels[room_jid]
390                full_subject = None
391
392                if gtk.gtk_version < (2, 6, 0) or gtk.pygtk_version < (2, 6, 0):
393                        subject = gtkgui_helpers.reduce_chars_newlines(subject, 80, 2)
394                else:
395                        subject = gtkgui_helpers.reduce_chars_newlines(subject, 0, 2)
396                subject = gtkgui_helpers.escape_for_pango_markup(subject)
397                name_label.set_markup(
398                '<span weight="heavy" size="x-large">%s</span>\n%s' % (room_jid, subject))
399                event_box = name_label.get_parent()
400                if subject == '':
401                        subject = _('This room has no subject')
402
403                if full_subject is not None:
404                        subject = full_subject # tooltip must always hold ALL the subject
405                self.subject_tooltip[room_jid].set_tip(event_box, subject)
406
407       
408        def on_change_subject_menuitem_activate(self, widget):
409                room_jid = self.get_active_jid()
410                subject = self.subjects[room_jid]
411                instance = dialogs.InputDialog(_('Changing Subject'),
412                        _('Please specify the new subject:'), subject)
413                response = instance.get_response()
414                if response == gtk.RESPONSE_OK:
415                        subject = instance.input_entry.get_text().decode('utf-8')
416                        gajim.connections[self.account].send_gc_subject(room_jid, subject)
417
418        def on_change_nick_menuitem_activate(self, widget):
419                room_jid = self.get_active_jid()
420                nick = self.nicks[room_jid]
421                instance = dialogs.InputDialog(_('Changing Nickname'),
422                        _('Please specify the new nickname you want to use:'), nick)
423                response = instance.get_response()
424                if response == gtk.RESPONSE_OK:
425                        nick = instance.input_entry.get_text().decode('utf-8')
426                        self.nicks[room_jid] = nick
427                        gajim.connections[self.account].change_gc_nick(room_jid, nick)
428       
429        def on_configure_room_menuitem_activate(self, widget):
430                room_jid = self.get_active_jid()
431                gajim.connections[self.account].request_gc_config(room_jid)
432
433        def on_bookmark_room_menuitem_activate(self, widget):
434                room_jid = self.get_active_jid()
435                bm = {
436                                'name': room_jid,
437                                'jid': room_jid,
438                                'autojoin': '0',
439                                'password': '',
440                                'nick': self.nicks[room_jid]
441                        }
442
443                for bookmark in gajim.connections[self.account].bookmarks:
444                        if bookmark['jid'] == bm['jid']:
445                                dialogs.ErrorDialog(
446                                        _('Bookmark already set'),
447                                        _('Room "%s" is already in your bookmarks.') %bm['jid']).get_response()
448                                return
449
450                gajim.connections[self.account].bookmarks.append(bm)
451                gajim.connections[self.account].store_bookmarks()
452
453                self.plugin.roster.make_menu()
454
455                dialogs.InformationDialog(
456                                _('Bookmark has been added successfully'),
457                                _('You can manage your bookmarks via Actions menu in your roster.'))
458
459        def on_message_textview_key_press_event(self, widget, event):
460                """When a key is pressed:
461                if enter is pressed without the shift key, message (if not empty) is sent
462                and printed in the conversation. Tab does autocomplete in nicknames"""
463                room_jid = self.get_active_jid()
464                conversation_textview = self.xmls[room_jid].get_widget(
465                        'conversation_textview')
466                message_buffer = widget.get_buffer()
467                start_iter, end_iter = message_buffer.get_bounds()
468                message = message_buffer.get_text(start_iter, end_iter, False).decode('utf-8')
469
470                if event.keyval == gtk.keysyms.ISO_Left_Tab: # SHIFT + TAB
471                        if (event.state & gtk.gdk.CONTROL_MASK): # CTRL + SHIFT + TAB 
472                                self.notebook.emit('key_press_event', event)
473                elif event.keyval == gtk.keysyms.Tab: # TAB
474                        if event.state & gtk.gdk.CONTROL_MASK: # CTRL + TAB
475                                self.notebook.emit('key_press_event', event)
476                        else:
477                                cursor_position = message_buffer.get_insert()
478                                end_iter = message_buffer.get_iter_at_mark(cursor_position)
479                                text = message_buffer.get_text(start_iter, end_iter, False).decode('utf-8')
480                                if not text or text.endswith(' '):
481                                        if not self.last_key_tabs[room_jid]: # if we are nick cycling, last char will always be space
482                                                return False
483
484                                splitted_text = text.split()
485                                # command completion
486                                if text.startswith('/') and len(splitted_text) == 1:
487                                        text = splitted_text[0]
488                                        if len(text) == 1: # user wants to cycle all commands
489                                                self.cmd_hits[room_jid] = self.muc_cmds
490                                        else:
491                                                # cycle possible commands depending on what the user typed
492                                                if self.last_key_tabs[room_jid] and \
493                                                                self.cmd_hits[room_jid][0].startswith(text.lstrip('/')):
494                                                        self.cmd_hits[room_jid].append(self.cmd_hits[room_jid][0])
495                                                        self.cmd_hits[room_jid].pop(0)
496                                                else: # find possible commands
497                                                        self.cmd_hits[room_jid] = []
498                                                        for cmd in self.muc_cmds:
499                                                                if cmd.startswith(text.lstrip('/')):
500                                                                        self.cmd_hits[room_jid].append(cmd)
501                                        if len(self.cmd_hits[room_jid]):
502                                                message_buffer.delete(start_iter, end_iter)
503                                                message_buffer.insert_at_cursor('/' + \
504                                                        self.cmd_hits[room_jid][0] + ' ')
505                                                self.last_key_tabs[room_jid] = True
506                                        return True
507
508                                # nick completion
509                                # check if tab is pressed with empty message
510                                if len(splitted_text): # if there are any words
511                                        begin = splitted_text[-1] # last word we typed
512
513                                if len(self.nick_hits[room_jid]) and \
514                                                self.nick_hits[room_jid][0].startswith(begin.replace(
515                                                self.gc_refer_to_nick_char, '')) and \
516                                                self.last_key_tabs[room_jid]: # we should cycle
517                                        self.nick_hits[room_jid].append(self.nick_hits[room_jid][0])
518                                        self.nick_hits[room_jid].pop(0)
519                                else:
520                                        self.nick_hits[room_jid] = [] # clear the hit list
521                                        list_nick = self.get_nick_list(room_jid)
522                                        for nick in list_nick: 
523                                                if nick.lower().startswith(begin.lower()): # the word is the begining of a nick
524                                                        self.nick_hits[room_jid].append(nick)
525                                if len(self.nick_hits[room_jid]):
526                                        if len(splitted_text) == 1: # This is the 1st word of the line
527                                                add = self.gc_refer_to_nick_char + ' '
528                                        else:
529                                                add = ' '
530                                        start_iter = end_iter.copy()
531                                        if self.last_key_tabs[room_jid] and begin.endswith(', '):
532                                                start_iter.backward_chars(len(begin) + 2) # have to accomodate for the added space from last completion
533                                        elif self.last_key_tabs[room_jid]:
534                                                start_iter.backward_chars(len(begin) + 1) # have to accomodate for the added space from last completion
535                                        else:
536                                                start_iter.backward_chars(len(begin))
537
538                                        message_buffer.delete(start_iter, end_iter)
539                                        message_buffer.insert_at_cursor(self.nick_hits[room_jid][0] + add)
540                                        self.last_key_tabs[room_jid] = True
541                                        return True
542                                self.last_key_tabs[room_jid] = False
543                                return False
544                elif event.keyval == gtk.keysyms.Page_Down: # PAGE DOWN
545