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

Revision 3277, 45.1 kB (checked in by nk, 3 years ago)

typo

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                        gajim.connections[self.account].change_gc_nick(room_jid, nick)
427       
428        def on_configure_room_menuitem_activate(self, widget):
429                room_jid = self.get_active_jid()
430                gajim.connections[self.account].request_gc_config(room_jid)
431
432        def on_bookmark_room_menuitem_activate(self, widget):
433                room_jid = self.get_active_jid()
434                bm = {
435                                'name': room_jid,
436                                'jid': room_jid,
437                                'autojoin': '0',
438                                'password': '',
439                                'nick': self.nicks[room_jid]
440                        }
441
442                for bookmark in gajim.connections[self.account].bookmarks:
443                        if bookmark['jid'] == bm['jid']:
444                                dialogs.ErrorDialog(
445                                        _('Bookmark already set'),
446                                        _('Room "%s" is already in your bookmarks.') %bm['jid']).get_response()
447                                return
448
449                gajim.connections[self.account].bookmarks.append(bm)
450                gajim.connections[self.account].store_bookmarks()
451
452                self.plugin.roster.make_menu()
453
454                dialogs.InformationDialog(
455                                _('Bookmark has been added successfully'),
456                                _('You can manage your bookmarks via Actions menu in your roster.'))
457
458        def on_message_textview_key_press_event(self, widget, event):
459                """When a key is pressed:
460                if enter is pressed without the shift key, message (if not empty) is sent
461                and printed in the conversation. Tab does autocomplete in nicknames"""
462                room_jid = self.get_active_jid()
463                conversation_textview = self.xmls[room_jid].get_widget(
464                        'conversation_textview')
465                message_buffer = widget.get_buffer()
466                start_iter, end_iter = message_buffer.get_bounds()
467                message = message_buffer.get_text(start_iter, end_iter, False).decode('utf-8')
468
469                if event.keyval == gtk.keysyms.ISO_Left_Tab: # SHIFT + TAB
470                        if (event.state & gtk.gdk.CONTROL_MASK): # CTRL + SHIFT + TAB 
471                                self.notebook.emit('key_press_event', event)
472                elif event.keyval == gtk.keysyms.Tab: # TAB
473                        if event.state & gtk.gdk.CONTROL_MASK: # CTRL + TAB
474                                self.notebook.emit('key_press_event', event)
475                        else:
476                                cursor_position = message_buffer.get_insert()
477                                end_iter = message_buffer.get_iter_at_mark(cursor_position)
478                                text = message_buffer.get_text(start_iter, end_iter, False).decode('utf-8')
479                                if not text or text.endswith(' '):
480                                        if not self.last_key_tabs[room_jid]: # if we are nick cycling, last char will always be space
481                                                return False
482
483                                splitted_text = text.split()
484                                # command completion
485                                if text.startswith('/') and len(splitted_text) == 1:
486                                        text = splitted_text[0]
487                                        if len(text) == 1: # user wants to cycle all commands
488                                                self.cmd_hits[room_jid] = self.muc_cmds
489                                        else:
490                                                # cycle possible commands depending on what the user typed
491                                                if self.last_key_tabs[room_jid] and \
492                                                                self.cmd_hits[room_jid][0].startswith(text.lstrip('/')):
493                                                        self.cmd_hits[room_jid].append(self.cmd_hits[room_jid][0])
494                                                        self.cmd_hits[room_jid].pop(0)
495                                                else: # find possible commands
496                                                        self.cmd_hits[room_jid] = []
497                                                        for cmd in self.muc_cmds:
498                                                                if cmd.startswith(text.lstrip('/')):
499                                                                        self.cmd_hits[room_jid].append(cmd)
500                                        if len(self.cmd_hits[room_jid]):
501                                                message_buffer.delete(start_iter, end_iter)
502                                                message_buffer.insert_at_cursor('/' + \
503                                                        self.cmd_hits[room_jid][0] + ' ')
504                                                self.last_key_tabs[room_jid] = True
505                                        return True
506
507                                # nick completion
508                                # check if tab is pressed with empty message
509                                if len(splitted_text): # if there are any words
510                                        begin = splitted_text[-1] # last word we typed
511
512                                if len(self.nick_hits[room_jid]) and \
513                                                self.nick_hits[room_jid][0].startswith(begin.replace(
514                                                self.gc_refer_to_nick_char, '')) and \
515                                                self.last_key_tabs[room_jid]: # we should cycle
516                                        self.nick_hits[room_jid].append(self.nick_hits[room_jid][0])
517                                        self.nick_hits[room_jid].pop(0)
518                                else:
519                                        self.nick_hits[room_jid] = [] # clear the hit list
520                                        list_nick = self.get_nick_list(room_jid)
521                                        for nick in list_nick: 
522                                                if nick.lower().startswith(begin.lower()): # the word is the begining of a nick
523                                                        self.nick_hits[room_jid].append(nick)
524                                if len(self.nick_hits[room_jid]):
525                                        if len(splitted_text) == 1: # This is the 1st word of the line
526                                                add = self.gc_refer_to_nick_char + ' '
527                                        else:
528                                                add = ' '
529                                        start_iter = end_iter.copy()
530                                        if self.last_key_tabs[room_jid] and begin.endswith(', '):
531                                                start_iter.backward_chars(len(begin) + 2) # have to accomodate for the added space from last completion
532                                        elif self.last_key_tabs[room_jid]:
533                                                start_iter.backward_chars(len(begin) + 1) # have to accomodate for the added space from last completion
534                                        else:
535                                                start_iter.backward_chars(len(begin))
536
537                                        message_buffer.delete(start_iter, end_iter)
538                                        message_buffer.insert_at_cursor(self.nick_hits[room_jid][0] + add)
539                                        self.last_key_tabs[room_jid] = True
540                                        return True
541                                self.last_key_tabs[room_jid] = False
542                                return False
543                elif event.keyval == gtk.keysyms.Page_Down: # PAGE DOWN
544                        if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE DOWN
545