root/tags/gajim-0.11.4/src/chat_control.py

Revision 8956, 67.2 kB (checked in by asterix, 10 months ago)

use accel groups

Line 
1##      chat_control.py
2##
3## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>
4## Copyright (C) 2006 Nikos Kouremenos <kourem@gmail.com>
5## Copyright (C) 2006 Travis Shirk <travis@pobox.com>
6## Copyright (C) 2006 Dimitur Kirov <dkirov@gmail.com>
7##
8## This program is free software; you can redistribute it and/or modify
9## it under the terms of the GNU General Public License as published
10## by the Free Software Foundation; version 2 only.
11##
12## This program is distributed in the hope that it will be useful,
13## but WITHOUT ANY WARRANTY; without even the implied warranty of
14## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15## GNU General Public License for more details.
16##
17
18import os
19import time
20import gtk
21import pango
22import gobject
23import gtkgui_helpers
24import message_control
25import dialogs
26import history_window
27import notify
28
29from common import gajim
30from common import helpers
31from message_control import MessageControl
32from conversation_textview import ConversationTextview
33from message_textview import MessageTextView
34from common.contacts import GC_Contact
35from common.logger import Constants
36constants = Constants()
37from common.rst_xhtml_generator import create_xhtml
38from common.xmpp.protocol import NS_XHTML
39
40try:
41        import gtkspell
42        HAS_GTK_SPELL = True
43except:
44        HAS_GTK_SPELL = False
45
46
47# the next script, executed in the "po" directory,
48# generates the following list.
49##!/bin/sh
50#LANG=$(for i in *.po; do  j=${i/.po/}; echo -n "_('"$j"')":" '"$j"', " ; done)
51#echo "{_('en'):'en'",$LANG"}"
52langs = {_('English'): 'en', _('Belarusian'): 'be', _('Bulgarian'): 'bg', _('Briton'): 'br', _('Czech'): 'cs', _('German'): 'de', _('Greek'): 'el', _('British'): 'en_GB', _('Esperanto'): 'eo', _('Spanish'): 'es', _('Basc'): 'eu', _('French'): 'fr', _('Croatian'): 'hr', _('Italian'): 'it', _('Norwegian (b)'): 'nb', _('Dutch'): 'nl', _('Norwegian'): 'no', _('Polish'): 'pl', _('Portuguese'): 'pt', _('Brazilian Portuguese'): 'pt_BR', _('Russian'): 'ru', _('Serbian'): 'sr', _('Slovak'): 'sk', _('Swedish'): 'sv', _('Chinese (Ch)'): 'zh_CN'}
53
54
55################################################################################
56class ChatControlBase(MessageControl):
57        '''A base class containing a banner, ConversationTextview, MessageTextView
58        '''
59        def get_font_attrs(self):
60                ''' get pango font  attributes for banner from theme settings '''
61                theme = gajim.config.get('roster_theme')
62                bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
63                bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs')
64
65                if bannerfont:
66                        font = pango.FontDescription(bannerfont)
67                else:
68                        font = pango.FontDescription('Normal')
69                if bannerfontattrs:
70                        # B attribute is set by default
71                        if 'B' in bannerfontattrs:
72                                font.set_weight(pango.WEIGHT_HEAVY)
73                        if 'I' in bannerfontattrs:
74                                font.set_style(pango.STYLE_ITALIC)
75
76                font_attrs = 'font_desc="%s"' % font.to_string()
77
78                # in case there is no font specified we use x-large font size
79                if font.get_size() == 0:
80                        font_attrs = '%s size="x-large"' % font_attrs
81                font.set_weight(pango.WEIGHT_NORMAL)
82                font_attrs_small = 'font_desc="%s" size="small"' % font.to_string()
83                return (font_attrs, font_attrs_small)
84
85        def get_nb_unread(self):
86                jid = self.contact.jid
87                if self.resource:
88                        jid += '/' + self.resource
89                type_ = self.type_id
90                return len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
91                        type_]))
92
93        def draw_banner(self):
94                '''Draw the fat line at the top of the window that
95                houses the icon, jid, ...
96                '''
97                self.draw_banner_text()
98                self._update_banner_state_image()
99                # Derived types MAY implement this
100
101        def draw_banner_text(self):
102                pass # Derived types SHOULD implement this
103
104        def update_ui(self):
105                self.draw_banner()
106                # Derived types SHOULD implement this
107
108        def repaint_themed_widgets(self):
109                self._paint_banner()
110                self.draw_banner()
111                # Derived classes MAY implement this
112
113        def _update_banner_state_image(self):
114                pass # Derived types MAY implement this
115
116        def handle_message_textview_mykey_press(self, widget, event_keyval,
117        event_keymod):
118                pass # Derived should implement this rather than connecting to the event itself.
119
120        def __init__(self, type_id, parent_win, widget_name, contact, acct,
121        resource = None):
122                MessageControl.__init__(self, type_id, parent_win, widget_name,
123                        contact, acct, resource = resource);
124                # when/if we do XHTML we will put formatting buttons back
125                widget = self.xml.get_widget('emoticons_button')
126                id = widget.connect('clicked', self.on_emoticons_button_clicked)
127                self.handlers[id] = widget
128
129                id = self.widget.connect('key_press_event', self._on_keypress_event)
130                self.handlers[id] = self.widget
131
132                widget = self.xml.get_widget('banner_eventbox')
133                widget.set_property('height-request', gajim.config.get('chat_avatar_height'))
134                id = widget.connect('button-press-event',
135                        self._on_banner_eventbox_button_press_event)
136                self.handlers[id] = widget
137
138                # Create textviews and connect signals
139                self.conv_textview = ConversationTextview(self.account)
140                id = self.conv_textview.tv.connect('key_press_event',
141                        self._conv_textview_key_press_event)
142                self.handlers[id] = self.conv_textview.tv
143
144                self.conv_scrolledwindow = self.xml.get_widget(
145                        'conversation_scrolledwindow')
146                self.conv_scrolledwindow.add(self.conv_textview.tv)
147                widget = self.conv_scrolledwindow.get_vadjustment()
148                id = widget.connect('value-changed',
149                        self.on_conversation_vadjustment_value_changed)
150                self.handlers[id] = widget
151                id = widget.connect('changed',
152                        self.on_conversation_vadjustment_changed)
153                self.handlers[id] = widget
154                self.scroll_to_end_id = None
155                self.was_at_the_end = True
156                # add MessageTextView to UI and connect signals
157                self.msg_scrolledwindow = self.xml.get_widget('message_scrolledwindow')
158                self.msg_textview = MessageTextView()
159                id = self.msg_textview.connect('mykeypress',
160                        self._on_message_textview_mykeypress_event)
161                self.handlers[id] = self.msg_textview
162                self.msg_scrolledwindow.add(self.msg_textview)
163                id = self.msg_textview.connect('key_press_event',
164                        self._on_message_textview_key_press_event)
165                self.handlers[id] = self.msg_textview
166                id = self.msg_textview.connect('size-request', self.size_request)
167                self.handlers[id] = self.msg_textview
168                id = self.msg_textview.connect('populate_popup',
169                        self.on_msg_textview_populate_popup)
170                self.handlers[id] = self.msg_textview
171       
172                self.update_font()
173
174                # Hook up send button
175                widget = self.xml.get_widget('send_button')
176                id = widget.connect('clicked', self._on_send_button_clicked)
177                self.handlers[id] = widget
178
179                # the following vars are used to keep history of user's messages
180                self.sent_history = []
181                self.sent_history_pos = 0
182                self.orig_msg = None
183
184                # Emoticons menu
185                # set image no matter if user wants at this time emoticons or not
186                # (so toggle works ok)
187                img = self.xml.get_widget('emoticons_button_image')
188                img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
189                        'smile.png'))
190                self.toggle_emoticons()
191
192                # Attach speller
193                if gajim.config.get('use_speller') and HAS_GTK_SPELL:
194                        try:
195                                spell = gtkspell.Spell(self.msg_textview)
196                                # loop removing non-existant dictionaries
197                                # iterating on a copy
198                                for lang in dict(langs):
199                                        try:
200                                                spell.set_language(langs[lang])
201                                        except:
202                                                del langs[lang]
203                                # now set the one the user selected
204                                per_type = 'contacts'
205                                if self.type_id == message_control.TYPE_GC:
206                                        per_type = 'rooms'
207                                lang = gajim.config.get_per(per_type, self.contact.jid,
208                                        'speller_language')
209                                if not lang:
210                                        # use the default one
211                                        lang = gajim.config.get('speller_language')
212                                if lang:
213                                        self.msg_textview.lang = lang
214                                        spell.set_language(lang)
215                        except (gobject.GError, RuntimeError), msg:
216                                dialogs.ErrorDialog(unicode(msg), _('If that is not your language '
217                                        'for which you want to highlight misspelled words, then please '
218                                        'set your $LANG as appropriate. Eg. for French do export '
219                                        'LANG=fr_FR or export LANG=fr_FR.UTF-8 in ~/.bash_profile or to '
220                                        'make it global in /etc/profile.\n\nHighlighting misspelled '
221                                        'words feature will not be used'))
222                                gajim.config.set('use_speller', False)
223
224                self.style_event_id = 0
225                self.conv_textview.tv.show()
226                self._paint_banner()
227
228                # For JEP-0172
229                self.user_nick = None
230
231        def on_msg_textview_populate_popup(self, textview, menu):
232                '''we override the default context menu and we prepend an option to switch languages'''
233                def _on_select_dictionary(widget, lang):
234                        per_type = 'contacts'
235                        if self.type_id == message_control.TYPE_GC:
236                                per_type = 'rooms'
237                        if not gajim.config.get_per(per_type, self.contact.jid):
238                                gajim.config.add_per(per_type, self.contact.jid)
239                        gajim.config.set_per(per_type, self.contact.jid, 'speller_language',
240                                lang)
241                        spell = gtkspell.get_from_text_view(self.msg_textview)
242                        self.msg_textview.lang = lang
243                        spell.set_language(lang)
244                        widget.set_active(True)
245
246                item = gtk.SeparatorMenuItem()
247                menu.prepend(item)
248
249                item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
250                menu.prepend(item)
251                id = item.connect('activate', self.msg_textview.clear)
252                self.handlers[id] = item
253
254                if gajim.config.get('use_speller') and HAS_GTK_SPELL:
255                        item = gtk.MenuItem(_('Spelling language'))
256                        menu.prepend(item)
257                        submenu = gtk.Menu()
258                        item.set_submenu(submenu)
259                        for lang in sorted(langs):
260                                item = gtk.CheckMenuItem(lang)
261                                if langs[lang] == self.msg_textview.lang:
262                                        item.set_active(True)
263                                submenu.append(item)
264                                id = item.connect('activate', _on_select_dictionary, langs[lang])
265                                self.handlers[id] = item
266
267                menu.show_all()
268
269        # moved from ChatControl
270        def _on_banner_eventbox_button_press_event(self, widget, event):
271                '''If right-clicked, show popup'''
272                if event.button == 3: # right click
273                        self.parent_win.popup_menu(event)
274
275        def _on_send_button_clicked(self, widget):
276                '''When send button is pressed: send the current message'''
277                if gajim.connections[self.account].connected < 2: # we are not connected
278                        dialogs.ErrorDialog(_('A connection is not available'),
279                                _('Your message can not be sent until you are connected.'))
280                        return
281                message_buffer = self.msg_textview.get_buffer()
282                start_iter = message_buffer.get_start_iter()
283                end_iter = message_buffer.get_end_iter()
284                message = message_buffer.get_text(start_iter, end_iter, 0).decode('utf-8')
285
286                # send the message
287                self.send_message(message)
288
289        def _paint_banner(self):
290                '''Repaint banner with theme color'''
291                theme = gajim.config.get('roster_theme')
292                bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
293                textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
294                # the backgrounds are colored by using an eventbox by
295                # setting the bg color of the eventbox and the fg of the name_label
296                banner_eventbox = self.xml.get_widget('banner_eventbox')
297                banner_name_label = self.xml.get_widget('banner_name_label')
298                self.disconnect_style_event(banner_name_label)
299                if bgcolor:
300                        banner_eventbox.modify_bg(gtk.STATE_NORMAL,
301                                gtk.gdk.color_parse(bgcolor))
302                        default_bg = False
303                else:
304                        default_bg = True
305                if textcolor:
306                        banner_name_label.modify_fg(gtk.STATE_NORMAL,
307                                gtk.gdk.color_parse(textcolor))
308                        default_fg = False
309                else:
310                        default_fg = True
311                if default_bg or default_fg:
312                        self._on_style_set_event(banner_name_label, None, default_fg,
313                                default_bg)
314       
315        def disconnect_style_event(self, widget):
316                if self.style_event_id:
317                        widget.disconnect(self.style_event_id)
318                        del self.handlers[self.style_event_id]
319                        self.style_event_id = 0
320       
321        def connect_style_event(self, widget, set_fg = False, set_bg = False):
322                self.disconnect_style_event(widget)
323                self.style_event_id = widget.connect('style-set',
324                        self._on_style_set_event, set_fg, set_bg)
325                self.handlers[self.style_event_id] = widget
326       
327        def _on_style_set_event(self, widget, style, *opts):
328                '''set style of widget from style class *.Frame.Eventbox
329                        opts[0] == True -> set fg color
330                        opts[1] == True -> set bg color'''
331                banner_eventbox = self.xml.get_widget('banner_eventbox')
332                self.disconnect_style_event(widget)
333                if opts[1]:
334                        bg_color = widget.style.bg[gtk.STATE_SELECTED]
335                        banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color)
336                if opts[0]:
337                        fg_color = widget.style.fg[gtk.STATE_SELECTED]
338                        widget.modify_fg(gtk.STATE_NORMAL, fg_color)
339                self.connect_style_event(widget, opts[0], opts[1])
340       
341        def _conv_textview_key_press_event(self, widget, event):
342                if gtk.gtk_version < (2, 12, 0):
343                        return
344                if event.state & (gtk.gdk.SHIFT_MASK | gtk.gdk.CONTROL_MASK):
345                        return False
346                self.parent_win.notebook.emit('key_press_event', event)
347
348        def _on_keypress_event(self, widget, event):
349                if event.state & gtk.gdk.CONTROL_MASK:
350                        # CTRL + l|L: clear conv_textview
351                        if event.keyval == gtk.keysyms.l or event.keyval == gtk.keysyms.L:
352                                self.conv_textview.clear()
353                                return True
354                        # CTRL + v: Paste into msg_textview
355                        elif event.keyval == gtk.keysyms.v:
356                                if not self.msg_textview.is_focus():
357                                        self.msg_textview.grab_focus()
358                                # Paste into the msg textview
359                                self.msg_textview.emit('key_press_event', event)
360                        # CTRL + u: emacs style clear line
361                        elif event.keyval == gtk.keysyms.u:
362                                self.clear(self.msg_textview) # clear message textview too
363                        elif event.keyval == gtk.keysyms.ISO_Left_Tab: # CTRL + SHIFT + TAB
364                                self.parent_win.move_to_next_unread_tab(False)
365                                return True
366                        elif event.keyval == gtk.keysyms.Tab: # CTRL + TAB
367                                self.parent_win.move_to_next_unread_tab(True)
368                                return True
369                        # CTRL + PAGE_[UP|DOWN]: send to parent notebook
370                        elif event.keyval == gtk.keysyms.Page_Down or \
371                                        event.keyval == gtk.keysyms.Page_Up:
372                                self.parent_win.notebook.emit('key_press_event', event)
373                                return True
374                elif event.keyval == gtk.keysyms.m and \
375                        (event.state & gtk.gdk.MOD1_MASK): # alt + m opens emoticons menu
376                        if gajim.config.get('emoticons_theme'):
377                                msg_tv = self.msg_textview
378                                def set_emoticons_menu_position(w, msg_tv = self.msg_textview):
379                                        window = msg_tv.get_window(gtk.TEXT_WINDOW_WIDGET)
380                                        # get the window position
381                                        origin = window.get_origin()
382                                        size = window.get_size()
383                                        buf = msg_tv.get_buffer()
384                                        # get the cursor position
385                                        cursor = msg_tv.get_iter_location(buf.get_iter_at_mark(
386                                                buf.get_insert()))
387                                        cursor =  msg_tv.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT,
388                                                cursor.x, cursor.y)
389                                        x = origin[0] + cursor[0]
390                                        y = origin[1] + size[1]
391                                        menu_width, menu_height = \
392                                                gajim.interface.emoticons_menu.size_request()
393                                        #FIXME: get_line_count is not so good
394                                        #get the iter of cursor, then tv.get_line_yrange
395                                        # so we know in which y we are typing (not how many lines we have
396                                        # then go show just above the current cursor line for up
397                                        # or just below the current cursor line for down
398                                        #TEST with having 3 lines and writing in the 2nd
399                                        if y + menu_height > gtk.gdk.screen_height():
400                                                # move menu just above cursor
401                                                y -= menu_height +\
402                                                        (msg_tv.allocation.height / buf.get_line_count())
403                                        #else: # move menu just below cursor
404                                        #       y -= (msg_tv.allocation.height / buf.get_line_count())
405                                        return (x, y, True) # push_in True
406                                gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
407                                gajim.interface.emoticons_menu.popup(None, None,
408                                        set_emoticons_menu_position, 1, 0)
409                return False
410
411        def _on_message_textview_key_press_event(self, widget, event):
412                if self.widget_name == 'muc_child_vbox':
413                        if event.keyval not in (gtk.keysyms.ISO_Left_Tab, gtk.keysyms.Tab):
414                                self.last_key_tabs = False
415                if event.state & gtk.gdk.SHIFT_MASK:
416                        # CTRL + SHIFT + TAB
417                        if event.state & gtk.gdk.CONTROL_MASK and \
418                                        event.keyval == gtk.keysyms.ISO_Left_Tab:
419                                self.parent_win.move_to_next_unread_tab(False)
420                                return True
421                        # SHIFT + PAGE_[UP|DOWN]: send to conv_textview
422                        elif event.keyval == gtk.keysyms.Page_Down or \
423                                        event.keyval == gtk.keysyms.Page_Up:
424                                self.conv_textview.tv.emit('key_press_event', event)
425                                return True
426                elif event.state & gtk.gdk.CONTROL_MASK:
427                        if event.keyval == gtk.keysyms.Tab: # CTRL + TAB
428                                self.parent_win.move_to_next_unread_tab(True)
429                                return True
430                        # CTRL + PAGE_[UP|DOWN]: send to parent notebook
431                        elif event.keyval == gtk.keysyms.Page_Down or \
432                                        event.keyval == gtk.keysyms.Page_Up:
433                                self.parent_win.notebook.emit('key_press_event', event)
434                                return True
435                        # we pressed a control key or ctrl+sth: we don't block
436                        # the event in order to let ctrl+c (copy text) and
437                        # others do their default work
438                        self.conv_textview.tv.emit('key_press_event', event)
439                return False
440
441        def _on_message_textview_mykeypress_event(self, widget, event_keyval,
442                event_keymod):
443                '''When a key is pressed:
444                if enter is pressed without the shift key, message (if not empty) is sent
445                and printed in the conversation'''
446
447                # NOTE: handles mykeypress which is custom signal connected to this
448                # CB in new_tab(). for this singal see message_textview.py
449                message_textview = widget
450                message_buffer = message_textview.get_buffer()
451                start_iter, end_iter = message_buffer.get_bounds()
452                message = message_buffer.get_text(start_iter, end_iter, False).decode(
453                        'utf-8')
454
455                # construct event instance from binding
456                event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
457                event.keyval = event_keyval
458                event.state = event_keymod
459                event.time = 0 # assign current time
460
461                if event.keyval == gtk.keysyms.Up:
462                        if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+UP
463                                self.sent_messages_scroll('up', widget.get_buffer())
464                elif event.keyval == gtk.keysyms.Down:
465                        if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+Down
466                                self.sent_messages_scroll('down', widget.get_buffer())
467                elif event.keyval == gtk.keysyms.Return or \
468                        event.keyval == gtk.keysyms.KP_Enter: # ENTER
469                        # NOTE: SHIFT + ENTER is not needed to be emulated as it is not
470                        # binding at all (textview's default action is newline)
471
472                        if gajim.config.get('send_on_ctrl_enter'):
473                                # here, we emulate GTK default action on ENTER (add new line)
474                                # normally I would add in keypress but it gets way to complex
475                                # to get instant result on changing this advanced setting
476                                if event.state == 0: # no ctrl, no shift just ENTER add newline
477                                        end_iter = message_buffer.get_end_iter()
478                                        message_buffer.insert_at_cursor('\n')
479                                        send_message = False
480                                elif event.state & gtk.gdk.CONTROL_MASK: # CTRL + ENTER
481                                        send_message = True
482                        else: # send on Enter, do newline on Ctrl Enter
483                                if event.state & gtk.gdk.CONTROL_MASK: # Ctrl + ENTER
484                                        end_iter = message_buffer.get_end_iter()
485                                        message_buffer.insert_at_cursor('\n')
486                                        send_message = False
487                                else: # ENTER
488                                        send_message = True
489                               
490                        if gajim.connections[self.account].connected < 2: # we are not connected
491                                dialogs.ErrorDialog(_('A connection is not available'),
492                                        _('Your message can not be sent until you are connected.'))
493                                send_message = False
494
495                        if send_message:
496                                self.send_message(message) # send the message
497                else:
498                        # Give the control itself a chance to process
499                        self.handle_message_textview_mykey_press(widget, event_keyval,
500                                event_keymod)
501
502        def _process_command(self, message):
503                if not message or message[0] != '/':
504                        return False
505
506                message = message[1:]
507                message_array = message.split(' ', 1)
508                command = message_array.pop(0).lower()
509                if message_array == ['']:
510                        message_array = []
511
512                if command == 'clear' and not len(message_array):
513                        self.conv_textview.clear() # clear conversation
514                        self.clear(self.msg_textview) # clear message textview too
515                        return True
516                elif message == 'compact' and not len(message_array):
517                        self.chat_buttons_set_visible(not self.hide_chat_buttons_current)
518                        self.clear(self.msg_textview)
519                        return True
520                return False
521
522        def send_message(self, message, keyID = '', type = 'chat', chatstate = None,
523        msg_id = None, composing_xep = None, resource = None):
524                '''Send the given message to the active tab. Doesn't return None if error
525                '''
526                if not message or message == '\n':
527                        return 1
528
529
530                if not self._process_command(message):
531                        ret = MessageControl.send_message(self, message, keyID, type = type,
532                                chatstate = chatstate, msg_id = msg_id,
533                                composing_xep = composing_xep, resource = resource,
534                                user_nick = self.user_nick)
535                        if ret:
536                                return ret
537                        # Record message history
538                        self.save_sent_message(message)
539
540                        # Be sure to send user nickname only once according to JEP-0172
541                        self.user_nick = None
542
543                # Clear msg input
544                message_buffer = self.msg_textview.get_buffer()
545                message_buffer.set_text('') # clear message buffer (and tv of course)
546
547        def save_sent_message(self, message):
548                #save the message, so user can scroll though the list with key up/down
549                size = len(self.sent_history)
550                #we don't want size of the buffer to grow indefinately
551                max_size = gajim.config.get('key_up_lines')
552                if size >= max_size:
553                        for i in xrange(0, size - 1):
554                                self.sent_history[i] = self.sent_history[i + 1]
555                        self.sent_history[max_size - 1] = message
556                else:
557                        self.sent_history.append(message)
558                        self.sent_history_pos = size + 1
559                self.orig_msg = None
560
561        def print_conversation_line(self, text, kind, name, tim,
562                other_tags_for_name = [], other_tags_for_time = [],
563                other_tags_for_text = [], count_as_new = True,
564                subject = None, old_kind = None, xhtml = None):
565                '''prints 'chat' type messages'''
566                jid = self.contact.jid
567                full_jid = self.get_full_jid()
568                textview = self.conv_textview
569                end = False
570                if self.was_at_the_end or kind == 'outgoing':
571                        end = True
572                textview.print_conversation_line(text, jid, kind, name, tim,
573                        other_tags_for_name, other_tags_for_time, other_tags_for_text,
574                        subject, old_kind, xhtml)
575
576                if not count_as_new:
577                        return
578                if kind == 'incoming':
579                        gajim.last_message_time[self.account][full_jid] = time.time()
580                if (not self.parent_win.get_active_jid() or \
581                full_jid != self.parent_win.get_active_jid() or \
582                not self.parent_win.is_active() or not end) and \
583                kind in ('incoming', 'incoming_queue'):
584                        gc_message = False
585                        if self.type_id  == message_control.TYPE_GC:
586                                gc_message = True
587                        if not gc_message or \
588                        (gc_message and (other_tags_for_text == ['marked'] or \
589                        gajim.config.get('notify_on_all_muc_messages'))):
590                        # we want to have save this message in events list
591                        # other_tags_for_text == ['marked'] --> highlighted gc message
592                                type_ = 'printed_' + self.type_id
593                                if gc_message:
594                                        type_ = 'printed_gc_msg'
595                                show_in_roster = notify.get_show_in_roster('message_received',
596                                        self.account, self.contact)
597                                show_in_systray = notify.get_show_in_systray('message_received',
598                                        self.account, self.contact)
599                                event = gajim.events.create_event(type_, None,
600                                        show_in_roster = show_in_roster,
601                                        show_in_systray = show_in_systray)
602                                gajim.events.add_event(self.account, full_jid, event)
603                                # We need to redraw contact if we show in roster
604                                if show_in_roster:
605                                        gajim.interface.roster.draw_contact(self.contact.jid,
606                                                self.account)
607                        self.parent_win.redraw_tab(self)
608                        ctrl = gajim.interface.msg_win_mgr.get_control(full_jid, self.account)
609                        if not self.parent_win.is_active():
610                                self.parent_win.show_title(True, ctrl) # Enabled Urgent hint
611                        else:
612                                self.parent_win.show_title(False, ctrl) # Disabled Urgent hint
613
614        def toggle_emoticons(self):
615                '''hide show emoticons_button and make sure emoticons_menu is always there
616                when needed'''
617                emoticons_button = self.xml.get_widget('emoticons_button')
618                if gajim.config.get('emoticons_theme'):
619                        emoticons_button.show()
620                        emoticons_button.set_no_show_all(False)
621                else:
622                        emoticons_button.hide()
623                        emoticons_button.set_no_show_all(True)
624
625        def append_emoticon(self, str_):
626                buffer = self.msg_textview.get_buffer()
627                if buffer.get_char_count():
628                        buffer.insert_at_cursor(' %s ' % str_)
629                else: # we are the beginning of buffer
630                        buffer.insert_at_cursor('%s ' % str_)
631                self.msg_textview.grab_focus()
632
633        def on_emoticons_button_clicked(self, widget):
634                '''popup emoticons menu'''
635                gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
636                gajim.interface.popup_emoticons_under_button(widget, self.parent_win)
637
638        def on_actions_button_clicked(self, widget):
639                '''popup action menu'''
640                menu = self.prepare_context_menu()
641                menu.show_all()
642                gtkgui_helpers.popup_emoticons_under_button(menu, widget,
643                        self.parent_win)
644
645        def update_font(self):
646                font = pango.FontDescription(gajim.config.get('conversation_font'))
647                self.conv_textview.tv.modify_font(font)
648                self.msg_textview.modify_font(font)
649
650        def update_tags(self):
651                self.conv_textview.update_tags()
652
653        def clear(self, tv):
654                buffer = tv.get_buffer()
655                start, end = buffer.get_bounds()
656                buffer.delete(start, end)
657
658        def _on_history_menuitem_activate(self, widget = None, jid = None):
659                '''When history menuitem is pressed: call history window'''
660                if not jid:
661                        jid = self.contact.jid
662
663                if gajim.interface.instances['logs'].has_key(jid):
664                        gajim.interface.instances['logs'][jid].window.present()
665                else:
666                        gajim.interface.instances['logs'][jid] = \
667                                history_window.HistoryWindow(jid, self.account)
668
669        def _on_compact_view_menuitem_activate(self, widget):
670                isactive = widget.get_active()
671                self.chat_buttons_set_visible(isactive)
672
673        def set_control_active(self, state):
674                if state:
675                        jid = self.contact.jid
676                        if self.was_at_the_end:
677                                # we are at the end
678                                type_ = 'printed_' + self.type_id
679                                if self.type_id == message_control.TYPE_GC:
680                                        type_ = 'printed_gc_msg'
681                                if not gajim.events.remove_events(self.account, self.get_full_jid(),
682                                types = [type_]):
683                                        # There were events to remove
684                                        self.redraw_after_event_removed(jid)
685                        self.msg_textview.grab_focus()
686                        # Note, we send None chatstate to preserve current
687                        self.parent_win.redraw_tab(self)
688
689        def bring_scroll_to_end(self, textview, diff_y = 0):
690                ''' scrolls to the end of textview if end is not visible '''
691                if self.scroll_to_end_id:
692                        # a scroll is already planned
693                        return
694                buffer = textview.get_buffer()
695                end_iter = buffer.get_end_iter()
696                end_rect = textview.get_iter_location(end_iter)
697                visible_rect = textview.get_visible_rect()
698                # scroll only if expected end is not visible
699                if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
700                        self.scroll_to_end_id = gobject.idle_add(self.scroll_to_end_iter,
701                                textview)
702
703        def scroll_to_end_iter(self, textview):
704                buffer = textview.get_buffer()
705                end_iter = buffer.get_end_iter()
706                textview.scroll_to_iter(end_iter, 0, False, 1, 1)
707                self.scroll_to_end_id = None
708                return False
709
710        def size_request(self, msg_textview , requisition):
711                ''' When message_textview changes its size. If the new height
712                will enlarge the window, enable the scrollbar automatic policy
713                Also enable scrollbar automatic policy for horizontal scrollbar
714                if message we have in message_textview is too big'''
715                if msg_textview.window is None:
716                        return
717
718                min_height = self.conv_scrolledwindow.get_property('height-request')
719                conversation_height = self.conv_textview.tv.window.get_size()[1]
720                message_height = msg_textview.window.get_size()[1]
721                message_width = msg_textview.window.get_size()[0]
722                # new tab is not exposed yet
723                if conversation_height < 2:
724                        return
725
726                if conversation_height < min_height:
727                        min_height = conversation_height
728
729                # we don't want to always resize in height the message_textview
730                # so we have minimum on conversation_textview's scrolled window
731                # but we also want to avoid window resizing so if we reach that
732                # minimum for conversation_textview and maximum for message_textview
733                # we set to automatic the scrollbar policy
734                diff_y =  message_height - requisition.height
735                if diff_y != 0:
736                        if conversation_height + diff_y < min_height:
737                                if message_height + conversation_height - min_height > min_height:
738                                        policy = self.msg_scrolledwindow.get_property(
739                                                'vscrollbar-policy')
740                                        # scroll only when scrollbar appear
741                                        if policy != gtk.POLICY_AUTOMATIC:
742                                                self.msg_scrolledwindow.set_property('vscrollbar-policy',
743                                                        gtk.POLICY_AUTOMATIC)
744                                                self.msg_scrolledwindow.set_property('height-request',
745                                                        message_height + conversation_height - min_height)
746                                                self.bring_scroll_to_end(msg_textview)
747                        else:
748                                self.msg_scrolledwindow.set_property('vscrollbar-policy',
749                                        gtk.POLICY_NEVER)
750                                self.msg_scrolledwindow.set_property('height-request', -1)
751
752                self.conv_textview.bring_scroll_to_end(diff_y - 18)
753               
754                # enable scrollbar automatic policy for horizontal scrollbar
755                # if message we have in message_textview is too big