root/branches/gajim_0.11/src/chat_control.py

Revision 7984, 65.8 kB (checked in by asterix, 20 months ago)

mrege diff from trunk

<
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                id = widget.connect('button-press-event',
134                        self._on_banner_eventbox_button_press_event)
135                self.handlers[id] = widget
136
137                # Create textviews and connect signals
138                self.conv_textview = ConversationTextview(self.account)
139
140                self.conv_scrolledwindow = self.xml.get_widget(
141                        'conversation_scrolledwindow')
142                self.conv_scrolledwindow.add(self.conv_textview.tv)
143                widget = self.conv_scrolledwindow.get_vadjustment()
144                id = widget.connect('value-changed',
145                        self.on_conversation_vadjustment_value_changed)
146                self.handlers[id] = widget
147                # add MessageTextView to UI and connect signals
148                self.msg_scrolledwindow = self.xml.get_widget('message_scrolledwindow')
149                self.msg_textview = MessageTextView()
150                id = self.msg_textview.connect('mykeypress',
151                        self._on_message_textview_mykeypress_event)
152                self.handlers[id] = self.msg_textview
153                self.msg_scrolledwindow.add(self.msg_textview)
154                id = self.msg_textview.connect('key_press_event',
155                        self._on_message_textview_key_press_event)
156                self.handlers[id] = self.msg_textview
157                id = self.msg_textview.connect('size-request', self.size_request)
158                self.handlers[id] = self.msg_textview
159                id = self.msg_textview.connect('populate_popup',
160                        self.on_msg_textview_populate_popup)
161                self.handlers[id] = self.msg_textview
162       
163                self.update_font()
164
165                # Hook up send button
166                widget = self.xml.get_widget('send_button')
167                id = widget.connect('clicked', self._on_send_button_clicked)
168                self.handlers[id] = widget
169
170                # the following vars are used to keep history of user's messages
171                self.sent_history = []
172                self.sent_history_pos = 0
173                self.orig_msg = None
174
175                # Emoticons menu
176                # set image no matter if user wants at this time emoticons or not
177                # (so toggle works ok)
178                img = self.xml.get_widget('emoticons_button_image')
179                img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
180                        'smile.png'))
181                self.toggle_emoticons()
182
183                # Attach speller
184                if gajim.config.get('use_speller') and HAS_GTK_SPELL:
185                        try:
186                                spell = gtkspell.Spell(self.msg_textview)
187                                # loop removing non-existant dictionaries
188                                # iterating on a copy
189                                for lang in dict(langs):
190                                        try:
191                                                spell.set_language(langs[lang])
192                                        except:
193                                                del langs[lang]
194                                # now set the one the user selected
195                                per_type = 'contacts'
196                                if self.type_id == message_control.TYPE_GC:
197                                        per_type = 'rooms'
198                                lang = gajim.config.get_per(per_type, self.contact.jid,
199                                        'speller_language')
200                                if not lang:
201                                        # use the default one
202                                        lang = gajim.config.get('speller_language')
203                                if lang:
204                                        self.msg_textview.lang = lang
205                                        spell.set_language(lang)
206                        except (gobject.GError, RuntimeError), msg:
207                                dialogs.ErrorDialog(unicode(msg), _('If that is not your language '
208                                        'for which you want to highlight misspelled words, then please '
209                                        'set your $LANG as appropriate. Eg. for French do export '
210                                        'LANG=fr_FR or export LANG=fr_FR.UTF-8 in ~/.bash_profile or to '
211                                        'make it global in /etc/profile.\n\nHighlighting misspelled '
212                                        'words feature will not be used'))
213                                gajim.config.set('use_speller', False)
214
215                self.style_event_id = 0
216                self.conv_textview.tv.show()
217                self._paint_banner()
218
219                # For JEP-0172
220                self.user_nick = None
221
222        def on_msg_textview_populate_popup(self, textview, menu):
223                '''we override the default context menu and we prepend an option to switch languages'''
224                def _on_select_dictionary(widget, lang):
225                        per_type = 'contacts'
226                        if self.type_id == message_control.TYPE_GC:
227                                per_type = 'rooms'
228                        if not gajim.config.get_per(per_type, self.contact.jid):
229                                gajim.config.add_per(per_type, self.contact.jid)
230                        gajim.config.set_per(per_type, self.contact.jid, 'speller_language',
231                                lang)
232                        spell = gtkspell.get_from_text_view(self.msg_textview)
233                        self.msg_textview.lang = lang
234                        spell.set_language(lang)
235                        widget.set_active(True)
236
237                item = gtk.SeparatorMenuItem()
238                menu.prepend(item)
239
240                item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
241                menu.prepend(item)
242                id = item.connect('activate', self.msg_textview.clear)
243                self.handlers[id] = item
244
245                if gajim.config.get('use_speller') and HAS_GTK_SPELL:
246                        item = gtk.MenuItem(_('Spelling language'))
247                        menu.prepend(item)
248                        submenu = gtk.Menu()
249                        item.set_submenu(submenu)
250                        for lang in sorted(langs):
251                                item = gtk.CheckMenuItem(lang)
252                                if langs[lang] == self.msg_textview.lang:
253                                        item.set_active(True)
254                                submenu.append(item)
255                                id = item.connect('activate', _on_select_dictionary, langs[lang])
256                                self.handlers[id] = item
257
258                menu.show_all()
259
260        # moved from ChatControl
261        def _on_banner_eventbox_button_press_event(self, widget, event):
262                '''If right-clicked, show popup'''
263                if event.button == 3: # right click
264                        self.parent_win.popup_menu(event)
265
266        def _on_send_button_clicked(self, widget):
267                '''When send button is pressed: send the current message'''
268                if gajim.connections[self.account].connected < 2: # we are not connected
269                        dialogs.ErrorDialog(_('A connection is not available'),
270                                _('Your message can not be sent until you are connected.'))
271                        return
272                message_buffer = self.msg_textview.get_buffer()
273                start_iter = message_buffer.get_start_iter()
274                end_iter = message_buffer.get_end_iter()
275                message = message_buffer.get_text(start_iter, end_iter, 0).decode('utf-8')
276
277                # send the message
278                self.send_message(message)
279
280        def _paint_banner(self):
281                '''Repaint banner with theme color'''
282                theme = gajim.config.get('roster_theme')
283                bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
284                textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
285                # the backgrounds are colored by using an eventbox by
286                # setting the bg color of the eventbox and the fg of the name_label
287                banner_eventbox = self.xml.get_widget('banner_eventbox')
288                banner_name_label = self.xml.get_widget('banner_name_label')
289                self.disconnect_style_event(banner_name_label)
290                if bgcolor:
291                        banner_eventbox.modify_bg(gtk.STATE_NORMAL,
292                                gtk.gdk.color_parse(bgcolor))
293                        default_bg = False
294                else:
295                        default_bg = True
296                if textcolor:
297                        banner_name_label.modify_fg(gtk.STATE_NORMAL,
298                                gtk.gdk.color_parse(textcolor))
299                        default_fg = False
300                else:
301                        default_fg = True
302                if default_bg or default_fg:
303                        self._on_style_set_event(banner_name_label, None, default_fg,
304                                default_bg)
305       
306        def disconnect_style_event(self, widget):
307                if self.style_event_id:
308                        widget.disconnect(self.style_event_id)
309                        del self.handlers[self.style_event_id]
310                        self.style_event_id = 0
311       
312        def connect_style_event(self, widget, set_fg = False, set_bg = False):
313                self.disconnect_style_event(widget)
314                self.style_event_id = widget.connect('style-set',
315                        self._on_style_set_event, set_fg, set_bg)
316                self.handlers[self.style_event_id] = widget
317       
318        def _on_style_set_event(self, widget, style, *opts):
319                '''set style of widget from style class *.Frame.Eventbox
320                        opts[0] == True -> set fg color
321                        opts[1] == True -> set bg color'''
322                banner_eventbox = self.xml.get_widget('banner_eventbox')
323                self.disconnect_style_event(widget)
324                if opts[1]:
325                        bg_color = widget.style.bg[gtk.STATE_SELECTED]
326                        banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color)
327                if opts[0]:
328                        fg_color = widget.style.fg[gtk.STATE_SELECTED]
329                        widget.modify_fg(gtk.STATE_NORMAL, fg_color)
330                self.connect_style_event(widget, opts[0], opts[1])
331       
332        def _on_keypress_event(self, widget, event):
333                if event.state & gtk.gdk.CONTROL_MASK:
334                        # CTRL + l|L: clear conv_textview
335                        if event.keyval == gtk.keysyms.l or event.keyval == gtk.keysyms.L:
336                                self.conv_textview.clear()
337                                return True
338                        # CTRL + v: Paste into msg_textview
339                        elif event.keyval == gtk.keysyms.v:
340                                if not self.msg_textview.is_focus():
341                                        self.msg_textview.grab_focus()
342                                # Paste into the msg textview
343                                self.msg_textview.emit('key_press_event', event)
344                        # CTRL + u: emacs style clear line
345                        elif event.keyval == gtk.keysyms.u:
346                                self.clear(self.msg_textview) # clear message textview too
347                        elif event.keyval == gtk.keysyms.ISO_Left_Tab: # CTRL + SHIFT + TAB
348                                self.parent_win.move_to_next_unread_tab(False)
349                                return True
350                        elif event.keyval == gtk.keysyms.Tab: # CTRL + TAB
351                                self.parent_win.move_to_next_unread_tab(True)
352                                return True
353                        # CTRL + PAGE_[UP|DOWN]: send to parent notebook
354                        elif event.keyval == gtk.keysyms.Page_Down or \
355                                        event.keyval == gtk.keysyms.Page_Up:
356                                self.parent_win.notebook.emit('key_press_event', event)
357                                return True
358                elif event.keyval == gtk.keysyms.m and \
359                        (event.state & gtk.gdk.MOD1_MASK): # alt + m opens emoticons menu
360                        if gajim.config.get('emoticons_theme'):
361                                msg_tv = self.msg_textview
362                                def set_emoticons_menu_position(w, msg_tv = self.msg_textview):
363                                        window = msg_tv.get_window(gtk.TEXT_WINDOW_WIDGET)
364                                        # get the window position
365                                        origin = window.get_origin()
366                                        size = window.get_size()
367                                        buf = msg_tv.get_buffer()
368                                        # get the cursor position
369                                        cursor = msg_tv.get_iter_location(buf.get_iter_at_mark(
370                                                buf.get_insert()))
371                                        cursor =  msg_tv.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT,
372                                                cursor.x, cursor.y)
373                                        x = origin[0] + cursor[0]
374                                        y = origin[1] + size[1]
375                                        menu_width, menu_height = \
376                                                gajim.interface.emoticons_menu.size_request()
377                                        #FIXME: get_line_count is not so good
378                                        #get the iter of cursor, then tv.get_line_yrange
379                                        # so we know in which y we are typing (not how many lines we have
380                                        # then go show just above the current cursor line for up
381                                        # or just below the current cursor line for down
382                                        #TEST with having 3 lines and writing in the 2nd
383                                        if y + menu_height > gtk.gdk.screen_height():
384                                                # move menu just above cursor
385                                                y -= menu_height +\
386                                                        (msg_tv.allocation.height / buf.get_line_count())
387                                        #else: # move menu just below cursor
388                                        #       y -= (msg_tv.allocation.height / buf.get_line_count())
389                                        return (x, y, True) # push_in True
390                                gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
391                                gajim.interface.emoticons_menu.popup(None, None,
392                                        set_emoticons_menu_position, 1, 0)
393                return False
394
395        def _on_message_textview_key_press_event(self, widget, event):
396                if self.widget_name == 'muc_child_vbox':
397                        if event.keyval not in (gtk.keysyms.ISO_Left_Tab, gtk.keysyms.Tab):
398                                self.last_key_tabs = False
399                if event.state & gtk.gdk.SHIFT_MASK:
400                        # CTRL + SHIFT + TAB
401                        if event.state & gtk.gdk.CONTROL_MASK and \
402                                        event.keyval == gtk.keysyms.ISO_Left_Tab:
403                                self.parent_win.move_to_next_unread_tab(False)
404                                return True
405                        # SHIFT + PAGE_[UP|DOWN]: send to conv_textview
406                        elif event.keyval == gtk.keysyms.Page_Down or \
407                                        event.keyval == gtk.keysyms.Page_Up:
408                                self.conv_textview.tv.emit('key_press_event', event)
409                                return True
410                elif event.state & gtk.gdk.CONTROL_MASK:
411                        if event.keyval == gtk.keysyms.Tab: # CTRL + TAB
412                                self.parent_win.move_to_next_unread_tab(True)
413                                return True
414                        # CTRL + PAGE_[UP|DOWN]: send to parent notebook
415                        elif event.keyval == gtk.keysyms.Page_Down or \
416                                        event.keyval == gtk.keysyms.Page_Up:
417                                self.parent_win.notebook.emit('key_press_event', event)
418                                return True
419                        # we pressed a control key or ctrl+sth: we don't block
420                        # the event in order to let ctrl+c (copy text) and
421                        # others do their default work
422                        self.conv_textview.tv.emit('key_press_event', event)
423                return False
424
425        def _on_message_textview_mykeypress_event(self, widget, event_keyval,
426                event_keymod):
427                '''When a key is pressed:
428                if enter is pressed without the shift key, message (if not empty) is sent
429                and printed in the conversation'''
430
431                # NOTE: handles mykeypress which is custom signal connected to this
432                # CB in new_tab(). for this singal see message_textview.py
433                message_textview = widget
434                message_buffer = message_textview.get_buffer()
435                start_iter, end_iter = message_buffer.get_bounds()
436                message = message_buffer.get_text(start_iter, end_iter, False).decode(
437                        'utf-8')
438
439                # construct event instance from binding
440                event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
441                event.keyval = event_keyval
442                event.state = event_keymod
443                event.time = 0 # assign current time
444
445                if event.keyval == gtk.keysyms.Up:
446                        if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+UP
447                                self.sent_messages_scroll('up', widget.get_buffer())
448                elif event.keyval == gtk.keysyms.Down:
449                        if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+Down
450                                self.sent_messages_scroll('down', widget.get_buffer())
451                elif event.keyval == gtk.keysyms.Return or \
452                        event.keyval == gtk.keysyms.KP_Enter: # ENTER
453                        # NOTE: SHIFT + ENTER is not needed to be emulated as it is not
454                        # binding at all (textview's default action is newline)
455
456                        if gajim.config.get('send_on_ctrl_enter'):
457                                # here, we emulate GTK default action on ENTER (add new line)
458                                # normally I would add in keypress but it gets way to complex
459                                # to get instant result on changing this advanced setting
460                                if event.state == 0: # no ctrl, no shift just ENTER add newline
461                                        end_iter = message_buffer.get_end_iter()
462                                        message_buffer.insert_at_cursor('\n')
463                                        send_message = False
464                                elif event.state & gtk.gdk.CONTROL_MASK: # CTRL + ENTER
465                                        send_message = True
466                        else: # send on Enter, do newline on Ctrl Enter
467                                if event.state & gtk.gdk.CONTROL_MASK: # Ctrl + ENTER
468                                        end_iter = message_buffer.get_end_iter()
469                                        message_buffer.insert_at_cursor('\n')
470                                        send_message = False
471                                else: # ENTER
472                                        send_message = True
473                               
474                        if gajim.connections[self.account].connected < 2: # we are not connected
475                                dialogs.ErrorDialog(_('A connection is not available'),
476                                        _('Your message can not be sent until you are connected.'))
477                                send_message = False
478
479                        if send_message:
480                                self.send_message(message) # send the message
481                else:
482                        # Give the control itself a chance to process
483                        self.handle_message_textview_mykey_press(widget, event_keyval,
484                                event_keymod)
485
486        def _process_command(self, message):
487                if not message or message[0] != '/':
488                        return False
489
490                message = message[1:]
491                message_array = message.split(' ', 1)
492                command = message_array.pop(0).lower()
493                if message_array == ['']:
494                        message_array = []
495
496                if command == 'clear' and not len(message_array):
497                        self.conv_textview.clear() # clear conversation
498                        self.clear(self.msg_textview) # clear message textview too
499                        return True
500                elif message == 'compact' and not len(message_array):
501                        self.chat_buttons_set_visible(not self.hide_chat_buttons_current)
502                        self.clear(self.msg_textview)
503                        return True
504                return False
505
506        def send_message(self, message, keyID = '', type = 'chat', chatstate = None,
507        msg_id = None, composing_jep = None, resource = None):
508                '''Send the given message to the active tab. Doesn't return None if error
509                '''
510                if not message or message == '\n':
511                        return 1
512
513
514                if not self._process_command(message):
515                        ret = MessageControl.send_message(self, message, keyID, type = type,
516                                chatstate = chatstate, msg_id = msg_id,
517                                composing_jep = composing_jep, resource = resource,
518                                user_nick = self.user_nick)
519                        if ret:
520                                return ret
521                        # Record message history
522                        self.save_sent_message(message)
523
524                        # Be sure to send user nickname only once according to JEP-0172
525                        self.user_nick = None
526
527                # Clear msg input
528                message_buffer = self.msg_textview.get_buffer()
529                message_buffer.set_text('') # clear message buffer (and tv of course)
530
531        def save_sent_message(self, message):
532                #save the message, so user can scroll though the list with key up/down
533                size = len(self.sent_history)
534                #we don't want size of the buffer to grow indefinately
535                max_size = gajim.config.get('key_up_lines')
536                if size >= max_size:
537                        for i in xrange(0, size - 1):
538                                self.sent_history[i] = self.sent_history[i + 1]
539                        self.sent_history[max_size - 1] = message
540                else:
541                        self.sent_history.append(message)
542                        self.sent_history_pos = size + 1
543                self.orig_msg = None
544
545        def print_conversation_line(self, text, kind, name, tim,
546                other_tags_for_name = [], other_tags_for_time = [],
547                other_tags_for_text = [], count_as_new = True,
548                subject = None, old_kind = None, xhtml = None):
549                '''prints 'chat' type messages'''
550                jid = self.contact.jid
551                full_jid = self.get_full_jid()
552                textview = self.conv_textview
553                end = False
554                if textview.at_the_end() or kind == 'outgoing':
555                        end = True
556                textview.print_conversation_line(text, jid, kind, name, tim,
557                        other_tags_for_name, other_tags_for_time, other_tags_for_text,
558                        subject, old_kind, xhtml)
559
560                if not count_as_new:
561                        return
562                if kind == 'incoming':
563                        gajim.last_message_time[self.account][full_jid] = time.time()
564                if (not self.parent_win.get_active_jid() or \
565                full_jid != self.parent_win.get_active_jid() or \
566                not self.parent_win.is_active() or not end) and \
567                kind in ('incoming', 'incoming_queue'):
568                        gc_message = False
569                        if self.type_id  == message_control.TYPE_GC:
570                                gc_message = True
571                        if not gc_message or \
572                        (gc_message and (other_tags_for_text == ['marked'] or \
573                        gajim.config.get('notify_on_all_muc_messages'))):
574                        # we want to have save this message in events list
575                        # other_tags_for_text == ['marked'] --> highlighted gc message
576                                type_ = 'printed_' + self.type_id
577                                if gc_message:
578                                        type_ = 'printed_gc_msg'
579                                show_in_roster = notify.get_show_in_roster('message_received',
580                                        self.account, self.contact)
581                                show_in_systray = notify.get_show_in_systray('message_received',
582                                        self.account, self.contact)
583                                event = gajim.events.create_event(type_, None,
584                                        show_in_roster = show_in_roster,
585                                        show_in_systray = show_in_systray)
586                                gajim.events.add_event(self.account, full_jid, event)
587                                # We need to redraw contact if we show in roster
588                                if show_in_roster:
589                                        gajim.interface.roster.draw_contact(self.contact.jid,
590                                                self.account)
591                        self.parent_win.redraw_tab(self)
592                        ctrl = gajim.interface.msg_win_mgr.get_control(full_jid, self.account)
593                        if not self.parent_win.is_active():
594                                self.parent_win.show_title(True, ctrl) # Enabled Urgent hint
595                        else:
596                                self.parent_win.show_title(False, ctrl) # Disabled Urgent hint
597
598        def toggle_emoticons(self):
599                '''hide show emoticons_button and make sure emoticons_menu is always there
600                when needed'''
601                emoticons_button = self.xml.get_widget('emoticons_button')
602                if gajim.config.get('emoticons_theme'):
603                        emoticons_button.show()
604                        emoticons_button.set_no_show_all(False)
605                else:
606                        emoticons_button.hide()
607                        emoticons_button.set_no_show_all(True)
608
609        def append_emoticon(self, str_):
610                buffer = self.msg_textview.get_buffer()
611                if buffer.get_char_count():
612                        buffer.insert_at_cursor(' %s ' % str_)
613                else: # we are the beginning of buffer
614                        buffer.insert_at_cursor('%s ' % str_)
615                self.msg_textview.grab_focus()
616
617        def on_emoticons_button_clicked(self, widget):
618                '''popup emoticons menu'''
619                gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
620                gajim.interface.popup_emoticons_under_button(widget, self.parent_win)
621
622        def on_actions_button_clicked(self, widget):
623                '''popup action menu'''
624                menu = self.prepare_context_menu()
625                menu.show_all()
626                gtkgui_helpers.popup_emoticons_under_button(menu, widget,
627                        self.parent_win)
628
629        def update_font(self):
630                font = pango.FontDescription(gajim.config.get('conversation_font'))
631                self.conv_textview.tv.modify_font(font)
632                self.msg_textview.modify_font(font)
633
634        def update_tags(self):
635                self.conv_textview.update_tags()
636
637        def clear(self, tv):
638                buffer = tv.get_buffer()
639                start, end = buffer.get_bounds()
640                buffer.delete(start, end)
641
642        def _on_history_menuitem_activate(self, widget = None, jid = None):
643                '''When history menuitem is pressed: call history window'''
644                if not jid:
645                        jid = self.contact.jid
646
647                if gajim.interface.instances['logs'].has_key(jid):
648                        gajim.interface.instances['logs'][jid].window.present()
649                else:
650                        gajim.interface.instances['logs'][jid] = \
651                                history_window.HistoryWindow(jid, self.account)
652
653        def _on_compact_view_menuitem_activate(self, widget):
654                isactive = widget.get_active()
655                self.chat_buttons_set_visible(isactive)
656
657        def set_control_active(self, state):
658                if state:
659                        jid = self.contact.jid
660                        if self.conv_textview.at_the_end():
661                                # we are at the end
662                                type_ = 'printed_' + self.type_id
663                                if self.type_id == message_control.TYPE_GC:
664                                        type_ = 'printed_gc_msg'
665                                if not gajim.events.remove_events(self.account, self.get_full_jid(),
666                                types = [type_]):
667                                        # There were events to remove
668                                        self.redraw_after_event_removed(jid)
669                        self.msg_textview.grab_focus()
670                        # Note, we send None chatstate to preserve current
671                        self.parent_win.redraw_tab(self)
672
673        def bring_scroll_to_end(self, textview, diff_y = 0):
674                ''' scrolls to the end of textview if end is not visible '''
675                buffer = textview.get_buffer()
676                end_iter = buffer.get_end_iter()
677                end_rect = textview.get_iter_location(end_iter)
678                visible_rect = textview.get_visible_rect()
679                # scroll only if expected end is not visible
680                if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
681                        gobject.idle_add(self.scroll_to_end_iter, textview)
682
683        def scroll_to_end_iter(self, textview):
684                buffer = textview.get_buffer()
685                end_iter = buffer.get_end_iter()
686                textview.scroll_to_iter(end_iter, 0, False, 1, 1)
687                return False
688
689        def size_request(self, msg_textview , requisition):
690                ''' When message_textview changes its size. If the new height
691                will enlarge the window, enable the scrollbar automatic policy
692                Also enable scrollbar automatic policy for horizontal scrollbar
693                if message we have in message_textview is too big'''
694                if msg_textview.window is None:
695                        return
696
697                min_height = self.conv_scrolledwindow.get_property('height-request')
698                conversation_height = self.conv_textview.tv.window.get_size()[1]
699                message_height = msg_textview.window.get_size()[1]
700                message_width = msg_textview.window.get_size()[0]
701                # new tab is not exposed yet
702                if conversation_height < 2:
703                        return
704
705                if conversation_height < min_height:
706                        min_height = conversation_height
707
708                # we don't want to always resize in height the message_textview
709                # so we have minimum on conversation_textview's scrolled window
710                # but we also want to avoid window resizing so if we reach that
711                # minimum for conversation_textview and maximum for message_textview
712                # we set to automatic the scrollbar policy
713                diff_y =  message_height - requisition.height
714                if diff_y != 0:
715                        if conversation_height + diff_y < min_height:
716                                if message_height + conversation_height - min_height > min_height:
717                                        policy = self.msg_scrolledwindow.get_property(
718                                                'vscrollbar-policy')
719                                        # scroll only when scrollbar appear
720                                        if policy != gtk.POLICY_AUTOMATIC:
721                                                self.msg_scrolledwindow.set_property('vscrollbar-policy',
722                                                        gtk.POLICY_AUTOMATIC)
723                                                self.msg_scrolledwindow.set_property('height-request',
724                                                        message_height + conversation_height - min_height)
725                                                self.bring_scroll_to_end(msg_textview)
726                        else:
727                                self.msg_scrolledwindow.set_property('vscrollbar-policy',
728                                        gtk.POLICY_NEVER)
729                                self.msg_scrolledwindow.set_property('height-request', -1)
730
731                self.conv_textview.bring_scroll_to_end(diff_y - 18)
732               
733                # enable scrollbar automatic policy for horizontal scrollbar
734                # if message we have in message_textview is too big
735                if requisition.width > message_width:
736                        self.msg_scrolledwindow.set_property('hscrollbar-policy',
737                                gtk.POLICY_AUTOMATIC)
738                else:
739                        self.msg_scrolledwindow.set_property('hscrollbar-policy',
740                                gtk.POLICY_NEVER)
741
742                return True
743
744        def on_conversation_vadjustment_value_changed(self, widget):
745                if self.resource:
746                        jid = self.contact.get_full_jid()
747                else:
748                        jid = self.contact.jid
749                type_ = self.type_id
750                if type_ == message_control.TYPE_GC:
751                        type_ = 'gc_msg'
752                if not len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
753                type_])):
754                        return
755                if self.conv_textview.at_the_end() and \