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

Revision 3314, 28.3 kB (checked in by nk, 3 years ago)

[andreasvc] make 52 x 52 for avatars customizable

Line 
1##      tabbed_chat_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 urllib
26import base64
27import os
28
29import dialogs
30import chat
31import gtkgui_helpers
32
33from common import gajim
34from common import helpers
35from common import i18n
36
37_ = i18n._
38APP = i18n.APP
39gtk.glade.bindtextdomain(APP, i18n.DIR)
40gtk.glade.textdomain(APP)
41
42GTKGUI_GLADE = 'gtkgui.glade'
43
44class TabbedChatWindow(chat.Chat):
45        """Class for tabbed chat window"""
46        def __init__(self, user, plugin, account):
47                chat.Chat.__init__(self, plugin, account, 'tabbed_chat_window')
48                self.contacts = {}
49                self.chatstates = {}
50                # keep check for possible paused timeouts per jid
51                self.possible_paused_timeout_id = {}
52                # keep check for possible inactive timeouts per jid
53                self.possible_inactive_timeout_id = {}
54                self.TARGET_TYPE_URI_LIST = 80
55                self.dnd_list = [ ( 'text/uri-list', 0, self.TARGET_TYPE_URI_LIST ) ]
56                self.new_tab(user)
57                self.show_title()
58               
59                # NOTE: if it not a window event, connect in new_tab function
60                signal_dict = {
61'on_tabbed_chat_window_destroy': self.on_tabbed_chat_window_destroy,
62'on_tabbed_chat_window_delete_event': self.on_tabbed_chat_window_delete_event,
63'on_tabbed_chat_window_focus_in_event': self.on_tabbed_chat_window_focus_in_event,
64'on_tabbed_chat_window_focus_out_event': self.on_tabbed_chat_window_focus_out_event,
65'on_chat_notebook_key_press_event': self.on_chat_notebook_key_press_event,
66'on_chat_notebook_switch_page': self.on_chat_notebook_switch_page, # in chat.py
67'on_tabbed_chat_window_motion_notify_event': self.on_tabbed_chat_window_motion_notify_event,
68         }
69
70                self.xml.signal_autoconnect(signal_dict)
71
72
73                if gajim.config.get('saveposition'):
74                        # get window position and size from config
75                        gtkgui_helpers.move_window(self.window, gajim.config.get('chat-x-position'),
76                                gajim.config.get('chat-y-position'))
77                        gtkgui_helpers.resize_window(self.window, gajim.config.get('chat-width'),
78                                        gajim.config.get('chat-height'))
79
80                # gtk+ doesn't make use of the motion notify on gtkwindow by default
81                # so this line adds that
82                self.window.set_events(gtk.gdk.POINTER_MOTION_MASK)
83               
84                self.window.show_all()
85       
86        def save_var(self, jid):
87                '''return the specific variable of a jid, like gpg_enabled
88                the return value have to be compatible with wthe one given to load_var'''
89                gpg_enabled = self.xmls[jid].get_widget('gpg_togglebutton').get_active()
90                return {'gpg_enabled': gpg_enabled}
91       
92        def load_var(self, jid, var):
93                if not self.xmls.has_key(jid):
94                        return
95                self.xmls[jid].get_widget('gpg_togglebutton').set_active(
96                        var['gpg_enabled'])
97               
98        def on_tabbed_chat_window_motion_notify_event(self, widget, event):
99                '''it gets called no matter if it is the active window or not'''
100                if widget.get_property('has-toplevel-focus'):
101                        # change chatstate only if window is the active one
102                        self.mouse_over_in_last_5_secs = True
103                        self.mouse_over_in_last_30_secs = True
104
105        def on_drag_data_received(self, widget, context, x, y, selection, target_type,
106timestamp, contact):
107                if target_type == self.TARGET_TYPE_URI_LIST:
108                        uri = selection.data.strip()
109                        uri_splitted = uri.split() # we may have more than one file dropped
110                        for uri in uri_splitted:
111                                path = helpers.get_file_path_from_dnd_dropped_uri(uri)
112                                if os.path.isfile(path): # is it file?
113                                        self.plugin.windows['file_transfers'].send_file(self.account,
114                                                contact, path)
115
116        def draw_widgets(self, contact):
117                """draw the widgets in a tab (status_image, contact_button ...)
118                according to the the information in the contact variable"""
119                jid = contact.jid
120                self.set_state_image(jid)
121                contact_button = self.xmls[jid].get_widget('contact_button')
122                contact_button.set_use_underline(False)
123                tb = self.xmls[jid].get_widget('gpg_togglebutton')
124                if contact.keyID: # we can do gpg
125                        tb.set_sensitive(True)
126                        tt = _('OpenPGP Encryption')
127                else:
128                        tb.set_sensitive(False)
129                        #we talk about a contact here
130                        tt = _('%s has not broadcasted an OpenPGP key nor you have assigned one') % contact.name
131                tip = gtk.Tooltips()
132                tip.set_tip(self.xmls[jid].get_widget('gpg_eventbox'), tt)
133
134                # add the fat line at the top
135                self.draw_name_banner(contact)
136
137        def draw_name_banner(self, contact, chatstate = None):
138                '''Draw the fat line at the top of the window that
139                houses the status icon, name, jid, and avatar'''
140                # this is the text for the big brown bar
141                # some chars need to be escaped..
142                jid = contact.jid
143                banner_name_label = self.xmls[jid].get_widget('banner_name_label')
144               
145                name = gtkgui_helpers.escape_for_pango_markup(contact.name)
146               
147                status = contact.status
148                #FIXME: when gtk2.4 is OOOOLD do it via glade2.10+ 
149                if gtk.pygtk_version >= (2, 6, 0) and gtk.gtk_version >= (2, 6, 0) and \
150                status is not None:
151                        banner_name_label.set_ellipsize(pango.ELLIPSIZE_END)
152                        status = gtkgui_helpers.reduce_chars_newlines(status, 0, 2)
153                #FIXME: remove me when gtk24 is OLD
154                elif status is not None:
155                        status = gtkgui_helpers.reduce_chars_newlines(status, 50, 2)
156
157                status = gtkgui_helpers.escape_for_pango_markup(status)
158
159                #FIXME: uncomment me when we support sending messages to specific resource
160                # composing full jid
161                #fulljid = jid
162                #if self.contacts[jid].resource:
163                #       fulljid += '/' + self.contacts[jid].resource
164                #label_text = '<span weight="heavy" size="x-large">%s</span>\n%s' \
165                #       % (name, fulljid)
166               
167               
168                st = gajim.config.get('chat_state_notifications')
169                if chatstate and st in ('composing_only', 'all'):
170                        if st == 'all':
171                                chatstate = helpers.get_uf_chatstate(chatstate)
172                        else: # 'composing_only'
173                                if chatstate in ('composing', 'paused'):
174                                        # only print composing, paused
175                                        chatstate = helpers.get_uf_chatstate(chatstate)
176                                else:
177                                        chatstate = ''
178                        label_text = \
179                        '<span weight="heavy" size="x-large">%s</span> %s' % (name, chatstate)
180                else:
181                        label_text = '<span weight="heavy" size="x-large">%s</span>' % name
182               
183                if status is not None:
184                        label_text += '\n%s' % status
185
186                # setup the label that holds name and jid
187                banner_name_label.set_markup(label_text)
188                self.paint_banner(jid)
189
190        def set_avatar(self, vcard):
191                if not vcard.has_key('PHOTO'):
192                        return
193                if type(vcard['PHOTO']) != type({}):
194                        return
195                img_decoded = None
196                if vcard['PHOTO'].has_key('BINVAL'):
197                        try:
198                                img_decoded = base64.decodestring(vcard['PHOTO']['BINVAL'])
199                        except:
200                                pass
201                elif vcard['PHOTO'].has_key('EXTVAL'):
202                        url = vcard['PHOTO']['EXTVAL']
203                        try:
204                                fd = urllib.urlopen(url)
205                                img_decoded = fd.read()
206                        except:
207                                pass
208                if img_decoded:
209                        pixbufloader = gtk.gdk.PixbufLoader()
210                        try:
211                                pixbufloader.write(img_decoded)
212                                pixbuf = pixbufloader.get_pixbuf()
213                                pixbufloader.close()
214                       
215                                w = gajim.config.get('avatar_width')
216                                h = gajim.config.get('avatar_height')
217                                scaled_buf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_HYPER)
218                                x = None
219                                if self.xmls.has_key(vcard['jid']):
220                                        x = self.xmls[vcard['jid']]
221                                # it can be xmls[jid/resource] if it's a vcard from pm
222                                elif self.xmls.has_key(vcard['jid'] + '/' + vcard['resource']):
223                                        x = self.xmls[vcard['jid'] + '/' + vcard['resource']]
224                                image = x.get_widget('avatar_image')
225                                image.set_from_pixbuf(scaled_buf)
226                                image.show_all()
227                        # we may get "unknown image format" and/or something like pixbuf can be None
228                        except (gobject.GError, AttributeError):
229                                pass
230
231        def set_state_image(self, jid):
232                prio = 0
233                if gajim.contacts[self.account].has_key(jid):
234                        contacts_list = gajim.contacts[self.account][jid]
235                else:
236                        contacts_list = [self.contacts[jid]]
237
238                user = contacts_list[0]
239                show = user.show
240                jid = user.jid
241                keyID = user.keyID
242
243                for u in contacts_list:
244                        if u.priority > prio:
245                                prio = u.priority
246                                show = u.show
247                                keyID = u.keyID
248                child = self.childs[jid]
249                hb = self.notebook.get_tab_label(child).get_children()[0]
250                status_image = hb.get_children()[0]
251                state_images = self.plugin.roster.get_appropriate_state_images(jid)
252                image = state_images[show]
253                banner_status_image = self.xmls[jid].get_widget('banner_status_image')
254
255                if keyID:
256                        self.xmls[jid].get_widget('gpg_togglebutton').set_sensitive(True)
257                else:
258                        self.xmls[jid].get_widget('gpg_togglebutton').set_sensitive(False)
259
260                if image.get_storage_type() == gtk.IMAGE_ANIMATION:
261                        banner_status_image.set_from_animation(image.get_animation())
262                        status_image.set_from_animation(image.get_animation())
263                elif image.get_storage_type() == gtk.IMAGE_PIXBUF:
264                        # make a copy because one will be scaled, one not (tab icon)
265                        pix = image.get_pixbuf()
266                        scaled_pix = pix.scale_simple(32, 32, gtk.gdk.INTERP_BILINEAR)
267                        banner_status_image.set_from_pixbuf(scaled_pix)
268                        status_image.set_from_pixbuf(pix)
269
270        def on_tabbed_chat_window_delete_event(self, widget, event):
271                '''close window'''
272                for jid in self.contacts:
273                        if time.time() - gajim.last_message_time[self.account][jid] < 2:
274                                # 2 seconds
275                                dialog = dialogs.ConfirmationDialog(
276                                        #%s is being replaced in the code with JID
277                                        _('You just received a new message from "%s"' % jid),
278                                        _('If you close this tab and you have history disabled, this message will be lost.'))
279                                if dialog.get_response() != gtk.RESPONSE_OK:
280                                        return True #stop the propagation of the event
281
282                if gajim.config.get('saveposition'):
283                        # save the window size and position
284                        x, y = self.window.get_position()
285                        gajim.config.set('chat-x-position', x)
286                        gajim.config.set('chat-y-position', y)
287                        width, height = self.window.get_size()
288                        gajim.config.set('chat-width', width)
289                        gajim.config.set('chat-height', height)
290
291        def on_tabbed_chat_window_destroy(self, widget):
292                #clean self.plugin.windows[self.account]['chats']
293                chat.Chat.on_window_destroy(self, widget, 'chats')
294
295        def on_tabbed_chat_window_focus_in_event(self, widget, event):
296                chat.Chat.on_chat_window_focus_in_event(self, widget, event)
297                # on focus in, send 'active' chatstate to current tab
298                self.send_chatstate('active')
299
300        def on_tabbed_chat_window_focus_out_event(self, widget, event):
301                '''catch focus out and minimized and send inactive chatstate;
302                minimize action also focuses out first so it's catched here'''
303                window_state = widget.window.get_state()
304                if window_state is None:
305                        return
306               
307                # focus-out is also emitted by showing context menu
308                # so check to see if we're really not paying attention to window/tab
309                # NOTE: if the user changes tab, switch-tab sends inactive to the tab
310                # we are leaving so we just send to active tab here
311                if self.popup_is_shown is False: # we are outside of the window
312                        # so no context menu, so send 'inactive' to active tab
313                        self.send_chatstate('inactive')
314
315        def on_chat_notebook_key_press_event(self, widget, event):
316                chat.Chat.on_chat_notebook_key_press_event(self, widget, event)
317
318        def on_send_file_menuitem_activate(self, widget):
319                jid = self.get_active_jid()
320                contact = gajim.get_first_contact_instance_from_jid(self.account, jid)
321                self.plugin.windows['file_transfers'].show_file_send_request( 
322                        self.account, contact)
323
324        def on_add_to_roster_menuitem_activate(self, widget):
325                jid = self.get_active_jid()
326                dialogs.AddNewContactWindow(self.plugin, self.account, jid)
327
328        def on_send_button_clicked(self, widget):
329                """When send button is pressed: send the current message"""
330                jid = self.get_active_jid()
331                message_textview = self.xmls[jid].get_widget('message_textview')
332                message_buffer = message_textview.get_buffer()
333                start_iter = message_buffer.get_start_iter()
334                end_iter = message_buffer.get_end_iter()
335                message = message_buffer.get_text(start_iter, end_iter, 0).decode('utf-8')
336
337                # send the message
338                self.send_message(message)
339
340                message_buffer.set_text('')
341
342        def remove_tab(self, jid):
343                if time.time() - gajim.last_message_time[self.account][jid] < 2:
344                        dialog = dialogs.ConfirmationDialog(
345                                _('You just received a new message from "%s"' % jid),
346                                _('If you close this tab and you have history disabled, the message will be lost.'))
347                        if dialog.get_response() != gtk.RESPONSE_OK:
348                                return
349
350                # chatstates - tab is destroyed, send gone
351                self.send_chatstate('gone', jid)
352               
353                chat.Chat.remove_tab(self, jid, 'chats')
354                del self.contacts[jid]
355       
356        def new_tab(self, contact):
357                '''when new tab is created'''
358                self.names[contact.jid] = contact.name
359                self.xmls[contact.jid] = gtk.glade.XML(GTKGUI_GLADE, 'chats_vbox', APP)
360                self.childs[contact.jid] = self.xmls[contact.jid].get_widget('chats_vbox')
361                self.contacts[contact.jid] = contact
362               
363               
364                self.childs[contact.jid].connect('drag_data_received',
365                        self.on_drag_data_received, contact)
366                self.childs[contact.jid].drag_dest_set( gtk.DEST_DEFAULT_MOTION |
367                        gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP,
368                                 self.dnd_list, gtk.gdk.ACTION_COPY)
369               
370                message_textview = self.xmls[contact.jid].get_widget('message_textview')
371                message_tv_buffer = message_textview.get_buffer()
372                message_tv_buffer.connect('insert-text',
373                        self.on_message_tv_buffer_insert_text, contact.jid)
374
375                if contact.jid in gajim.encrypted_chats[self.account]:
376                        self.xmls[contact.jid].get_widget('gpg_togglebutton').set_active(True)
377               
378                xm = gtk.glade.XML(GTKGUI_GLADE, 'tabbed_chat_popup_menu', APP)
379                xm.signal_autoconnect(self)
380                self.tabbed_chat_popup_menu = xm.get_widget('tabbed_chat_popup_menu')
381               
382                chat.Chat.new_tab(self, contact.jid)
383                self.redraw_tab(contact.jid)
384                self.draw_widgets(contact)
385
386                # restore previous conversation
387                self.restore_conversation(contact.jid)
388
389                # print queued messages
390                if gajim.awaiting_messages[self.account].has_key(contact.jid):
391                        self.read_queue(contact.jid)
392
393                gajim.connections[self.account].request_vcard(contact.jid)
394                self.childs[contact.jid].show_all()
395
396                # chatstates
397                self.reset_kbd_mouse_timeout_vars()
398               
399                self.chatstates[contact.jid] = None # OUR current chatstate with contact
400                self.possible_paused_timeout_id[contact.jid] =\
401                        gobject.timeout_add(5000, self.check_for_possible_paused_chatstate,
402                                contact.jid)
403                self.possible_inactive_timeout_id[contact.jid] =\
404                        gobject.timeout_add(30000, self.check_for_possible_inactive_chatstate,
405                                contact.jid)
406               
407        def handle_incoming_chatstate(self, account, jid, chatstate):
408                ''' handle incoming chatstate that jid SENT TO us '''
409                contact = gajim.get_first_contact_instance_from_jid(account, jid)
410                self.draw_name_banner(contact, chatstate)
411
412        def check_for_possible_paused_chatstate(self, jid):
413                ''' did we move mouse of that window or wrote something in message textview
414                in the last 5 seconds?
415                if yes we go active for mouse, composing for kbd
416                if no we go paused if we were previously composing '''
417                if jid not in self.xmls:
418                        # the tab with jid is no longer open. stop timer
419                        return False # stop looping
420                current_state = self.chatstates[jid]
421                if current_state is False: # jid doesn't support chatstates
422                        return False # stop looping
423               
424                if self.mouse_over_in_last_5_secs:
425                        self.send_chatstate('active', jid)
426                elif self.kbd_activity_in_last_5_secs:
427                        self.send_chatstate('composing', jid)
428                else:
429                        if self.chatstates[jid] == 'composing':
430                                self.send_chatstate('paused', jid) # pause composing
431               
432                # assume no activity and let the motion-notify or 'insert-text' make them True
433                # refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds!
434                self.reset_kbd_mouse_timeout_vars()
435                return True # loop forever
436
437        def check_for_possible_inactive_chatstate(self, jid):
438                ''' did we move mouse over that window or wrote something in message textview
439                in the last 30 seconds?
440                if yes we go active
441                if no we go inactive '''
442                if jid not in self.xmls:
443                        # the tab with jid is no longer open. stop timer
444                        return False # stop looping
445
446                current_state = self.chatstates[jid]
447                if current_state is False: # jid doesn't support chatstates
448                        return False # stop looping
449               
450                if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs:
451                        return True # loop forever
452               
453                if not (self.mouse_over_in_last_30_secs or\
454                self.kbd_activity_in_last_30_secs):
455                        self.send_chatstate('inactive', jid)
456
457                # assume no activity and let the motion-notify or 'insert-text' make them True
458                # refresh 30 seconds too or else it's 30 - 5 = 25 seconds!
459                self.reset_kbd_mouse_timeout_vars()
460               
461                return True # loop forever
462
463        def on_message_tv_buffer_insert_text(self, textbuffer, textiter, text,
464        length, jid):
465                self.kbd_activity_in_last_5_secs = True
466                self.kbd_activity_in_last_30_secs = True
467                self.send_chatstate('composing', jid)
468
469        def reset_kbd_mouse_timeout_vars(self):
470                self.kbd_activity_in_last_5_secs = False
471                self.mouse_over_in_last_5_secs = False
472                self.mouse_over_in_last_30_secs = False
473                self.kbd_activity_in_last_30_secs = False
474
475        def on_message_textview_key_press_event(self, widget, event):
476                """When a key is pressed:
477                if enter is pressed without the shift key, message (if not empty) is sent
478                and printed in the conversation"""
479
480                jid = self.get_active_jid()
481                conversation_textview = widget
482                message_buffer = conversation_textview.get_buffer()
483                start_iter, end_iter = message_buffer.get_bounds()
484                message = message_buffer.get_text(start_iter, end_iter, False).decode('utf-8')
485
486                if event.keyval == gtk.keysyms.ISO_Left_Tab: # SHIFT + TAB
487                        if event.state & gtk.gdk.CONTROL_MASK: # CTRL + SHIFT + TAB
488                                self.notebook.emit('key_press_event', event)
489                if event.keyval == gtk.keysyms.Tab:
490                        if event.state & gtk.gdk.CONTROL_MASK: # CTRL + TAB
491                                self.notebook.emit('key_press_event', event)
492                elif event.keyval == gtk.keysyms.Page_Down: # PAGE DOWN
493                        if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE DOWN
494                                self.notebook.emit('key_press_event', event)
495                        elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE DOWN
496                                conversation_textview.emit('key_press_event', event)
497                elif event.keyval == gtk.keysyms.Page_Up: # PAGE UP
498                        if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE UP
499                                self.notebook.emit('key_press_event', event)
500                        elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE UP
501                                conversation_textview.emit('key_press_event', event)
502                elif event.keyval == gtk.keysyms.Up:
503                        if event.state & gtk.gdk.CONTROL_MASK: #Ctrl+UP
504                                self.sent_messages_scroll(jid, 'up', widget.get_buffer())
505                                return True # override the default gtk+ thing for ctrl+up
506                elif event.keyval == gtk.keysyms.Down:
507                        if event.state & gtk.gdk.CONTROL_MASK: #Ctrl+Down
508                                self.sent_messages_scroll(jid, 'down', widget.get_buffer())
509                                return True # override the default gtk+ thing for ctrl+down
510                elif event.keyval == gtk.keysyms.Return or \
511                        event.keyval == gtk.keysyms.KP_Enter: # ENTER
512                        if gajim.config.get('send_on_ctrl_enter'): 
513                                if not (event.state & gtk.gdk.CONTROL_MASK):
514                                        return False
515                        elif (event.state & gtk.gdk.SHIFT_MASK):
516                                        return False
517                        if gajim.connections[self.account].connected < 2: #we are not connected
518                                dialogs.ErrorDialog(_('A connection is not available'),
519                                        _('Your message can not be sent until you are connected.')).get_response()
520                                return True
521
522                        # send the message
523                        self.send_message(message)
524
525                        message_buffer.set_text('')
526                        return True
527
528        def send_chatstate(self, state, jid = None):
529                ''' sends OUR chatstate as STANDLONE chat state message (eg. no body)
530                to jid only if new chatstate is different
531                from the previous one
532                if jid is not specified, send to active tab'''
533                # JEP 85 does not allow resending the same chatstate
534                # this function checks for that and just returns so it's safe to call it
535                # with same state.
536               
537                # This functions also checks for violation in state transitions
538                # and raises RuntimeException with appropriate message
539                # more on that http://www.jabber.org/jeps/jep-0085.html#statechart
540
541                # do not send nothing if we have chat state notifications disabled
542                # that means we won't reply to the <active/> from other peer
543                # so we do not broadcast jep85 capabalities
544                if gajim.config.get('chat_state_notifications') == 'disabled':
545                        return
546
547                if jid is None:
548                        jid = self.get_active_jid()
549                       
550                contact = gajim.get_first_contact_instance_from_jid(self.account, jid)
551
552                if contact is None:
553                        # contact was from pm in MUC, and left the room so contact is None
554                        # so we cannot send chatstate anymore
555                        return
556
557                if contact.chatstate is False: # jid cannot do jep85
558                        return
559
560                # if the new state we wanna send (state) equals
561                # the current state (contact.chastate) then return
562                if contact.chatstate == state:
563                        return
564
565                if contact.chatstate is None:
566                        # we don't know anything about jid, so return
567                        # NOTE:
568                        # send 'active', set current state to 'ask' and return is done
569                        # in self.send_message() because we need REAL message (with <body>)
570                        # for that procedure so return to make sure we send only once 'active'
571                        # until we know peer supports jep85
572                        return 
573
574                if contact.chatstate == 'ask':
575                        return
576
577                # prevent going paused if we we were not composing (JEP violation)
578                if state == 'paused' and not contact.chatstate == 'composing':
579                        gajim.connections[self.account].send_message(jid, None, None,
580                                chatstate = 'active') # go active before
581                        contact.chatstate = 'active'
582                        self.reset_kbd_mouse_timeout_vars()
583               
584                # if we're inactive prevent composing (JEP violation)
585                if contact.chatstate == 'inactive' and state == 'composing':
586                        gajim.connections[self.account].send_message(jid, None, None,
587                                chatstate = 'active') # go active before
588                        contact.chatstate = 'active'
589                        self.reset_kbd_mouse_timeout_vars()
590
591                gajim.connections[self.account].send_message(jid, None, None,
592                        chatstate = state)
593                contact.chatstate = state
594                if contact.chatstate == 'active':
595                        self.reset_kbd_mouse_timeout_vars()
596               
597        def send_message(self, message):
598                """Send the given message to the active tab"""
599                if not message:
600                        return
601
602                jid = self.get_active_jid()
603                contact = gajim.get_first_contact_instance_from_jid(self.account, jid)
604                if contact is None:
605                        # contact was from pm in MUC, and left the room, or we left the room
606                        room,