diff --git a/src/chat_control.py b/src/chat_control.py
index bf1680469..d399043a6 100644
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -33,25 +33,17 @@ from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GdkPixbuf
from gi.repository import Pango
-from gi.repository import GObject
from gi.repository import GLib
import gtkgui_helpers
import gui_menu_builder
import message_control
import dialogs
-import history_window
-import notify
-import re
-from common import events
from common import gajim
from common import helpers
from common import exceptions
from common import ged
from common import i18n
-from message_control import MessageControl
-from conversation_textview import ConversationTextview
-from message_textview import MessageTextView
from common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
from common.contacts import GC_Contact
from common.logger import constants
@@ -63,1263 +55,15 @@ from nbxmpp.protocol import NS_CHATSTATES
from common.connection_handlers_events import MessageOutgoingEvent
from common.exceptions import GajimGeneralException
-from command_system.implementation.middleware import ChatCommandProcessor
-from command_system.implementation.middleware import CommandTools
from command_system.implementation.hosts import ChatCommands
-# Here we load the module with the standard commands, so they are being detected
-# and dispatched.
-from command_system.implementation.standard import StandardChatCommands
-from command_system.implementation.execute import Execute, Show
-
try:
import gtkspell
HAS_GTK_SPELL = True
except (ImportError, ValueError):
HAS_GTK_SPELL = False
-from common import dbus_support
-if dbus_support.supported:
- import dbus
- import remote_control
-
-################################################################################
-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
-
- def make_href(self, match):
- url_color = gajim.config.get('urlmsgcolor')
- url = match.group()
- if not '://' in url:
- url = 'http://' + url
- return '%s' % (url,
- url_color, match.group())
-
- def get_font_attrs(self):
- """
- Get pango font attributes for banner from theme settings
- """
- theme = gajim.config.get('roster_theme')
- bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
- bannerfontattrs = gajim.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(gajim.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()
- gajim.plugin_manager.gui_extension_point('chat_control_base_draw_banner',
- self)
-
- def update_toolbar(self):
- """
- update state of buttons in toolbar
- """
- self._update_toolbar()
- gajim.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._paint_banner()
- 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 _nec_ping_sent(self, obj):
- if self.contact != obj.contact:
- return
- self.print_conversation(_('Ping?'), 'status')
-
- def _nec_ping_error(self, obj):
- if self.contact != obj.contact:
- return
- self.print_conversation(_('Error.'), 'status')
-
- 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 gajim.connections[self.account].seclabel_supported:
- gajim.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 = gajim.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 = gajim.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)
-
- widget = self.xml.get_object('history_button')
- # set document-open-recent icon for history button
- if gtkgui_helpers.gtk_icon_theme.has_icon('document-open-recent'):
- img = self.xml.get_object('history_image')
- img.set_from_icon_name('document-open-recent', Gtk.IconSize.MENU)
-
- id_ = widget.connect('clicked', self._on_history_menuitem_activate)
- self.handlers[id_] = widget
-
- # when/if we do XHTML we will put formatting buttons back
- widget = self.xml.get_object('emoticons_button')
- widget.set_sensitive(False)
- id_ = widget.connect('clicked', self.on_emoticons_button_clicked)
- self.handlers[id_] = widget
-
- # 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')
- 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('value-changed',
- self.on_conversation_vadjustment_value_changed)
- self.handlers[id_] = widget
- id_ = widget.connect('changed',
- self.on_conversation_vadjustment_changed)
- self.handlers[id_] = widget
- self.scroll_to_end_id = None
- self.was_at_the_end = True
- self.correcting = False
- self.last_sent_msg = None
- self.last_received_txt = {} # one per name
- self.last_received_id = {} # one per name
-
- # add MessageTextView to UI and connect signals
- self.msg_scrolledwindow = self.xml.get_object('message_scrolledwindow')
- self.msg_textview = MessageTextView()
- self.msg_scrolledwindow.add(self.msg_textview)
- 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('configure-event',
- self.on_configure_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)
-
- self.update_font()
-
- # Hook up send button
- widget = self.xml.get_object('send_button')
- id_ = widget.connect('clicked', self._on_send_button_clicked)
- widget.set_sensitive(False)
- self.handlers[id_] = widget
-
- # 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
-
- # Emoticons menu
- # set image no matter if user wants at this time emoticons or not
- # (so toggle works ok)
- img = self.xml.get_object('emoticons_button_image')
- img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
- 'smile.png'))
- self.toggle_emoticons()
-
- # Attach speller
- if gajim.config.get('use_speller') and HAS_GTK_SPELL:
- self.set_speller()
- self.conv_textview.tv.show()
- self._paint_banner()
-
- # For XEP-0172
- self.user_nick = None
-
- self.smooth = True
-
- self.command_hits = []
- self.last_key_tabs = False
-
- # PluginSystem: adding GUI extension point for ChatControlBase
- # instance object (also subclasses, eg. ChatControl or GroupchatControl)
- gajim.plugin_manager.gui_extension_point('chat_control_base', self)
-
- gajim.ged.register_event_handler('our-show', ged.GUI1,
- self._nec_our_status)
- gajim.ged.register_event_handler('ping-sent', ged.GUI1,
- self._nec_ping_sent)
- gajim.ged.register_event_handler('ping-reply', ged.GUI1,
- self._nec_ping_reply)
- gajim.ged.register_event_handler('ping-error', ged.GUI1,
- self._nec_ping_error)
-
- # This is bascially a very nasty hack to surpass the inability
- # to properly use the super, because of the old code.
- CommandTools.__init__(self)
-
- def set_speller(self):
- # now set the one the user selected
- per_type = 'contacts'
- if self.type_id == message_control.TYPE_GC:
- per_type = 'rooms'
- lang = gajim.config.get_per(per_type, self.contact.jid,
- 'speller_language')
- if not lang:
- # use the default one
- lang = gajim.config.get('speller_language')
- if not lang:
- lang = gajim.LANG
- if lang:
- try:
- self.spell = gtkspell.Spell(self.msg_textview, lang)
- self.msg_textview.lang = lang
- self.spell.connect('language_changed', self.on_language_changed)
- except (GObject.GError, RuntimeError, TypeError, OSError):
- dialogs.AspellDictError(lang)
-
- def on_language_changed(self, spell, lang):
- per_type = 'contacts'
- if self.type_id == message_control.TYPE_GC:
- per_type = 'rooms'
- if not gajim.config.get_per(per_type, self.contact.jid):
- gajim.config.add_per(per_type, self.contact.jid)
- gajim.config.set_per(per_type, self.contact.jid, 'speller_language',
- lang)
- self.msg_textview.lang = lang
-
- def on_banner_label_populate_popup(self, label, menu):
- """
- Override the default context menu and add our own menutiems
- """
- 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()
- # PluginSystem: removing GUI extension points connected with ChatControlBase
- # instance object
- gajim.plugin_manager.remove_gui_extension_point('chat_control_base',
- self)
- gajim.plugin_manager.remove_gui_extension_point(
- 'chat_control_base_draw_banner', self)
- gajim.plugin_manager.remove_gui_extension_point('print_special_text',
- self)
- gajim.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):
- 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 _on_send_button_clicked(self, widget):
- """
- When send button is pressed: send the current message
- """
- message_buffer = self.msg_textview.get_buffer()
- start_iter = message_buffer.get_start_iter()
- end_iter = message_buffer.get_end_iter()
- message = message_buffer.get_text(start_iter, end_iter, False)
- xhtml = self.msg_textview.get_xhtml()
-
- # send the message
- self.send_message(message, xhtml=xhtml)
-
- def _paint_banner(self):
- """
- Repaint banner with theme color
- """
- theme = gajim.config.get('roster_theme')
- bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
- textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
- # the backgrounds are colored by using an eventbox by
- # setting the bg color of the eventbox and the fg of the name_label
- banner_eventbox = self.xml.get_object('banner_eventbox')
- banner_name_label = self.xml.get_object('banner_name_label')
- self.disconnect_style_event(banner_name_label)
- self.disconnect_style_event(self.banner_status_label)
- if bgcolor:
- color = Gdk.RGBA()
- Gdk.RGBA.parse(color, bgcolor)
- banner_eventbox.override_background_color(Gtk.StateType.NORMAL,
- color)
- default_bg = False
- else:
- default_bg = True
- if textcolor:
- color = Gdk.RGBA()
- Gdk.RGBA.parse(color, textcolor)
- banner_name_label.override_color(Gtk.StateType.NORMAL,
- color)
- self.banner_status_label.override_color(
- Gtk.StateType.NORMAL, color)
- default_fg = False
- else:
- default_fg = True
- if default_bg or default_fg:
- self._on_style_set_event(banner_name_label, None, default_fg,
- default_bg)
- if self.banner_status_label.get_realized():
- # Widget is realized
- self._on_style_set_event(self.banner_status_label, None, default_fg,
- default_bg)
-
- def disconnect_style_event(self, widget):
- # Try to find the event_id
- for id_ in self.handlers.keys():
- if self.handlers[id_] == widget:
- widget.disconnect(id_)
- del self.handlers[id_]
- break
-
- def connect_style_event(self, widget, set_fg=False, set_bg=False):
- self.disconnect_style_event(widget)
- id_ = widget.connect('style-set', self._on_style_set_event, set_fg,
- set_bg)
- self.handlers[id_] = widget
-
- def _on_style_set_event(self, widget, style, *opts):
- """
- Set style of widget from style class *.Frame.Eventbox
- opts[0] == True -> set fg color
- opts[1] == True -> set bg color
- """
- banner_eventbox = self.xml.get_object('banner_eventbox')
- self.disconnect_style_event(widget)
- context = widget.get_style_context()
- if opts[1]:
- bg_color = context.get_background_color(Gtk.StateFlags.SELECTED)
- banner_eventbox.override_background_color(Gtk.StateType.NORMAL, bg_color)
- if opts[0]:
- fg_color = context.get_color(Gtk.StateFlags.SELECTED)
- widget.override_color(Gtk.StateType.NORMAL, fg_color)
- self.connect_style_event(widget, opts[0], opts[1])
-
- 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)) or (
- event.get_state() & Gdk.ModifierType.SHIFT_MASK and \
- event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up)):
- return False
- self.parent_win.notebook.event(event)
- return True
-
- def show_emoticons_menu(self):
- if not gajim.config.get('emoticons_theme'):
- return
- gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
- gajim.interface.emoticons_menu.popup(None, None, None, None, 1, 0)
-
- 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 hasnt,
- # we save the buffer into the undo list. But be carefull we're not
- # pressiong 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
- if event_state & Gdk.ModifierType.SHIFT_MASK:
- return True
- message_textview = widget
- message_buffer = message_textview.get_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 gajim.config.get('send_on_ctrl_enter'):
- if event_state & Gdk.ModifierType.CONTROL_MASK: # CTRL + ENTER
- send_message = True
- else:
- end_iter = message_buffer.get_end_iter()
- message_buffer.insert_at_cursor('\n')
- send_message = False
-
- else: # send on Enter, do newline on Ctrl Enter
- if event_state & Gdk.ModifierType.CONTROL_MASK: # Ctrl + ENTER
- end_iter = message_buffer.get_end_iter()
- message_buffer.insert_at_cursor('\n')
- send_message = False
- else: # ENTER
- send_message = True
-
- if gajim.connections[self.account].connected < 2 and send_message:
- # we are not connected
- dialogs.ErrorDialog(_('A connection is not available'),
- _('Your message can not be sent until you are connected.'))
- send_message = False
-
- if send_message:
- self.send_message(message, xhtml=xhtml) # send the message
- 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 get_seclabel(self):
- label = None
- if self.seclabel_combo is not None:
- idx = self.seclabel_combo.get_active()
- if idx != -1:
- cat = gajim.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,
- msg_id=None, resource=None, xhtml=None, callback=None, callback_args=[],
- 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
-
- label = self.get_seclabel()
-
- def _cb(obj, msg, cb, *cb_args):
- self.last_sent_msg = msg
- if cb:
- cb(obj, msg, *cb_args)
-
- if self.correcting and self.last_sent_msg:
- correction_msg = self.last_sent_msg
- else:
- correction_msg = None
-
- gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
- account=self.account, jid=self.contact.jid, message=message,
- keyID=keyID, type_=type_, chatstate=chatstate, msg_id=msg_id,
- resource=resource, user_nick=self.user_nick, xhtml=xhtml,
- label=label, callback=_cb, callback_args=[callback] + callback_args,
- control=self, attention=attention, correction_msg=correction_msg, automatic_message=False))
-
- # 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 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 indefinately
- max_size = gajim.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=[], other_tags_for_time=[], other_tags_for_text=[],
- 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={}):
- """
- 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.was_at_the_end or kind == 'outgoing':
- end = True
-
- 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)
-
- if xep0184_id is not None:
- textview.show_xep0184_warning(xep0184_id)
-
- if not count_as_new:
- return
- if kind in ('incoming', 'incoming_queue', 'outgoing'):
- self.last_received_txt[name] = text
- if correct_id:
- self.last_received_id[name] = correct_id[0]
- if kind == 'incoming':
- if not self.type_id == message_control.TYPE_GC or \
- gajim.config.get('notify_on_all_muc_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
- gajim.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 gajim.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, self.session)
- show_in_systray = notify.get_show_in_systray(event,
- self.account, self.contact, event_type.type_)
-
- event = event_type(text, subject, self, msg_log_id,
- show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- gajim.events.add_event(self.account, full_jid, event)
- # We need to redraw contact if we show in roster
- if show_in_roster:
- gajim.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 and make sure emoticons_menu is always there
- when needed
- """
- emoticons_button = self.xml.get_object('emoticons_button')
- if gajim.config.get('emoticons_theme'):
- emoticons_button.show()
- emoticons_button.set_no_show_all(False)
- else:
- emoticons_button.hide()
- emoticons_button.set_no_show_all(True)
-
- def append_emoticon(self, str_):
- buffer_ = self.msg_textview.get_buffer()
- if buffer_.get_char_count():
- buffer_.insert_at_cursor(' %s ' % str_)
- else: # we are the beginning of buffer
- buffer_.insert_at_cursor('%s ' % str_)
- self.msg_textview.grab_focus()
-
- def on_emoticons_button_clicked(self, widget):
- """
- Popup emoticons menu
- """
- gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
- gajim.interface.popup_emoticons_under_button(widget, self.parent_win)
-
- 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 on_actions_button_clicked(self, widget):
- """
- Popup action menu
- """
- menu = self.prepare_context_menu(hide_buttonbar_items=True)
- menu.show_all()
- menu.attach_to_widget(widget, None)
- gtkgui_helpers.popup_emoticons_under_button(menu, widget,
- self.parent_win)
-
- def update_font(self):
- font = Pango.FontDescription(gajim.config.get('conversation_font'))
- self.conv_textview.tv.override_font(font)
- self.msg_textview.override_font(font)
-
- 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 gajim.interface.instances:
- gajim.interface.instances['logs'].window.present()
- gajim.interface.instances['logs'].open_history(jid, self.account)
- else:
- gajim.interface.instances['logs'] = \
- history_window.HistoryWindow(jid, self.account)
-
- def _on_send_file(self, gc_contact=None):
- """
- gc_contact can be set when we are in a groupchat control
- """
- def _on_ok(c):
- gajim.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 = gajim.interface.msg_win_mgr.get_gc_control(
- gc_contact.room_jid, self.account)
- self_contact = gajim.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 = gajim.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, he/she will know your '
- 'real Jabber ID.') % 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 grouchat is minimized, unparent the tab, put it in roster etc
- """
- old_value = True
- non_minimized_gc = gajim.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:
- gajim.config.set_per('accounts', self.account, 'non_minimized_gc',
- ' '.join(non_minimized_gc))
-
- def set_control_active(self, state):
- if state:
- jid = self.contact.jid
- if self.was_at_the_end:
- # 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 gajim.events.remove_events(self.account, self.get_full_jid(),
- types=type_):
- # There were events to remove
- self.redraw_after_event_removed(jid)
-
- def bring_scroll_to_end(self, textview, diff_y=0):
- """
- Scroll to the end of textview if end is not visible
- """
- if self.scroll_to_end_id:
- # a scroll is already planned
- return
- buffer_ = textview.get_buffer()
- end_iter = buffer_.get_end_iter()
- end_rect = textview.get_iter_location(end_iter)
- visible_rect = textview.get_visible_rect()
- # scroll only if expected end is not visible
- if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
- self.scroll_to_end_id = GLib.idle_add(self.scroll_to_end_iter,
- textview)
-
- def scroll_to_end_iter(self, textview):
- buffer_ = textview.get_buffer()
- end_iter = buffer_.get_end_iter()
- textview.scroll_to_iter(end_iter, 0, False, 1, 1)
- self.scroll_to_end_id = None
- return False
-
- def on_configure_event(self, msg_textview, event):
- """
- When message_textview changes its size: if the new height will enlarge
- the window, enable the scrollbar automatic policy. Also enable scrollbar
- automatic policy for horizontal scrollbar if message we have in
- message_textview is too big
- """
- if msg_textview.get_window() is None:
- return
-
- min_height = self.conv_scrolledwindow.get_property('height-request')
- conversation_height = self.conv_textview.tv.get_window().get_size()[1]
- message_height = msg_textview.get_window().get_size()[1]
- message_width = msg_textview.get_window().get_size()[0]
- # new tab is not exposed yet
- if conversation_height < 2:
- return
-
- if conversation_height < min_height:
- min_height = conversation_height
-
- # we don't want to always resize in height the message_textview
- # so we have minimum on conversation_textview's scrolled window
- # but we also want to avoid window resizing so if we reach that
- # minimum for conversation_textview and maximum for message_textview
- # we set to automatic the scrollbar policy
- diff_y = message_height - event.height
- if diff_y != 0:
- if conversation_height + diff_y < min_height:
- if message_height + conversation_height - min_height > min_height:
- policy = self.msg_scrolledwindow.get_property(
- 'vscrollbar-policy')
- if policy != Gtk.PolicyType.AUTOMATIC:
- self.msg_scrolledwindow.set_property('vscrollbar-policy',
- Gtk.PolicyType.AUTOMATIC)
- self.msg_scrolledwindow.set_property('height-request',
- message_height + conversation_height - min_height)
- else:
- self.msg_scrolledwindow.set_property('vscrollbar-policy',
- Gtk.PolicyType.NEVER)
- self.msg_scrolledwindow.set_property('height-request', -1)
-
- self.smooth = True # reinit the flag
- # enable scrollbar automatic policy for horizontal scrollbar
- # if message we have in message_textview is too big
- if event.width > message_width:
- self.msg_scrolledwindow.set_property('hscrollbar-policy',
- Gtk.PolicyType.AUTOMATIC)
- else:
- self.msg_scrolledwindow.set_property('hscrollbar-policy',
- Gtk.PolicyType.NEVER)
-
- return True
-
- def on_conversation_vadjustment_changed(self, adjustment):
- # used to stay at the end of the textview when we shrink conversation
- # textview.
- if self.was_at_the_end:
- if self.conv_textview.at_the_end():
- # we are at the end
- self.conv_textview.bring_scroll_to_end(-18)
- else:
- self.conv_textview.bring_scroll_to_end(-18, use_smooth=False)
- self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value()\
- - adjustment.get_page_size()) < 18
-
- def on_conversation_vadjustment_value_changed(self, adjustment):
- # stop automatic scroll when we manually scroll
- if not self.conv_textview.auto_scrolling:
- self.conv_textview.stop_scrolling()
- self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value() \
- - adjustment.get_page_size()) < 18
- 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(gajim.events.get_events(self.account, jid, types_list)):
- return
- if not self.parent_win:
- return
- if self.conv_textview.at_the_end() and \
- 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 gajim.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 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 = gajim.get_room_and_nick_from_fjid(jid)
- groupchat_control = gajim.interface.msg_win_mgr.get_gc_control(
- room_jid, self.account)
- if room_jid in gajim.interface.minimized_controls[self.account]:
- groupchat_control = \
- gajim.interface.minimized_controls[self.account][room_jid]
- contact = gajim.contacts.get_contact_with_highest_priority(
- self.account, room_jid)
- if contact:
- gajim.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:
- gajim.interface.roster.draw_contact(jid, self.account)
- gajim.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
- context = self.msg_textview.get_style_context()
- state = Gtk.StateFlags.NORMAL
- self.old_message_tv_color = context.get_background_color(state)
- color = Gdk.RGBA()
- Gdk.RGBA.parse(color, 'PaleGoldenrod')
- self.msg_textview.override_background_color(Gtk.StateType.NORMAL,
- color)
- message = history[pos - 1]
- msg_buf.set_text(message)
- return
- if self.correcting:
- # We were previously correcting
- self.msg_textview.override_background_color(Gtk.StateType.NORMAL,
- self.old_message_tv_color)
- 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 lighten_color(self, color):
- p = 0.4
- mask = 0
- color.red = int((color.red * p) + (mask * (1 - p)))
- color.green = int((color.green * p) + (mask * (1 - p)))
- color.blue = int((color.blue * p) + (mask * (1 - p)))
- return color
-
- 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:
- GLib.idle_add(self.conv_textview.scroll_to_end_iter)
-
- widget.set_no_show_all(state)
- if state:
- widget.hide()
- else:
- widget.show_all()
-
- def chat_buttons_set_visible(self, state):
- """
- Toggle chat buttons
- """
- MessageControl.chat_buttons_set_visible(self, state)
- self.widget_set_visible(self.xml.get_object('actions_hbox'), state)
-
- 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()
-
+from chat_control_base import ChatControlBase
################################################################################
class ChatControl(ChatControlBase):
diff --git a/src/chat_control_base.py b/src/chat_control_base.py
new file mode 100644
index 000000000..745c760ad
--- /dev/null
+++ b/src/chat_control_base.py
@@ -0,0 +1,1298 @@
+# -*- coding:utf-8 -*-
+## src/chat_control_base.py
+##
+## Copyright (C) 2006 Dimitur Kirov
+## Copyright (C) 2006-2014 Yann Leboulanger
+## Copyright (C) 2006-2008 Jean-Marie Traissard
+## Nikos Kouremenos
+## Travis Shirk
+## Copyright (C) 2007 Lukas Petrovicky
+## Julien Pivotto
+## Copyright (C) 2007-2008 Brendan Taylor
+## Stephan Erb
+## Copyright (C) 2008 Jonathan Schleifer
+##
+## 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 .
+##
+
+import os
+import time
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import Pango
+from gi.repository import GObject
+from gi.repository import GLib
+import gtkgui_helpers
+import message_control
+import dialogs
+import history_window
+import notify
+import re
+
+from common import events
+from common import gajim
+from common import helpers
+from common import ged
+from message_control import MessageControl
+from conversation_textview import ConversationTextview
+from message_textview import MessageTextView
+from common.contacts import GC_Contact
+from common.connection_handlers_events import MessageOutgoingEvent
+
+from command_system.implementation.middleware import ChatCommandProcessor
+from command_system.implementation.middleware import CommandTools
+
+try:
+ import gtkspell
+ HAS_GTK_SPELL = True
+except (ImportError, ValueError):
+ HAS_GTK_SPELL = False
+
+################################################################################
+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
+
+ def make_href(self, match):
+ url_color = gajim.config.get('urlmsgcolor')
+ url = match.group()
+ if not '://' in url:
+ url = 'http://' + url
+ return '%s' % (url,
+ url_color, match.group())
+
+ def get_font_attrs(self):
+ """
+ Get pango font attributes for banner from theme settings
+ """
+ theme = gajim.config.get('roster_theme')
+ bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
+ bannerfontattrs = gajim.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(gajim.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()
+ gajim.plugin_manager.gui_extension_point('chat_control_base_draw_banner',
+ self)
+
+ def update_toolbar(self):
+ """
+ update state of buttons in toolbar
+ """
+ self._update_toolbar()
+ gajim.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._paint_banner()
+ 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 _nec_ping_sent(self, obj):
+ if self.contact != obj.contact:
+ return
+ self.print_conversation(_('Ping?'), 'status')
+
+ def _nec_ping_error(self, obj):
+ if self.contact != obj.contact:
+ return
+ self.print_conversation(_('Error.'), 'status')
+
+ 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 gajim.connections[self.account].seclabel_supported:
+ gajim.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 = gajim.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 = gajim.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)
+
+ widget = self.xml.get_object('history_button')
+ # set document-open-recent icon for history button
+ if gtkgui_helpers.gtk_icon_theme.has_icon('document-open-recent'):
+ img = self.xml.get_object('history_image')
+ img.set_from_icon_name('document-open-recent', Gtk.IconSize.MENU)
+
+ id_ = widget.connect('clicked', self._on_history_menuitem_activate)
+ self.handlers[id_] = widget
+
+ # when/if we do XHTML we will put formatting buttons back
+ widget = self.xml.get_object('emoticons_button')
+ widget.set_sensitive(False)
+ id_ = widget.connect('clicked', self.on_emoticons_button_clicked)
+ self.handlers[id_] = widget
+
+ # 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')
+ 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('value-changed',
+ self.on_conversation_vadjustment_value_changed)
+ self.handlers[id_] = widget
+ id_ = widget.connect('changed',
+ self.on_conversation_vadjustment_changed)
+ self.handlers[id_] = widget
+ self.scroll_to_end_id = None
+ self.was_at_the_end = True
+ self.correcting = False
+ self.last_sent_msg = None
+ self.last_received_txt = {} # one per name
+ self.last_received_id = {} # one per name
+
+ # add MessageTextView to UI and connect signals
+ self.msg_scrolledwindow = self.xml.get_object('message_scrolledwindow')
+ self.msg_textview = MessageTextView()
+ self.msg_scrolledwindow.add(self.msg_textview)
+ 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('configure-event',
+ self.on_configure_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)
+
+ self.update_font()
+
+ # Hook up send button
+ widget = self.xml.get_object('send_button')
+ id_ = widget.connect('clicked', self._on_send_button_clicked)
+ widget.set_sensitive(False)
+ self.handlers[id_] = widget
+
+ # 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
+
+ # Emoticons menu
+ # set image no matter if user wants at this time emoticons or not
+ # (so toggle works ok)
+ img = self.xml.get_object('emoticons_button_image')
+ img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
+ 'smile.png'))
+ self.toggle_emoticons()
+
+ # Attach speller
+ if gajim.config.get('use_speller') and HAS_GTK_SPELL:
+ self.set_speller()
+ self.conv_textview.tv.show()
+ self._paint_banner()
+
+ # For XEP-0172
+ self.user_nick = None
+
+ self.smooth = True
+
+ self.command_hits = []
+ self.last_key_tabs = False
+
+ # PluginSystem: adding GUI extension point for ChatControlBase
+ # instance object (also subclasses, eg. ChatControl or GroupchatControl)
+ gajim.plugin_manager.gui_extension_point('chat_control_base', self)
+
+ gajim.ged.register_event_handler('our-show', ged.GUI1,
+ self._nec_our_status)
+ gajim.ged.register_event_handler('ping-sent', ged.GUI1,
+ self._nec_ping_sent)
+ gajim.ged.register_event_handler('ping-reply', ged.GUI1,
+ self._nec_ping_reply)
+ gajim.ged.register_event_handler('ping-error', ged.GUI1,
+ self._nec_ping_error)
+
+ # This is bascially a very nasty hack to surpass the inability
+ # to properly use the super, because of the old code.
+ CommandTools.__init__(self)
+
+ def set_speller(self):
+ # now set the one the user selected
+ per_type = 'contacts'
+ if self.type_id == message_control.TYPE_GC:
+ per_type = 'rooms'
+ lang = gajim.config.get_per(per_type, self.contact.jid,
+ 'speller_language')
+ if not lang:
+ # use the default one
+ lang = gajim.config.get('speller_language')
+ if not lang:
+ lang = gajim.LANG
+ if lang:
+ try:
+ self.spell = gtkspell.Spell(self.msg_textview, lang)
+ self.msg_textview.lang = lang
+ self.spell.connect('language_changed', self.on_language_changed)
+ except (GObject.GError, RuntimeError, TypeError, OSError):
+ dialogs.AspellDictError(lang)
+
+ def on_language_changed(self, spell, lang):
+ per_type = 'contacts'
+ if self.type_id == message_control.TYPE_GC:
+ per_type = 'rooms'
+ if not gajim.config.get_per(per_type, self.contact.jid):
+ gajim.config.add_per(per_type, self.contact.jid)
+ gajim.config.set_per(per_type, self.contact.jid, 'speller_language',
+ lang)
+ self.msg_textview.lang = lang
+
+ def on_banner_label_populate_popup(self, label, menu):
+ """
+ Override the default context menu and add our own menutiems
+ """
+ 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()
+ # PluginSystem: removing GUI extension points connected with ChatControlBase
+ # instance object
+ gajim.plugin_manager.remove_gui_extension_point('chat_control_base',
+ self)
+ gajim.plugin_manager.remove_gui_extension_point(
+ 'chat_control_base_draw_banner', self)
+ gajim.plugin_manager.remove_gui_extension_point('print_special_text',
+ self)
+ gajim.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):
+ 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 _on_send_button_clicked(self, widget):
+ """
+ When send button is pressed: send the current message
+ """
+ message_buffer = self.msg_textview.get_buffer()
+ start_iter = message_buffer.get_start_iter()
+ end_iter = message_buffer.get_end_iter()
+ message = message_buffer.get_text(start_iter, end_iter, False)
+ xhtml = self.msg_textview.get_xhtml()
+
+ # send the message
+ self.send_message(message, xhtml=xhtml)
+
+ def _paint_banner(self):
+ """
+ Repaint banner with theme color
+ """
+ theme = gajim.config.get('roster_theme')
+ bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
+ textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
+ # the backgrounds are colored by using an eventbox by
+ # setting the bg color of the eventbox and the fg of the name_label
+ banner_eventbox = self.xml.get_object('banner_eventbox')
+ banner_name_label = self.xml.get_object('banner_name_label')
+ self.disconnect_style_event(banner_name_label)
+ self.disconnect_style_event(self.banner_status_label)
+ if bgcolor:
+ color = Gdk.RGBA()
+ Gdk.RGBA.parse(color, bgcolor)
+ banner_eventbox.override_background_color(Gtk.StateType.NORMAL,
+ color)
+ default_bg = False
+ else:
+ default_bg = True
+ if textcolor:
+ color = Gdk.RGBA()
+ Gdk.RGBA.parse(color, textcolor)
+ banner_name_label.override_color(Gtk.StateType.NORMAL,
+ color)
+ self.banner_status_label.override_color(
+ Gtk.StateType.NORMAL, color)
+ default_fg = False
+ else:
+ default_fg = True
+ if default_bg or default_fg:
+ self._on_style_set_event(banner_name_label, None, default_fg,
+ default_bg)
+ if self.banner_status_label.get_realized():
+ # Widget is realized
+ self._on_style_set_event(self.banner_status_label, None, default_fg,
+ default_bg)
+
+ def disconnect_style_event(self, widget):
+ # Try to find the event_id
+ for id_ in self.handlers.keys():
+ if self.handlers[id_] == widget:
+ widget.disconnect(id_)
+ del self.handlers[id_]
+ break
+
+ def connect_style_event(self, widget, set_fg=False, set_bg=False):
+ self.disconnect_style_event(widget)
+ id_ = widget.connect('style-set', self._on_style_set_event, set_fg,
+ set_bg)
+ self.handlers[id_] = widget
+
+ def _on_style_set_event(self, widget, style, *opts):
+ """
+ Set style of widget from style class *.Frame.Eventbox
+ opts[0] == True -> set fg color
+ opts[1] == True -> set bg color
+ """
+ banner_eventbox = self.xml.get_object('banner_eventbox')
+ self.disconnect_style_event(widget)
+ context = widget.get_style_context()
+ if opts[1]:
+ bg_color = context.get_background_color(Gtk.StateFlags.SELECTED)
+ banner_eventbox.override_background_color(Gtk.StateType.NORMAL, bg_color)
+ if opts[0]:
+ fg_color = context.get_color(Gtk.StateFlags.SELECTED)
+ widget.override_color(Gtk.StateType.NORMAL, fg_color)
+ self.connect_style_event(widget, opts[0], opts[1])
+
+ 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)) or (
+ event.get_state() & Gdk.ModifierType.SHIFT_MASK and \
+ event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up)):
+ return False
+ self.parent_win.notebook.event(event)
+ return True
+
+ def show_emoticons_menu(self):
+ if not gajim.config.get('emoticons_theme'):
+ return
+ gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
+ gajim.interface.emoticons_menu.popup(None, None, None, None, 1, 0)
+
+ 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 hasnt,
+ # we save the buffer into the undo list. But be carefull we're not
+ # pressiong 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
+ if event_state & Gdk.ModifierType.SHIFT_MASK:
+ return True
+ message_textview = widget
+ message_buffer = message_textview.get_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 gajim.config.get('send_on_ctrl_enter'):
+ if event_state & Gdk.ModifierType.CONTROL_MASK: # CTRL + ENTER
+ send_message = True
+ else:
+ end_iter = message_buffer.get_end_iter()
+ message_buffer.insert_at_cursor('\n')
+ send_message = False
+
+ else: # send on Enter, do newline on Ctrl Enter
+ if event_state & Gdk.ModifierType.CONTROL_MASK: # Ctrl + ENTER
+ end_iter = message_buffer.get_end_iter()
+ message_buffer.insert_at_cursor('\n')
+ send_message = False
+ else: # ENTER
+ send_message = True
+
+ if gajim.connections[self.account].connected < 2 and send_message:
+ # we are not connected
+ dialogs.ErrorDialog(_('A connection is not available'),
+ _('Your message can not be sent until you are connected.'))
+ send_message = False
+
+ if send_message:
+ self.send_message(message, xhtml=xhtml) # send the message
+ 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 get_seclabel(self):
+ label = None
+ if self.seclabel_combo is not None:
+ idx = self.seclabel_combo.get_active()
+ if idx != -1:
+ cat = gajim.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,
+ msg_id=None, resource=None, xhtml=None, callback=None, callback_args=[],
+ 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
+
+ label = self.get_seclabel()
+
+ def _cb(obj, msg, cb, *cb_args):
+ self.last_sent_msg = msg
+ if cb:
+ cb(obj, msg, *cb_args)
+
+ if self.correcting and self.last_sent_msg:
+ correction_msg = self.last_sent_msg
+ else:
+ correction_msg = None
+
+ gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
+ account=self.account, jid=self.contact.jid, message=message,
+ keyID=keyID, type_=type_, chatstate=chatstate, msg_id=msg_id,
+ resource=resource, user_nick=self.user_nick, xhtml=xhtml,
+ label=label, callback=_cb, callback_args=[callback] + callback_args,
+ control=self, attention=attention, correction_msg=correction_msg, automatic_message=False))
+
+ # 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 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 indefinately
+ max_size = gajim.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=[], other_tags_for_time=[], other_tags_for_text=[],
+ 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={}):
+ """
+ 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.was_at_the_end or kind == 'outgoing':
+ end = True
+
+ 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)
+
+ if xep0184_id is not None:
+ textview.show_xep0184_warning(xep0184_id)
+
+ if not count_as_new:
+ return
+ if kind in ('incoming', 'incoming_queue', 'outgoing'):
+ self.last_received_txt[name] = text
+ if correct_id:
+ self.last_received_id[name] = correct_id[0]
+ if kind == 'incoming':
+ if not self.type_id == message_control.TYPE_GC or \
+ gajim.config.get('notify_on_all_muc_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
+ gajim.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 gajim.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, self.session)
+ show_in_systray = notify.get_show_in_systray(event,
+ self.account, self.contact, event_type.type_)
+
+ event = event_type(text, subject, self, msg_log_id,
+ show_in_roster=show_in_roster,
+ show_in_systray=show_in_systray)
+ gajim.events.add_event(self.account, full_jid, event)
+ # We need to redraw contact if we show in roster
+ if show_in_roster:
+ gajim.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 and make sure emoticons_menu is always there
+ when needed
+ """
+ emoticons_button = self.xml.get_object('emoticons_button')
+ if gajim.config.get('emoticons_theme'):
+ emoticons_button.show()
+ emoticons_button.set_no_show_all(False)
+ else:
+ emoticons_button.hide()
+ emoticons_button.set_no_show_all(True)
+
+ def append_emoticon(self, str_):
+ buffer_ = self.msg_textview.get_buffer()
+ if buffer_.get_char_count():
+ buffer_.insert_at_cursor(' %s ' % str_)
+ else: # we are the beginning of buffer
+ buffer_.insert_at_cursor('%s ' % str_)
+ self.msg_textview.grab_focus()
+
+ def on_emoticons_button_clicked(self, widget):
+ """
+ Popup emoticons menu
+ """
+ gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
+ gajim.interface.popup_emoticons_under_button(widget, self.parent_win)
+
+ 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 on_actions_button_clicked(self, widget):
+ """
+ Popup action menu
+ """
+ menu = self.prepare_context_menu(hide_buttonbar_items=True)
+ menu.show_all()
+ menu.attach_to_widget(widget, None)
+ gtkgui_helpers.popup_emoticons_under_button(menu, widget,
+ self.parent_win)
+
+ def update_font(self):
+ font = Pango.FontDescription(gajim.config.get('conversation_font'))
+ self.conv_textview.tv.override_font(font)
+ self.msg_textview.override_font(font)
+
+ 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 gajim.interface.instances:
+ gajim.interface.instances['logs'].window.present()
+ gajim.interface.instances['logs'].open_history(jid, self.account)
+ else:
+ gajim.interface.instances['logs'] = \
+ history_window.HistoryWindow(jid, self.account)
+
+ def _on_send_file(self, gc_contact=None):
+ """
+ gc_contact can be set when we are in a groupchat control
+ """
+ def _on_ok(c):
+ gajim.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 = gajim.interface.msg_win_mgr.get_gc_control(
+ gc_contact.room_jid, self.account)
+ self_contact = gajim.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 = gajim.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, he/she will know your '
+ 'real Jabber ID.') % 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 grouchat is minimized, unparent the tab, put it in roster etc
+ """
+ old_value = True
+ non_minimized_gc = gajim.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:
+ gajim.config.set_per('accounts', self.account, 'non_minimized_gc',
+ ' '.join(non_minimized_gc))
+
+ def set_control_active(self, state):
+ if state:
+ jid = self.contact.jid
+ if self.was_at_the_end:
+ # 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 gajim.events.remove_events(self.account, self.get_full_jid(),
+ types=type_):
+ # There were events to remove
+ self.redraw_after_event_removed(jid)
+
+ def bring_scroll_to_end(self, textview, diff_y=0):
+ """
+ Scroll to the end of textview if end is not visible
+ """
+ if self.scroll_to_end_id:
+ # a scroll is already planned
+ return
+ buffer_ = textview.get_buffer()
+ end_iter = buffer_.get_end_iter()
+ end_rect = textview.get_iter_location(end_iter)
+ visible_rect = textview.get_visible_rect()
+ # scroll only if expected end is not visible
+ if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
+ self.scroll_to_end_id = GLib.idle_add(self.scroll_to_end_iter,
+ textview)
+
+ def scroll_to_end_iter(self, textview):
+ buffer_ = textview.get_buffer()
+ end_iter = buffer_.get_end_iter()
+ textview.scroll_to_iter(end_iter, 0, False, 1, 1)
+ self.scroll_to_end_id = None
+ return False
+
+ def on_configure_event(self, msg_textview, event):
+ """
+ When message_textview changes its size: if the new height will enlarge
+ the window, enable the scrollbar automatic policy. Also enable scrollbar
+ automatic policy for horizontal scrollbar if message we have in
+ message_textview is too big
+ """
+ if msg_textview.get_window() is None:
+ return
+
+ min_height = self.conv_scrolledwindow.get_property('height-request')
+ conversation_height = self.conv_textview.tv.get_window().get_size()[1]
+ message_height = msg_textview.get_window().get_size()[1]
+ message_width = msg_textview.get_window().get_size()[0]
+ # new tab is not exposed yet
+ if conversation_height < 2:
+ return
+
+ if conversation_height < min_height:
+ min_height = conversation_height
+
+ # we don't want to always resize in height the message_textview
+ # so we have minimum on conversation_textview's scrolled window
+ # but we also want to avoid window resizing so if we reach that
+ # minimum for conversation_textview and maximum for message_textview
+ # we set to automatic the scrollbar policy
+ diff_y = message_height - event.height
+ if diff_y != 0:
+ if conversation_height + diff_y < min_height:
+ if message_height + conversation_height - min_height > min_height:
+ policy = self.msg_scrolledwindow.get_property(
+ 'vscrollbar-policy')
+ if policy != Gtk.PolicyType.AUTOMATIC:
+ self.msg_scrolledwindow.set_property('vscrollbar-policy',
+ Gtk.PolicyType.AUTOMATIC)
+ self.msg_scrolledwindow.set_property('height-request',
+ message_height + conversation_height - min_height)
+ else:
+ self.msg_scrolledwindow.set_property('vscrollbar-policy',
+ Gtk.PolicyType.NEVER)
+ self.msg_scrolledwindow.set_property('height-request', -1)
+
+ self.smooth = True # reinit the flag
+ # enable scrollbar automatic policy for horizontal scrollbar
+ # if message we have in message_textview is too big
+ if event.width > message_width:
+ self.msg_scrolledwindow.set_property('hscrollbar-policy',
+ Gtk.PolicyType.AUTOMATIC)
+ else:
+ self.msg_scrolledwindow.set_property('hscrollbar-policy',
+ Gtk.PolicyType.NEVER)
+
+ return True
+
+ def on_conversation_vadjustment_changed(self, adjustment):
+ # used to stay at the end of the textview when we shrink conversation
+ # textview.
+ if self.was_at_the_end:
+ if self.conv_textview.at_the_end():
+ # we are at the end
+ self.conv_textview.bring_scroll_to_end(-18)
+ else:
+ self.conv_textview.bring_scroll_to_end(-18, use_smooth=False)
+ self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value()\
+ - adjustment.get_page_size()) < 18
+
+ def on_conversation_vadjustment_value_changed(self, adjustment):
+ # stop automatic scroll when we manually scroll
+ if not self.conv_textview.auto_scrolling:
+ self.conv_textview.stop_scrolling()
+ self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value() \
+ - adjustment.get_page_size()) < 18
+ 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(gajim.events.get_events(self.account, jid, types_list)):
+ return
+ if not self.parent_win:
+ return
+ if self.conv_textview.at_the_end() and \
+ 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 gajim.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 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 = gajim.get_room_and_nick_from_fjid(jid)
+ groupchat_control = gajim.interface.msg_win_mgr.get_gc_control(
+ room_jid, self.account)
+ if room_jid in gajim.interface.minimized_controls[self.account]:
+ groupchat_control = \
+ gajim.interface.minimized_controls[self.account][room_jid]
+ contact = gajim.contacts.get_contact_with_highest_priority(
+ self.account, room_jid)
+ if contact:
+ gajim.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:
+ gajim.interface.roster.draw_contact(jid, self.account)
+ gajim.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
+ context = self.msg_textview.get_style_context()
+ state = Gtk.StateFlags.NORMAL
+ self.old_message_tv_color = context.get_background_color(state)
+ color = Gdk.RGBA()
+ Gdk.RGBA.parse(color, 'PaleGoldenrod')
+ self.msg_textview.override_background_color(Gtk.StateType.NORMAL,
+ color)
+ message = history[pos - 1]
+ msg_buf.set_text(message)
+ return
+ if self.correcting:
+ # We were previously correcting
+ self.msg_textview.override_background_color(Gtk.StateType.NORMAL,
+ self.old_message_tv_color)
+ 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 lighten_color(self, color):
+ p = 0.4
+ mask = 0
+ color.red = int((color.red * p) + (mask * (1 - p)))
+ color.green = int((color.green * p) + (mask * (1 - p)))
+ color.blue = int((color.blue * p) + (mask * (1 - p)))
+ return color
+
+ 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:
+ GLib.idle_add(self.conv_textview.scroll_to_end_iter)
+
+ widget.set_no_show_all(state)
+ if state:
+ widget.hide()
+ else:
+ widget.show_all()
+
+ def chat_buttons_set_visible(self, state):
+ """
+ Toggle chat buttons
+ """
+ MessageControl.chat_buttons_set_visible(self, state)
+ self.widget_set_visible(self.xml.get_object('actions_hbox'), state)
+
+ 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()
diff --git a/src/config.py b/src/config.py
index 53339c4a4..d21029587 100644
--- a/src/config.py
+++ b/src/config.py
@@ -45,7 +45,7 @@ import gtkgui_helpers
import dialogs
import cell_renderer_image
import message_control
-import chat_control
+from chat_control_base import ChatControlBase
import dataforms_widget
import profile_window
@@ -690,7 +690,7 @@ class PreferencesWindow:
def apply_speller(self):
for ctrl in self._get_all_controls():
- if isinstance(ctrl, chat_control.ChatControlBase):
+ if isinstance(ctrl, ChatControlBase):
try:
spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
except (TypeError, RuntimeError, OSError):
@@ -701,7 +701,7 @@ class PreferencesWindow:
def remove_speller(self):
for ctrl in self._get_all_controls():
- if isinstance(ctrl, chat_control.ChatControlBase):
+ if isinstance(ctrl, ChatControlBase):
try:
spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
except (TypeError, RuntimeError):
diff --git a/src/groupchat_control.py b/src/groupchat_control.py
index fa3f445d1..17f5f4f9e 100644
--- a/src/groupchat_control.py
+++ b/src/groupchat_control.py
@@ -54,7 +54,7 @@ from common import ged
from common import i18n
from chat_control import ChatControl
-from chat_control import ChatControlBase
+from chat_control_base import ChatControlBase
from common.exceptions import GajimGeneralException
from command_system.implementation.hosts import PrivateChatCommands
diff --git a/src/gui_interface.py b/src/gui_interface.py
index a1f1068d5..f0570a3e0 100644
--- a/src/gui_interface.py
+++ b/src/gui_interface.py
@@ -61,7 +61,7 @@ import dialogs
import notify
import message_control
-from chat_control import ChatControlBase
+from chat_control_base import ChatControlBase
from chat_control import ChatControl
from groupchat_control import GroupchatControl
from groupchat_control import PrivateChatControl
diff --git a/src/message_window.py b/src/message_window.py
index c20faa18b..1e24fca84 100644
--- a/src/message_window.py
+++ b/src/message_window.py
@@ -38,7 +38,7 @@ import common
import gtkgui_helpers
import message_control
import dialogs
-from chat_control import ChatControlBase
+from chat_control_base import ChatControlBase
from common import gajim