1441 lines
		
	
	
	
		
			58 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1441 lines
		
	
	
	
		
			58 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# -*- coding:utf-8 -*-
 | 
						|
## src/chat_control_base.py
 | 
						|
##
 | 
						|
## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
 | 
						|
## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
 | 
						|
## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
 | 
						|
##                         Nikos Kouremenos <kourem AT gmail.com>
 | 
						|
##                         Travis Shirk <travis AT pobox.com>
 | 
						|
## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
 | 
						|
##                    Julien Pivotto <roidelapluie AT gmail.com>
 | 
						|
## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
 | 
						|
##                         Stephan Erb <steve-e AT h3c.de>
 | 
						|
## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
 | 
						|
##
 | 
						|
## This file is part of Gajim.
 | 
						|
##
 | 
						|
## Gajim is free software; you can redistribute it and/or modify
 | 
						|
## it under the terms of the GNU General Public License as published
 | 
						|
## by the Free Software Foundation; version 3 only.
 | 
						|
##
 | 
						|
## Gajim is distributed in the hope that it will be useful,
 | 
						|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | 
						|
## GNU General Public License for more details.
 | 
						|
##
 | 
						|
## You should have received a copy of the GNU General Public License
 | 
						|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
 | 
						|
##
 | 
						|
 | 
						|
import os
 | 
						|
import time
 | 
						|
from gi.repository import Gtk
 | 
						|
from gi.repository import Gdk
 | 
						|
from gi.repository import Pango
 | 
						|
from gi.repository import GLib
 | 
						|
from gi.repository import Gio
 | 
						|
 | 
						|
from gajim import gtkgui_helpers
 | 
						|
from gajim import message_control
 | 
						|
from gajim import dialogs
 | 
						|
from gajim import history_window
 | 
						|
from gajim import notify
 | 
						|
import re
 | 
						|
 | 
						|
from gajim import emoticons
 | 
						|
from gajim.common import events
 | 
						|
from gajim.common import app
 | 
						|
from gajim.common import helpers
 | 
						|
from gajim.common import ged
 | 
						|
from gajim.common import i18n
 | 
						|
from gajim.message_control import MessageControl
 | 
						|
from gajim.conversation_textview import ConversationTextview
 | 
						|
from gajim.message_textview import MessageTextView
 | 
						|
from gajim.common.contacts import GC_Contact
 | 
						|
from gajim.common.connection_handlers_events import MessageOutgoingEvent
 | 
						|
 | 
						|
from gajim.command_system.implementation.middleware import ChatCommandProcessor
 | 
						|
from gajim.command_system.implementation.middleware import CommandTools
 | 
						|
 | 
						|
# The members of these modules are not referenced directly anywhere in this
 | 
						|
# module, but still they need to be kept around. Importing them automatically
 | 
						|
# registers the contained CommandContainers with the command system, thereby
 | 
						|
# populating the list of available commands.
 | 
						|
from gajim.command_system.implementation import standard
 | 
						|
from gajim.command_system.implementation import execute
 | 
						|
 | 
						|
if app.is_installed('GSPELL'):
 | 
						|
    from gi.repository import Gspell
 | 
						|
 | 
						|
 | 
						|
################################################################################
 | 
						|
class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
 | 
						|
    """
 | 
						|
    A base class containing a banner, ConversationTextview, MessageTextView
 | 
						|
    """
 | 
						|
 | 
						|
    keymap = Gdk.Keymap.get_default()
 | 
						|
    try:
 | 
						|
        keycode_c = keymap.get_entries_for_keyval(Gdk.KEY_c)[1][0].keycode
 | 
						|
    except TypeError:
 | 
						|
        keycode_c = 54
 | 
						|
    try:
 | 
						|
        keycode_ins = keymap.get_entries_for_keyval(Gdk.KEY_Insert)[1][0].keycode
 | 
						|
    except TypeError:
 | 
						|
        keycode_ins = 118
 | 
						|
    except IndexError:
 | 
						|
        # There is no KEY_Insert (MacOS)
 | 
						|
        keycode_ins = None
 | 
						|
 | 
						|
    def make_href(self, match):
 | 
						|
        url_color = app.config.get('urlmsgcolor')
 | 
						|
        url = match.group()
 | 
						|
        if not '://' in url:
 | 
						|
            url = 'http://' + url
 | 
						|
        return '<a href="%s"><span color="%s">%s</span></a>' % (url,
 | 
						|
                url_color, match.group())
 | 
						|
 | 
						|
    def get_font_attrs(self):
 | 
						|
        """
 | 
						|
        Get pango font attributes for banner from theme settings
 | 
						|
        """
 | 
						|
        theme = app.config.get('roster_theme')
 | 
						|
        bannerfont = app.config.get_per('themes', theme, 'bannerfont')
 | 
						|
        bannerfontattrs = app.config.get_per('themes', theme, 'bannerfontattrs')
 | 
						|
 | 
						|
        if bannerfont:
 | 
						|
            font = Pango.FontDescription(bannerfont)
 | 
						|
        else:
 | 
						|
            font = Pango.FontDescription('Normal')
 | 
						|
        if bannerfontattrs:
 | 
						|
            # B attribute is set by default
 | 
						|
            if 'B' in bannerfontattrs:
 | 
						|
                font.set_weight(Pango.Weight.HEAVY)
 | 
						|
            if 'I' in bannerfontattrs:
 | 
						|
                font.set_style(Pango.Style.ITALIC)
 | 
						|
 | 
						|
        font_attrs = 'font_desc="%s"' % font.to_string()
 | 
						|
 | 
						|
        # in case there is no font specified we use x-large font size
 | 
						|
        if font.get_size() == 0:
 | 
						|
            font_attrs = '%s size="x-large"' % font_attrs
 | 
						|
        font.set_weight(Pango.Weight.NORMAL)
 | 
						|
        font_attrs_small = 'font_desc="%s" size="small"' % font.to_string()
 | 
						|
        return (font_attrs, font_attrs_small)
 | 
						|
 | 
						|
    def get_nb_unread(self):
 | 
						|
        jid = self.contact.jid
 | 
						|
        if self.resource:
 | 
						|
            jid += '/' + self.resource
 | 
						|
        type_ = self.type_id
 | 
						|
        return len(app.events.get_events(self.account, jid, ['printed_' + type_,
 | 
						|
                type_]))
 | 
						|
 | 
						|
    def draw_banner(self):
 | 
						|
        """
 | 
						|
        Draw the fat line at the top of the window that houses the icon, jid, etc
 | 
						|
 | 
						|
        Derived types MAY implement this.
 | 
						|
        """
 | 
						|
        self.draw_banner_text()
 | 
						|
        self._update_banner_state_image()
 | 
						|
        app.plugin_manager.gui_extension_point('chat_control_base_draw_banner',
 | 
						|
            self)
 | 
						|
 | 
						|
    def update_toolbar(self):
 | 
						|
        """
 | 
						|
        update state of buttons in toolbar
 | 
						|
        """
 | 
						|
        self._update_toolbar()
 | 
						|
        app.plugin_manager.gui_extension_point(
 | 
						|
            'chat_control_base_update_toolbar', self)
 | 
						|
 | 
						|
    def draw_banner_text(self):
 | 
						|
        """
 | 
						|
        Derived types SHOULD implement this
 | 
						|
        """
 | 
						|
        pass
 | 
						|
 | 
						|
    def update_ui(self):
 | 
						|
        """
 | 
						|
        Derived types SHOULD implement this
 | 
						|
        """
 | 
						|
        self.draw_banner()
 | 
						|
 | 
						|
    def repaint_themed_widgets(self):
 | 
						|
        """
 | 
						|
        Derived types MAY implement this
 | 
						|
        """
 | 
						|
        self.draw_banner()
 | 
						|
 | 
						|
    def _update_banner_state_image(self):
 | 
						|
        """
 | 
						|
        Derived types MAY implement this
 | 
						|
        """
 | 
						|
        pass
 | 
						|
 | 
						|
    def _update_toolbar(self):
 | 
						|
        """
 | 
						|
        Derived types MAY implement this
 | 
						|
        """
 | 
						|
        pass
 | 
						|
 | 
						|
    def _nec_our_status(self, obj):
 | 
						|
        if self.account != obj.conn.name:
 | 
						|
            return
 | 
						|
        if obj.show == 'offline' or (obj.show == 'invisible' and \
 | 
						|
        obj.conn.is_zeroconf):
 | 
						|
            self.got_disconnected()
 | 
						|
        else:
 | 
						|
            # Other code rejoins all GCs, so we don't do it here
 | 
						|
            if not self.type_id == message_control.TYPE_GC:
 | 
						|
                self.got_connected()
 | 
						|
        if self.parent_win:
 | 
						|
            self.parent_win.redraw_tab(self)
 | 
						|
 | 
						|
    def status_url_clicked(self, widget, url):
 | 
						|
        helpers.launch_browser_mailer('url', url)
 | 
						|
 | 
						|
    def setup_seclabel(self, combo):
 | 
						|
        self.seclabel_combo = combo
 | 
						|
        self.seclabel_combo.hide()
 | 
						|
        self.seclabel_combo.set_no_show_all(True)
 | 
						|
        lb = Gtk.ListStore(str)
 | 
						|
        self.seclabel_combo.set_model(lb)
 | 
						|
        cell = Gtk.CellRendererText()
 | 
						|
        cell.set_property('xpad', 5)  # padding for status text
 | 
						|
        self.seclabel_combo.pack_start(cell, True)
 | 
						|
        # text to show is in in first column of liststore
 | 
						|
        self.seclabel_combo.add_attribute(cell, 'text', 0)
 | 
						|
        if app.connections[self.account].seclabel_supported:
 | 
						|
            app.connections[self.account].seclabel_catalogue(self.contact.jid, self.on_seclabels_ready)
 | 
						|
 | 
						|
    def on_seclabels_ready(self):
 | 
						|
        lb = self.seclabel_combo.get_model()
 | 
						|
        lb.clear()
 | 
						|
        i = 0
 | 
						|
        sel = 0
 | 
						|
        catalogue = app.connections[self.account].seclabel_catalogues[
 | 
						|
            self.contact.jid]
 | 
						|
        for label in catalogue[2]:
 | 
						|
            lb.append([label])
 | 
						|
            if label == catalogue[3]:
 | 
						|
                sel = i
 | 
						|
            i += 1
 | 
						|
        self.seclabel_combo.set_active(sel)
 | 
						|
        self.seclabel_combo.set_no_show_all(False)
 | 
						|
        self.seclabel_combo.show_all()
 | 
						|
 | 
						|
    def __init__(self, type_id, parent_win, widget_name, contact, acct,
 | 
						|
    resource=None):
 | 
						|
        # Undo needs this variable to know if space has been pressed.
 | 
						|
        # Initialize it to True so empty textview is saved in undo list
 | 
						|
        self.space_pressed = True
 | 
						|
 | 
						|
        if resource is None:
 | 
						|
            # We very likely got a contact with a random resource.
 | 
						|
            # This is bad, we need the highest for caps etc.
 | 
						|
            c = app.contacts.get_contact_with_highest_priority(acct,
 | 
						|
                contact.jid)
 | 
						|
            if c and not isinstance(c, GC_Contact):
 | 
						|
                contact = c
 | 
						|
 | 
						|
        MessageControl.__init__(self, type_id, parent_win, widget_name,
 | 
						|
            contact, acct, resource=resource)
 | 
						|
 | 
						|
        if self.TYPE_ID != message_control.TYPE_GC:
 | 
						|
            # Create banner and connect signals
 | 
						|
            widget = self.xml.get_object('banner_eventbox')
 | 
						|
            id_ = widget.connect('button-press-event',
 | 
						|
                self._on_banner_eventbox_button_press_event)
 | 
						|
            self.handlers[id_] = widget
 | 
						|
 | 
						|
        self.urlfinder = re.compile(
 | 
						|
            r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]")
 | 
						|
 | 
						|
        self.banner_status_label = self.xml.get_object('banner_label')
 | 
						|
        if self.banner_status_label is not None:
 | 
						|
            id_ = self.banner_status_label.connect('populate_popup',
 | 
						|
                self.on_banner_label_populate_popup)
 | 
						|
            self.handlers[id_] = self.banner_status_label
 | 
						|
 | 
						|
        # Init DND
 | 
						|
        self.TARGET_TYPE_URI_LIST = 80
 | 
						|
        self.dnd_list = [Gtk.TargetEntry.new('text/uri-list', 0,
 | 
						|
            self.TARGET_TYPE_URI_LIST), Gtk.TargetEntry.new('MY_TREE_MODEL_ROW',
 | 
						|
            Gtk.TargetFlags.SAME_APP, 0)]
 | 
						|
        id_ = self.widget.connect('drag_data_received',
 | 
						|
            self._on_drag_data_received)
 | 
						|
        self.handlers[id_] = self.widget
 | 
						|
        self.widget.drag_dest_set(Gtk.DestDefaults.MOTION |
 | 
						|
            Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
 | 
						|
            self.dnd_list, Gdk.DragAction.COPY)
 | 
						|
 | 
						|
        # Create textviews and connect signals
 | 
						|
        self.conv_textview = ConversationTextview(self.account)
 | 
						|
        id_ = self.conv_textview.connect('quote', self.on_quote)
 | 
						|
        self.handlers[id_] = self.conv_textview.tv
 | 
						|
        id_ = self.conv_textview.tv.connect('key_press_event',
 | 
						|
            self._conv_textview_key_press_event)
 | 
						|
        self.handlers[id_] = self.conv_textview.tv
 | 
						|
        # FIXME: DND on non editable TextView, find a better way
 | 
						|
        self.drag_entered = False
 | 
						|
        id_ = self.conv_textview.tv.connect('drag_data_received',
 | 
						|
            self._on_drag_data_received)
 | 
						|
        self.handlers[id_] = self.conv_textview.tv
 | 
						|
        id_ = self.conv_textview.tv.connect('drag_motion', self._on_drag_motion)
 | 
						|
        self.handlers[id_] = self.conv_textview.tv
 | 
						|
        id_ = self.conv_textview.tv.connect('drag_leave', self._on_drag_leave)
 | 
						|
        self.handlers[id_] = self.conv_textview.tv
 | 
						|
        self.conv_textview.tv.drag_dest_set(Gtk.DestDefaults.MOTION |
 | 
						|
            Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
 | 
						|
            self.dnd_list, Gdk.DragAction.COPY)
 | 
						|
 | 
						|
        self.conv_scrolledwindow = self.xml.get_object(
 | 
						|
            'conversation_scrolledwindow')
 | 
						|
        self.conv_scrolledwindow.add(self.conv_textview.tv)
 | 
						|
        widget = self.conv_scrolledwindow.get_vadjustment()
 | 
						|
        id_ = widget.connect('changed',
 | 
						|
            self.on_conversation_vadjustment_changed)
 | 
						|
        self.handlers[id_] = widget
 | 
						|
 | 
						|
        vscrollbar = self.conv_scrolledwindow.get_vscrollbar()
 | 
						|
        id_ = vscrollbar.connect('button-release-event',
 | 
						|
                                 self._on_scrollbar_button_release)
 | 
						|
        self.handlers[id_] = vscrollbar
 | 
						|
 | 
						|
        self.correcting = False
 | 
						|
        self.last_sent_msg = None
 | 
						|
 | 
						|
        # add MessageTextView to UI and connect signals
 | 
						|
        self.msg_textview = MessageTextView()
 | 
						|
        self.msg_scrolledwindow = ScrolledWindow()
 | 
						|
        self.msg_scrolledwindow.add(self.msg_textview)
 | 
						|
 | 
						|
        hbox = self.xml.get_object('hbox')
 | 
						|
        hbox.pack_start(self.msg_scrolledwindow, True, True, 0)
 | 
						|
 | 
						|
        id_ = self.msg_textview.connect('key_press_event',
 | 
						|
            self._on_message_textview_key_press_event)
 | 
						|
        self.handlers[id_] = self.msg_textview
 | 
						|
        id_ = self.msg_textview.connect('populate_popup',
 | 
						|
            self.on_msg_textview_populate_popup)
 | 
						|
        self.handlers[id_] = self.msg_textview
 | 
						|
        # Setup DND
 | 
						|
        id_ = self.msg_textview.connect('drag_data_received',
 | 
						|
            self._on_drag_data_received)
 | 
						|
        self.handlers[id_] = self.msg_textview
 | 
						|
        self.msg_textview.drag_dest_set(Gtk.DestDefaults.MOTION |
 | 
						|
            Gtk.DestDefaults.HIGHLIGHT, self.dnd_list, Gdk.DragAction.COPY)
 | 
						|
 | 
						|
        # the following vars are used to keep history of user's messages
 | 
						|
        self.sent_history = []
 | 
						|
        self.sent_history_pos = 0
 | 
						|
        self.received_history = []
 | 
						|
        self.received_history_pos = 0
 | 
						|
        self.orig_msg = None
 | 
						|
 | 
						|
        self.set_emoticon_popover()
 | 
						|
 | 
						|
        # Attach speller
 | 
						|
        self.spell_checker = None
 | 
						|
        self.set_speller()
 | 
						|
        self.conv_textview.tv.show()
 | 
						|
 | 
						|
        # For XEP-0172
 | 
						|
        self.user_nick = None
 | 
						|
 | 
						|
        self.command_hits = []
 | 
						|
        self.last_key_tabs = False
 | 
						|
 | 
						|
        # chatstate timers and state
 | 
						|
        self.reset_kbd_mouse_timeout_vars()
 | 
						|
        self.possible_paused_timeout_id = None
 | 
						|
        self.possible_inactive_timeout_id = None
 | 
						|
        message_tv_buffer = self.msg_textview.get_buffer()
 | 
						|
        id_ = message_tv_buffer.connect('changed',
 | 
						|
            self._on_message_tv_buffer_changed)
 | 
						|
        self.handlers[id_] = message_tv_buffer
 | 
						|
        if parent_win is not None:
 | 
						|
            id_ = parent_win.window.connect('motion-notify-event',
 | 
						|
                self._on_window_motion_notify)
 | 
						|
            self.handlers[id_] = parent_win.window
 | 
						|
            self._schedule_activity_timers()
 | 
						|
 | 
						|
        self.encryption = self.get_encryption_state()
 | 
						|
 | 
						|
        # PluginSystem: adding GUI extension point for ChatControlBase
 | 
						|
        # instance object (also subclasses, eg. ChatControl or GroupchatControl)
 | 
						|
        app.plugin_manager.gui_extension_point('chat_control_base', self)
 | 
						|
 | 
						|
        app.ged.register_event_handler('our-show', ged.GUI1,
 | 
						|
            self._nec_our_status)
 | 
						|
        app.ged.register_event_handler('ping-sent', ged.GUI1,
 | 
						|
            self._nec_ping)
 | 
						|
        app.ged.register_event_handler('ping-reply', ged.GUI1,
 | 
						|
            self._nec_ping)
 | 
						|
        app.ged.register_event_handler('ping-error', ged.GUI1,
 | 
						|
            self._nec_ping)
 | 
						|
 | 
						|
        # This is basically a very nasty hack to surpass the inability
 | 
						|
        # to properly use the super, because of the old code.
 | 
						|
        CommandTools.__init__(self)
 | 
						|
 | 
						|
    def add_actions(self):
 | 
						|
        action = Gio.SimpleAction.new_stateful(
 | 
						|
            "set-encryption-%s" % self.control_id,
 | 
						|
            GLib.VariantType.new("s"),
 | 
						|
            GLib.Variant("s", self.encryption or 'disabled'))
 | 
						|
        action.connect("change-state", self.change_encryption)
 | 
						|
        self.parent_win.window.add_action(action)
 | 
						|
 | 
						|
        action = Gio.SimpleAction.new(
 | 
						|
            'browse-history-%s' % self.control_id, GLib.VariantType.new('s'))
 | 
						|
        action.connect('activate', self._on_history)
 | 
						|
        self.parent_win.window.add_action(action)
 | 
						|
 | 
						|
        action = Gio.SimpleAction.new(
 | 
						|
            'send-file-%s' % self.control_id, None)
 | 
						|
        action.connect('activate', self._on_send_file)
 | 
						|
        action.set_enabled(False)
 | 
						|
        self.parent_win.window.add_action(action)
 | 
						|
 | 
						|
        action = Gio.SimpleAction.new(
 | 
						|
            'send-file-httpupload-%s' % self.control_id, None)
 | 
						|
        action.connect('activate', self._on_send_httpupload)
 | 
						|
        action.set_enabled(False)
 | 
						|
        self.parent_win.window.add_action(action)
 | 
						|
 | 
						|
        action = Gio.SimpleAction.new(
 | 
						|
            'send-file-jingle-%s' % self.control_id, None)
 | 
						|
        action.connect('activate', self._on_send_jingle)
 | 
						|
        action.set_enabled(False)
 | 
						|
        self.parent_win.window.add_action(action)
 | 
						|
 | 
						|
    # Actions
 | 
						|
 | 
						|
    def _on_history(self, action, param):
 | 
						|
        """
 | 
						|
        When history menuitem is pressed: call history window
 | 
						|
        """
 | 
						|
        jid = param.get_string()
 | 
						|
        if jid == 'none':
 | 
						|
            jid = self.contact.jid
 | 
						|
 | 
						|
        if 'logs' in app.interface.instances:
 | 
						|
            app.interface.instances['logs'].window.present()
 | 
						|
            app.interface.instances['logs'].open_history(jid, self.account)
 | 
						|
        else:
 | 
						|
            app.interface.instances['logs'] = \
 | 
						|
                    history_window.HistoryWindow(jid, self.account)
 | 
						|
 | 
						|
    def change_encryption(self, action, param):
 | 
						|
        encryption = param.get_string()
 | 
						|
        if encryption == 'disabled':
 | 
						|
            encryption = None
 | 
						|
 | 
						|
        if self.encryption == encryption:
 | 
						|
            return
 | 
						|
 | 
						|
        if encryption:
 | 
						|
            plugin = app.plugin_manager.encryption_plugins[encryption]
 | 
						|
            if not plugin.activate_encryption(self):
 | 
						|
                return
 | 
						|
 | 
						|
        action.set_state(param)
 | 
						|
        self.set_encryption_state(encryption)
 | 
						|
        self.set_encryption_menu_icon()
 | 
						|
        self.set_lock_image()
 | 
						|
 | 
						|
    def set_encryption_state(self, encryption):
 | 
						|
        config_key = '%s-%s' % (self.account, self.contact.jid)
 | 
						|
        self.encryption = encryption
 | 
						|
        app.config.set_per('encryption', config_key,
 | 
						|
                             'encryption', self.encryption or '')
 | 
						|
 | 
						|
    def get_encryption_state(self):
 | 
						|
        config_key = '%s-%s' % (self.account, self.contact.jid)
 | 
						|
        state = app.config.get_per('encryption', config_key, 'encryption')
 | 
						|
        if not state:
 | 
						|
            return None
 | 
						|
        if state not in app.plugin_manager.encryption_plugins:
 | 
						|
            self.set_encryption_state(None)
 | 
						|
            return None
 | 
						|
        return state
 | 
						|
 | 
						|
    def set_encryption_menu_icon(self):
 | 
						|
        image = self.encryption_menu.get_image()
 | 
						|
        if image is None:
 | 
						|
            image = Gtk.Image()
 | 
						|
            self.encryption_menu.set_image(image)
 | 
						|
        if not self.encryption:
 | 
						|
            image.set_from_icon_name('channel-insecure-symbolic',
 | 
						|
                                     Gtk.IconSize.MENU)
 | 
						|
        else:
 | 
						|
            image.set_from_icon_name('channel-secure-symbolic',
 | 
						|
                                     Gtk.IconSize.MENU)
 | 
						|
 | 
						|
    def set_speller(self):
 | 
						|
        if not app.is_installed('GSPELL') or not app.config.get('use_speller'):
 | 
						|
            return
 | 
						|
 | 
						|
        gspell_lang = self.get_speller_language()
 | 
						|
        if gspell_lang is None:
 | 
						|
            return
 | 
						|
 | 
						|
        self.spell_checker = Gspell.Checker.new(gspell_lang)
 | 
						|
        spell_buffer = Gspell.TextBuffer.get_from_gtk_text_buffer(
 | 
						|
            self.msg_textview.get_buffer())
 | 
						|
        spell_buffer.set_spell_checker(self.spell_checker)
 | 
						|
        spell_view = Gspell.TextView.get_from_gtk_text_view(self.msg_textview)
 | 
						|
        spell_view.set_inline_spell_checking(False)
 | 
						|
        spell_view.set_enable_language_menu(True)
 | 
						|
 | 
						|
        self.spell_checker.connect('notify::language', self.on_language_changed)
 | 
						|
 | 
						|
    def get_speller_language(self):
 | 
						|
        per_type = 'contacts'
 | 
						|
        if self.type_id == 'gc':
 | 
						|
            per_type = 'rooms'
 | 
						|
        lang = app.config.get_per(
 | 
						|
            per_type, self.contact.jid, 'speller_language')
 | 
						|
        if not lang:
 | 
						|
            # use the default one
 | 
						|
            lang = app.config.get('speller_language')
 | 
						|
            if not lang:
 | 
						|
                lang = i18n.LANG
 | 
						|
        gspell_lang = Gspell.language_lookup(lang)
 | 
						|
        if gspell_lang is None:
 | 
						|
            gspell_lang = Gspell.language_get_default()
 | 
						|
        return gspell_lang
 | 
						|
 | 
						|
    def on_language_changed(self, checker, param):
 | 
						|
        gspell_lang = checker.get_language()
 | 
						|
        per_type = 'contacts'
 | 
						|
        if self.type_id == message_control.TYPE_GC:
 | 
						|
            per_type = 'rooms'
 | 
						|
        if not app.config.get_per(per_type, self.contact.jid):
 | 
						|
            app.config.add_per(per_type, self.contact.jid)
 | 
						|
        app.config.set_per(per_type, self.contact.jid,
 | 
						|
                           'speller_language', gspell_lang.get_code())
 | 
						|
 | 
						|
    def on_banner_label_populate_popup(self, label, menu):
 | 
						|
        """
 | 
						|
        Override the default context menu and add our own menuitems
 | 
						|
        """
 | 
						|
        item = Gtk.SeparatorMenuItem.new()
 | 
						|
        menu.prepend(item)
 | 
						|
 | 
						|
        menu2 = self.prepare_context_menu()
 | 
						|
        i = 0
 | 
						|
        for item in menu2:
 | 
						|
            menu2.remove(item)
 | 
						|
            menu.prepend(item)
 | 
						|
            menu.reorder_child(item, i)
 | 
						|
            i += 1
 | 
						|
        menu.show_all()
 | 
						|
 | 
						|
    def shutdown(self):
 | 
						|
        super(ChatControlBase, self).shutdown()
 | 
						|
        # Disconnect timer callbacks
 | 
						|
        if self.possible_paused_timeout_id:
 | 
						|
            GLib.source_remove(self.possible_paused_timeout_id)
 | 
						|
        if self.possible_inactive_timeout_id:
 | 
						|
            GLib.source_remove(self.possible_inactive_timeout_id)
 | 
						|
        # PluginSystem: removing GUI extension points connected with ChatControlBase
 | 
						|
        # instance object
 | 
						|
        app.plugin_manager.remove_gui_extension_point('chat_control_base',
 | 
						|
            self)
 | 
						|
        app.plugin_manager.remove_gui_extension_point(
 | 
						|
            'chat_control_base_draw_banner', self)
 | 
						|
        app.ged.remove_event_handler('our-show', ged.GUI1,
 | 
						|
            self._nec_our_status)
 | 
						|
 | 
						|
    def on_msg_textview_populate_popup(self, textview, menu):
 | 
						|
        """
 | 
						|
        Override the default context menu and we prepend an option to switch
 | 
						|
        languages
 | 
						|
        """
 | 
						|
        item = Gtk.MenuItem.new_with_mnemonic(_('_Undo'))
 | 
						|
        menu.prepend(item)
 | 
						|
        id_ = item.connect('activate', self.msg_textview.undo)
 | 
						|
        self.handlers[id_] = item
 | 
						|
 | 
						|
        item = Gtk.SeparatorMenuItem.new()
 | 
						|
        menu.prepend(item)
 | 
						|
 | 
						|
        item = Gtk.MenuItem.new_with_mnemonic(_('_Clear'))
 | 
						|
        menu.prepend(item)
 | 
						|
        id_ = item.connect('activate', self.msg_textview.clear)
 | 
						|
        self.handlers[id_] = item
 | 
						|
 | 
						|
        menu.show_all()
 | 
						|
 | 
						|
    def on_quote(self, widget, text):
 | 
						|
        self.msg_textview.remove_placeholder()
 | 
						|
        text = '>' + text.replace('\n', '\n>') + '\n'
 | 
						|
        message_buffer = self.msg_textview.get_buffer()
 | 
						|
        message_buffer.insert_at_cursor(text)
 | 
						|
 | 
						|
    # moved from ChatControl
 | 
						|
    def _on_banner_eventbox_button_press_event(self, widget, event):
 | 
						|
        """
 | 
						|
        If right-clicked, show popup
 | 
						|
        """
 | 
						|
        if event.button == 3:  # right click
 | 
						|
            self.parent_win.popup_menu(event)
 | 
						|
 | 
						|
    def _conv_textview_key_press_event(self, widget, event):
 | 
						|
        # translate any layout to latin_layout
 | 
						|
        valid, entries = self.keymap.get_entries_for_keyval(event.keyval)
 | 
						|
        keycode = entries[0].keycode
 | 
						|
        if (event.get_state() & Gdk.ModifierType.CONTROL_MASK and keycode in (
 | 
						|
        self.keycode_c, self.keycode_ins)):
 | 
						|
            return False
 | 
						|
        
 | 
						|
        if event.get_state() & Gdk.ModifierType.SHIFT_MASK and \
 | 
						|
        event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up):
 | 
						|
            self._on_scroll(None, event.keyval)
 | 
						|
            return False
 | 
						|
        self.parent_win.notebook.event(event)
 | 
						|
        return True
 | 
						|
 | 
						|
    def _on_message_textview_key_press_event(self, widget, event):
 | 
						|
        if event.keyval == Gdk.KEY_space:
 | 
						|
            self.space_pressed = True
 | 
						|
 | 
						|
        elif (self.space_pressed or self.msg_textview.undo_pressed) and \
 | 
						|
        event.keyval not in (Gdk.KEY_Control_L, Gdk.KEY_Control_R) and \
 | 
						|
        not (event.keyval == Gdk.KEY_z and event.get_state() & Gdk.ModifierType.CONTROL_MASK):
 | 
						|
            # If the space key has been pressed and now it hasn't,
 | 
						|
            # we save the buffer into the undo list. But be careful we're not
 | 
						|
            # pressing Control again (as in ctrl+z)
 | 
						|
            _buffer = widget.get_buffer()
 | 
						|
            start_iter, end_iter = _buffer.get_bounds()
 | 
						|
            self.msg_textview.save_undo(_buffer.get_text(start_iter, end_iter, True))
 | 
						|
            self.space_pressed = False
 | 
						|
 | 
						|
        # Ctrl [+ Shift] + Tab are not forwarded to notebook. We handle it here
 | 
						|
        if self.widget_name == 'groupchat_control':
 | 
						|
            if event.keyval not in (Gdk.KEY_ISO_Left_Tab, Gdk.KEY_Tab):
 | 
						|
                self.last_key_tabs = False
 | 
						|
        if event.get_state() & Gdk.ModifierType.SHIFT_MASK:
 | 
						|
            # CTRL + SHIFT + TAB
 | 
						|
            if event.get_state() & Gdk.ModifierType.CONTROL_MASK and \
 | 
						|
                            event.keyval == Gdk.KEY_ISO_Left_Tab:
 | 
						|
                self.parent_win.move_to_next_unread_tab(False)
 | 
						|
                return True
 | 
						|
            # SHIFT + PAGE_[UP|DOWN]: send to conv_textview
 | 
						|
            elif event.keyval == Gdk.KEY_Page_Down or \
 | 
						|
                            event.keyval == Gdk.KEY_Page_Up:
 | 
						|
                self.conv_textview.tv.event(event)
 | 
						|
                return True
 | 
						|
        elif event.get_state() & Gdk.ModifierType.CONTROL_MASK:
 | 
						|
            if event.keyval == Gdk.KEY_Tab:  # CTRL + TAB
 | 
						|
                self.parent_win.move_to_next_unread_tab(True)
 | 
						|
                return True
 | 
						|
 | 
						|
        message_buffer = self.msg_textview.get_buffer()
 | 
						|
        event_state = event.get_state()
 | 
						|
        if event.keyval == Gdk.KEY_Tab:
 | 
						|
            start, end = message_buffer.get_bounds()
 | 
						|
            position = message_buffer.get_insert()
 | 
						|
            end = message_buffer.get_iter_at_mark(position)
 | 
						|
            text = message_buffer.get_text(start, end, False)
 | 
						|
            splitted = text.split()
 | 
						|
            if (text.startswith(self.COMMAND_PREFIX) and not
 | 
						|
            text.startswith(self.COMMAND_PREFIX * 2) and len(splitted) == 1):
 | 
						|
                text = splitted[0]
 | 
						|
                bare = text.lstrip(self.COMMAND_PREFIX)
 | 
						|
                if len(text) == 1:
 | 
						|
                    self.command_hits = []
 | 
						|
                    for command in self.list_commands():
 | 
						|
                        for name in command.names:
 | 
						|
                            self.command_hits.append(name)
 | 
						|
                else:
 | 
						|
                    if (self.last_key_tabs and self.command_hits and
 | 
						|
                            self.command_hits[0].startswith(bare)):
 | 
						|
                        self.command_hits.append(self.command_hits.pop(0))
 | 
						|
                    else:
 | 
						|
                        self.command_hits = []
 | 
						|
                        for command in self.list_commands():
 | 
						|
                            for name in command.names:
 | 
						|
                                if name.startswith(bare):
 | 
						|
                                    self.command_hits.append(name)
 | 
						|
 | 
						|
                if self.command_hits:
 | 
						|
                    message_buffer.delete(start, end)
 | 
						|
                    message_buffer.insert_at_cursor(self.COMMAND_PREFIX + \
 | 
						|
                        self.command_hits[0] + ' ')
 | 
						|
                    self.last_key_tabs = True
 | 
						|
                return True
 | 
						|
            if self.widget_name != 'groupchat_control':
 | 
						|
                self.last_key_tabs = False
 | 
						|
        if event.keyval == Gdk.KEY_Up:
 | 
						|
            if event_state & Gdk.ModifierType.CONTROL_MASK:
 | 
						|
                if event_state & Gdk.ModifierType.SHIFT_MASK: # Ctrl+Shift+UP
 | 
						|
                    self.scroll_messages('up', message_buffer, 'received')
 | 
						|
                else:  # Ctrl+UP
 | 
						|
                    self.scroll_messages('up', message_buffer, 'sent')
 | 
						|
                return True
 | 
						|
        elif event.keyval == Gdk.KEY_Down:
 | 
						|
            if event_state & Gdk.ModifierType.CONTROL_MASK:
 | 
						|
                if event_state & Gdk.ModifierType.SHIFT_MASK: # Ctrl+Shift+Down
 | 
						|
                    self.scroll_messages('down', message_buffer, 'received')
 | 
						|
                else:  # Ctrl+Down
 | 
						|
                    self.scroll_messages('down', message_buffer, 'sent')
 | 
						|
                return True
 | 
						|
        elif event.keyval == Gdk.KEY_Return or \
 | 
						|
        event.keyval == Gdk.KEY_KP_Enter:  # ENTER
 | 
						|
            message_textview = widget
 | 
						|
            message_buffer = message_textview.get_buffer()
 | 
						|
            emoticons.replace_with_codepoint(message_buffer)
 | 
						|
            start_iter, end_iter = message_buffer.get_bounds()
 | 
						|
            message = message_buffer.get_text(start_iter, end_iter, False)
 | 
						|
            xhtml = self.msg_textview.get_xhtml()
 | 
						|
 | 
						|
            if event_state & Gdk.ModifierType.SHIFT_MASK:
 | 
						|
                send_message = False
 | 
						|
            else:
 | 
						|
                is_ctrl_enter = bool(event_state & Gdk.ModifierType.CONTROL_MASK)
 | 
						|
                send_message = is_ctrl_enter == app.config.get('send_on_ctrl_enter')
 | 
						|
 | 
						|
            if send_message and app.connections[self.account].connected < 2:
 | 
						|
                # we are not connected
 | 
						|
                app.interface.raise_dialog('not-connected-while-sending')
 | 
						|
            elif send_message:
 | 
						|
                self.send_message(message, xhtml=xhtml)
 | 
						|
            else:
 | 
						|
                message_buffer.insert_at_cursor('\n')
 | 
						|
                mark = message_buffer.get_insert()
 | 
						|
                iter_ = message_buffer.get_iter_at_mark(mark)
 | 
						|
                if message_buffer.get_end_iter().equal(iter_):
 | 
						|
                    GLib.idle_add(gtkgui_helpers.scroll_to_end, self.msg_scrolledwindow)
 | 
						|
 | 
						|
            return True
 | 
						|
        elif event.keyval == Gdk.KEY_z: # CTRL+z
 | 
						|
            if event_state & Gdk.ModifierType.CONTROL_MASK:
 | 
						|
                self.msg_textview.undo()
 | 
						|
                return True
 | 
						|
 | 
						|
        return False
 | 
						|
 | 
						|
    def _on_drag_data_received(self, widget, context, x, y, selection,
 | 
						|
                    target_type, timestamp):
 | 
						|
        """
 | 
						|
        Derived types SHOULD implement this
 | 
						|
        """
 | 
						|
        pass
 | 
						|
 | 
						|
    def _on_drag_leave(self, widget, context, time):
 | 
						|
        # FIXME: DND on non editable TextView, find a better way
 | 
						|
        self.drag_entered = False
 | 
						|
        self.conv_textview.tv.set_editable(False)
 | 
						|
 | 
						|
    def _on_drag_motion(self, widget, context, x, y, time):
 | 
						|
        # FIXME: DND on non editable TextView, find a better way
 | 
						|
        if not self.drag_entered:
 | 
						|
            # We drag new data over the TextView, make it editable to catch dnd
 | 
						|
            self.drag_entered_conv = True
 | 
						|
            self.conv_textview.tv.set_editable(True)
 | 
						|
 | 
						|
    def drag_data_file_transfer(self, contact, selection, widget):
 | 
						|
        # get file transfer preference
 | 
						|
        ft_pref = app.config.get_per('accounts', self.account,
 | 
						|
                                     'filetransfer_preference')
 | 
						|
        win = self.parent_win.window
 | 
						|
        con = app.connections[self.account]
 | 
						|
        httpupload = win.lookup_action(
 | 
						|
            'send-file-httpupload-%s' % self.control_id)
 | 
						|
        jingle = win.lookup_action('send-file-jingle-%s' % self.control_id)
 | 
						|
 | 
						|
        # we may have more than one file dropped
 | 
						|
        uri_splitted = selection.get_uris()
 | 
						|
        for uri in uri_splitted:
 | 
						|
            path = helpers.get_file_path_from_dnd_dropped_uri(uri)
 | 
						|
            if not os.path.isfile(path):  # is it a file?
 | 
						|
                continue
 | 
						|
            if self.type_id == message_control.TYPE_GC:
 | 
						|
                # groupchat only supports httpupload on drag and drop
 | 
						|
                if httpupload.get_enabled():
 | 
						|
                    # use httpupload
 | 
						|
                    con.get_module('HTTPUpload').check_file_before_transfer(
 | 
						|
                        path, self.encryption, contact,
 | 
						|
                        self.session, groupchat=True)
 | 
						|
            else:
 | 
						|
                if httpupload.get_enabled() and jingle.get_enabled():
 | 
						|
                    if ft_pref == 'httpupload':
 | 
						|
                        con.get_module('HTTPUpload').check_file_before_transfer(
 | 
						|
                            path, self.encryption, contact, self.session)
 | 
						|
                    else:
 | 
						|
                        ft = app.interface.instances['file_transfers']
 | 
						|
                        ft.send_file(self.account, contact, path)
 | 
						|
                elif httpupload.get_enabled():
 | 
						|
                    con.get_module('HTTPUpload').check_file_before_transfer(
 | 
						|
                        path, self.encryption, contact, self.session)
 | 
						|
                elif jingle.get_enabled():
 | 
						|
                    ft = app.interface.instances['file_transfers']
 | 
						|
                    ft.send_file(self.account, contact, path)
 | 
						|
 | 
						|
    def get_seclabel(self):
 | 
						|
        label = None
 | 
						|
        if self.seclabel_combo is not None:
 | 
						|
            idx = self.seclabel_combo.get_active()
 | 
						|
            if idx != -1:
 | 
						|
                cat = app.connections[self.account].seclabel_catalogues[self.contact.jid]
 | 
						|
                lname = cat[2][idx]
 | 
						|
                label = cat[1][lname]
 | 
						|
        return label
 | 
						|
 | 
						|
    def send_message(self, message, keyID='', type_='chat', chatstate=None,
 | 
						|
    resource=None, xhtml=None, process_commands=True, attention=False):
 | 
						|
        """
 | 
						|
        Send the given message to the active tab. Doesn't return None if error
 | 
						|
        """
 | 
						|
        if not message or message == '\n':
 | 
						|
            return None
 | 
						|
 | 
						|
        if process_commands and self.process_as_command(message):
 | 
						|
            return
 | 
						|
 | 
						|
        # refresh timers
 | 
						|
        self.reset_kbd_mouse_timeout_vars()
 | 
						|
 | 
						|
        notifications = app.config.get('outgoing_chat_state_notifications')
 | 
						|
        if (self.contact.jid == app.get_jid_from_account(self.account) or
 | 
						|
                notifications == 'disabled'):
 | 
						|
            chatstate = None
 | 
						|
 | 
						|
        label = self.get_seclabel()
 | 
						|
 | 
						|
        if self.correcting and self.last_sent_msg:
 | 
						|
            correct_id = self.last_sent_msg
 | 
						|
        else:
 | 
						|
            correct_id = None
 | 
						|
 | 
						|
        app.nec.push_outgoing_event(MessageOutgoingEvent(None,
 | 
						|
            account=self.account, jid=self.contact.jid, message=message,
 | 
						|
            keyID=keyID, type_=type_, chatstate=chatstate,
 | 
						|
            resource=resource, user_nick=self.user_nick, xhtml=xhtml,
 | 
						|
            label=label, control=self, attention=attention, correct_id=correct_id,
 | 
						|
            automatic_message=False, encryption=self.encryption))
 | 
						|
 | 
						|
        # Record the history of sent messages
 | 
						|
        self.save_message(message, 'sent')
 | 
						|
 | 
						|
        # Be sure to send user nickname only once according to JEP-0172
 | 
						|
        self.user_nick = None
 | 
						|
 | 
						|
        # Clear msg input
 | 
						|
        message_buffer = self.msg_textview.get_buffer()
 | 
						|
        message_buffer.set_text('') # clear message buffer (and tv of course)
 | 
						|
 | 
						|
    def check_for_possible_paused_chatstate(self, arg):
 | 
						|
        """
 | 
						|
        Did we move mouse of that window or write something in message textview
 | 
						|
        in the last 5 seconds? If yes - we go active for mouse, composing for
 | 
						|
        kbd.  If not - we go paused if we were previously composing
 | 
						|
        """
 | 
						|
        contact = self.contact
 | 
						|
        jid = contact.jid
 | 
						|
        current_state = contact.our_chatstate
 | 
						|
        if current_state is False:  # jid doesn't support chatstates
 | 
						|
            self.possible_paused_timeout_id = None
 | 
						|
            return False  # stop looping
 | 
						|
 | 
						|
        if current_state == 'composing':
 | 
						|
            if not self.kbd_activity_in_last_5_secs:
 | 
						|
                if self.msg_textview.has_text():
 | 
						|
                    self.send_chatstate('paused', self.contact)
 | 
						|
                else:
 | 
						|
                    self.send_chatstate('active', self.contact)
 | 
						|
        elif current_state == 'inactive':
 | 
						|
            if (self.mouse_over_in_last_5_secs and
 | 
						|
                    jid == self.parent_win.get_active_jid()):
 | 
						|
                self.send_chatstate('active', self.contact)
 | 
						|
 | 
						|
        # assume no activity and let the motion-notify or 'insert-text' make them
 | 
						|
        # True refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds!
 | 
						|
        self.reset_kbd_mouse_timeout_vars()
 | 
						|
        return True  # loop forever
 | 
						|
 | 
						|
    def check_for_possible_inactive_chatstate(self, arg):
 | 
						|
        """
 | 
						|
        Did we move mouse over that window or wrote something in message textview
 | 
						|
        in the last 30 seconds? if yes - we go active. If no - we go inactive
 | 
						|
        """
 | 
						|
        contact = self.contact
 | 
						|
 | 
						|
        current_state = contact.our_chatstate
 | 
						|
        if current_state is False: # jid doesn't support chatstates
 | 
						|
            self.possible_inactive_timeout_id = None
 | 
						|
            return False # stop looping
 | 
						|
 | 
						|
        if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs:
 | 
						|
            return True # loop forever
 | 
						|
 | 
						|
        if not self.mouse_over_in_last_30_secs or \
 | 
						|
        self.kbd_activity_in_last_30_secs:
 | 
						|
            self.send_chatstate('inactive', contact)
 | 
						|
 | 
						|
        # assume no activity and let the motion-notify or 'insert-text' make them
 | 
						|
        # True refresh 30 seconds too or else it's 30 - 5 = 25 seconds!
 | 
						|
        self.reset_kbd_mouse_timeout_vars()
 | 
						|
        return True # loop forever
 | 
						|
 | 
						|
    def _schedule_activity_timers(self):
 | 
						|
        if self.possible_paused_timeout_id:
 | 
						|
            GLib.source_remove(self.possible_paused_timeout_id)
 | 
						|
        if self.possible_inactive_timeout_id:
 | 
						|
            GLib.source_remove(self.possible_inactive_timeout_id)
 | 
						|
        self.possible_paused_timeout_id = GLib.timeout_add_seconds(5,
 | 
						|
                self.check_for_possible_paused_chatstate, None)
 | 
						|
        self.possible_inactive_timeout_id = GLib.timeout_add_seconds(30,
 | 
						|
                self.check_for_possible_inactive_chatstate, None)
 | 
						|
 | 
						|
    def reset_kbd_mouse_timeout_vars(self):
 | 
						|
        self.kbd_activity_in_last_5_secs = False
 | 
						|
        self.mouse_over_in_last_5_secs = False
 | 
						|
        self.mouse_over_in_last_30_secs = False
 | 
						|
        self.kbd_activity_in_last_30_secs = False
 | 
						|
 | 
						|
    def _on_window_motion_notify(self, widget, event):
 | 
						|
        """
 | 
						|
        It gets called no matter if it is the active window or not
 | 
						|
        """
 | 
						|
        if not self.parent_win:
 | 
						|
            # when a groupchat is minimized there is no parent window
 | 
						|
            return
 | 
						|
        if self.parent_win.get_active_jid() == self.contact.jid:
 | 
						|
            # if window is the active one, change vars assisting chatstate
 | 
						|
            self.mouse_over_in_last_5_secs = True
 | 
						|
            self.mouse_over_in_last_30_secs = True
 | 
						|
 | 
						|
    def _on_message_tv_buffer_changed(self, textbuffer):
 | 
						|
        self.kbd_activity_in_last_5_secs = True
 | 
						|
        self.kbd_activity_in_last_30_secs = True
 | 
						|
        if not self.msg_textview.has_text():
 | 
						|
            return
 | 
						|
        self.send_chatstate('composing', self.contact)
 | 
						|
 | 
						|
    def save_message(self, message, msg_type):
 | 
						|
        # save the message, so user can scroll though the list with key up/down
 | 
						|
        if msg_type == 'sent':
 | 
						|
            history = self.sent_history
 | 
						|
            pos = self.sent_history_pos
 | 
						|
        else:
 | 
						|
            history = self.received_history
 | 
						|
            pos = self.received_history_pos
 | 
						|
        size = len(history)
 | 
						|
        scroll = False if pos == size else True # are we scrolling?
 | 
						|
        # we don't want size of the buffer to grow indefinitely
 | 
						|
        max_size = app.config.get('key_up_lines')
 | 
						|
        for i in range(size - max_size + 1):
 | 
						|
            if pos == 0:
 | 
						|
                break
 | 
						|
            history.pop(0)
 | 
						|
            pos -= 1
 | 
						|
        history.append(message)
 | 
						|
        if not scroll or msg_type == 'sent':
 | 
						|
            pos = len(history)
 | 
						|
        if msg_type == 'sent':
 | 
						|
            self.sent_history_pos = pos
 | 
						|
            self.orig_msg = None
 | 
						|
        else:
 | 
						|
            self.received_history_pos = pos
 | 
						|
 | 
						|
    def print_conversation_line(self, text, kind, name, tim,
 | 
						|
    other_tags_for_name=None, other_tags_for_time=None, other_tags_for_text=None,
 | 
						|
    count_as_new=True, subject=None, old_kind=None, xhtml=None, simple=False,
 | 
						|
    xep0184_id=None, graphics=True, displaymarking=None, msg_log_id=None,
 | 
						|
    msg_stanza_id=None, correct_id=None, additional_data=None,
 | 
						|
    encrypted=None):
 | 
						|
        """
 | 
						|
        Print 'chat' type messages
 | 
						|
        correct_id = (message_id, correct_id)
 | 
						|
        """
 | 
						|
        jid = self.contact.jid
 | 
						|
        full_jid = self.get_full_jid()
 | 
						|
        textview = self.conv_textview
 | 
						|
        end = False
 | 
						|
        if self.conv_textview.autoscroll or kind == 'outgoing':
 | 
						|
            end = True
 | 
						|
 | 
						|
        if other_tags_for_name is None:
 | 
						|
            other_tags_for_name = []
 | 
						|
        if other_tags_for_time is None:
 | 
						|
            other_tags_for_time = []
 | 
						|
        if other_tags_for_text is None:
 | 
						|
            other_tags_for_text = []
 | 
						|
        if additional_data is None:
 | 
						|
            additional_data = {}
 | 
						|
 | 
						|
        textview.print_conversation_line(text, jid, kind, name, tim,
 | 
						|
            other_tags_for_name, other_tags_for_time, other_tags_for_text,
 | 
						|
            subject, old_kind, xhtml, simple=simple, graphics=graphics,
 | 
						|
            displaymarking=displaymarking, msg_stanza_id=msg_stanza_id,
 | 
						|
            correct_id=correct_id, additional_data=additional_data,
 | 
						|
            encrypted=encrypted)
 | 
						|
 | 
						|
        if xep0184_id is not None:
 | 
						|
            textview.add_xep0184_mark(xep0184_id)
 | 
						|
 | 
						|
        if not count_as_new:
 | 
						|
            return
 | 
						|
        if kind == 'incoming':
 | 
						|
            if not self.type_id == message_control.TYPE_GC or \
 | 
						|
            app.config.get('notify_on_all_muc_messages') or \
 | 
						|
            app.config.get_per('rooms', jid, 'notify_on_all_messages') or \
 | 
						|
            'marked' in other_tags_for_text:
 | 
						|
                # it's a normal message, or a muc message with want to be
 | 
						|
                # notified about if quitting just after
 | 
						|
                # other_tags_for_text == ['marked'] --> highlighted gc message
 | 
						|
                app.last_message_time[self.account][full_jid] = time.time()
 | 
						|
 | 
						|
        if kind in ('incoming', 'incoming_queue'):
 | 
						|
            # Record the history of received messages
 | 
						|
            self.save_message(text, 'received')
 | 
						|
 | 
						|
        if kind in ('incoming', 'incoming_queue', 'error'):
 | 
						|
            gc_message = False
 | 
						|
            if self.type_id == message_control.TYPE_GC:
 | 
						|
                gc_message = True
 | 
						|
 | 
						|
            if ((self.parent_win and (not self.parent_win.get_active_control() or \
 | 
						|
            self != self.parent_win.get_active_control() or \
 | 
						|
            not self.parent_win.is_active() or not end)) or \
 | 
						|
            (gc_message and \
 | 
						|
            jid in app.interface.minimized_controls[self.account])) and \
 | 
						|
            kind in ('incoming', 'incoming_queue', 'error'):
 | 
						|
                # we want to have save this message in events list
 | 
						|
                # other_tags_for_text == ['marked'] --> highlighted gc message
 | 
						|
                if gc_message:
 | 
						|
                    if 'marked' in other_tags_for_text:
 | 
						|
                        event_type = events.PrintedMarkedGcMsgEvent
 | 
						|
                    else:
 | 
						|
                        event_type = events.PrintedGcMsgEvent
 | 
						|
                    event = 'gc_message_received'
 | 
						|
                else:
 | 
						|
                    if self.type_id == message_control.TYPE_CHAT:
 | 
						|
                        event_type = events.PrintedChatEvent
 | 
						|
                    else:
 | 
						|
                        event_type = events.PrintedPmEvent
 | 
						|
                    event = 'message_received'
 | 
						|
                show_in_roster = notify.get_show_in_roster(event,
 | 
						|
                    self.account, self.contact.jid, self.session)
 | 
						|
                show_in_systray = notify.get_show_in_systray(event,
 | 
						|
                    self.account, self.contact.jid, event_type.type_)
 | 
						|
 | 
						|
                event = event_type(text, subject, self, msg_log_id,
 | 
						|
                    show_in_roster=show_in_roster,
 | 
						|
                    show_in_systray=show_in_systray)
 | 
						|
                app.events.add_event(self.account, full_jid, event)
 | 
						|
                # We need to redraw contact if we show in roster
 | 
						|
                if show_in_roster:
 | 
						|
                    app.interface.roster.draw_contact(self.contact.jid,
 | 
						|
                        self.account)
 | 
						|
 | 
						|
        if not self.parent_win:
 | 
						|
            return
 | 
						|
 | 
						|
        if (not self.parent_win.get_active_control() or \
 | 
						|
        self != self.parent_win.get_active_control() or \
 | 
						|
        not self.parent_win.is_active() or not end) and \
 | 
						|
        kind in ('incoming', 'incoming_queue', 'error'):
 | 
						|
            self.parent_win.redraw_tab(self)
 | 
						|
            if not self.parent_win.is_active():
 | 
						|
                self.parent_win.show_title(True, self) # Enabled Urgent hint
 | 
						|
            else:
 | 
						|
                self.parent_win.show_title(False, self) # Disabled Urgent hint
 | 
						|
 | 
						|
    def toggle_emoticons(self):
 | 
						|
        """
 | 
						|
        Hide show emoticons_button
 | 
						|
        """
 | 
						|
        if app.config.get('emoticons_theme'):
 | 
						|
            self.emoticons_button.set_no_show_all(False)
 | 
						|
            self.emoticons_button.show()
 | 
						|
        else:
 | 
						|
            self.emoticons_button.set_no_show_all(True)
 | 
						|
            self.emoticons_button.hide()
 | 
						|
 | 
						|
    def set_emoticon_popover(self):
 | 
						|
        if not app.config.get('emoticons_theme'):
 | 
						|
            return
 | 
						|
 | 
						|
        if not self.parent_win:
 | 
						|
            return
 | 
						|
 | 
						|
        popover = emoticons.get_popover()
 | 
						|
        popover.set_callbacks(self.msg_textview)
 | 
						|
        emoticons_button = self.xml.get_object('emoticons_button')
 | 
						|
        emoticons_button.set_popover(popover)
 | 
						|
 | 
						|
    def on_color_menuitem_activate(self, widget):
 | 
						|
        color_dialog = Gtk.ColorChooserDialog(None, self.parent_win.window)
 | 
						|
        color_dialog.set_use_alpha(False)
 | 
						|
        color_dialog.connect('response', self.msg_textview.color_set)
 | 
						|
        color_dialog.show_all()
 | 
						|
 | 
						|
    def on_font_menuitem_activate(self, widget):
 | 
						|
        font_dialog = Gtk.FontChooserDialog(None, self.parent_win.window)
 | 
						|
        start, finish = self.msg_textview.get_active_iters()
 | 
						|
        font_dialog.connect('response', self.msg_textview.font_set, start, finish)
 | 
						|
        font_dialog.show_all()
 | 
						|
 | 
						|
    def on_formatting_menuitem_activate(self, widget):
 | 
						|
        tag = widget.get_name()
 | 
						|
        self.msg_textview.set_tag(tag)
 | 
						|
 | 
						|
    def on_clear_formatting_menuitem_activate(self, widget):
 | 
						|
        self.msg_textview.clear_tags()
 | 
						|
 | 
						|
    def update_tags(self):
 | 
						|
        self.conv_textview.update_tags()
 | 
						|
 | 
						|
    def clear(self, tv):
 | 
						|
        buffer_ = tv.get_buffer()
 | 
						|
        start, end = buffer_.get_bounds()
 | 
						|
        buffer_.delete(start, end)
 | 
						|
 | 
						|
    def _on_history_menuitem_activate(self, widget=None, jid=None):
 | 
						|
        """
 | 
						|
        When history menuitem is pressed: call history window
 | 
						|
        """
 | 
						|
        if not jid:
 | 
						|
            jid = self.contact.jid
 | 
						|
 | 
						|
        if 'logs' in app.interface.instances:
 | 
						|
            app.interface.instances['logs'].window.present()
 | 
						|
            app.interface.instances['logs'].open_history(jid, self.account)
 | 
						|
        else:
 | 
						|
            app.interface.instances['logs'] = \
 | 
						|
                    history_window.HistoryWindow(jid, self.account)
 | 
						|
 | 
						|
    def _on_send_file(self, action, param):
 | 
						|
        # get file transfer preference
 | 
						|
        ft_pref = app.config.get_per('accounts', self.account,
 | 
						|
                                     'filetransfer_preference')
 | 
						|
 | 
						|
        win = self.parent_win.window
 | 
						|
        httpupload = win.lookup_action(
 | 
						|
            'send-file-httpupload-%s' % self.control_id)
 | 
						|
        jingle = win.lookup_action('send-file-jingle-%s' % self.control_id)
 | 
						|
 | 
						|
        if httpupload.get_enabled() and jingle.get_enabled():
 | 
						|
            if ft_pref == 'httpupload':
 | 
						|
                httpupload.activate()
 | 
						|
            else:
 | 
						|
                jingle.activate()
 | 
						|
        elif httpupload.get_enabled():
 | 
						|
            httpupload.activate()
 | 
						|
        elif jingle.get_enabled():
 | 
						|
            jingle.activate()
 | 
						|
 | 
						|
    def _on_send_httpupload(self, action, param):
 | 
						|
        app.interface.send_httpupload(self)
 | 
						|
 | 
						|
    def _on_send_jingle(self, action, param):
 | 
						|
        self._on_send_file_jingle()
 | 
						|
 | 
						|
    def _on_send_file_jingle(self, gc_contact=None):
 | 
						|
        """
 | 
						|
        gc_contact can be set when we are in a groupchat control
 | 
						|
        """
 | 
						|
        def _on_ok(c):
 | 
						|
            app.interface.instances['file_transfers'].show_file_send_request(
 | 
						|
                    self.account, c)
 | 
						|
        if self.type_id == message_control.TYPE_PM:
 | 
						|
            gc_contact = self.gc_contact
 | 
						|
        if gc_contact:
 | 
						|
            # gc or pm
 | 
						|
            gc_control = app.interface.msg_win_mgr.get_gc_control(
 | 
						|
                    gc_contact.room_jid, self.account)
 | 
						|
            self_contact = app.contacts.get_gc_contact(self.account,
 | 
						|
                    gc_control.room_jid, gc_control.nick)
 | 
						|
            if gc_control.is_anonymous and gc_contact.affiliation not in ['admin',
 | 
						|
            'owner'] and self_contact.affiliation in ['admin', 'owner']:
 | 
						|
                contact = app.contacts.get_contact(self.account, gc_contact.jid)
 | 
						|
                if not contact or contact.sub not in ('both', 'to'):
 | 
						|
                    prim_text = _('Really send file?')
 | 
						|
                    sec_text = _('If you send a file to %s, your real JID will '
 | 
						|
                        'be revealed.') % gc_contact.name
 | 
						|
                    dialog = dialogs.NonModalConfirmationDialog(prim_text,
 | 
						|
                        sec_text, on_response_ok=(_on_ok, gc_contact))
 | 
						|
                    dialog.popup()
 | 
						|
                    return
 | 
						|
            _on_ok(gc_contact)
 | 
						|
            return
 | 
						|
        _on_ok(self.contact)
 | 
						|
 | 
						|
    def on_minimize_menuitem_toggled(self, widget):
 | 
						|
        """
 | 
						|
        When a groupchat is minimized, unparent the tab, put it in roster etc
 | 
						|
        """
 | 
						|
        old_value = True
 | 
						|
        non_minimized_gc = app.config.get_per('accounts', self.account,
 | 
						|
                'non_minimized_gc').split()
 | 
						|
        if self.contact.jid in non_minimized_gc:
 | 
						|
            old_value = False
 | 
						|
        minimize = widget.get_active()
 | 
						|
        if not minimize and not self.contact.jid in non_minimized_gc:
 | 
						|
            non_minimized_gc.append(self.contact.jid)
 | 
						|
        if minimize and self.contact.jid in non_minimized_gc:
 | 
						|
            non_minimized_gc.remove(self.contact.jid)
 | 
						|
        if old_value != minimize:
 | 
						|
            app.config.set_per('accounts', self.account, 'non_minimized_gc',
 | 
						|
                    ' '.join(non_minimized_gc))
 | 
						|
 | 
						|
    def on_notify_menuitem_toggled(self, widget):
 | 
						|
        app.config.set_per('rooms', self.contact.jid, 'notify_on_all_messages',
 | 
						|
            widget.get_active())
 | 
						|
 | 
						|
    def set_control_active(self, state):
 | 
						|
        if state:
 | 
						|
            self.set_emoticon_popover()
 | 
						|
            jid = self.contact.jid
 | 
						|
            if self.conv_textview.autoscroll:
 | 
						|
                # we are at the end
 | 
						|
                type_ = ['printed_' + self.type_id]
 | 
						|
                if self.type_id == message_control.TYPE_GC:
 | 
						|
                    type_ = ['printed_gc_msg', 'printed_marked_gc_msg']
 | 
						|
                if not app.events.remove_events(self.account, self.get_full_jid(),
 | 
						|
                types=type_):
 | 
						|
                    # There were events to remove
 | 
						|
                    self.redraw_after_event_removed(jid)
 | 
						|
            # send chatstate inactive to the one we're leaving
 | 
						|
            # and active to the one we visit
 | 
						|
            if self.msg_textview.has_text():
 | 
						|
                self.send_chatstate('paused', self.contact)
 | 
						|
            else:
 | 
						|
                self.send_chatstate('active', self.contact)
 | 
						|
            self.reset_kbd_mouse_timeout_vars()
 | 
						|
            self._schedule_activity_timers()
 | 
						|
        else:
 | 
						|
            self.send_chatstate('inactive', self.contact)
 | 
						|
 | 
						|
    def scroll_to_end(self, force=False):
 | 
						|
        self.conv_textview.scroll_to_end(force)
 | 
						|
 | 
						|
    def _on_edge_reached(self, scrolledwindow, pos):
 | 
						|
        if pos != Gtk.PositionType.BOTTOM:
 | 
						|
            return
 | 
						|
        # Remove all events and set autoscroll True
 | 
						|
        app.log('autoscroll').info('Autoscroll enabled')
 | 
						|
        self.conv_textview.autoscroll = True
 | 
						|
        if self.resource:
 | 
						|
            jid = self.contact.get_full_jid()
 | 
						|
        else:
 | 
						|
            jid = self.contact.jid
 | 
						|
        types_list = []
 | 
						|
        type_ = self.type_id
 | 
						|
        if type_ == message_control.TYPE_GC:
 | 
						|
            type_ = 'gc_msg'
 | 
						|
            types_list = ['printed_' + type_, type_, 'printed_marked_gc_msg']
 | 
						|
        else: # Not a GC
 | 
						|
            types_list = ['printed_' + type_, type_]
 | 
						|
 | 
						|
        if not len(app.events.get_events(self.account, jid, types_list)):
 | 
						|
            return
 | 
						|
        if not self.parent_win:
 | 
						|
            return
 | 
						|
        if self.parent_win.get_active_control() == self and \
 | 
						|
        self.parent_win.window.is_active():
 | 
						|
            # we are at the end
 | 
						|
            if self.type_id == message_control.TYPE_GC:
 | 
						|
                if not app.events.remove_events(self.account, jid,
 | 
						|
                types=types_list):
 | 
						|
                    self.redraw_after_event_removed(jid)
 | 
						|
            elif self.session and self.session.remove_events(types_list):
 | 
						|
                # There were events to remove
 | 
						|
                self.redraw_after_event_removed(jid)
 | 
						|
 | 
						|
    def _on_scrollbar_button_release(self, scrollbar, event):
 | 
						|
        if event.get_button()[1] != 1:
 | 
						|
            # We want only to catch the left mouse button
 | 
						|
            return
 | 
						|
        if not gtkgui_helpers.at_the_end(scrollbar.get_parent()):
 | 
						|
            app.log('autoscroll').info('Autoscroll disabled')
 | 
						|
            self.conv_textview.autoscroll = False
 | 
						|
 | 
						|
    def has_focus(self):
 | 
						|
        if self.parent_win:
 | 
						|
            if self.parent_win.window.get_property('has-toplevel-focus'):
 | 
						|
                if self == self.parent_win.get_active_control():
 | 
						|
                    return True
 | 
						|
        return False
 | 
						|
 | 
						|
    def _on_scroll(self, widget, event):
 | 
						|
        if not self.conv_textview.autoscroll:
 | 
						|
            # autoscroll is already disabled
 | 
						|
            return
 | 
						|
 | 
						|
        if widget is None:
 | 
						|
            # call from _conv_textview_key_press_event()
 | 
						|
            # SHIFT + Gdk.KEY_Page_Up
 | 
						|
            if event != Gdk.KEY_Page_Up:
 | 
						|
                return
 | 
						|
        else:
 | 
						|
            # On scrolling UP disable autoscroll
 | 
						|
            # get_scroll_direction() sets has_direction only TRUE
 | 
						|
            # if smooth scrolling is deactivated. If we have smooth
 | 
						|
            # smooth scrolling we have to use get_scroll_deltas()
 | 
						|
            has_direction, direction = event.get_scroll_direction()
 | 
						|
            if not has_direction:
 | 
						|
                direction = None
 | 
						|
                smooth, delta_x, delta_y = event.get_scroll_deltas()
 | 
						|
                if smooth:
 | 
						|
                    if delta_y < 0:
 | 
						|
                        direction = Gdk.ScrollDirection.UP
 | 
						|
                    elif delta_y > 0:
 | 
						|
                        direction = Gdk.ScrollDirection.DOWN
 | 
						|
                    elif delta_x < 0:
 | 
						|
                        direction = Gdk.ScrollDirection.LEFT
 | 
						|
                    elif delta_x > 0:
 | 
						|
                        direction = Gdk.ScrollDirection.RIGHT
 | 
						|
                else:
 | 
						|
                    app.log('autoscroll').warning(
 | 
						|
                        'Scroll directions cant be determined')
 | 
						|
 | 
						|
            if direction != Gdk.ScrollDirection.UP:
 | 
						|
                return
 | 
						|
        # Check if we have a Scrollbar
 | 
						|
        adjustment = self.conv_scrolledwindow.get_vadjustment()
 | 
						|
        if adjustment.get_upper() != adjustment.get_page_size():
 | 
						|
            app.log('autoscroll').info('Autoscroll disabled')
 | 
						|
            self.conv_textview.autoscroll = False
 | 
						|
 | 
						|
    def on_conversation_vadjustment_changed(self, adjustment):
 | 
						|
        self.scroll_to_end()
 | 
						|
 | 
						|
    def redraw_after_event_removed(self, jid):
 | 
						|
        """
 | 
						|
        We just removed a 'printed_*' event, redraw contact in roster or
 | 
						|
        gc_roster and titles in roster and msg_win
 | 
						|
        """
 | 
						|
        self.parent_win.redraw_tab(self)
 | 
						|
        self.parent_win.show_title()
 | 
						|
        # TODO : get the contact and check notify.get_show_in_roster()
 | 
						|
        if self.type_id == message_control.TYPE_PM:
 | 
						|
            room_jid, nick = app.get_room_and_nick_from_fjid(jid)
 | 
						|
            groupchat_control = app.interface.msg_win_mgr.get_gc_control(
 | 
						|
                    room_jid, self.account)
 | 
						|
            if room_jid in app.interface.minimized_controls[self.account]:
 | 
						|
                groupchat_control = \
 | 
						|
                        app.interface.minimized_controls[self.account][room_jid]
 | 
						|
            contact = app.contacts.get_contact_with_highest_priority(
 | 
						|
                self.account, room_jid)
 | 
						|
            if contact:
 | 
						|
                app.interface.roster.draw_contact(room_jid, self.account)
 | 
						|
            if groupchat_control:
 | 
						|
                groupchat_control.draw_contact(nick)
 | 
						|
                if groupchat_control.parent_win:
 | 
						|
                    groupchat_control.parent_win.redraw_tab(groupchat_control)
 | 
						|
        else:
 | 
						|
            app.interface.roster.draw_contact(jid, self.account)
 | 
						|
            app.interface.roster.show_title()
 | 
						|
 | 
						|
    def scroll_messages(self, direction, msg_buf, msg_type):
 | 
						|
        if msg_type == 'sent':
 | 
						|
            history = self.sent_history
 | 
						|
            pos = self.sent_history_pos
 | 
						|
            self.received_history_pos = len(self.received_history)
 | 
						|
        else:
 | 
						|
            history = self.received_history
 | 
						|
            pos = self.received_history_pos
 | 
						|
            self.sent_history_pos = len(self.sent_history)
 | 
						|
        size = len(history)
 | 
						|
        if self.orig_msg is None:
 | 
						|
            # user was typing something and then went into history, so save
 | 
						|
            # whatever is already typed
 | 
						|
            start_iter = msg_buf.get_start_iter()
 | 
						|
            end_iter = msg_buf.get_end_iter()
 | 
						|
            self.orig_msg = msg_buf.get_text(start_iter, end_iter, False)
 | 
						|
        if pos == size and size > 0 and direction == 'up' and \
 | 
						|
        msg_type == 'sent' and not self.correcting and (not \
 | 
						|
        history[pos - 1].startswith('/') or history[pos - 1].startswith('/me')):
 | 
						|
            self.correcting = True
 | 
						|
            gtkgui_helpers.add_css_class(
 | 
						|
                self.msg_textview, 'msgcorrectingcolor')
 | 
						|
            message = history[pos - 1]
 | 
						|
            msg_buf.set_text(message)
 | 
						|
            return
 | 
						|
        if self.correcting:
 | 
						|
            # We were previously correcting
 | 
						|
            gtkgui_helpers.remove_css_class(
 | 
						|
                self.msg_textview, 'msgcorrectingcolor')
 | 
						|
        self.correcting = False
 | 
						|
        pos += -1 if direction == 'up' else +1
 | 
						|
        if pos == -1:
 | 
						|
            return
 | 
						|
        if pos >= size:
 | 
						|
            pos = size
 | 
						|
            message = self.orig_msg
 | 
						|
            self.orig_msg = None
 | 
						|
        else:
 | 
						|
            message = history[pos]
 | 
						|
        if msg_type == 'sent':
 | 
						|
            self.sent_history_pos = pos
 | 
						|
        else:
 | 
						|
            self.received_history_pos = pos
 | 
						|
            if self.orig_msg is not None:
 | 
						|
                message = '> %s\n' % message.replace('\n', '\n> ')
 | 
						|
        msg_buf.set_text(message)
 | 
						|
 | 
						|
    def widget_set_visible(self, widget, state):
 | 
						|
        """
 | 
						|
        Show or hide a widget
 | 
						|
        """
 | 
						|
        # make the last message visible, when changing to "full view"
 | 
						|
        if not state:
 | 
						|
            self.scroll_to_end()
 | 
						|
 | 
						|
        widget.set_no_show_all(state)
 | 
						|
        if state:
 | 
						|
            widget.hide()
 | 
						|
        else:
 | 
						|
            widget.show_all()
 | 
						|
 | 
						|
    def got_connected(self):
 | 
						|
        self.msg_textview.set_sensitive(True)
 | 
						|
        self.msg_textview.set_editable(True)
 | 
						|
        self.update_toolbar()
 | 
						|
 | 
						|
    def got_disconnected(self):
 | 
						|
        self.msg_textview.set_sensitive(False)
 | 
						|
        self.msg_textview.set_editable(False)
 | 
						|
        self.conv_textview.tv.grab_focus()
 | 
						|
 | 
						|
        self.no_autonegotiation = False
 | 
						|
        self.update_toolbar()
 | 
						|
 | 
						|
 | 
						|
class ScrolledWindow(Gtk.ScrolledWindow):
 | 
						|
    def __init__(self, *args, **kwargs):
 | 
						|
        Gtk.ScrolledWindow.__init__(self, *args, **kwargs)
 | 
						|
 | 
						|
        self.set_overlay_scrolling(False)
 | 
						|
        self.set_max_content_height(100)
 | 
						|
        self.set_propagate_natural_height(True)
 | 
						|
        self.get_style_context().add_class('scrolled-no-border')
 | 
						|
        self.get_style_context().add_class('no-scroll-indicator')
 | 
						|
        self.get_style_context().add_class('scrollbar-style')
 | 
						|
        self.set_shadow_type(Gtk.ShadowType.IN)
 | 
						|
 | 
						|
    def do_get_preferred_height(self):
 | 
						|
        min_height, natural_height = Gtk.ScrolledWindow.do_get_preferred_height(self)
 | 
						|
        # Gtk Bug: If policy is set to Automatic, the ScrolledWindow
 | 
						|
        # has a min size of around 46-82 depending on the System. Because
 | 
						|
        # we want it smaller, we set policy NEVER if the height is < 90
 | 
						|
        # so the ScrolledWindow will shrink to around 26 (1 line height).
 | 
						|
        # Once it gets over 90 its no problem to restore the policy.
 | 
						|
        if natural_height < 90:
 | 
						|
            GLib.idle_add(self.set_policy,
 | 
						|
                          Gtk.PolicyType.AUTOMATIC,
 | 
						|
                          Gtk.PolicyType.NEVER)
 | 
						|
        else:
 | 
						|
            GLib.idle_add(self.set_policy,
 | 
						|
                          Gtk.PolicyType.AUTOMATIC,
 | 
						|
                          Gtk.PolicyType.AUTOMATIC)
 | 
						|
 | 
						|
        return min_height, natural_height
 |