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

Revision 3091, 28.2 kB (checked in by asterix, 3 years ago)

restore_conversation is back

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_user(user)
57                self.show_title()
58               
59                # NOTE: if it not a window event, connect in new_user 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                        self.window.move(gajim.config.get('chat-x-position'),
76                                        gajim.config.get('chat-y-position'))
77                        self.window.resize(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                                scaled_buf = pixbuf.scale_simple(52, 52, gtk.gdk.INTERP_HYPER)
216                                x = None
217                                if self.xmls.has_key(vcard['jid']):
218                                        x = self.xmls[vcard['jid']]
219                                # it can be xmls[jid/resource] if it's a vcard from pm
220                                elif self.xmls.has_key(vcard['jid'] + '/' + vcard['resource']):
221                                        x = self.xmls[vcard['jid'] + '/' + vcard['resource']]
222                                image = x.get_widget('avatar_image')
223                                image.set_from_pixbuf(scaled_buf)
224                                image.show_all()
225                        except gobject.GError: # we may get "unknown image format"
226                                pass
227
228        def set_state_image(self, jid):
229                prio = 0
230                if gajim.contacts[self.account].has_key(jid):
231                        contacts_list = gajim.contacts[self.account][jid]
232                else:
233                        contacts_list = [self.contacts[jid]]
234
235                user = contacts_list[0]
236                show = user.show
237                jid = user.jid
238                keyID = user.keyID
239
240                for u in contacts_list:
241                        if u.priority > prio:
242                                prio = u.priority
243                                show = u.show
244                                keyID = u.keyID
245                child = self.childs[jid]
246                hb = self.notebook.get_tab_label(child).get_children()[0]
247                status_image = hb.get_children()[0]
248                state_images = self.plugin.roster.get_appropriate_state_images(jid)
249                image = state_images[show]
250                banner_status_image = self.xmls[jid].get_widget('banner_status_image')
251
252                if keyID:
253                        self.xmls[jid].get_widget('gpg_togglebutton').set_sensitive(True)
254                else:
255                        self.xmls[jid].get_widget('gpg_togglebutton').set_sensitive(False)
256
257                if image.get_storage_type() == gtk.IMAGE_ANIMATION:
258                        banner_status_image.set_from_animation(image.get_animation())
259                        status_image.set_from_animation(image.get_animation())
260                elif image.get_storage_type() == gtk.IMAGE_PIXBUF:
261                        # make a copy because one will be scaled, one not (tab icon)
262                        pix = image.get_pixbuf()
263                        scaled_pix = pix.scale_simple(32, 32, gtk.gdk.INTERP_BILINEAR)
264                        banner_status_image.set_from_pixbuf(scaled_pix)
265                        status_image.set_from_pixbuf(pix)
266
267        def on_tabbed_chat_window_delete_event(self, widget, event):
268                '''close window'''
269                for jid in self.contacts:
270                        if time.time() - gajim.last_message_time[self.account][jid] < 2:
271                                # 2 seconds
272                                dialog = dialogs.ConfirmationDialog(
273                                        #%s is being replaced in the code with JID
274                                        _('You just received a new message from "%s"' % jid),
275                                        _('If you close the window, this message will be lost.'))
276                                if dialog.get_response() != gtk.RESPONSE_OK:
277                                        return True #stop the propagation of the event
278
279                if gajim.config.get('saveposition'):
280                        # save the window size and position
281                        x, y = self.window.get_position()
282                        gajim.config.set('chat-x-position', x)
283                        gajim.config.set('chat-y-position', y)
284                        width, height = self.window.get_size()
285                        gajim.config.set('chat-width', width)
286                        gajim.config.set('chat-height', height)
287
288        def on_tabbed_chat_window_destroy(self, widget):
289                #clean self.plugin.windows[self.account]['chats']
290                chat.Chat.on_window_destroy(self, widget, 'chats')
291
292        def on_tabbed_chat_window_focus_in_event(self, widget, event):
293                chat.Chat.on_chat_window_focus_in_event(self, widget, event)
294                # on focus in, send 'active' chatstate to current tab
295                self.send_chatstate('active')
296
297        def on_tabbed_chat_window_focus_out_event(self, widget, event):
298                '''catch focus out and minimized and send inactive chatstate;
299                minimize action also focuses out first so it's catched here'''
300                window_state = widget.window.get_state()
301                if window_state is None:
302                        return
303               
304                # focus-out is also emitted by showing context menu
305                # so check to see if we're really not paying attention to window/tab
306                # NOTE: if the user changes tab, switch-tab sends inactive to the tab
307                # we are leaving so we just send to active tab here
308                if self.popup_is_shown is False: # we are outside of the window
309                        # so no context menu, so send 'inactive' to active tab
310                        self.send_chatstate('inactive')
311
312        def on_chat_notebook_key_press_event(self, widget, event):
313                chat.Chat.on_chat_notebook_key_press_event(self, widget, event)
314
315        def on_send_file_menuitem_activate(self, widget):
316                jid = self.get_active_jid()
317                contact = gajim.get_first_contact_instance_from_jid(self.account, jid)
318                self.plugin.windows['file_transfers'].show_file_send_request( 
319                        self.account, contact)
320
321        def on_add_to_roster_menuitem_activate(self, widget):
322                jid = self.get_active_jid()
323                dialogs.AddNewContactWindow(self.plugin, self.account, jid)
324
325        def on_send_button_clicked(self, widget):
326                """When send button is pressed: send the current message"""
327                jid = self.get_active_jid()
328                message_textview = self.xmls[jid].get_widget('message_textview')
329                message_buffer = message_textview.get_buffer()
330                start_iter = message_buffer.get_start_iter()
331                end_iter = message_buffer.get_end_iter()
332                message = message_buffer.get_text(start_iter, end_iter, 0)
333
334                # send the message
335                self.send_message(message)
336
337                message_buffer.set_text('')
338
339        def remove_tab(self, jid):
340                if time.time() - gajim.last_message_time[self.account][jid] < 2:
341                        dialog = dialogs.ConfirmationDialog(
342                                _('You just received a new message from "%s"' % jid),
343                                _('If you close this tab, the message will be lost.'))
344                        if dialog.get_response() != gtk.RESPONSE_OK:
345                                return
346
347                # chatstates - tab is destroyed, send gone
348                self.send_chatstate('gone', jid)
349               
350                chat.Chat.remove_tab(self, jid, 'chats')
351                del self.contacts[jid]
352       
353        def new_user(self, contact):
354                '''when new tab is created'''
355                self.names[contact.jid] = contact.name
356                self.xmls[contact.jid] = gtk.glade.XML(GTKGUI_GLADE, 'chats_vbox', APP)
357                self.childs[contact.jid] = self.xmls[contact.jid].get_widget('chats_vbox')
358                self.contacts[contact.jid] = contact
359               
360               
361                self.childs[contact.jid].connect('drag_data_received',
362                        self.on_drag_data_received, contact)
363                self.childs[contact.jid].drag_dest_set( gtk.DEST_DEFAULT_MOTION |
364                        gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP,
365                                 self.dnd_list, gtk.gdk.ACTION_COPY)
366               
367                message_textview = self.xmls[contact.jid].get_widget('message_textview')
368                message_tv_buffer = message_textview.get_buffer()
369                message_tv_buffer.connect('insert-text',
370                        self.on_message_tv_buffer_insert_text, contact.jid)
371
372                if contact.jid in gajim.encrypted_chats[self.account]:
373                        self.xmls[contact.jid].get_widget('gpg_togglebutton').set_active(True)
374               
375                xm = gtk.glade.XML(GTKGUI_GLADE, 'tabbed_chat_popup_menu', APP)
376                xm.signal_autoconnect(self)
377                self.tabbed_chat_popup_menu = xm.get_widget('tabbed_chat_popup_menu')
378               
379                chat.Chat.new_tab(self, contact.jid)
380                self.redraw_tab(contact.jid)
381                self.draw_widgets(contact)
382
383                # restore previous conversation
384                self.restore_conversation(contact.jid)
385
386                # print queued messages
387                if gajim.awaiting_messages[self.account].has_key(contact.jid):
388                        self.read_queue(contact.jid)
389
390                gajim.connections[self.account].request_vcard(contact.jid)
391                self.childs[contact.jid].show_all()
392
393                # chatstates
394                self.reset_kbd_mouse_timeout_vars()
395               
396                self.chatstates[contact.jid] = None # OUR current chatstate with contact
397                self.possible_paused_timeout_id[contact.jid] =\
398                        gobject.timeout_add(5000, self.check_for_possible_paused_chatstate,
399                                contact.jid)
400                self.possible_inactive_timeout_id[contact.jid] =\
401                        gobject.timeout_add(30000, self.check_for_possible_inactive_chatstate,
402                                contact.jid)
403               
404        def handle_incoming_chatstate(self, account, jid, chatstate):
405                ''' handle incoming chatstate that jid SENT TO us '''
406                contact = gajim.get_first_contact_instance_from_jid(account, jid)
407                self.draw_name_banner(contact, chatstate)
408
409        def check_for_possible_paused_chatstate(self, jid):
410                ''' did we move mouse of that window or wrote something in message textview
411                in the last 5 seconds?
412                if yes we go active for mouse, composing for kbd
413                if no we go paused if we were previously composing '''
414                if jid not in self.xmls:
415                        # the tab with jid is no longer open. stop timer
416                        return False # stop looping
417                current_state = self.chatstates[jid]
418                if current_state is False: # jid doesn't support chatstates
419                        return False # stop looping
420               
421                if self.mouse_over_in_last_5_secs:
422                        self.send_chatstate('active', jid)
423                elif self.kbd_activity_in_last_5_secs:
424                        self.send_chatstate('composing', jid)
425                else:
426                        if self.chatstates[jid] == 'composing':
427                                self.send_chatstate('paused', jid) # pause composing
428               
429                # assume no activity and let the motion-notify or 'insert-text' make them True
430                # refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds!
431                self.reset_kbd_mouse_timeout_vars()
432                return True # loop forever
433
434        def check_for_possible_inactive_chatstate(self, jid):
435                ''' did we move mouse over that window or wrote something in message textview
436                in the last 30 seconds?
437                if yes we go active
438                if no we go inactive '''
439                if jid not in self.xmls:
440                        # the tab with jid is no longer open. stop timer
441                        return False # stop looping
442
443                current_state = self.chatstates[jid]
444                if current_state is False: # jid doesn't support chatstates
445                        return False # stop looping
446               
447                if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs:
448                        return True # loop forever
449               
450                if not (self.mouse_over_in_last_30_secs or\
451                self.kbd_activity_in_last_30_secs):
452                        self.send_chatstate('inactive', jid)
453
454                # assume no activity and let the motion-notify or 'insert-text' make them True
455                # refresh 30 seconds too or else it's 30 - 5 = 25 seconds!
456                self.reset_kbd_mouse_timeout_vars()
457               
458                return True # loop forever
459
460        def on_message_tv_buffer_insert_text(self, textbuffer, textiter, text,
461        length, jid):
462                self.kbd_activity_in_last_5_secs = True
463                self.kbd_activity_in_last_30_secs = True
464                self.send_chatstate('composing', jid)
465
466        def reset_kbd_mouse_timeout_vars(self):
467                self.kbd_activity_in_last_5_secs = False
468                self.mouse_over_in_last_5_secs = False
469                self.mouse_over_in_last_30_secs = False
470                self.kbd_activity_in_last_30_secs = False
471
472        def on_message_textview_key_press_event(self, widget, event):
473                """When a key is pressed:
474                if enter is pressed without the shift key, message (if not empty) is sent
475                and printed in the conversation"""
476
477                jid = self.get_active_jid()
478                conversation_textview = widget
479                message_buffer = conversation_textview.get_buffer()
480                start_iter, end_iter = message_buffer.get_bounds()
481                message = message_buffer.get_text(start_iter, end_iter, False)
482
483                if event.keyval == gtk.keysyms.ISO_Left_Tab: # SHIFT + TAB
484                        if event.state & gtk.gdk.CONTROL_MASK: # CTRL + SHIFT + TAB
485                                self.notebook.emit('key_press_event', event)
486                if event.keyval == gtk.keysyms.Tab:
487                        if event.state & gtk.gdk.CONTROL_MASK: # CTRL + TAB
488                                self.notebook.emit('key_press_event', event)
489                elif event.keyval == gtk.keysyms.Page_Down: # PAGE DOWN
490                        if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE DOWN
491                                self.notebook.emit('key_press_event', event)
492                        elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE DOWN
493                                conversation_textview.emit('key_press_event', event)
494                elif event.keyval == gtk.keysyms.Page_Up: # PAGE UP
495                        if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE UP
496                                self.notebook.emit('key_press_event', event)
497                        elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE UP
498                                conversation_textview.emit('key_press_event', event)
499                elif event.keyval == gtk.keysyms.Up:
500                        if event.state & gtk.gdk.CONTROL_MASK: #Ctrl+UP
501                                self.sent_messages_scroll(jid, 'up', widget.get_buffer())
502                                return True # override the default gtk+ thing for ctrl+up
503                elif event.keyval == gtk.keysyms.Down:
504                        if event.state & gtk.gdk.CONTROL_MASK: #Ctrl+Down
505                                self.sent_messages_scroll(jid, 'down', widget.get_buffer())
506                                return True # override the default gtk+ thing for ctrl+down
507                elif event.keyval == gtk.keysyms.Return or \
508                        event.keyval == gtk.keysyms.KP_Enter: # ENTER
509                        if gajim.config.get('send_on_ctrl_enter'): 
510                                if not (event.state & gtk.gdk.CONTROL_MASK):
511                                        return False
512                        elif (event.state & gtk.gdk.SHIFT_MASK):
513                                        return False
514                        if gajim.connections[self.account].connected < 2: #we are not connected
515                                dialogs.ErrorDialog(_('A connection is not available'),
516                                        _('Your message can not be sent until you are connected.')).get_response()
517                                return True
518
519                        # send the message
520                        self.send_message(message)
521
522                        message_buffer.set_text('')
523                        return True
524
525        def send_chatstate(self, state, jid = None):
526                ''' sends OUR chatstate as STANDLONE chat state message (eg. no body)
527                to jid only if new chatstate is different
528                from the previous one
529                if jid is not specified, send to active tab'''
530                # JEP 85 does not allow resending the same chatstate
531                # this function checks for that and just returns so it's safe to call it
532                # with same state.
533               
534                # This functions also checks for violation in state transitions
535                # and raises RuntimeException with appropriate message
536                # more on that http://www.jabber.org/jeps/jep-0085.html#statechart
537
538                # do not send nothing if we have chat state notifications disabled
539                # that means we won't reply to the <active/> from other peer
540                # so we do not broadcast jep85 capabalities
541                if gajim.config.get('chat_state_notifications') == 'disabled':
542                        return
543
544                if jid is None:
545                        jid = self.get_active_jid()
546                       
547                contact = gajim.get_first_contact_instance_from_jid(self.account, jid)
548
549                if contact is None:
550                        # contact was from pm in MUC, and left the room so contact is None
551                        # so we cannot send chatstate anymore
552                        return
553
554                if contact.chatstate is False: # jid cannot do jep85
555                        return
556
557                # if the new state we wanna send (state) equals
558                # the current state (contact.chastate) then return
559                if contact.chatstate == state:
560                        return
561
562                if contact.chatstate is None:
563                        # we don't know anything about jid, so return
564                        # NOTE:
565                        # send 'active', set current state to 'ask' and return is done
566                        # in self.send_message() because we need REAL message (with <body>)
567                        # for that procedure so return to make sure we send only once 'active'
568                        # until we know peer supports jep85
569                        return 
570
571                if contact.chatstate == 'ask':
572                        return
573
574                # prevent going paused if we we were not composing (JEP violation)
575                if state == 'paused' and not contact.chatstate == 'composing':
576                        gajim.connections[self.account].send_message(jid, None, None,
577                                chatstate = 'active') # go active before
578                        contact.chatstate = 'active'
579                        self.reset_kbd_mouse_timeout_vars()
580               
581                # if we're inactive prevent composing (JEP violation)
582                if contact.chatstate == 'inactive' and state == 'composing':
583                        gajim.connections[self.account].send_message(jid, None, None,
584                                chatstate = 'active') # go active before
585                        contact.chatstate = 'active'
586                        self.reset_kbd_mouse_timeout_vars()
587
588                gajim.connections[self.account].send_message(jid, None, None,
589                        chatstate = state)
590                contact.chatstate = state
591                if contact.chatstate == 'active':
592                        self.reset_kbd_mouse_timeout_vars()
593               
594        def send_message(self, message):
595                """Send the given message to the active tab"""
596                if not message:
597                        return
598
599                jid = self.get_active_jid()
600                contact = gajim.get_first_contact_instance_from_jid(self.account, jid)
601                if contact is None:
602                        # contact was from pm in MUC, and left the room, or we left the room
603                        room, nick = gajim.get_room_and_nick_from_fjid(jid)
604                        dialogs.ErrorDialog(_('Sending private message failed'),
605                                #in second %s code replaces with nickname
606                                _('You are no longer in room "%s" or "%s" has left.') % \
607                                (room, nick))