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

Revision 3106, 43.5 kB (checked in by nk, 3 years ago)

temporary workaround of ngettext() limitation

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