Philipp Hörist pushed to branch master at gajim / gajim
Commits:
-
80221afc
by Emmanuel Gil Peyrot at 2017-01-04T22:57:05+00:00
-
09c82c31
by Philipp Hörist at 2017-01-05T00:05:39+01:00
6 changed files:
- src/chat_control.py
- + src/chat_control_base.py
- src/config.py
- src/groupchat_control.py
- src/gui_interface.py
- src/message_window.py
Changes:
| ... | ... | @@ -33,25 +33,17 @@ from gi.repository import Gtk |
| 33 | 33 |
from gi.repository import Gdk
|
| 34 | 34 |
from gi.repository import GdkPixbuf
|
| 35 | 35 |
from gi.repository import Pango
|
| 36 |
-from gi.repository import GObject
|
|
| 37 | 36 |
from gi.repository import GLib
|
| 38 | 37 |
import gtkgui_helpers
|
| 39 | 38 |
import gui_menu_builder
|
| 40 | 39 |
import message_control
|
| 41 | 40 |
import dialogs
|
| 42 |
-import history_window
|
|
| 43 |
-import notify
|
|
| 44 |
-import re
|
|
| 45 | 41 |
|
| 46 |
-from common import events
|
|
| 47 | 42 |
from common import gajim
|
| 48 | 43 |
from common import helpers
|
| 49 | 44 |
from common import exceptions
|
| 50 | 45 |
from common import ged
|
| 51 | 46 |
from common import i18n
|
| 52 |
-from message_control import MessageControl
|
|
| 53 |
-from conversation_textview import ConversationTextview
|
|
| 54 |
-from message_textview import MessageTextView
|
|
| 55 | 47 |
from common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
|
| 56 | 48 |
from common.contacts import GC_Contact
|
| 57 | 49 |
from common.logger import constants
|
| ... | ... | @@ -63,1263 +55,15 @@ from nbxmpp.protocol import NS_CHATSTATES |
| 63 | 55 |
from common.connection_handlers_events import MessageOutgoingEvent
|
| 64 | 56 |
from common.exceptions import GajimGeneralException
|
| 65 | 57 |
|
| 66 |
-from command_system.implementation.middleware import ChatCommandProcessor
|
|
| 67 |
-from command_system.implementation.middleware import CommandTools
|
|
| 68 | 58 |
from command_system.implementation.hosts import ChatCommands
|
| 69 | 59 |
|
| 70 |
-# Here we load the module with the standard commands, so they are being detected
|
|
| 71 |
-# and dispatched.
|
|
| 72 |
-from command_system.implementation.standard import StandardChatCommands
|
|
| 73 |
-from command_system.implementation.execute import Execute, Show
|
|
| 74 |
- |
|
| 75 | 60 |
try:
|
| 76 | 61 |
import gtkspell
|
| 77 | 62 |
HAS_GTK_SPELL = True
|
| 78 | 63 |
except (ImportError, ValueError):
|
| 79 | 64 |
HAS_GTK_SPELL = False
|
| 80 | 65 |
|
| 81 |
-from common import dbus_support
|
|
| 82 |
-if dbus_support.supported:
|
|
| 83 |
- import dbus
|
|
| 84 |
- import remote_control
|
|
| 85 |
- |
|
| 86 |
-################################################################################
|
|
| 87 |
-class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|
| 88 |
- """
|
|
| 89 |
- A base class containing a banner, ConversationTextview, MessageTextView
|
|
| 90 |
- """
|
|
| 91 |
- |
|
| 92 |
- keymap = Gdk.Keymap.get_default()
|
|
| 93 |
- try:
|
|
| 94 |
- keycode_c = keymap.get_entries_for_keyval(Gdk.KEY_c)[1][0].keycode
|
|
| 95 |
- except TypeError:
|
|
| 96 |
- keycode_c = 54
|
|
| 97 |
- try:
|
|
| 98 |
- keycode_ins = keymap.get_entries_for_keyval(Gdk.KEY_Insert)[1][0].keycode
|
|
| 99 |
- except TypeError:
|
|
| 100 |
- keycode_ins = 118
|
|
| 101 |
- |
|
| 102 |
- def make_href(self, match):
|
|
| 103 |
- url_color = gajim.config.get('urlmsgcolor')
|
|
| 104 |
- url = match.group()
|
|
| 105 |
- if not '://' in url:
|
|
| 106 |
- url = 'http://' + url
|
|
| 107 |
- return '<a href="%s"><span color="%s">%s</span></a>' % (url,
|
|
| 108 |
- url_color, match.group())
|
|
| 109 |
- |
|
| 110 |
- def get_font_attrs(self):
|
|
| 111 |
- """
|
|
| 112 |
- Get pango font attributes for banner from theme settings
|
|
| 113 |
- """
|
|
| 114 |
- theme = gajim.config.get('roster_theme')
|
|
| 115 |
- bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
|
|
| 116 |
- bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs')
|
|
| 117 |
- |
|
| 118 |
- if bannerfont:
|
|
| 119 |
- font = Pango.FontDescription(bannerfont)
|
|
| 120 |
- else:
|
|
| 121 |
- font = Pango.FontDescription('Normal')
|
|
| 122 |
- if bannerfontattrs:
|
|
| 123 |
- # B attribute is set by default
|
|
| 124 |
- if 'B' in bannerfontattrs:
|
|
| 125 |
- font.set_weight(Pango.Weight.HEAVY)
|
|
| 126 |
- if 'I' in bannerfontattrs:
|
|
| 127 |
- font.set_style(Pango.Style.ITALIC)
|
|
| 128 |
- |
|
| 129 |
- font_attrs = 'font_desc="%s"' % font.to_string()
|
|
| 130 |
- |
|
| 131 |
- # in case there is no font specified we use x-large font size
|
|
| 132 |
- if font.get_size() == 0:
|
|
| 133 |
- font_attrs = '%s size="x-large"' % font_attrs
|
|
| 134 |
- font.set_weight(Pango.Weight.NORMAL)
|
|
| 135 |
- font_attrs_small = 'font_desc="%s" size="small"' % font.to_string()
|
|
| 136 |
- return (font_attrs, font_attrs_small)
|
|
| 137 |
- |
|
| 138 |
- def get_nb_unread(self):
|
|
| 139 |
- jid = self.contact.jid
|
|
| 140 |
- if self.resource:
|
|
| 141 |
- jid += '/' + self.resource
|
|
| 142 |
- type_ = self.type_id
|
|
| 143 |
- return len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
|
|
| 144 |
- type_]))
|
|
| 145 |
- |
|
| 146 |
- def draw_banner(self):
|
|
| 147 |
- """
|
|
| 148 |
- Draw the fat line at the top of the window that houses the icon, jid, etc
|
|
| 149 |
- |
|
| 150 |
- Derived types MAY implement this.
|
|
| 151 |
- """
|
|
| 152 |
- self.draw_banner_text()
|
|
| 153 |
- self._update_banner_state_image()
|
|
| 154 |
- gajim.plugin_manager.gui_extension_point('chat_control_base_draw_banner',
|
|
| 155 |
- self)
|
|
| 156 |
- |
|
| 157 |
- def update_toolbar(self):
|
|
| 158 |
- """
|
|
| 159 |
- update state of buttons in toolbar
|
|
| 160 |
- """
|
|
| 161 |
- self._update_toolbar()
|
|
| 162 |
- gajim.plugin_manager.gui_extension_point(
|
|
| 163 |
- 'chat_control_base_update_toolbar', self)
|
|
| 164 |
- |
|
| 165 |
- def draw_banner_text(self):
|
|
| 166 |
- """
|
|
| 167 |
- Derived types SHOULD implement this
|
|
| 168 |
- """
|
|
| 169 |
- pass
|
|
| 170 |
- |
|
| 171 |
- def update_ui(self):
|
|
| 172 |
- """
|
|
| 173 |
- Derived types SHOULD implement this
|
|
| 174 |
- """
|
|
| 175 |
- self.draw_banner()
|
|
| 176 |
- |
|
| 177 |
- def repaint_themed_widgets(self):
|
|
| 178 |
- """
|
|
| 179 |
- Derived types MAY implement this
|
|
| 180 |
- """
|
|
| 181 |
- self._paint_banner()
|
|
| 182 |
- self.draw_banner()
|
|
| 183 |
- |
|
| 184 |
- def _update_banner_state_image(self):
|
|
| 185 |
- """
|
|
| 186 |
- Derived types MAY implement this
|
|
| 187 |
- """
|
|
| 188 |
- pass
|
|
| 189 |
- |
|
| 190 |
- def _update_toolbar(self):
|
|
| 191 |
- """
|
|
| 192 |
- Derived types MAY implement this
|
|
| 193 |
- """
|
|
| 194 |
- pass
|
|
| 195 |
- |
|
| 196 |
- def _nec_our_status(self, obj):
|
|
| 197 |
- if self.account != obj.conn.name:
|
|
| 198 |
- return
|
|
| 199 |
- if obj.show == 'offline' or (obj.show == 'invisible' and \
|
|
| 200 |
- obj.conn.is_zeroconf):
|
|
| 201 |
- self.got_disconnected()
|
|
| 202 |
- else:
|
|
| 203 |
- # Other code rejoins all GCs, so we don't do it here
|
|
| 204 |
- if not self.type_id == message_control.TYPE_GC:
|
|
| 205 |
- self.got_connected()
|
|
| 206 |
- if self.parent_win:
|
|
| 207 |
- self.parent_win.redraw_tab(self)
|
|
| 208 |
- |
|
| 209 |
- def _nec_ping_sent(self, obj):
|
|
| 210 |
- if self.contact != obj.contact:
|
|
| 211 |
- return
|
|
| 212 |
- self.print_conversation(_('Ping?'), 'status')
|
|
| 213 |
- |
|
| 214 |
- def _nec_ping_error(self, obj):
|
|
| 215 |
- if self.contact != obj.contact:
|
|
| 216 |
- return
|
|
| 217 |
- self.print_conversation(_('Error.'), 'status')
|
|
| 218 |
- |
|
| 219 |
- def status_url_clicked(self, widget, url):
|
|
| 220 |
- helpers.launch_browser_mailer('url', url)
|
|
| 221 |
- |
|
| 222 |
- def setup_seclabel(self, combo):
|
|
| 223 |
- self.seclabel_combo = combo
|
|
| 224 |
- self.seclabel_combo.hide()
|
|
| 225 |
- self.seclabel_combo.set_no_show_all(True)
|
|
| 226 |
- lb = Gtk.ListStore(str)
|
|
| 227 |
- self.seclabel_combo.set_model(lb)
|
|
| 228 |
- cell = Gtk.CellRendererText()
|
|
| 229 |
- cell.set_property('xpad', 5) # padding for status text
|
|
| 230 |
- self.seclabel_combo.pack_start(cell, True)
|
|
| 231 |
- # text to show is in in first column of liststore
|
|
| 232 |
- self.seclabel_combo.add_attribute(cell, 'text', 0)
|
|
| 233 |
- if gajim.connections[self.account].seclabel_supported:
|
|
| 234 |
- gajim.connections[self.account].seclabel_catalogue(self.contact.jid, self.on_seclabels_ready)
|
|
| 235 |
- |
|
| 236 |
- def on_seclabels_ready(self):
|
|
| 237 |
- lb = self.seclabel_combo.get_model()
|
|
| 238 |
- lb.clear()
|
|
| 239 |
- i = 0
|
|
| 240 |
- sel = 0
|
|
| 241 |
- catalogue = gajim.connections[self.account].seclabel_catalogues[
|
|
| 242 |
- self.contact.jid]
|
|
| 243 |
- for label in catalogue[2]:
|
|
| 244 |
- lb.append([label])
|
|
| 245 |
- if label == catalogue[3]:
|
|
| 246 |
- sel = i
|
|
| 247 |
- i += 1
|
|
| 248 |
- self.seclabel_combo.set_active(sel)
|
|
| 249 |
- self.seclabel_combo.set_no_show_all(False)
|
|
| 250 |
- self.seclabel_combo.show_all()
|
|
| 251 |
- |
|
| 252 |
- def __init__(self, type_id, parent_win, widget_name, contact, acct,
|
|
| 253 |
- resource=None):
|
|
| 254 |
- # Undo needs this variable to know if space has been pressed.
|
|
| 255 |
- # Initialize it to True so empty textview is saved in undo list
|
|
| 256 |
- self.space_pressed = True
|
|
| 257 |
- |
|
| 258 |
- if resource is None:
|
|
| 259 |
- # We very likely got a contact with a random resource.
|
|
| 260 |
- # This is bad, we need the highest for caps etc.
|
|
| 261 |
- c = gajim.contacts.get_contact_with_highest_priority(acct,
|
|
| 262 |
- contact.jid)
|
|
| 263 |
- if c and not isinstance(c, GC_Contact):
|
|
| 264 |
- contact = c
|
|
| 265 |
- |
|
| 266 |
- MessageControl.__init__(self, type_id, parent_win, widget_name,
|
|
| 267 |
- contact, acct, resource=resource)
|
|
| 268 |
- |
|
| 269 |
- widget = self.xml.get_object('history_button')
|
|
| 270 |
- # set document-open-recent icon for history button
|
|
| 271 |
- if gtkgui_helpers.gtk_icon_theme.has_icon('document-open-recent'):
|
|
| 272 |
- img = self.xml.get_object('history_image')
|
|
| 273 |
- img.set_from_icon_name('document-open-recent', Gtk.IconSize.MENU)
|
|
| 274 |
- |
|
| 275 |
- id_ = widget.connect('clicked', self._on_history_menuitem_activate)
|
|
| 276 |
- self.handlers[id_] = widget
|
|
| 277 |
- |
|
| 278 |
- # when/if we do XHTML we will put formatting buttons back
|
|
| 279 |
- widget = self.xml.get_object('emoticons_button')
|
|
| 280 |
- widget.set_sensitive(False)
|
|
| 281 |
- id_ = widget.connect('clicked', self.on_emoticons_button_clicked)
|
|
| 282 |
- self.handlers[id_] = widget
|
|
| 283 |
- |
|
| 284 |
- # Create banner and connect signals
|
|
| 285 |
- widget = self.xml.get_object('banner_eventbox')
|
|
| 286 |
- id_ = widget.connect('button-press-event',
|
|
| 287 |
- self._on_banner_eventbox_button_press_event)
|
|
| 288 |
- self.handlers[id_] = widget
|
|
| 289 |
- |
|
| 290 |
- self.urlfinder = re.compile(
|
|
| 291 |
- r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]")
|
|
| 292 |
- |
|
| 293 |
- self.banner_status_label = self.xml.get_object('banner_label')
|
|
| 294 |
- id_ = self.banner_status_label.connect('populate_popup',
|
|
| 295 |
- self.on_banner_label_populate_popup)
|
|
| 296 |
- self.handlers[id_] = self.banner_status_label
|
|
| 297 |
- |
|
| 298 |
- # Init DND
|
|
| 299 |
- self.TARGET_TYPE_URI_LIST = 80
|
|
| 300 |
- self.dnd_list = [Gtk.TargetEntry.new('text/uri-list', 0,
|
|
| 301 |
- self.TARGET_TYPE_URI_LIST), Gtk.TargetEntry.new('MY_TREE_MODEL_ROW',
|
|
| 302 |
- Gtk.TargetFlags.SAME_APP, 0)]
|
|
| 303 |
- id_ = self.widget.connect('drag_data_received',
|
|
| 304 |
- self._on_drag_data_received)
|
|
| 305 |
- self.handlers[id_] = self.widget
|
|
| 306 |
- self.widget.drag_dest_set(Gtk.DestDefaults.MOTION |
|
|
| 307 |
- Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
|
|
| 308 |
- self.dnd_list, Gdk.DragAction.COPY)
|
|
| 309 |
- |
|
| 310 |
- # Create textviews and connect signals
|
|
| 311 |
- self.conv_textview = ConversationTextview(self.account)
|
|
| 312 |
- id_ = self.conv_textview.connect('quote', self.on_quote)
|
|
| 313 |
- self.handlers[id_] = self.conv_textview.tv
|
|
| 314 |
- id_ = self.conv_textview.tv.connect('key_press_event',
|
|
| 315 |
- self._conv_textview_key_press_event)
|
|
| 316 |
- self.handlers[id_] = self.conv_textview.tv
|
|
| 317 |
- # FIXME: DND on non editable TextView, find a better way
|
|
| 318 |
- self.drag_entered = False
|
|
| 319 |
- id_ = self.conv_textview.tv.connect('drag_data_received',
|
|
| 320 |
- self._on_drag_data_received)
|
|
| 321 |
- self.handlers[id_] = self.conv_textview.tv
|
|
| 322 |
- id_ = self.conv_textview.tv.connect('drag_motion', self._on_drag_motion)
|
|
| 323 |
- self.handlers[id_] = self.conv_textview.tv
|
|
| 324 |
- id_ = self.conv_textview.tv.connect('drag_leave', self._on_drag_leave)
|
|
| 325 |
- self.handlers[id_] = self.conv_textview.tv
|
|
| 326 |
- self.conv_textview.tv.drag_dest_set(Gtk.DestDefaults.MOTION |
|
|
| 327 |
- Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
|
|
| 328 |
- self.dnd_list, Gdk.DragAction.COPY)
|
|
| 329 |
- |
|
| 330 |
- self.conv_scrolledwindow = self.xml.get_object(
|
|
| 331 |
- 'conversation_scrolledwindow')
|
|
| 332 |
- self.conv_scrolledwindow.add(self.conv_textview.tv)
|
|
| 333 |
- widget = self.conv_scrolledwindow.get_vadjustment()
|
|
| 334 |
- id_ = widget.connect('value-changed',
|
|
| 335 |
- self.on_conversation_vadjustment_value_changed)
|
|
| 336 |
- self.handlers[id_] = widget
|
|
| 337 |
- id_ = widget.connect('changed',
|
|
| 338 |
- self.on_conversation_vadjustment_changed)
|
|
| 339 |
- self.handlers[id_] = widget
|
|
| 340 |
- self.scroll_to_end_id = None
|
|
| 341 |
- self.was_at_the_end = True
|
|
| 342 |
- self.correcting = False
|
|
| 343 |
- self.last_sent_msg = None
|
|
| 344 |
- self.last_received_txt = {} # one per name
|
|
| 345 |
- self.last_received_id = {} # one per name
|
|
| 346 |
- |
|
| 347 |
- # add MessageTextView to UI and connect signals
|
|
| 348 |
- self.msg_scrolledwindow = self.xml.get_object('message_scrolledwindow')
|
|
| 349 |
- self.msg_textview = MessageTextView()
|
|
| 350 |
- self.msg_scrolledwindow.add(self.msg_textview)
|
|
| 351 |
- id_ = self.msg_textview.connect('key_press_event',
|
|
| 352 |
- self._on_message_textview_key_press_event)
|
|
| 353 |
- self.handlers[id_] = self.msg_textview
|
|
| 354 |
- id_ = self.msg_textview.connect('configure-event',
|
|
| 355 |
- self.on_configure_event)
|
|
| 356 |
- self.handlers[id_] = self.msg_textview
|
|
| 357 |
- id_ = self.msg_textview.connect('populate_popup',
|
|
| 358 |
- self.on_msg_textview_populate_popup)
|
|
| 359 |
- self.handlers[id_] = self.msg_textview
|
|
| 360 |
- # Setup DND
|
|
| 361 |
- id_ = self.msg_textview.connect('drag_data_received',
|
|
| 362 |
- self._on_drag_data_received)
|
|
| 363 |
- self.handlers[id_] = self.msg_textview
|
|
| 364 |
- self.msg_textview.drag_dest_set(Gtk.DestDefaults.MOTION |
|
|
| 365 |
- Gtk.DestDefaults.HIGHLIGHT, self.dnd_list, Gdk.DragAction.COPY)
|
|
| 366 |
- |
|
| 367 |
- self.update_font()
|
|
| 368 |
- |
|
| 369 |
- # Hook up send button
|
|
| 370 |
- widget = self.xml.get_object('send_button')
|
|
| 371 |
- id_ = widget.connect('clicked', self._on_send_button_clicked)
|
|
| 372 |
- widget.set_sensitive(False)
|
|
| 373 |
- self.handlers[id_] = widget
|
|
| 374 |
- |
|
| 375 |
- # the following vars are used to keep history of user's messages
|
|
| 376 |
- self.sent_history = []
|
|
| 377 |
- self.sent_history_pos = 0
|
|
| 378 |
- self.received_history = []
|
|
| 379 |
- self.received_history_pos = 0
|
|
| 380 |
- self.orig_msg = None
|
|
| 381 |
- |
|
| 382 |
- # Emoticons menu
|
|
| 383 |
- # set image no matter if user wants at this time emoticons or not
|
|
| 384 |
- # (so toggle works ok)
|
|
| 385 |
- img = self.xml.get_object('emoticons_button_image')
|
|
| 386 |
- img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
|
|
| 387 |
- 'smile.png'))
|
|
| 388 |
- self.toggle_emoticons()
|
|
| 389 |
- |
|
| 390 |
- # Attach speller
|
|
| 391 |
- if gajim.config.get('use_speller') and HAS_GTK_SPELL:
|
|
| 392 |
- self.set_speller()
|
|
| 393 |
- self.conv_textview.tv.show()
|
|
| 394 |
- self._paint_banner()
|
|
| 395 |
- |
|
| 396 |
- # For XEP-0172
|
|
| 397 |
- self.user_nick = None
|
|
| 398 |
- |
|
| 399 |
- self.smooth = True
|
|
| 400 |
- |
|
| 401 |
- self.command_hits = []
|
|
| 402 |
- self.last_key_tabs = False
|
|
| 403 |
- |
|
| 404 |
- # PluginSystem: adding GUI extension point for ChatControlBase
|
|
| 405 |
- # instance object (also subclasses, eg. ChatControl or GroupchatControl)
|
|
| 406 |
- gajim.plugin_manager.gui_extension_point('chat_control_base', self)
|
|
| 407 |
- |
|
| 408 |
- gajim.ged.register_event_handler('our-show', ged.GUI1,
|
|
| 409 |
- self._nec_our_status)
|
|
| 410 |
- gajim.ged.register_event_handler('ping-sent', ged.GUI1,
|
|
| 411 |
- self._nec_ping_sent)
|
|
| 412 |
- gajim.ged.register_event_handler('ping-reply', ged.GUI1,
|
|
| 413 |
- self._nec_ping_reply)
|
|
| 414 |
- gajim.ged.register_event_handler('ping-error', ged.GUI1,
|
|
| 415 |
- self._nec_ping_error)
|
|
| 416 |
- |
|
| 417 |
- # This is bascially a very nasty hack to surpass the inability
|
|
| 418 |
- # to properly use the super, because of the old code.
|
|
| 419 |
- CommandTools.__init__(self)
|
|
| 420 |
- |
|
| 421 |
- def set_speller(self):
|
|
| 422 |
- # now set the one the user selected
|
|
| 423 |
- per_type = 'contacts'
|
|
| 424 |
- if self.type_id == message_control.TYPE_GC:
|
|
| 425 |
- per_type = 'rooms'
|
|
| 426 |
- lang = gajim.config.get_per(per_type, self.contact.jid,
|
|
| 427 |
- 'speller_language')
|
|
| 428 |
- if not lang:
|
|
| 429 |
- # use the default one
|
|
| 430 |
- lang = gajim.config.get('speller_language')
|
|
| 431 |
- if not lang:
|
|
| 432 |
- lang = gajim.LANG
|
|
| 433 |
- if lang:
|
|
| 434 |
- try:
|
|
| 435 |
- self.spell = gtkspell.Spell(self.msg_textview, lang)
|
|
| 436 |
- self.msg_textview.lang = lang
|
|
| 437 |
- self.spell.connect('language_changed', self.on_language_changed)
|
|
| 438 |
- except (GObject.GError, RuntimeError, TypeError, OSError):
|
|
| 439 |
- dialogs.AspellDictError(lang)
|
|
| 440 |
- |
|
| 441 |
- def on_language_changed(self, spell, lang):
|
|
| 442 |
- per_type = 'contacts'
|
|
| 443 |
- if self.type_id == message_control.TYPE_GC:
|
|
| 444 |
- per_type = 'rooms'
|
|
| 445 |
- if not gajim.config.get_per(per_type, self.contact.jid):
|
|
| 446 |
- gajim.config.add_per(per_type, self.contact.jid)
|
|
| 447 |
- gajim.config.set_per(per_type, self.contact.jid, 'speller_language',
|
|
| 448 |
- lang)
|
|
| 449 |
- self.msg_textview.lang = lang
|
|
| 450 |
- |
|
| 451 |
- def on_banner_label_populate_popup(self, label, menu):
|
|
| 452 |
- """
|
|
| 453 |
- Override the default context menu and add our own menutiems
|
|
| 454 |
- """
|
|
| 455 |
- item = Gtk.SeparatorMenuItem.new()
|
|
| 456 |
- menu.prepend(item)
|
|
| 457 |
- |
|
| 458 |
- menu2 = self.prepare_context_menu()
|
|
| 459 |
- i = 0
|
|
| 460 |
- for item in menu2:
|
|
| 461 |
- menu2.remove(item)
|
|
| 462 |
- menu.prepend(item)
|
|
| 463 |
- menu.reorder_child(item, i)
|
|
| 464 |
- i += 1
|
|
| 465 |
- menu.show_all()
|
|
| 466 |
- |
|
| 467 |
- def shutdown(self):
|
|
| 468 |
- super(ChatControlBase, self).shutdown()
|
|
| 469 |
- # PluginSystem: removing GUI extension points connected with ChatControlBase
|
|
| 470 |
- # instance object
|
|
| 471 |
- gajim.plugin_manager.remove_gui_extension_point('chat_control_base',
|
|
| 472 |
- self)
|
|
| 473 |
- gajim.plugin_manager.remove_gui_extension_point(
|
|
| 474 |
- 'chat_control_base_draw_banner', self)
|
|
| 475 |
- gajim.plugin_manager.remove_gui_extension_point('print_special_text',
|
|
| 476 |
- self)
|
|
| 477 |
- gajim.ged.remove_event_handler('our-show', ged.GUI1,
|
|
| 478 |
- self._nec_our_status)
|
|
| 479 |
- |
|
| 480 |
- def on_msg_textview_populate_popup(self, textview, menu):
|
|
| 481 |
- """
|
|
| 482 |
- Override the default context menu and we prepend an option to switch
|
|
| 483 |
- languages
|
|
| 484 |
- """
|
|
| 485 |
- item = Gtk.MenuItem.new_with_mnemonic(_('_Undo'))
|
|
| 486 |
- menu.prepend(item)
|
|
| 487 |
- id_ = item.connect('activate', self.msg_textview.undo)
|
|
| 488 |
- self.handlers[id_] = item
|
|
| 489 |
- |
|
| 490 |
- item = Gtk.SeparatorMenuItem.new()
|
|
| 491 |
- menu.prepend(item)
|
|
| 492 |
- |
|
| 493 |
- item = Gtk.MenuItem.new_with_mnemonic(_('_Clear'))
|
|
| 494 |
- menu.prepend(item)
|
|
| 495 |
- id_ = item.connect('activate', self.msg_textview.clear)
|
|
| 496 |
- self.handlers[id_] = item
|
|
| 497 |
- |
|
| 498 |
- menu.show_all()
|
|
| 499 |
- |
|
| 500 |
- def on_quote(self, widget, text):
|
|
| 501 |
- text = '>' + text.replace('\n', '\n>') + '\n'
|
|
| 502 |
- message_buffer = self.msg_textview.get_buffer()
|
|
| 503 |
- message_buffer.insert_at_cursor(text)
|
|
| 504 |
- |
|
| 505 |
- # moved from ChatControl
|
|
| 506 |
- def _on_banner_eventbox_button_press_event(self, widget, event):
|
|
| 507 |
- """
|
|
| 508 |
- If right-clicked, show popup
|
|
| 509 |
- """
|
|
| 510 |
- if event.button == 3: # right click
|
|
| 511 |
- self.parent_win.popup_menu(event)
|
|
| 512 |
- |
|
| 513 |
- def _on_send_button_clicked(self, widget):
|
|
| 514 |
- """
|
|
| 515 |
- When send button is pressed: send the current message
|
|
| 516 |
- """
|
|
| 517 |
- message_buffer = self.msg_textview.get_buffer()
|
|
| 518 |
- start_iter = message_buffer.get_start_iter()
|
|
| 519 |
- end_iter = message_buffer.get_end_iter()
|
|
| 520 |
- message = message_buffer.get_text(start_iter, end_iter, False)
|
|
| 521 |
- xhtml = self.msg_textview.get_xhtml()
|
|
| 522 |
- |
|
| 523 |
- # send the message
|
|
| 524 |
- self.send_message(message, xhtml=xhtml)
|
|
| 525 |
- |
|
| 526 |
- def _paint_banner(self):
|
|
| 527 |
- """
|
|
| 528 |
- Repaint banner with theme color
|
|
| 529 |
- """
|
|
| 530 |
- theme = gajim.config.get('roster_theme')
|
|
| 531 |
- bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
|
|
| 532 |
- textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
|
|
| 533 |
- # the backgrounds are colored by using an eventbox by
|
|
| 534 |
- # setting the bg color of the eventbox and the fg of the name_label
|
|
| 535 |
- banner_eventbox = self.xml.get_object('banner_eventbox')
|
|
| 536 |
- banner_name_label = self.xml.get_object('banner_name_label')
|
|
| 537 |
- self.disconnect_style_event(banner_name_label)
|
|
| 538 |
- self.disconnect_style_event(self.banner_status_label)
|
|
| 539 |
- if bgcolor:
|
|
| 540 |
- color = Gdk.RGBA()
|
|
| 541 |
- Gdk.RGBA.parse(color, bgcolor)
|
|
| 542 |
- banner_eventbox.override_background_color(Gtk.StateType.NORMAL,
|
|
| 543 |
- color)
|
|
| 544 |
- default_bg = False
|
|
| 545 |
- else:
|
|
| 546 |
- default_bg = True
|
|
| 547 |
- if textcolor:
|
|
| 548 |
- color = Gdk.RGBA()
|
|
| 549 |
- Gdk.RGBA.parse(color, textcolor)
|
|
| 550 |
- banner_name_label.override_color(Gtk.StateType.NORMAL,
|
|
| 551 |
- color)
|
|
| 552 |
- self.banner_status_label.override_color(
|
|
| 553 |
- Gtk.StateType.NORMAL, color)
|
|
| 554 |
- default_fg = False
|
|
| 555 |
- else:
|
|
| 556 |
- default_fg = True
|
|
| 557 |
- if default_bg or default_fg:
|
|
| 558 |
- self._on_style_set_event(banner_name_label, None, default_fg,
|
|
| 559 |
- default_bg)
|
|
| 560 |
- if self.banner_status_label.get_realized():
|
|
| 561 |
- # Widget is realized
|
|
| 562 |
- self._on_style_set_event(self.banner_status_label, None, default_fg,
|
|
| 563 |
- default_bg)
|
|
| 564 |
- |
|
| 565 |
- def disconnect_style_event(self, widget):
|
|
| 566 |
- # Try to find the event_id
|
|
| 567 |
- for id_ in self.handlers.keys():
|
|
| 568 |
- if self.handlers[id_] == widget:
|
|
| 569 |
- widget.disconnect(id_)
|
|
| 570 |
- del self.handlers[id_]
|
|
| 571 |
- break
|
|
| 572 |
- |
|
| 573 |
- def connect_style_event(self, widget, set_fg=False, set_bg=False):
|
|
| 574 |
- self.disconnect_style_event(widget)
|
|
| 575 |
- id_ = widget.connect('style-set', self._on_style_set_event, set_fg,
|
|
| 576 |
- set_bg)
|
|
| 577 |
- self.handlers[id_] = widget
|
|
| 578 |
- |
|
| 579 |
- def _on_style_set_event(self, widget, style, *opts):
|
|
| 580 |
- """
|
|
| 581 |
- Set style of widget from style class *.Frame.Eventbox
|
|
| 582 |
- opts[0] == True -> set fg color
|
|
| 583 |
- opts[1] == True -> set bg color
|
|
| 584 |
- """
|
|
| 585 |
- banner_eventbox = self.xml.get_object('banner_eventbox')
|
|
| 586 |
- self.disconnect_style_event(widget)
|
|
| 587 |
- context = widget.get_style_context()
|
|
| 588 |
- if opts[1]:
|
|
| 589 |
- bg_color = context.get_background_color(Gtk.StateFlags.SELECTED)
|
|
| 590 |
- banner_eventbox.override_background_color(Gtk.StateType.NORMAL, bg_color)
|
|
| 591 |
- if opts[0]:
|
|
| 592 |
- fg_color = context.get_color(Gtk.StateFlags.SELECTED)
|
|
| 593 |
- widget.override_color(Gtk.StateType.NORMAL, fg_color)
|
|
| 594 |
- self.connect_style_event(widget, opts[0], opts[1])
|
|
| 595 |
- |
|
| 596 |
- def _conv_textview_key_press_event(self, widget, event):
|
|
| 597 |
- # translate any layout to latin_layout
|
|
| 598 |
- valid, entries = self.keymap.get_entries_for_keyval(event.keyval)
|
|
| 599 |
- keycode = entries[0].keycode
|
|
| 600 |
- if (event.get_state() & Gdk.ModifierType.CONTROL_MASK and keycode in (
|
|
| 601 |
- self.keycode_c, self.keycode_ins)) or (
|
|
| 602 |
- event.get_state() & Gdk.ModifierType.SHIFT_MASK and \
|
|
| 603 |
- event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up)):
|
|
| 604 |
- return False
|
|
| 605 |
- self.parent_win.notebook.event(event)
|
|
| 606 |
- return True
|
|
| 607 |
- |
|
| 608 |
- def show_emoticons_menu(self):
|
|
| 609 |
- if not gajim.config.get('emoticons_theme'):
|
|
| 610 |
- return
|
|
| 611 |
- gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
|
|
| 612 |
- gajim.interface.emoticons_menu.popup(None, None, None, None, 1, 0)
|
|
| 613 |
- |
|
| 614 |
- def _on_message_textview_key_press_event(self, widget, event):
|
|
| 615 |
- if event.keyval == Gdk.KEY_space:
|
|
| 616 |
- self.space_pressed = True
|
|
| 617 |
- |
|
| 618 |
- elif (self.space_pressed or self.msg_textview.undo_pressed) and \
|
|
| 619 |
- event.keyval not in (Gdk.KEY_Control_L, Gdk.KEY_Control_R) and \
|
|
| 620 |
- not (event.keyval == Gdk.KEY_z and event.get_state() & Gdk.ModifierType.CONTROL_MASK):
|
|
| 621 |
- # If the space key has been pressed and now it hasnt,
|
|
| 622 |
- # we save the buffer into the undo list. But be carefull we're not
|
|
| 623 |
- # pressiong Control again (as in ctrl+z)
|
|
| 624 |
- _buffer = widget.get_buffer()
|
|
| 625 |
- start_iter, end_iter = _buffer.get_bounds()
|
|
| 626 |
- self.msg_textview.save_undo(_buffer.get_text(start_iter, end_iter, True))
|
|
| 627 |
- self.space_pressed = False
|
|
| 628 |
- |
|
| 629 |
- # Ctrl [+ Shift] + Tab are not forwarded to notebook. We handle it here
|
|
| 630 |
- if self.widget_name == 'groupchat_control':
|
|
| 631 |
- if event.keyval not in (Gdk.KEY_ISO_Left_Tab, Gdk.KEY_Tab):
|
|
| 632 |
- self.last_key_tabs = False
|
|
| 633 |
- if event.get_state() & Gdk.ModifierType.SHIFT_MASK:
|
|
| 634 |
- # CTRL + SHIFT + TAB
|
|
| 635 |
- if event.get_state() & Gdk.ModifierType.CONTROL_MASK and \
|
|
| 636 |
- event.keyval == Gdk.KEY_ISO_Left_Tab:
|
|
| 637 |
- self.parent_win.move_to_next_unread_tab(False)
|
|
| 638 |
- return True
|
|
| 639 |
- # SHIFT + PAGE_[UP|DOWN]: send to conv_textview
|
|
| 640 |
- elif event.keyval == Gdk.KEY_Page_Down or \
|
|
| 641 |
- event.keyval == Gdk.KEY_Page_Up:
|
|
| 642 |
- self.conv_textview.tv.event(event)
|
|
| 643 |
- return True
|
|
| 644 |
- elif event.get_state() & Gdk.ModifierType.CONTROL_MASK:
|
|
| 645 |
- if event.keyval == Gdk.KEY_Tab: # CTRL + TAB
|
|
| 646 |
- self.parent_win.move_to_next_unread_tab(True)
|
|
| 647 |
- return True
|
|
| 648 |
- |
|
| 649 |
- message_buffer = self.msg_textview.get_buffer()
|
|
| 650 |
- event_state = event.get_state()
|
|
| 651 |
- if event.keyval == Gdk.KEY_Tab:
|
|
| 652 |
- start, end = message_buffer.get_bounds()
|
|
| 653 |
- position = message_buffer.get_insert()
|
|
| 654 |
- end = message_buffer.get_iter_at_mark(position)
|
|
| 655 |
- text = message_buffer.get_text(start, end, False)
|
|
| 656 |
- splitted = text.split()
|
|
| 657 |
- if (text.startswith(self.COMMAND_PREFIX) and not
|
|
| 658 |
- text.startswith(self.COMMAND_PREFIX * 2) and len(splitted) == 1):
|
|
| 659 |
- text = splitted[0]
|
|
| 660 |
- bare = text.lstrip(self.COMMAND_PREFIX)
|
|
| 661 |
- if len(text) == 1:
|
|
| 662 |
- self.command_hits = []
|
|
| 663 |
- for command in self.list_commands():
|
|
| 664 |
- for name in command.names:
|
|
| 665 |
- self.command_hits.append(name)
|
|
| 666 |
- else:
|
|
| 667 |
- if (self.last_key_tabs and self.command_hits and
|
|
| 668 |
- self.command_hits[0].startswith(bare)):
|
|
| 669 |
- self.command_hits.append(self.command_hits.pop(0))
|
|
| 670 |
- else:
|
|
| 671 |
- self.command_hits = []
|
|
| 672 |
- for command in self.list_commands():
|
|
| 673 |
- for name in command.names:
|
|
| 674 |
- if name.startswith(bare):
|
|
| 675 |
- self.command_hits.append(name)
|
|
| 676 |
- |
|
| 677 |
- if self.command_hits:
|
|
| 678 |
- message_buffer.delete(start, end)
|
|
| 679 |
- message_buffer.insert_at_cursor(self.COMMAND_PREFIX + \
|
|
| 680 |
- self.command_hits[0] + ' ')
|
|
| 681 |
- self.last_key_tabs = True
|
|
| 682 |
- return True
|
|
| 683 |
- if self.widget_name != 'groupchat_control':
|
|
| 684 |
- self.last_key_tabs = False
|
|
| 685 |
- if event.keyval == Gdk.KEY_Up:
|
|
| 686 |
- if event_state & Gdk.ModifierType.CONTROL_MASK:
|
|
| 687 |
- if event_state & Gdk.ModifierType.SHIFT_MASK: # Ctrl+Shift+UP
|
|
| 688 |
- self.scroll_messages('up', message_buffer, 'received')
|
|
| 689 |
- else: # Ctrl+UP
|
|
| 690 |
- self.scroll_messages('up', message_buffer, 'sent')
|
|
| 691 |
- return True
|
|
| 692 |
- elif event.keyval == Gdk.KEY_Down:
|
|
| 693 |
- if event_state & Gdk.ModifierType.CONTROL_MASK:
|
|
| 694 |
- if event_state & Gdk.ModifierType.SHIFT_MASK: # Ctrl+Shift+Down
|
|
| 695 |
- self.scroll_messages('down', message_buffer, 'received')
|
|
| 696 |
- else: # Ctrl+Down
|
|
| 697 |
- self.scroll_messages('down', message_buffer, 'sent')
|
|
| 698 |
- return True
|
|
| 699 |
- |
|
| 700 |
- elif event.keyval == Gdk.KEY_Return or \
|
|
| 701 |
- event.keyval == Gdk.KEY_KP_Enter: # ENTER
|
|
| 702 |
- if event_state & Gdk.ModifierType.SHIFT_MASK:
|
|
| 703 |
- return True
|
|
| 704 |
- message_textview = widget
|
|
| 705 |
- message_buffer = message_textview.get_buffer()
|
|
| 706 |
- start_iter, end_iter = message_buffer.get_bounds()
|
|
| 707 |
- message = message_buffer.get_text(start_iter, end_iter, False)
|
|
| 708 |
- xhtml = self.msg_textview.get_xhtml()
|
|
| 709 |
- |
|
| 710 |
- if gajim.config.get('send_on_ctrl_enter'):
|
|
| 711 |
- if event_state & Gdk.ModifierType.CONTROL_MASK: # CTRL + ENTER
|
|
| 712 |
- send_message = True
|
|
| 713 |
- else:
|
|
| 714 |
- end_iter = message_buffer.get_end_iter()
|
|
| 715 |
- message_buffer.insert_at_cursor('\n')
|
|
| 716 |
- send_message = False
|
|
| 717 |
- |
|
| 718 |
- else: # send on Enter, do newline on Ctrl Enter
|
|
| 719 |
- if event_state & Gdk.ModifierType.CONTROL_MASK: # Ctrl + ENTER
|
|
| 720 |
- end_iter = message_buffer.get_end_iter()
|
|
| 721 |
- message_buffer.insert_at_cursor('\n')
|
|
| 722 |
- send_message = False
|
|
| 723 |
- else: # ENTER
|
|
| 724 |
- send_message = True
|
|
| 725 |
- |
|
| 726 |
- if gajim.connections[self.account].connected < 2 and send_message:
|
|
| 727 |
- # we are not connected
|
|
| 728 |
- dialogs.ErrorDialog(_('A connection is not available'),
|
|
| 729 |
- _('Your message can not be sent until you are connected.'))
|
|
| 730 |
- send_message = False
|
|
| 731 |
- |
|
| 732 |
- if send_message:
|
|
| 733 |
- self.send_message(message, xhtml=xhtml) # send the message
|
|
| 734 |
- return True
|
|
| 735 |
- elif event.keyval == Gdk.KEY_z: # CTRL+z
|
|
| 736 |
- if event_state & Gdk.ModifierType.CONTROL_MASK:
|
|
| 737 |
- self.msg_textview.undo()
|
|
| 738 |
- return True
|
|
| 739 |
- |
|
| 740 |
- return False
|
|
| 741 |
- |
|
| 742 |
- def _on_drag_data_received(self, widget, context, x, y, selection,
|
|
| 743 |
- target_type, timestamp):
|
|
| 744 |
- """
|
|
| 745 |
- Derived types SHOULD implement this
|
|
| 746 |
- """
|
|
| 747 |
- pass
|
|
| 748 |
- |
|
| 749 |
- def _on_drag_leave(self, widget, context, time):
|
|
| 750 |
- # FIXME: DND on non editable TextView, find a better way
|
|
| 751 |
- self.drag_entered = False
|
|
| 752 |
- self.conv_textview.tv.set_editable(False)
|
|
| 753 |
- |
|
| 754 |
- def _on_drag_motion(self, widget, context, x, y, time):
|
|
| 755 |
- # FIXME: DND on non editable TextView, find a better way
|
|
| 756 |
- if not self.drag_entered:
|
|
| 757 |
- # We drag new data over the TextView, make it editable to catch dnd
|
|
| 758 |
- self.drag_entered_conv = True
|
|
| 759 |
- self.conv_textview.tv.set_editable(True)
|
|
| 760 |
- |
|
| 761 |
- def get_seclabel(self):
|
|
| 762 |
- label = None
|
|
| 763 |
- if self.seclabel_combo is not None:
|
|
| 764 |
- idx = self.seclabel_combo.get_active()
|
|
| 765 |
- if idx != -1:
|
|
| 766 |
- cat = gajim.connections[self.account].seclabel_catalogues[self.contact.jid]
|
|
| 767 |
- lname = cat[2][idx]
|
|
| 768 |
- label = cat[1][lname]
|
|
| 769 |
- return label
|
|
| 770 |
- |
|
| 771 |
- def send_message(self, message, keyID='', type_='chat', chatstate=None,
|
|
| 772 |
- msg_id=None, resource=None, xhtml=None, callback=None, callback_args=[],
|
|
| 773 |
- process_commands=True, attention=False):
|
|
| 774 |
- """
|
|
| 775 |
- Send the given message to the active tab. Doesn't return None if error
|
|
| 776 |
- """
|
|
| 777 |
- if not message or message == '\n':
|
|
| 778 |
- return None
|
|
| 779 |
- |
|
| 780 |
- if process_commands and self.process_as_command(message):
|
|
| 781 |
- return
|
|
| 782 |
- |
|
| 783 |
- label = self.get_seclabel()
|
|
| 784 |
- |
|
| 785 |
- def _cb(obj, msg, cb, *cb_args):
|
|
| 786 |
- self.last_sent_msg = msg
|
|
| 787 |
- if cb:
|
|
| 788 |
- cb(obj, msg, *cb_args)
|
|
| 789 |
- |
|
| 790 |
- if self.correcting and self.last_sent_msg:
|
|
| 791 |
- correction_msg = self.last_sent_msg
|
|
| 792 |
- else:
|
|
| 793 |
- correction_msg = None
|
|
| 794 |
- |
|
| 795 |
- gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
|
|
| 796 |
- account=self.account, jid=self.contact.jid, message=message,
|
|
| 797 |
- keyID=keyID, type_=type_, chatstate=chatstate, msg_id=msg_id,
|
|
| 798 |
- resource=resource, user_nick=self.user_nick, xhtml=xhtml,
|
|
| 799 |
- label=label, callback=_cb, callback_args=[callback] + callback_args,
|
|
| 800 |
- control=self, attention=attention, correction_msg=correction_msg, automatic_message=False))
|
|
| 801 |
- |
|
| 802 |
- # Record the history of sent messages
|
|
| 803 |
- self.save_message(message, 'sent')
|
|
| 804 |
- |
|
| 805 |
- # Be sure to send user nickname only once according to JEP-0172
|
|
| 806 |
- self.user_nick = None
|
|
| 807 |
- |
|
| 808 |
- # Clear msg input
|
|
| 809 |
- message_buffer = self.msg_textview.get_buffer()
|
|
| 810 |
- message_buffer.set_text('') # clear message buffer (and tv of course)
|
|
| 811 |
- |
|
| 812 |
- def save_message(self, message, msg_type):
|
|
| 813 |
- # save the message, so user can scroll though the list with key up/down
|
|
| 814 |
- if msg_type == 'sent':
|
|
| 815 |
- history = self.sent_history
|
|
| 816 |
- pos = self.sent_history_pos
|
|
| 817 |
- else:
|
|
| 818 |
- history = self.received_history
|
|
| 819 |
- pos = self.received_history_pos
|
|
| 820 |
- size = len(history)
|
|
| 821 |
- scroll = False if pos == size else True # are we scrolling?
|
|
| 822 |
- # we don't want size of the buffer to grow indefinately
|
|
| 823 |
- max_size = gajim.config.get('key_up_lines')
|
|
| 824 |
- for i in range(size - max_size + 1):
|
|
| 825 |
- if pos == 0:
|
|
| 826 |
- break
|
|
| 827 |
- history.pop(0)
|
|
| 828 |
- pos -= 1
|
|
| 829 |
- history.append(message)
|
|
| 830 |
- if not scroll or msg_type == 'sent':
|
|
| 831 |
- pos = len(history)
|
|
| 832 |
- if msg_type == 'sent':
|
|
| 833 |
- self.sent_history_pos = pos
|
|
| 834 |
- self.orig_msg = None
|
|
| 835 |
- else:
|
|
| 836 |
- self.received_history_pos = pos
|
|
| 837 |
- |
|
| 838 |
- def print_conversation_line(self, text, kind, name, tim,
|
|
| 839 |
- other_tags_for_name=[], other_tags_for_time=[], other_tags_for_text=[],
|
|
| 840 |
- count_as_new=True, subject=None, old_kind=None, xhtml=None, simple=False,
|
|
| 841 |
- xep0184_id=None, graphics=True, displaymarking=None, msg_log_id=None,
|
|
| 842 |
- msg_stanza_id=None, correct_id=None, additional_data={}):
|
|
| 843 |
- """
|
|
| 844 |
- Print 'chat' type messages
|
|
| 845 |
- correct_id = (message_id, correct_id)
|
|
| 846 |
- """
|
|
| 847 |
- jid = self.contact.jid
|
|
| 848 |
- full_jid = self.get_full_jid()
|
|
| 849 |
- textview = self.conv_textview
|
|
| 850 |
- end = False
|
|
| 851 |
- if self.was_at_the_end or kind == 'outgoing':
|
|
| 852 |
- end = True
|
|
| 853 |
- |
|
| 854 |
- textview.print_conversation_line(text, jid, kind, name, tim,
|
|
| 855 |
- other_tags_for_name, other_tags_for_time, other_tags_for_text,
|
|
| 856 |
- subject, old_kind, xhtml, simple=simple, graphics=graphics,
|
|
| 857 |
- displaymarking=displaymarking, msg_stanza_id=msg_stanza_id,
|
|
| 858 |
- correct_id=correct_id, additional_data=additional_data)
|
|
| 859 |
- |
|
| 860 |
- if xep0184_id is not None:
|
|
| 861 |
- textview.show_xep0184_warning(xep0184_id)
|
|
| 862 |
- |
|
| 863 |
- if not count_as_new:
|
|
| 864 |
- return
|
|
| 865 |
- if kind in ('incoming', 'incoming_queue', 'outgoing'):
|
|
| 866 |
- self.last_received_txt[name] = text
|
|
| 867 |
- if correct_id:
|
|
| 868 |
- self.last_received_id[name] = correct_id[0]
|
|
| 869 |
- if kind == 'incoming':
|
|
| 870 |
- if not self.type_id == message_control.TYPE_GC or \
|
|
| 871 |
- gajim.config.get('notify_on_all_muc_messages') or \
|
|
| 872 |
- 'marked' in other_tags_for_text:
|
|
| 873 |
- # it's a normal message, or a muc message with want to be
|
|
| 874 |
- # notified about if quitting just after
|
|
| 875 |
- # other_tags_for_text == ['marked'] --> highlighted gc message
|
|
| 876 |
- gajim.last_message_time[self.account][full_jid] = time.time()
|
|
| 877 |
- |
|
| 878 |
- if kind in ('incoming', 'incoming_queue'):
|
|
| 879 |
- # Record the history of received messages
|
|
| 880 |
- self.save_message(text, 'received')
|
|
| 881 |
- |
|
| 882 |
- if kind in ('incoming', 'incoming_queue', 'error'):
|
|
| 883 |
- gc_message = False
|
|
| 884 |
- if self.type_id == message_control.TYPE_GC:
|
|
| 885 |
- gc_message = True
|
|
| 886 |
- |
|
| 887 |
- if ((self.parent_win and (not self.parent_win.get_active_control() or \
|
|
| 888 |
- self != self.parent_win.get_active_control() or \
|
|
| 889 |
- not self.parent_win.is_active() or not end)) or \
|
|
| 890 |
- (gc_message and \
|
|
| 891 |
- jid in gajim.interface.minimized_controls[self.account])) and \
|
|
| 892 |
- kind in ('incoming', 'incoming_queue', 'error'):
|
|
| 893 |
- # we want to have save this message in events list
|
|
| 894 |
- # other_tags_for_text == ['marked'] --> highlighted gc message
|
|
| 895 |
- if gc_message:
|
|
| 896 |
- if 'marked' in other_tags_for_text:
|
|
| 897 |
- event_type = events.PrintedMarkedGcMsgEvent
|
|
| 898 |
- else:
|
|
| 899 |
- event_type = events.PrintedGcMsgEvent
|
|
| 900 |
- event = 'gc_message_received'
|
|
| 901 |
- else:
|
|
| 902 |
- if self.type_id == message_control.TYPE_CHAT:
|
|
| 903 |
- event_type = events.PrintedChatEvent
|
|
| 904 |
- else:
|
|
| 905 |
- event_type = events.PrintedPmEvent
|
|
| 906 |
- event = 'message_received'
|
|
| 907 |
- show_in_roster = notify.get_show_in_roster(event,
|
|
| 908 |
- self.account, self.contact, self.session)
|
|
| 909 |
- show_in_systray = notify.get_show_in_systray(event,
|
|
| 910 |
- self.account, self.contact, event_type.type_)
|
|
| 911 |
- |
|
| 912 |
- event = event_type(text, subject, self, msg_log_id,
|
|
| 913 |
- show_in_roster=show_in_roster,
|
|
| 914 |
- show_in_systray=show_in_systray)
|
|
| 915 |
- gajim.events.add_event(self.account, full_jid, event)
|
|
| 916 |
- # We need to redraw contact if we show in roster
|
|
| 917 |
- if show_in_roster:
|
|
| 918 |
- gajim.interface.roster.draw_contact(self.contact.jid,
|
|
| 919 |
- self.account)
|
|
| 920 |
- |
|
| 921 |
- if not self.parent_win:
|
|
| 922 |
- return
|
|
| 923 |
- |
|
| 924 |
- if (not self.parent_win.get_active_control() or \
|
|
| 925 |
- self != self.parent_win.get_active_control() or \
|
|
| 926 |
- not self.parent_win.is_active() or not end) and \
|
|
| 927 |
- kind in ('incoming', 'incoming_queue', 'error'):
|
|
| 928 |
- self.parent_win.redraw_tab(self)
|
|
| 929 |
- if not self.parent_win.is_active():
|
|
| 930 |
- self.parent_win.show_title(True, self) # Enabled Urgent hint
|
|
| 931 |
- else:
|
|
| 932 |
- self.parent_win.show_title(False, self) # Disabled Urgent hint
|
|
| 933 |
- |
|
| 934 |
- def toggle_emoticons(self):
|
|
| 935 |
- """
|
|
| 936 |
- Hide show emoticons_button and make sure emoticons_menu is always there
|
|
| 937 |
- when needed
|
|
| 938 |
- """
|
|
| 939 |
- emoticons_button = self.xml.get_object('emoticons_button')
|
|
| 940 |
- if gajim.config.get('emoticons_theme'):
|
|
| 941 |
- emoticons_button.show()
|
|
| 942 |
- emoticons_button.set_no_show_all(False)
|
|
| 943 |
- else:
|
|
| 944 |
- emoticons_button.hide()
|
|
| 945 |
- emoticons_button.set_no_show_all(True)
|
|
| 946 |
- |
|
| 947 |
- def append_emoticon(self, str_):
|
|
| 948 |
- buffer_ = self.msg_textview.get_buffer()
|
|
| 949 |
- if buffer_.get_char_count():
|
|
| 950 |
- buffer_.insert_at_cursor(' %s ' % str_)
|
|
| 951 |
- else: # we are the beginning of buffer
|
|
| 952 |
- buffer_.insert_at_cursor('%s ' % str_)
|
|
| 953 |
- self.msg_textview.grab_focus()
|
|
| 954 |
- |
|
| 955 |
- def on_emoticons_button_clicked(self, widget):
|
|
| 956 |
- """
|
|
| 957 |
- Popup emoticons menu
|
|
| 958 |
- """
|
|
| 959 |
- gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
|
|
| 960 |
- gajim.interface.popup_emoticons_under_button(widget, self.parent_win)
|
|
| 961 |
- |
|
| 962 |
- def on_color_menuitem_activate(self, widget):
|
|
| 963 |
- color_dialog = Gtk.ColorChooserDialog(None, self.parent_win.window)
|
|
| 964 |
- color_dialog.set_use_alpha(False)
|
|
| 965 |
- color_dialog.connect('response', self.msg_textview.color_set)
|
|
| 966 |
- color_dialog.show_all()
|
|
| 967 |
- |
|
| 968 |
- def on_font_menuitem_activate(self, widget):
|
|
| 969 |
- font_dialog = Gtk.FontChooserDialog(None, self.parent_win.window)
|
|
| 970 |
- start, finish = self.msg_textview.get_active_iters()
|
|
| 971 |
- font_dialog.connect('response', self.msg_textview.font_set, start, finish)
|
|
| 972 |
- font_dialog.show_all()
|
|
| 973 |
- |
|
| 974 |
- def on_formatting_menuitem_activate(self, widget):
|
|
| 975 |
- tag = widget.get_name()
|
|
| 976 |
- self.msg_textview.set_tag(tag)
|
|
| 977 |
- |
|
| 978 |
- def on_clear_formatting_menuitem_activate(self, widget):
|
|
| 979 |
- self.msg_textview.clear_tags()
|
|
| 980 |
- |
|
| 981 |
- def on_actions_button_clicked(self, widget):
|
|
| 982 |
- """
|
|
| 983 |
- Popup action menu
|
|
| 984 |
- """
|
|
| 985 |
- menu = self.prepare_context_menu(hide_buttonbar_items=True)
|
|
| 986 |
- menu.show_all()
|
|
| 987 |
- menu.attach_to_widget(widget, None)
|
|
| 988 |
- gtkgui_helpers.popup_emoticons_under_button(menu, widget,
|
|
| 989 |
- self.parent_win)
|
|
| 990 |
- |
|
| 991 |
- def update_font(self):
|
|
| 992 |
- font = Pango.FontDescription(gajim.config.get('conversation_font'))
|
|
| 993 |
- self.conv_textview.tv.override_font(font)
|
|
| 994 |
- self.msg_textview.override_font(font)
|
|
| 995 |
- |
|
| 996 |
- def update_tags(self):
|
|
| 997 |
- self.conv_textview.update_tags()
|
|
| 998 |
- |
|
| 999 |
- def clear(self, tv):
|
|
| 1000 |
- buffer_ = tv.get_buffer()
|
|
| 1001 |
- start, end = buffer_.get_bounds()
|
|
| 1002 |
- buffer_.delete(start, end)
|
|
| 1003 |
- |
|
| 1004 |
- def _on_history_menuitem_activate(self, widget=None, jid=None):
|
|
| 1005 |
- """
|
|
| 1006 |
- When history menuitem is pressed: call history window
|
|
| 1007 |
- """
|
|
| 1008 |
- if not jid:
|
|
| 1009 |
- jid = self.contact.jid
|
|
| 1010 |
- |
|
| 1011 |
- if 'logs' in gajim.interface.instances:
|
|
| 1012 |
- gajim.interface.instances['logs'].window.present()
|
|
| 1013 |
- gajim.interface.instances['logs'].open_history(jid, self.account)
|
|
| 1014 |
- else:
|
|
| 1015 |
- gajim.interface.instances['logs'] = \
|
|
| 1016 |
- history_window.HistoryWindow(jid, self.account)
|
|
| 1017 |
- |
|
| 1018 |
- def _on_send_file(self, gc_contact=None):
|
|
| 1019 |
- """
|
|
| 1020 |
- gc_contact can be set when we are in a groupchat control
|
|
| 1021 |
- """
|
|
| 1022 |
- def _on_ok(c):
|
|
| 1023 |
- gajim.interface.instances['file_transfers'].show_file_send_request(
|
|
| 1024 |
- self.account, c)
|
|
| 1025 |
- if self.TYPE_ID == message_control.TYPE_PM:
|
|
| 1026 |
- gc_contact = self.gc_contact
|
|
| 1027 |
- if gc_contact:
|
|
| 1028 |
- # gc or pm
|
|
| 1029 |
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(
|
|
| 1030 |
- gc_contact.room_jid, self.account)
|
|
| 1031 |
- self_contact = gajim.contacts.get_gc_contact(self.account,
|
|
| 1032 |
- gc_control.room_jid, gc_control.nick)
|
|
| 1033 |
- if gc_control.is_anonymous and gc_contact.affiliation not in ['admin',
|
|
| 1034 |
- 'owner'] and self_contact.affiliation in ['admin', 'owner']:
|
|
| 1035 |
- contact = gajim.contacts.get_contact(self.account, gc_contact.jid)
|
|
| 1036 |
- if not contact or contact.sub not in ('both', 'to'):
|
|
| 1037 |
- prim_text = _('Really send file?')
|
|
| 1038 |
- sec_text = _('If you send a file to %s, he/she will know your '
|
|
| 1039 |
- 'real Jabber ID.') % gc_contact.name
|
|
| 1040 |
- dialog = dialogs.NonModalConfirmationDialog(prim_text,
|
|
| 1041 |
- sec_text, on_response_ok=(_on_ok, gc_contact))
|
|
| 1042 |
- dialog.popup()
|
|
| 1043 |
- return
|
|
| 1044 |
- _on_ok(gc_contact)
|
|
| 1045 |
- return
|
|
| 1046 |
- _on_ok(self.contact)
|
|
| 1047 |
- |
|
| 1048 |
- def on_minimize_menuitem_toggled(self, widget):
|
|
| 1049 |
- """
|
|
| 1050 |
- When a grouchat is minimized, unparent the tab, put it in roster etc
|
|
| 1051 |
- """
|
|
| 1052 |
- old_value = True
|
|
| 1053 |
- non_minimized_gc = gajim.config.get_per('accounts', self.account,
|
|
| 1054 |
- 'non_minimized_gc').split()
|
|
| 1055 |
- if self.contact.jid in non_minimized_gc:
|
|
| 1056 |
- old_value = False
|
|
| 1057 |
- minimize = widget.get_active()
|
|
| 1058 |
- if not minimize and not self.contact.jid in non_minimized_gc:
|
|
| 1059 |
- non_minimized_gc.append(self.contact.jid)
|
|
| 1060 |
- if minimize and self.contact.jid in non_minimized_gc:
|
|
| 1061 |
- non_minimized_gc.remove(self.contact.jid)
|
|
| 1062 |
- if old_value != minimize:
|
|
| 1063 |
- gajim.config.set_per('accounts', self.account, 'non_minimized_gc',
|
|
| 1064 |
- ' '.join(non_minimized_gc))
|
|
| 1065 |
- |
|
| 1066 |
- def set_control_active(self, state):
|
|
| 1067 |
- if state:
|
|
| 1068 |
- jid = self.contact.jid
|
|
| 1069 |
- if self.was_at_the_end:
|
|
| 1070 |
- # we are at the end
|
|
| 1071 |
- type_ = ['printed_' + self.type_id]
|
|
| 1072 |
- if self.type_id == message_control.TYPE_GC:
|
|
| 1073 |
- type_ = ['printed_gc_msg', 'printed_marked_gc_msg']
|
|
| 1074 |
- if not gajim.events.remove_events(self.account, self.get_full_jid(),
|
|
| 1075 |
- types=type_):
|
|
| 1076 |
- # There were events to remove
|
|
| 1077 |
- self.redraw_after_event_removed(jid)
|
|
| 1078 |
- |
|
| 1079 |
- def bring_scroll_to_end(self, textview, diff_y=0):
|
|
| 1080 |
- """
|
|
| 1081 |
- Scroll to the end of textview if end is not visible
|
|
| 1082 |
- """
|
|
| 1083 |
- if self.scroll_to_end_id:
|
|
| 1084 |
- # a scroll is already planned
|
|
| 1085 |
- return
|
|
| 1086 |
- buffer_ = textview.get_buffer()
|
|
| 1087 |
- end_iter = buffer_.get_end_iter()
|
|
| 1088 |
- end_rect = textview.get_iter_location(end_iter)
|
|
| 1089 |
- visible_rect = textview.get_visible_rect()
|
|
| 1090 |
- # scroll only if expected end is not visible
|
|
| 1091 |
- if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
|
|
| 1092 |
- self.scroll_to_end_id = GLib.idle_add(self.scroll_to_end_iter,
|
|
| 1093 |
- textview)
|
|
| 1094 |
- |
|
| 1095 |
- def scroll_to_end_iter(self, textview):
|
|
| 1096 |
- buffer_ = textview.get_buffer()
|
|
| 1097 |
- end_iter = buffer_.get_end_iter()
|
|
| 1098 |
- textview.scroll_to_iter(end_iter, 0, False, 1, 1)
|
|
| 1099 |
- self.scroll_to_end_id = None
|
|
| 1100 |
- return False
|
|
| 1101 |
- |
|
| 1102 |
- def on_configure_event(self, msg_textview, event):
|
|
| 1103 |
- """
|
|
| 1104 |
- When message_textview changes its size: if the new height will enlarge
|
|
| 1105 |
- the window, enable the scrollbar automatic policy. Also enable scrollbar
|
|
| 1106 |
- automatic policy for horizontal scrollbar if message we have in
|
|
| 1107 |
- message_textview is too big
|
|
| 1108 |
- """
|
|
| 1109 |
- if msg_textview.get_window() is None:
|
|
| 1110 |
- return
|
|
| 1111 |
- |
|
| 1112 |
- min_height = self.conv_scrolledwindow.get_property('height-request')
|
|
| 1113 |
- conversation_height = self.conv_textview.tv.get_window().get_size()[1]
|
|
| 1114 |
- message_height = msg_textview.get_window().get_size()[1]
|
|
| 1115 |
- message_width = msg_textview.get_window().get_size()[0]
|
|
| 1116 |
- # new tab is not exposed yet
|
|
| 1117 |
- if conversation_height < 2:
|
|
| 1118 |
- return
|
|
| 1119 |
- |
|
| 1120 |
- if conversation_height < min_height:
|
|
| 1121 |
- min_height = conversation_height
|
|
| 1122 |
- |
|
| 1123 |
- # we don't want to always resize in height the message_textview
|
|
| 1124 |
- # so we have minimum on conversation_textview's scrolled window
|
|
| 1125 |
- # but we also want to avoid window resizing so if we reach that
|
|
| 1126 |
- # minimum for conversation_textview and maximum for message_textview
|
|
| 1127 |
- # we set to automatic the scrollbar policy
|
|
| 1128 |
- diff_y = message_height - event.height
|
|
| 1129 |
- if diff_y != 0:
|
|
| 1130 |
- if conversation_height + diff_y < min_height:
|
|
| 1131 |
- if message_height + conversation_height - min_height > min_height:
|
|
| 1132 |
- policy = self.msg_scrolledwindow.get_property(
|
|
| 1133 |
- 'vscrollbar-policy')
|
|
| 1134 |
- if policy != Gtk.PolicyType.AUTOMATIC:
|
|
| 1135 |
- self.msg_scrolledwindow.set_property('vscrollbar-policy',
|
|
| 1136 |
- Gtk.PolicyType.AUTOMATIC)
|
|
| 1137 |
- self.msg_scrolledwindow.set_property('height-request',
|
|
| 1138 |
- message_height + conversation_height - min_height)
|
|
| 1139 |
- else:
|
|
| 1140 |
- self.msg_scrolledwindow.set_property('vscrollbar-policy',
|
|
| 1141 |
- Gtk.PolicyType.NEVER)
|
|
| 1142 |
- self.msg_scrolledwindow.set_property('height-request', -1)
|
|
| 1143 |
- |
|
| 1144 |
- self.smooth = True # reinit the flag
|
|
| 1145 |
- # enable scrollbar automatic policy for horizontal scrollbar
|
|
| 1146 |
- # if message we have in message_textview is too big
|
|
| 1147 |
- if event.width > message_width:
|
|
| 1148 |
- self.msg_scrolledwindow.set_property('hscrollbar-policy',
|
|
| 1149 |
- Gtk.PolicyType.AUTOMATIC)
|
|
| 1150 |
- else:
|
|
| 1151 |
- self.msg_scrolledwindow.set_property('hscrollbar-policy',
|
|
| 1152 |
- Gtk.PolicyType.NEVER)
|
|
| 1153 |
- |
|
| 1154 |
- return True
|
|
| 1155 |
- |
|
| 1156 |
- def on_conversation_vadjustment_changed(self, adjustment):
|
|
| 1157 |
- # used to stay at the end of the textview when we shrink conversation
|
|
| 1158 |
- # textview.
|
|
| 1159 |
- if self.was_at_the_end:
|
|
| 1160 |
- if self.conv_textview.at_the_end():
|
|
| 1161 |
- # we are at the end
|
|
| 1162 |
- self.conv_textview.bring_scroll_to_end(-18)
|
|
| 1163 |
- else:
|
|
| 1164 |
- self.conv_textview.bring_scroll_to_end(-18, use_smooth=False)
|
|
| 1165 |
- self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value()\
|
|
| 1166 |
- - adjustment.get_page_size()) < 18
|
|
| 1167 |
- |
|
| 1168 |
- def on_conversation_vadjustment_value_changed(self, adjustment):
|
|
| 1169 |
- # stop automatic scroll when we manually scroll
|
|
| 1170 |
- if not self.conv_textview.auto_scrolling:
|
|
| 1171 |
- self.conv_textview.stop_scrolling()
|
|
| 1172 |
- self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value() \
|
|
| 1173 |
- - adjustment.get_page_size()) < 18
|
|
| 1174 |
- if self.resource:
|
|
| 1175 |
- jid = self.contact.get_full_jid()
|
|
| 1176 |
- else:
|
|
| 1177 |
- jid = self.contact.jid
|
|
| 1178 |
- types_list = []
|
|
| 1179 |
- type_ = self.type_id
|
|
| 1180 |
- if type_ == message_control.TYPE_GC:
|
|
| 1181 |
- type_ = 'gc_msg'
|
|
| 1182 |
- types_list = ['printed_' + type_, type_, 'printed_marked_gc_msg']
|
|
| 1183 |
- else: # Not a GC
|
|
| 1184 |
- types_list = ['printed_' + type_, type_]
|
|
| 1185 |
- |
|
| 1186 |
- if not len(gajim.events.get_events(self.account, jid, types_list)):
|
|
| 1187 |
- return
|
|
| 1188 |
- if not self.parent_win:
|
|
| 1189 |
- return
|
|
| 1190 |
- if self.conv_textview.at_the_end() and \
|
|
| 1191 |
- self.parent_win.get_active_control() == self and \
|
|
| 1192 |
- self.parent_win.window.is_active():
|
|
| 1193 |
- # we are at the end
|
|
| 1194 |
- if self.type_id == message_control.TYPE_GC:
|
|
| 1195 |
- if not gajim.events.remove_events(self.account, jid,
|
|
| 1196 |
- types=types_list):
|
|
| 1197 |
- self.redraw_after_event_removed(jid)
|
|
| 1198 |
- elif self.session and self.session.remove_events(types_list):
|
|
| 1199 |
- # There were events to remove
|
|
| 1200 |
- self.redraw_after_event_removed(jid)
|
|
| 1201 |
- |
|
| 1202 |
- def redraw_after_event_removed(self, jid):
|
|
| 1203 |
- """
|
|
| 1204 |
- We just removed a 'printed_*' event, redraw contact in roster or
|
|
| 1205 |
- gc_roster and titles in roster and msg_win
|
|
| 1206 |
- """
|
|
| 1207 |
- self.parent_win.redraw_tab(self)
|
|
| 1208 |
- self.parent_win.show_title()
|
|
| 1209 |
- # TODO : get the contact and check notify.get_show_in_roster()
|
|
| 1210 |
- if self.type_id == message_control.TYPE_PM:
|
|
| 1211 |
- room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
|
|
| 1212 |
- groupchat_control = gajim.interface.msg_win_mgr.get_gc_control(
|
|
| 1213 |
- room_jid, self.account)
|
|
| 1214 |
- if room_jid in gajim.interface.minimized_controls[self.account]:
|
|
| 1215 |
- groupchat_control = \
|
|
| 1216 |
- gajim.interface.minimized_controls[self.account][room_jid]
|
|
| 1217 |
- contact = gajim.contacts.get_contact_with_highest_priority(
|
|
| 1218 |
- self.account, room_jid)
|
|
| 1219 |
- if contact:
|
|
| 1220 |
- gajim.interface.roster.draw_contact(room_jid, self.account)
|
|
| 1221 |
- if groupchat_control:
|
|
| 1222 |
- groupchat_control.draw_contact(nick)
|
|
| 1223 |
- if groupchat_control.parent_win:
|
|
| 1224 |
- groupchat_control.parent_win.redraw_tab(groupchat_control)
|
|
| 1225 |
- else:
|
|
| 1226 |
- gajim.interface.roster.draw_contact(jid, self.account)
|
|
| 1227 |
- gajim.interface.roster.show_title()
|
|
| 1228 |
- |
|
| 1229 |
- def scroll_messages(self, direction, msg_buf, msg_type):
|
|
| 1230 |
- if msg_type == 'sent':
|
|
| 1231 |
- history = self.sent_history
|
|
| 1232 |
- pos = self.sent_history_pos
|
|
| 1233 |
- self.received_history_pos = len(self.received_history)
|
|
| 1234 |
- else:
|
|
| 1235 |
- history = self.received_history
|
|
| 1236 |
- pos = self.received_history_pos
|
|
| 1237 |
- self.sent_history_pos = len(self.sent_history)
|
|
| 1238 |
- size = len(history)
|
|
| 1239 |
- if self.orig_msg is None:
|
|
| 1240 |
- # user was typing something and then went into history, so save
|
|
| 1241 |
- # whatever is already typed
|
|
| 1242 |
- start_iter = msg_buf.get_start_iter()
|
|
| 1243 |
- end_iter = msg_buf.get_end_iter()
|
|
| 1244 |
- self.orig_msg = msg_buf.get_text(start_iter, end_iter, False)
|
|
| 1245 |
- if pos == size and size > 0 and direction == 'up' and \
|
|
| 1246 |
- msg_type == 'sent' and not self.correcting and (not \
|
|
| 1247 |
- history[pos - 1].startswith('/') or history[pos - 1].startswith('/me')):
|
|
| 1248 |
- self.correcting = True
|
|
| 1249 |
- context = self.msg_textview.get_style_context()
|
|
| 1250 |
- state = Gtk.StateFlags.NORMAL
|
|
| 1251 |
- self.old_message_tv_color = context.get_background_color(state)
|
|
| 1252 |
- color = Gdk.RGBA()
|
|
| 1253 |
- Gdk.RGBA.parse(color, 'PaleGoldenrod')
|
|
| 1254 |
- self.msg_textview.override_background_color(Gtk.StateType.NORMAL,
|
|
| 1255 |
- color)
|
|
| 1256 |
- message = history[pos - 1]
|
|
| 1257 |
- msg_buf.set_text(message)
|
|
| 1258 |
- return
|
|
| 1259 |
- if self.correcting:
|
|
| 1260 |
- # We were previously correcting
|
|
| 1261 |
- self.msg_textview.override_background_color(Gtk.StateType.NORMAL,
|
|
| 1262 |
- self.old_message_tv_color)
|
|
| 1263 |
- self.correcting = False
|
|
| 1264 |
- pos += -1 if direction == 'up' else +1
|
|
| 1265 |
- if pos == -1:
|
|
| 1266 |
- return
|
|
| 1267 |
- if pos >= size:
|
|
| 1268 |
- pos = size
|
|
| 1269 |
- message = self.orig_msg
|
|
| 1270 |
- self.orig_msg = None
|
|
| 1271 |
- else:
|
|
| 1272 |
- message = history[pos]
|
|
| 1273 |
- if msg_type == 'sent':
|
|
| 1274 |
- self.sent_history_pos = pos
|
|
| 1275 |
- else:
|
|
| 1276 |
- self.received_history_pos = pos
|
|
| 1277 |
- if self.orig_msg is not None:
|
|
| 1278 |
- message = '> %s\n' % message.replace('\n', '\n> ')
|
|
| 1279 |
- msg_buf.set_text(message)
|
|
| 1280 |
- |
|
| 1281 |
- def lighten_color(self, color):
|
|
| 1282 |
- p = 0.4
|
|
| 1283 |
- mask = 0
|
|
| 1284 |
- color.red = int((color.red * p) + (mask * (1 - p)))
|
|
| 1285 |
- color.green = int((color.green * p) + (mask * (1 - p)))
|
|
| 1286 |
- color.blue = int((color.blue * p) + (mask * (1 - p)))
|
|
| 1287 |
- return color
|
|
| 1288 |
- |
|
| 1289 |
- def widget_set_visible(self, widget, state):
|
|
| 1290 |
- """
|
|
| 1291 |
- Show or hide a widget
|
|
| 1292 |
- """
|
|
| 1293 |
- # make the last message visible, when changing to "full view"
|
|
| 1294 |
- if not state:
|
|
| 1295 |
- GLib.idle_add(self.conv_textview.scroll_to_end_iter)
|
|
| 1296 |
- |
|
| 1297 |
- widget.set_no_show_all(state)
|
|
| 1298 |
- if state:
|
|
| 1299 |
- widget.hide()
|
|
| 1300 |
- else:
|
|
| 1301 |
- widget.show_all()
|
|
| 1302 |
- |
|
| 1303 |
- def chat_buttons_set_visible(self, state):
|
|
| 1304 |
- """
|
|
| 1305 |
- Toggle chat buttons
|
|
| 1306 |
- """
|
|
| 1307 |
- MessageControl.chat_buttons_set_visible(self, state)
|
|
| 1308 |
- self.widget_set_visible(self.xml.get_object('actions_hbox'), state)
|
|
| 1309 |
- |
|
| 1310 |
- def got_connected(self):
|
|
| 1311 |
- self.msg_textview.set_sensitive(True)
|
|
| 1312 |
- self.msg_textview.set_editable(True)
|
|
| 1313 |
- self.update_toolbar()
|
|
| 1314 |
- |
|
| 1315 |
- def got_disconnected(self):
|
|
| 1316 |
- self.msg_textview.set_sensitive(False)
|
|
| 1317 |
- self.msg_textview.set_editable(False)
|
|
| 1318 |
- self.conv_textview.tv.grab_focus()
|
|
| 1319 |
- |
|
| 1320 |
- self.no_autonegotiation = False
|
|
| 1321 |
- self.update_toolbar()
|
|
| 1322 |
- |
|
| 66 |
+from chat_control_base import ChatControlBase
|
|
| 1323 | 67 |
|
| 1324 | 68 |
################################################################################
|
| 1325 | 69 |
class ChatControl(ChatControlBase):
|
| 1 |
+# -*- coding:utf-8 -*-
|
|
| 2 |
+## src/chat_control_base.py
|
|
| 3 |
+##
|
|
| 4 |
+## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
|
|
| 5 |
+## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
|
|
| 6 |
+## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
|
|
| 7 |
+## Nikos Kouremenos <kourem AT gmail.com>
|
|
| 8 |
+## Travis Shirk <travis AT pobox.com>
|
|
| 9 |
+## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
|
|
| 10 |
+## Julien Pivotto <roidelapluie AT gmail.com>
|
|
| 11 |
+## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
|
|
| 12 |
+## Stephan Erb <steve-e AT h3c.de>
|
|
| 13 |
+## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
|
|
| 14 |
+##
|
|
| 15 |
+## This file is part of Gajim.
|
|
| 16 |
+##
|
|
| 17 |
+## Gajim is free software; you can redistribute it and/or modify
|
|
| 18 |
+## it under the terms of the GNU General Public License as published
|
|
| 19 |
+## by the Free Software Foundation; version 3 only.
|
|
| 20 |
+##
|
|
| 21 |
+## Gajim is distributed in the hope that it will be useful,
|
|
| 22 |
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 23 |
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 24 |
+## GNU General Public License for more details.
|
|
| 25 |
+##
|
|
| 26 |
+## You should have received a copy of the GNU General Public License
|
|
| 27 |
+## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
| 28 |
+##
|
|
| 29 |
+ |
|
| 30 |
+import os
|
|
| 31 |
+import time
|
|
| 32 |
+from gi.repository import Gtk
|
|
| 33 |
+from gi.repository import Gdk
|
|
| 34 |
+from gi.repository import Pango
|
|
| 35 |
+from gi.repository import GObject
|
|
| 36 |
+from gi.repository import GLib
|
|
| 37 |
+import gtkgui_helpers
|
|
| 38 |
+import message_control
|
|
| 39 |
+import dialogs
|
|
| 40 |
+import history_window
|
|
| 41 |
+import notify
|
|
| 42 |
+import re
|
|
| 43 |
+ |
|
| 44 |
+from common import events
|
|
| 45 |
+from common import gajim
|
|
| 46 |
+from common import helpers
|
|
| 47 |
+from common import ged
|
|
| 48 |
+from message_control import MessageControl
|
|
| 49 |
+from conversation_textview import ConversationTextview
|
|
| 50 |
+from message_textview import MessageTextView
|
|
| 51 |
+from common.contacts import GC_Contact
|
|
| 52 |
+from common.connection_handlers_events import MessageOutgoingEvent
|
|
| 53 |
+ |
|
| 54 |
+from command_system.implementation.middleware import ChatCommandProcessor
|
|
| 55 |
+from command_system.implementation.middleware import CommandTools
|
|
| 56 |
+ |
|
| 57 |
+try:
|
|
| 58 |
+ import gtkspell
|
|
| 59 |
+ HAS_GTK_SPELL = True
|
|
| 60 |
+except (ImportError, ValueError):
|
|
| 61 |
+ HAS_GTK_SPELL = False
|
|
| 62 |
+ |
|
| 63 |
+################################################################################
|
|
| 64 |
+class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|
| 65 |
+ """
|
|
| 66 |
+ A base class containing a banner, ConversationTextview, MessageTextView
|
|
| 67 |
+ """
|
|
| 68 |
+ |
|
| 69 |
+ keymap = Gdk.Keymap.get_default()
|
|
| 70 |
+ try:
|
|
| 71 |
+ keycode_c = keymap.get_entries_for_keyval(Gdk.KEY_c)[1][0].keycode
|
|
| 72 |
+ except TypeError:
|
|
| 73 |
+ keycode_c = 54
|
|
| 74 |
+ try:
|
|
| 75 |
+ keycode_ins = keymap.get_entries_for_keyval(Gdk.KEY_Insert)[1][0].keycode
|
|
| 76 |
+ except TypeError:
|
|
| 77 |
+ keycode_ins = 118
|
|
| 78 |
+ |
|
| 79 |
+ def make_href(self, match):
|
|
| 80 |
+ url_color = gajim.config.get('urlmsgcolor')
|
|
| 81 |
+ url = match.group()
|
|
| 82 |
+ if not '://' in url:
|
|
| 83 |
+ url = 'http://' + url
|
|
| 84 |
+ return '<a href="%s"><span color="%s">%s</span></a>' % (url,
|
|
| 85 |
+ url_color, match.group())
|
|
| 86 |
+ |
|
| 87 |
+ def get_font_attrs(self):
|
|
| 88 |
+ """
|
|
| 89 |
+ Get pango font attributes for banner from theme settings
|
|
| 90 |
+ """
|
|
| 91 |
+ theme = gajim.config.get('roster_theme')
|
|
| 92 |
+ bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
|
|
| 93 |
+ bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs')
|
|
| 94 |
+ |
|
| 95 |
+ if bannerfont:
|
|
| 96 |
+ font = Pango.FontDescription(bannerfont)
|
|
| 97 |
+ else:
|
|
| 98 |
+ font = Pango.FontDescription('Normal')
|
|
| 99 |
+ if bannerfontattrs:
|
|
| 100 |
+ # B attribute is set by default
|
|
| 101 |
+ if 'B' in bannerfontattrs:
|
|
| 102 |
+ font.set_weight(Pango.Weight.HEAVY)
|
|
| 103 |
+ if 'I' in bannerfontattrs:
|
|
| 104 |
+ font.set_style(Pango.Style.ITALIC)
|
|
| 105 |
+ |
|
| 106 |
+ font_attrs = 'font_desc="%s"' % font.to_string()
|
|
| 107 |
+ |
|
| 108 |
+ # in case there is no font specified we use x-large font size
|
|
| 109 |
+ if font.get_size() == 0:
|
|
| 110 |
+ font_attrs = '%s size="x-large"' % font_attrs
|
|
| 111 |
+ font.set_weight(Pango.Weight.NORMAL)
|
|
| 112 |
+ font_attrs_small = 'font_desc="%s" size="small"' % font.to_string()
|
|
| 113 |
+ return (font_attrs, font_attrs_small)
|
|
| 114 |
+ |
|
| 115 |
+ def get_nb_unread(self):
|
|
| 116 |
+ jid = self.contact.jid
|
|
| 117 |
+ if self.resource:
|
|
| 118 |
+ jid += '/' + self.resource
|
|
| 119 |
+ type_ = self.type_id
|
|
| 120 |
+ return len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
|
|
| 121 |
+ type_]))
|
|
| 122 |
+ |
|
| 123 |
+ def draw_banner(self):
|
|
| 124 |
+ """
|
|
| 125 |
+ Draw the fat line at the top of the window that houses the icon, jid, etc
|
|
| 126 |
+ |
|
| 127 |
+ Derived types MAY implement this.
|
|
| 128 |
+ """
|
|
| 129 |
+ self.draw_banner_text()
|
|
| 130 |
+ self._update_banner_state_image()
|
|
| 131 |
+ gajim.plugin_manager.gui_extension_point('chat_control_base_draw_banner',
|
|
| 132 |
+ self)
|
|
| 133 |
+ |
|
| 134 |
+ def update_toolbar(self):
|
|
| 135 |
+ """
|
|
| 136 |
+ update state of buttons in toolbar
|
|
| 137 |
+ """
|
|
| 138 |
+ self._update_toolbar()
|
|
| 139 |
+ gajim.plugin_manager.gui_extension_point(
|
|
| 140 |
+ 'chat_control_base_update_toolbar', self)
|
|
| 141 |
+ |
|
| 142 |
+ def draw_banner_text(self):
|
|
| 143 |
+ """
|
|
| 144 |
+ Derived types SHOULD implement this
|
|
| 145 |
+ """
|
|
| 146 |
+ pass
|
|
| 147 |
+ |
|
| 148 |
+ def update_ui(self):
|
|
| 149 |
+ """
|
|
| 150 |
+ Derived types SHOULD implement this
|
|
| 151 |
+ """
|
|
| 152 |
+ self.draw_banner()
|
|
| 153 |
+ |
|
| 154 |
+ def repaint_themed_widgets(self):
|
|
| 155 |
+ """
|
|
| 156 |
+ Derived types MAY implement this
|
|
| 157 |
+ """
|
|
| 158 |
+ self._paint_banner()
|
|
| 159 |
+ self.draw_banner()
|
|
| 160 |
+ |
|
| 161 |
+ def _update_banner_state_image(self):
|
|
| 162 |
+ """
|
|
| 163 |
+ Derived types MAY implement this
|
|
| 164 |
+ """
|
|
| 165 |
+ pass
|
|
| 166 |
+ |
|
| 167 |
+ def _update_toolbar(self):
|
|
| 168 |
+ """
|
|
| 169 |
+ Derived types MAY implement this
|
|
| 170 |
+ """
|
|
| 171 |
+ pass
|
|
| 172 |
+ |
|
| 173 |
+ def _nec_our_status(self, obj):
|
|
| 174 |
+ if self.account != obj.conn.name:
|
|
| 175 |
+ return
|
|
| 176 |
+ if obj.show == 'offline' or (obj.show == 'invisible' and \
|
|
| 177 |
+ obj.conn.is_zeroconf):
|
|
| 178 |
+ self.got_disconnected()
|
|
| 179 |
+ else:
|
|
| 180 |
+ # Other code rejoins all GCs, so we don't do it here
|
|
| 181 |
+ if not self.type_id == message_control.TYPE_GC:
|
|
| 182 |
+ self.got_connected()
|
|
| 183 |
+ if self.parent_win:
|
|
| 184 |
+ self.parent_win.redraw_tab(self)
|
|
| 185 |
+ |
|
| 186 |
+ def _nec_ping_sent(self, obj):
|
|
| 187 |
+ if self.contact != obj.contact:
|
|
| 188 |
+ return
|
|
| 189 |
+ self.print_conversation(_('Ping?'), 'status')
|
|
| 190 |
+ |
|
| 191 |
+ def _nec_ping_error(self, obj):
|
|
| 192 |
+ if self.contact != obj.contact:
|
|
| 193 |
+ return
|
|
| 194 |
+ self.print_conversation(_('Error.'), 'status')
|
|
| 195 |
+ |
|
| 196 |
+ def status_url_clicked(self, widget, url):
|
|
| 197 |
+ helpers.launch_browser_mailer('url', url)
|
|
| 198 |
+ |
|
| 199 |
+ def setup_seclabel(self, combo):
|
|
| 200 |
+ self.seclabel_combo = combo
|
|
| 201 |
+ self.seclabel_combo.hide()
|
|
| 202 |
+ self.seclabel_combo.set_no_show_all(True)
|
|
| 203 |
+ lb = Gtk.ListStore(str)
|
|
| 204 |
+ self.seclabel_combo.set_model(lb)
|
|
| 205 |
+ cell = Gtk.CellRendererText()
|
|
| 206 |
+ cell.set_property('xpad', 5) # padding for status text
|
|
| 207 |
+ self.seclabel_combo.pack_start(cell, True)
|
|
| 208 |
+ # text to show is in in first column of liststore
|
|
| 209 |
+ self.seclabel_combo.add_attribute(cell, 'text', 0)
|
|
| 210 |
+ if gajim.connections[self.account].seclabel_supported:
|
|
| 211 |
+ gajim.connections[self.account].seclabel_catalogue(self.contact.jid, self.on_seclabels_ready)
|
|
| 212 |
+ |
|
| 213 |
+ def on_seclabels_ready(self):
|
|
| 214 |
+ lb = self.seclabel_combo.get_model()
|
|
| 215 |
+ lb.clear()
|
|
| 216 |
+ i = 0
|
|
| 217 |
+ sel = 0
|
|
| 218 |
+ catalogue = gajim.connections[self.account].seclabel_catalogues[
|
|
| 219 |
+ self.contact.jid]
|
|
| 220 |
+ for label in catalogue[2]:
|
|
| 221 |
+ lb.append([label])
|
|
| 222 |
+ if label == catalogue[3]:
|
|
| 223 |
+ sel = i
|
|
| 224 |
+ i += 1
|
|
| 225 |
+ self.seclabel_combo.set_active(sel)
|
|
| 226 |
+ self.seclabel_combo.set_no_show_all(False)
|
|
| 227 |
+ self.seclabel_combo.show_all()
|
|
| 228 |
+ |
|
| 229 |
+ def __init__(self, type_id, parent_win, widget_name, contact, acct,
|
|
| 230 |
+ resource=None):
|
|
| 231 |
+ # Undo needs this variable to know if space has been pressed.
|
|
| 232 |
+ # Initialize it to True so empty textview is saved in undo list
|
|
| 233 |
+ self.space_pressed = True
|
|
| 234 |
+ |
|
| 235 |
+ if resource is None:
|
|
| 236 |
+ # We very likely got a contact with a random resource.
|
|
| 237 |
+ # This is bad, we need the highest for caps etc.
|
|
| 238 |
+ c = gajim.contacts.get_contact_with_highest_priority(acct,
|
|
| 239 |
+ contact.jid)
|
|
| 240 |
+ if c and not isinstance(c, GC_Contact):
|
|
| 241 |
+ contact = c
|
|
| 242 |
+ |
|
| 243 |
+ MessageControl.__init__(self, type_id, parent_win, widget_name,
|
|
| 244 |
+ contact, acct, resource=resource)
|
|
| 245 |
+ |
|
| 246 |
+ widget = self.xml.get_object('history_button')
|
|
| 247 |
+ # set document-open-recent icon for history button
|
|
| 248 |
+ if gtkgui_helpers.gtk_icon_theme.has_icon('document-open-recent'):
|
|
| 249 |
+ img = self.xml.get_object('history_image')
|
|
| 250 |
+ img.set_from_icon_name('document-open-recent', Gtk.IconSize.MENU)
|
|
| 251 |
+ |
|
| 252 |
+ id_ = widget.connect('clicked', self._on_history_menuitem_activate)
|
|
| 253 |
+ self.handlers[id_] = widget
|
|
| 254 |
+ |
|
| 255 |
+ # when/if we do XHTML we will put formatting buttons back
|
|
| 256 |
+ widget = self.xml.get_object('emoticons_button')
|
|
| 257 |
+ widget.set_sensitive(False)
|
|
| 258 |
+ id_ = widget.connect('clicked', self.on_emoticons_button_clicked)
|
|
| 259 |
+ self.handlers[id_] = widget
|
|
| 260 |
+ |
|
| 261 |
+ # Create banner and connect signals
|
|
| 262 |
+ widget = self.xml.get_object('banner_eventbox')
|
|
| 263 |
+ id_ = widget.connect('button-press-event',
|
|
| 264 |
+ self._on_banner_eventbox_button_press_event)
|
|
| 265 |
+ self.handlers[id_] = widget
|
|
| 266 |
+ |
|
| 267 |
+ self.urlfinder = re.compile(
|
|
| 268 |
+ r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]")
|
|
| 269 |
+ |
|
| 270 |
+ self.banner_status_label = self.xml.get_object('banner_label')
|
|
| 271 |
+ id_ = self.banner_status_label.connect('populate_popup',
|
|
| 272 |
+ self.on_banner_label_populate_popup)
|
|
| 273 |
+ self.handlers[id_] = self.banner_status_label
|
|
| 274 |
+ |
|
| 275 |
+ # Init DND
|
|
| 276 |
+ self.TARGET_TYPE_URI_LIST = 80
|
|
| 277 |
+ self.dnd_list = [Gtk.TargetEntry.new('text/uri-list', 0,
|
|
| 278 |
+ self.TARGET_TYPE_URI_LIST), Gtk.TargetEntry.new('MY_TREE_MODEL_ROW',
|
|
| 279 |
+ Gtk.TargetFlags.SAME_APP, 0)]
|
|
| 280 |
+ id_ = self.widget.connect('drag_data_received',
|
|
| 281 |
+ self._on_drag_data_received)
|
|
| 282 |
+ self.handlers[id_] = self.widget
|
|
| 283 |
+ self.widget.drag_dest_set(Gtk.DestDefaults.MOTION |
|
|
| 284 |
+ Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
|
|
| 285 |
+ self.dnd_list, Gdk.DragAction.COPY)
|
|
| 286 |
+ |
|
| 287 |
+ # Create textviews and connect signals
|
|
| 288 |
+ self.conv_textview = ConversationTextview(self.account)
|
|
| 289 |
+ id_ = self.conv_textview.connect('quote', self.on_quote)
|
|
| 290 |
+ self.handlers[id_] = self.conv_textview.tv
|
|
| 291 |
+ id_ = self.conv_textview.tv.connect('key_press_event',
|
|
| 292 |
+ self._conv_textview_key_press_event)
|
|
| 293 |
+ self.handlers[id_] = self.conv_textview.tv
|
|
| 294 |
+ # FIXME: DND on non editable TextView, find a better way
|
|
| 295 |
+ self.drag_entered = False
|
|
| 296 |
+ id_ = self.conv_textview.tv.connect('drag_data_received',
|
|
| 297 |
+ self._on_drag_data_received)
|
|
| 298 |
+ self.handlers[id_] = self.conv_textview.tv
|
|
| 299 |
+ id_ = self.conv_textview.tv.connect('drag_motion', self._on_drag_motion)
|
|
| 300 |
+ self.handlers[id_] = self.conv_textview.tv
|
|
| 301 |
+ id_ = self.conv_textview.tv.connect('drag_leave', self._on_drag_leave)
|
|
| 302 |
+ self.handlers[id_] = self.conv_textview.tv
|
|
| 303 |
+ self.conv_textview.tv.drag_dest_set(Gtk.DestDefaults.MOTION |
|
|
| 304 |
+ Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
|
|
| 305 |
+ self.dnd_list, Gdk.DragAction.COPY)
|
|
| 306 |
+ |
|
| 307 |
+ self.conv_scrolledwindow = self.xml.get_object(
|
|
| 308 |
+ 'conversation_scrolledwindow')
|
|
| 309 |
+ self.conv_scrolledwindow.add(self.conv_textview.tv)
|
|
| 310 |
+ widget = self.conv_scrolledwindow.get_vadjustment()
|
|
| 311 |
+ id_ = widget.connect('value-changed',
|
|
| 312 |
+ self.on_conversation_vadjustment_value_changed)
|
|
| 313 |
+ self.handlers[id_] = widget
|
|
| 314 |
+ id_ = widget.connect('changed',
|
|
| 315 |
+ self.on_conversation_vadjustment_changed)
|
|
| 316 |
+ self.handlers[id_] = widget
|
|
| 317 |
+ self.scroll_to_end_id = None
|
|
| 318 |
+ self.was_at_the_end = True
|
|
| 319 |
+ self.correcting = False
|
|
| 320 |
+ self.last_sent_msg = None
|
|
| 321 |
+ self.last_received_txt = {} # one per name
|
|
| 322 |
+ self.last_received_id = {} # one per name
|
|
| 323 |
+ |
|
| 324 |
+ # add MessageTextView to UI and connect signals
|
|
| 325 |
+ self.msg_scrolledwindow = self.xml.get_object('message_scrolledwindow')
|
|
| 326 |
+ self.msg_textview = MessageTextView()
|
|
| 327 |
+ self.msg_scrolledwindow.add(self.msg_textview)
|
|
| 328 |
+ id_ = self.msg_textview.connect('key_press_event',
|
|
| 329 |
+ self._on_message_textview_key_press_event)
|
|
| 330 |
+ self.handlers[id_] = self.msg_textview
|
|
| 331 |
+ id_ = self.msg_textview.connect('configure-event',
|
|
| 332 |
+ self.on_configure_event)
|
|
| 333 |
+ self.handlers[id_] = self.msg_textview
|
|
| 334 |
+ id_ = self.msg_textview.connect('populate_popup',
|
|
| 335 |
+ self.on_msg_textview_populate_popup)
|
|
| 336 |
+ self.handlers[id_] = self.msg_textview
|
|
| 337 |
+ # Setup DND
|
|
| 338 |
+ id_ = self.msg_textview.connect('drag_data_received',
|
|
| 339 |
+ self._on_drag_data_received)
|
|
| 340 |
+ self.handlers[id_] = self.msg_textview
|
|
| 341 |
+ self.msg_textview.drag_dest_set(Gtk.DestDefaults.MOTION |
|
|
| 342 |
+ Gtk.DestDefaults.HIGHLIGHT, self.dnd_list, Gdk.DragAction.COPY)
|
|
| 343 |
+ |
|
| 344 |
+ self.update_font()
|
|
| 345 |
+ |
|
| 346 |
+ # Hook up send button
|
|
| 347 |
+ widget = self.xml.get_object('send_button')
|
|
| 348 |
+ id_ = widget.connect('clicked', self._on_send_button_clicked)
|
|
| 349 |
+ widget.set_sensitive(False)
|
|
| 350 |
+ self.handlers[id_] = widget
|
|
| 351 |
+ |
|
| 352 |
+ # the following vars are used to keep history of user's messages
|
|
| 353 |
+ self.sent_history = []
|
|
| 354 |
+ self.sent_history_pos = 0
|
|
| 355 |
+ self.received_history = []
|
|
| 356 |
+ self.received_history_pos = 0
|
|
| 357 |
+ self.orig_msg = None
|
|
| 358 |
+ |
|
| 359 |
+ # Emoticons menu
|
|
| 360 |
+ # set image no matter if user wants at this time emoticons or not
|
|
| 361 |
+ # (so toggle works ok)
|
|
| 362 |
+ img = self.xml.get_object('emoticons_button_image')
|
|
| 363 |
+ img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
|
|
| 364 |
+ 'smile.png'))
|
|
| 365 |
+ self.toggle_emoticons()
|
|
| 366 |
+ |
|
| 367 |
+ # Attach speller
|
|
| 368 |
+ if gajim.config.get('use_speller') and HAS_GTK_SPELL:
|
|
| 369 |
+ self.set_speller()
|
|
| 370 |
+ self.conv_textview.tv.show()
|
|
| 371 |
+ self._paint_banner()
|
|
| 372 |
+ |
|
| 373 |
+ # For XEP-0172
|
|
| 374 |
+ self.user_nick = None
|
|
| 375 |
+ |
|
| 376 |
+ self.smooth = True
|
|
| 377 |
+ |
|
| 378 |
+ self.command_hits = []
|
|
| 379 |
+ self.last_key_tabs = False
|
|
| 380 |
+ |
|
| 381 |
+ # PluginSystem: adding GUI extension point for ChatControlBase
|
|
| 382 |
+ # instance object (also subclasses, eg. ChatControl or GroupchatControl)
|
|
| 383 |
+ gajim.plugin_manager.gui_extension_point('chat_control_base', self)
|
|
| 384 |
+ |
|
| 385 |
+ gajim.ged.register_event_handler('our-show', ged.GUI1,
|
|
| 386 |
+ self._nec_our_status)
|
|
| 387 |
+ gajim.ged.register_event_handler('ping-sent', ged.GUI1,
|
|
| 388 |
+ self._nec_ping_sent)
|
|
| 389 |
+ gajim.ged.register_event_handler('ping-reply', ged.GUI1,
|
|
| 390 |
+ self._nec_ping_reply)
|
|
| 391 |
+ gajim.ged.register_event_handler('ping-error', ged.GUI1,
|
|
| 392 |
+ self._nec_ping_error)
|
|
| 393 |
+ |
|
| 394 |
+ # This is bascially a very nasty hack to surpass the inability
|
|
| 395 |
+ # to properly use the super, because of the old code.
|
|
| 396 |
+ CommandTools.__init__(self)
|
|
| 397 |
+ |
|
| 398 |
+ def set_speller(self):
|
|
| 399 |
+ # now set the one the user selected
|
|
| 400 |
+ per_type = 'contacts'
|
|
| 401 |
+ if self.type_id == message_control.TYPE_GC:
|
|
| 402 |
+ per_type = 'rooms'
|
|
| 403 |
+ lang = gajim.config.get_per(per_type, self.contact.jid,
|
|
| 404 |
+ 'speller_language')
|
|
| 405 |
+ if not lang:
|
|
| 406 |
+ # use the default one
|
|
| 407 |
+ lang = gajim.config.get('speller_language')
|
|
| 408 |
+ if not lang:
|
|
| 409 |
+ lang = gajim.LANG
|
|
| 410 |
+ if lang:
|
|
| 411 |
+ try:
|
|
| 412 |
+ self.spell = gtkspell.Spell(self.msg_textview, lang)
|
|
| 413 |
+ self.msg_textview.lang = lang
|
|
| 414 |
+ self.spell.connect('language_changed', self.on_language_changed)
|
|
| 415 |
+ except (GObject.GError, RuntimeError, TypeError, OSError):
|
|
| 416 |
+ dialogs.AspellDictError(lang)
|
|
| 417 |
+ |
|
| 418 |
+ def on_language_changed(self, spell, lang):
|
|
| 419 |
+ per_type = 'contacts'
|
|
| 420 |
+ if self.type_id == message_control.TYPE_GC:
|
|
| 421 |
+ per_type = 'rooms'
|
|
| 422 |
+ if not gajim.config.get_per(per_type, self.contact.jid):
|
|
| 423 |
+ gajim.config.add_per(per_type, self.contact.jid)
|
|
| 424 |
+ gajim.config.set_per(per_type, self.contact.jid, 'speller_language',
|
|
| 425 |
+ lang)
|
|
| 426 |
+ self.msg_textview.lang = lang
|
|
| 427 |
+ |
|
| 428 |
+ def on_banner_label_populate_popup(self, label, menu):
|
|
| 429 |
+ """
|
|
| 430 |
+ Override the default context menu and add our own menutiems
|
|
| 431 |
+ """
|
|
| 432 |
+ item = Gtk.SeparatorMenuItem.new()
|
|
| 433 |
+ menu.prepend(item)
|
|
| 434 |
+ |
|
| 435 |
+ menu2 = self.prepare_context_menu()
|
|
| 436 |
+ i = 0
|
|
| 437 |
+ for item in menu2:
|
|
| 438 |
+ menu2.remove(item)
|
|
| 439 |
+ menu.prepend(item)
|
|
| 440 |
+ menu.reorder_child(item, i)
|
|
| 441 |
+ i += 1
|
|
| 442 |
+ menu.show_all()
|
|
| 443 |
+ |
|
| 444 |
+ def shutdown(self):
|
|
| 445 |
+ super(ChatControlBase, self).shutdown()
|
|
| 446 |
+ # PluginSystem: removing GUI extension points connected with ChatControlBase
|
|
| 447 |
+ # instance object
|
|
| 448 |
+ gajim.plugin_manager.remove_gui_extension_point('chat_control_base',
|
|
| 449 |
+ self)
|
|
| 450 |
+ gajim.plugin_manager.remove_gui_extension_point(
|
|
| 451 |
+ 'chat_control_base_draw_banner', self)
|
|
| 452 |
+ gajim.plugin_manager.remove_gui_extension_point('print_special_text',
|
|
| 453 |
+ self)
|
|
| 454 |
+ gajim.ged.remove_event_handler('our-show', ged.GUI1,
|
|
| 455 |
+ self._nec_our_status)
|
|
| 456 |
+ |
|
| 457 |
+ def on_msg_textview_populate_popup(self, textview, menu):
|
|
| 458 |
+ """
|
|
| 459 |
+ Override the default context menu and we prepend an option to switch
|
|
| 460 |
+ languages
|
|
| 461 |
+ """
|
|
| 462 |
+ item = Gtk.MenuItem.new_with_mnemonic(_('_Undo'))
|
|
| 463 |
+ menu.prepend(item)
|
|
| 464 |
+ id_ = item.connect('activate', self.msg_textview.undo)
|
|
| 465 |
+ self.handlers[id_] = item
|
|
| 466 |
+ |
|
| 467 |
+ item = Gtk.SeparatorMenuItem.new()
|
|
| 468 |
+ menu.prepend(item)
|
|
| 469 |
+ |
|
| 470 |
+ item = Gtk.MenuItem.new_with_mnemonic(_('_Clear'))
|
|
| 471 |
+ menu.prepend(item)
|
|
| 472 |
+ id_ = item.connect('activate', self.msg_textview.clear)
|
|
| 473 |
+ self.handlers[id_] = item
|
|
| 474 |
+ |
|
| 475 |
+ menu.show_all()
|
|
| 476 |
+ |
|
| 477 |
+ def on_quote(self, widget, text):
|
|
| 478 |
+ text = '>' + text.replace('\n', '\n>') + '\n'
|
|
| 479 |
+ message_buffer = self.msg_textview.get_buffer()
|
|
| 480 |
+ message_buffer.insert_at_cursor(text)
|
|
| 481 |
+ |
|
| 482 |
+ # moved from ChatControl
|
|
| 483 |
+ def _on_banner_eventbox_button_press_event(self, widget, event):
|
|
| 484 |
+ """
|
|
| 485 |
+ If right-clicked, show popup
|
|
| 486 |
+ """
|
|
| 487 |
+ if event.button == 3: # right click
|
|
| 488 |
+ self.parent_win.popup_menu(event)
|
|
| 489 |
+ |
|
| 490 |
+ def _on_send_button_clicked(self, widget):
|
|
| 491 |
+ """
|
|
| 492 |
+ When send button is pressed: send the current message
|
|
| 493 |
+ """
|
|
| 494 |
+ message_buffer = self.msg_textview.get_buffer()
|
|
| 495 |
+ start_iter = message_buffer.get_start_iter()
|
|
| 496 |
+ end_iter = message_buffer.get_end_iter()
|
|
| 497 |
+ message = message_buffer.get_text(start_iter, end_iter, False)
|
|
| 498 |
+ xhtml = self.msg_textview.get_xhtml()
|
|
| 499 |
+ |
|
| 500 |
+ # send the message
|
|
| 501 |
+ self.send_message(message, xhtml=xhtml)
|
|
| 502 |
+ |
|
| 503 |
+ def _paint_banner(self):
|
|
| 504 |
+ """
|
|
| 505 |
+ Repaint banner with theme color
|
|
| 506 |
+ """
|
|
| 507 |
+ theme = gajim.config.get('roster_theme')
|
|
| 508 |
+ bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
|
|
| 509 |
+ textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
|
|
| 510 |
+ # the backgrounds are colored by using an eventbox by
|
|
| 511 |
+ # setting the bg color of the eventbox and the fg of the name_label
|
|
| 512 |
+ banner_eventbox = self.xml.get_object('banner_eventbox')
|
|
| 513 |
+ banner_name_label = self.xml.get_object('banner_name_label')
|
|
| 514 |
+ self.disconnect_style_event(banner_name_label)
|
|
| 515 |
+ self.disconnect_style_event(self.banner_status_label)
|
|
| 516 |
+ if bgcolor:
|
|
| 517 |
+ color = Gdk.RGBA()
|
|
| 518 |
+ Gdk.RGBA.parse(color, bgcolor)
|
|
| 519 |
+ banner_eventbox.override_background_color(Gtk.StateType.NORMAL,
|
|
| 520 |
+ color)
|
|
| 521 |
+ default_bg = False
|
|
| 522 |
+ else:
|
|
| 523 |
+ default_bg = True
|
|
| 524 |
+ if textcolor:
|
|
| 525 |
+ color = Gdk.RGBA()
|
|
| 526 |
+ Gdk.RGBA.parse(color, textcolor)
|
|
| 527 |
+ banner_name_label.override_color(Gtk.StateType.NORMAL,
|
|
| 528 |
+ color)
|
|
| 529 |
+ self.banner_status_label.override_color(
|
|
| 530 |
+ Gtk.StateType.NORMAL, color)
|
|
| 531 |
+ default_fg = False
|
|
| 532 |
+ else:
|
|
| 533 |
+ default_fg = True
|
|
| 534 |
+ if default_bg or default_fg:
|
|
| 535 |
+ self._on_style_set_event(banner_name_label, None, default_fg,
|
|
| 536 |
+ default_bg)
|
|
| 537 |
+ if self.banner_status_label.get_realized():
|
|
| 538 |
+ # Widget is realized
|
|
| 539 |
+ self._on_style_set_event(self.banner_status_label, None, default_fg,
|
|
| 540 |
+ default_bg)
|
|
| 541 |
+ |
|
| 542 |
+ def disconnect_style_event(self, widget):
|
|
| 543 |
+ # Try to find the event_id
|
|
| 544 |
+ for id_ in self.handlers.keys():
|
|
| 545 |
+ if self.handlers[id_] == widget:
|
|
| 546 |
+ widget.disconnect(id_)
|
|
| 547 |
+ del self.handlers[id_]
|
|
| 548 |
+ break
|
|
| 549 |
+ |
|
| 550 |
+ def connect_style_event(self, widget, set_fg=False, set_bg=False):
|
|
| 551 |
+ self.disconnect_style_event(widget)
|
|
| 552 |
+ id_ = widget.connect('style-set', self._on_style_set_event, set_fg,
|
|
| 553 |
+ set_bg)
|
|
| 554 |
+ self.handlers[id_] = widget
|
|
| 555 |
+ |
|
| 556 |
+ def _on_style_set_event(self, widget, style, *opts):
|
|
| 557 |
+ """
|
|
| 558 |
+ Set style of widget from style class *.Frame.Eventbox
|
|
| 559 |
+ opts[0] == True -> set fg color
|
|
| 560 |
+ opts[1] == True -> set bg color
|
|
| 561 |
+ """
|
|
| 562 |
+ banner_eventbox = self.xml.get_object('banner_eventbox')
|
|
| 563 |
+ self.disconnect_style_event(widget)
|
|
| 564 |
+ context = widget.get_style_context()
|
|
| 565 |
+ if opts[1]:
|
|
| 566 |
+ bg_color = context.get_background_color(Gtk.StateFlags.SELECTED)
|
|
| 567 |
+ banner_eventbox.override_background_color(Gtk.StateType.NORMAL, bg_color)
|
|
| 568 |
+ if opts[0]:
|
|
| 569 |
+ fg_color = context.get_color(Gtk.StateFlags.SELECTED)
|
|
| 570 |
+ widget.override_color(Gtk.StateType.NORMAL, fg_color)
|
|
| 571 |
+ self.connect_style_event(widget, opts[0], opts[1])
|
|
| 572 |
+ |
|
| 573 |
+ def _conv_textview_key_press_event(self, widget, event):
|
|
| 574 |
+ # translate any layout to latin_layout
|
|
| 575 |
+ valid, entries = self.keymap.get_entries_for_keyval(event.keyval)
|
|
| 576 |
+ keycode = entries[0].keycode
|
|
| 577 |
+ if (event.get_state() & Gdk.ModifierType.CONTROL_MASK and keycode in (
|
|
| 578 |
+ self.keycode_c, self.keycode_ins)) or (
|
|
| 579 |
+ event.get_state() & Gdk.ModifierType.SHIFT_MASK and \
|
|
| 580 |
+ event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up)):
|
|
| 581 |
+ return False
|
|
| 582 |
+ self.parent_win.notebook.event(event)
|
|
| 583 |
+ return True
|
|
| 584 |
+ |
|
| 585 |
+ def show_emoticons_menu(self):
|
|
| 586 |
+ if not gajim.config.get('emoticons_theme'):
|
|
| 587 |
+ return
|
|
| 588 |
+ gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
|
|
| 589 |
+ gajim.interface.emoticons_menu.popup(None, None, None, None, 1, 0)
|
|
| 590 |
+ |
|
| 591 |
+ def _on_message_textview_key_press_event(self, widget, event):
|
|
| 592 |
+ if event.keyval == Gdk.KEY_space:
|
|
| 593 |
+ self.space_pressed = True
|
|
| 594 |
+ |
|
| 595 |
+ elif (self.space_pressed or self.msg_textview.undo_pressed) and \
|
|
| 596 |
+ event.keyval not in (Gdk.KEY_Control_L, Gdk.KEY_Control_R) and \
|
|
| 597 |
+ not (event.keyval == Gdk.KEY_z and event.get_state() & Gdk.ModifierType.CONTROL_MASK):
|
|
| 598 |
+ # If the space key has been pressed and now it hasnt,
|
|
| 599 |
+ # we save the buffer into the undo list. But be carefull we're not
|
|
| 600 |
+ # pressiong Control again (as in ctrl+z)
|
|
| 601 |
+ _buffer = widget.get_buffer()
|
|
| 602 |
+ start_iter, end_iter = _buffer.get_bounds()
|
|
| 603 |
+ self.msg_textview.save_undo(_buffer.get_text(start_iter, end_iter, True))
|
|
| 604 |
+ self.space_pressed = False
|
|
| 605 |
+ |
|
| 606 |
+ # Ctrl [+ Shift] + Tab are not forwarded to notebook. We handle it here
|
|
| 607 |
+ if self.widget_name == 'groupchat_control':
|
|
| 608 |
+ if event.keyval not in (Gdk.KEY_ISO_Left_Tab, Gdk.KEY_Tab):
|
|
| 609 |
+ self.last_key_tabs = False
|
|
| 610 |
+ if event.get_state() & Gdk.ModifierType.SHIFT_MASK:
|
|
| 611 |
+ # CTRL + SHIFT + TAB
|
|
| 612 |
+ if event.get_state() & Gdk.ModifierType.CONTROL_MASK and \
|
|
| 613 |
+ event.keyval == Gdk.KEY_ISO_Left_Tab:
|
|
| 614 |
+ self.parent_win.move_to_next_unread_tab(False)
|
|
| 615 |
+ return True
|
|
| 616 |
+ # SHIFT + PAGE_[UP|DOWN]: send to conv_textview
|
|
| 617 |
+ elif event.keyval == Gdk.KEY_Page_Down or \
|
|
| 618 |
+ event.keyval == Gdk.KEY_Page_Up:
|
|
| 619 |
+ self.conv_textview.tv.event(event)
|
|
| 620 |
+ return True
|
|
| 621 |
+ elif event.get_state() & Gdk.ModifierType.CONTROL_MASK:
|
|
| 622 |
+ if event.keyval == Gdk.KEY_Tab: # CTRL + TAB
|
|
| 623 |
+ self.parent_win.move_to_next_unread_tab(True)
|
|
| 624 |
+ return True
|
|
| 625 |
+ |
|
| 626 |
+ message_buffer = self.msg_textview.get_buffer()
|
|
| 627 |
+ event_state = event.get_state()
|
|
| 628 |
+ if event.keyval == Gdk.KEY_Tab:
|
|
| 629 |
+ start, end = message_buffer.get_bounds()
|
|
| 630 |
+ position = message_buffer.get_insert()
|
|
| 631 |
+ end = message_buffer.get_iter_at_mark(position)
|
|
| 632 |
+ text = message_buffer.get_text(start, end, False)
|
|
| 633 |
+ splitted = text.split()
|
|
| 634 |
+ if (text.startswith(self.COMMAND_PREFIX) and not
|
|
| 635 |
+ text.startswith(self.COMMAND_PREFIX * 2) and len(splitted) == 1):
|
|
| 636 |
+ text = splitted[0]
|
|
| 637 |
+ bare = text.lstrip(self.COMMAND_PREFIX)
|
|
| 638 |
+ if len(text) == 1:
|
|
| 639 |
+ self.command_hits = []
|
|
| 640 |
+ for command in self.list_commands():
|
|
| 641 |
+ for name in command.names:
|
|
| 642 |
+ self.command_hits.append(name)
|
|
| 643 |
+ else:
|
|
| 644 |
+ if (self.last_key_tabs and self.command_hits and
|
|
| 645 |
+ self.command_hits[0].startswith(bare)):
|
|
| 646 |
+ self.command_hits.append(self.command_hits.pop(0))
|
|
| 647 |
+ else:
|
|
| 648 |
+ self.command_hits = []
|
|
| 649 |
+ for command in self.list_commands():
|
|
| 650 |
+ for name in command.names:
|
|
| 651 |
+ if name.startswith(bare):
|
|
| 652 |
+ self.command_hits.append(name)
|
|
| 653 |
+ |
|
| 654 |
+ if self.command_hits:
|
|
| 655 |
+ message_buffer.delete(start, end)
|
|
| 656 |
+ message_buffer.insert_at_cursor(self.COMMAND_PREFIX + \
|
|
| 657 |
+ self.command_hits[0] + ' ')
|
|
| 658 |
+ self.last_key_tabs = True
|
|
| 659 |
+ return True
|
|
| 660 |
+ if self.widget_name != 'groupchat_control':
|
|
| 661 |
+ self.last_key_tabs = False
|
|
| 662 |
+ if event.keyval == Gdk.KEY_Up:
|
|
| 663 |
+ if event_state & Gdk.ModifierType.CONTROL_MASK:
|
|
| 664 |
+ if event_state & Gdk.ModifierType.SHIFT_MASK: # Ctrl+Shift+UP
|
|
| 665 |
+ self.scroll_messages('up', message_buffer, 'received')
|
|
| 666 |
+ else: # Ctrl+UP
|
|
| 667 |
+ self.scroll_messages('up', message_buffer, 'sent')
|
|
| 668 |
+ return True
|
|
| 669 |
+ elif event.keyval == Gdk.KEY_Down:
|
|
| 670 |
+ if event_state & Gdk.ModifierType.CONTROL_MASK:
|
|
| 671 |
+ if event_state & Gdk.ModifierType.SHIFT_MASK: # Ctrl+Shift+Down
|
|
| 672 |
+ self.scroll_messages('down', message_buffer, 'received')
|
|
| 673 |
+ else: # Ctrl+Down
|
|
| 674 |
+ self.scroll_messages('down', message_buffer, 'sent')
|
|
| 675 |
+ return True
|
|
| 676 |
+ |
|
| 677 |
+ elif event.keyval == Gdk.KEY_Return or \
|
|
| 678 |
+ event.keyval == Gdk.KEY_KP_Enter: # ENTER
|
|
| 679 |
+ if event_state & Gdk.ModifierType.SHIFT_MASK:
|
|
| 680 |
+ return True
|
|
| 681 |
+ message_textview = widget
|
|
| 682 |
+ message_buffer = message_textview.get_buffer()
|
|
| 683 |
+ start_iter, end_iter = message_buffer.get_bounds()
|
|
| 684 |
+ message = message_buffer.get_text(start_iter, end_iter, False)
|
|
| 685 |
+ xhtml = self.msg_textview.get_xhtml()
|
|
| 686 |
+ |
|
| 687 |
+ if gajim.config.get('send_on_ctrl_enter'):
|
|
| 688 |
+ if event_state & Gdk.ModifierType.CONTROL_MASK: # CTRL + ENTER
|
|
| 689 |
+ send_message = True
|
|
| 690 |
+ else:
|
|
| 691 |
+ end_iter = message_buffer.get_end_iter()
|
|
| 692 |
+ message_buffer.insert_at_cursor('\n')
|
|
| 693 |
+ send_message = False
|
|
| 694 |
+ |
|
| 695 |
+ else: # send on Enter, do newline on Ctrl Enter
|
|
| 696 |
+ if event_state & Gdk.ModifierType.CONTROL_MASK: # Ctrl + ENTER
|
|
| 697 |
+ end_iter = message_buffer.get_end_iter()
|
|
| 698 |
+ message_buffer.insert_at_cursor('\n')
|
|
| 699 |
+ send_message = False
|
|
| 700 |
+ else: # ENTER
|
|
| 701 |
+ send_message = True
|
|
| 702 |
+ |
|
| 703 |
+ if gajim.connections[self.account].connected < 2 and send_message:
|
|
| 704 |
+ # we are not connected
|
|
| 705 |
+ dialogs.ErrorDialog(_('A connection is not available'),
|
|
| 706 |
+ _('Your message can not be sent until you are connected.'))
|
|
| 707 |
+ send_message = False
|
|
| 708 |
+ |
|
| 709 |
+ if send_message:
|
|
| 710 |
+ self.send_message(message, xhtml=xhtml) # send the message
|
|
| 711 |
+ return True
|
|
| 712 |
+ elif event.keyval == Gdk.KEY_z: # CTRL+z
|
|
| 713 |
+ if event_state & Gdk.ModifierType.CONTROL_MASK:
|
|
| 714 |
+ self.msg_textview.undo()
|
|
| 715 |
+ return True
|
|
| 716 |
+ |
|
| 717 |
+ return False
|
|
| 718 |
+ |
|
| 719 |
+ def _on_drag_data_received(self, widget, context, x, y, selection,
|
|
| 720 |
+ target_type, timestamp):
|
|
| 721 |
+ """
|
|
| 722 |
+ Derived types SHOULD implement this
|
|
| 723 |
+ """
|
|
| 724 |
+ pass
|
|
| 725 |
+ |
|
| 726 |
+ def _on_drag_leave(self, widget, context, time):
|
|
| 727 |
+ # FIXME: DND on non editable TextView, find a better way
|
|
| 728 |
+ self.drag_entered = False
|
|
| 729 |
+ self.conv_textview.tv.set_editable(False)
|
|
| 730 |
+ |
|
| 731 |
+ def _on_drag_motion(self, widget, context, x, y, time):
|
|
| 732 |
+ # FIXME: DND on non editable TextView, find a better way
|
|
| 733 |
+ if not self.drag_entered:
|
|
| 734 |
+ # We drag new data over the TextView, make it editable to catch dnd
|
|
| 735 |
+ self.drag_entered_conv = True
|
|
| 736 |
+ self.conv_textview.tv.set_editable(True)
|
|
| 737 |
+ |
|
| 738 |
+ def get_seclabel(self):
|
|
| 739 |
+ label = None
|
|
| 740 |
+ if self.seclabel_combo is not None:
|
|
| 741 |
+ idx = self.seclabel_combo.get_active()
|
|
| 742 |
+ if idx != -1:
|
|
| 743 |
+ cat = gajim.connections[self.account].seclabel_catalogues[self.contact.jid]
|
|
| 744 |
+ lname = cat[2][idx]
|
|
| 745 |
+ label = cat[1][lname]
|
|
| 746 |
+ return label
|
|
| 747 |
+ |
|
| 748 |
+ def send_message(self, message, keyID='', type_='chat', chatstate=None,
|
|
| 749 |
+ msg_id=None, resource=None, xhtml=None, callback=None, callback_args=[],
|
|
| 750 |
+ process_commands=True, attention=False):
|
|
| 751 |
+ """
|
|
| 752 |
+ Send the given message to the active tab. Doesn't return None if error
|
|
| 753 |
+ """
|
|
| 754 |
+ if not message or message == '\n':
|
|
| 755 |
+ return None
|
|
| 756 |
+ |
|
| 757 |
+ if process_commands and self.process_as_command(message):
|
|
| 758 |
+ return
|
|
| 759 |
+ |
|
| 760 |
+ label = self.get_seclabel()
|
|
| 761 |
+ |
|
| 762 |
+ def _cb(obj, msg, cb, *cb_args):
|
|
| 763 |
+ self.last_sent_msg = msg
|
|
| 764 |
+ if cb:
|
|
| 765 |
+ cb(obj, msg, *cb_args)
|
|
| 766 |
+ |
|
| 767 |
+ if self.correcting and self.last_sent_msg:
|
|
| 768 |
+ correction_msg = self.last_sent_msg
|
|
| 769 |
+ else:
|
|
| 770 |
+ correction_msg = None
|
|
| 771 |
+ |
|
| 772 |
+ gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
|
|
| 773 |
+ account=self.account, jid=self.contact.jid, message=message,
|
|
| 774 |
+ keyID=keyID, type_=type_, chatstate=chatstate, msg_id=msg_id,
|
|
| 775 |
+ resource=resource, user_nick=self.user_nick, xhtml=xhtml,
|
|
| 776 |
+ label=label, callback=_cb, callback_args=[callback] + callback_args,
|
|
| 777 |
+ control=self, attention=attention, correction_msg=correction_msg, automatic_message=False))
|
|
| 778 |
+ |
|
| 779 |
+ # Record the history of sent messages
|
|
| 780 |
+ self.save_message(message, 'sent')
|
|
| 781 |
+ |
|
| 782 |
+ # Be sure to send user nickname only once according to JEP-0172
|
|
| 783 |
+ self.user_nick = None
|
|
| 784 |
+ |
|
| 785 |
+ # Clear msg input
|
|
| 786 |
+ message_buffer = self.msg_textview.get_buffer()
|
|
| 787 |
+ message_buffer.set_text('') # clear message buffer (and tv of course)
|
|
| 788 |
+ |
|
| 789 |
+ def save_message(self, message, msg_type):
|
|
| 790 |
+ # save the message, so user can scroll though the list with key up/down
|
|
| 791 |
+ if msg_type == 'sent':
|
|
| 792 |
+ history = self.sent_history
|
|
| 793 |
+ pos = self.sent_history_pos
|
|
| 794 |
+ else:
|
|
| 795 |
+ history = self.received_history
|
|
| 796 |
+ pos = self.received_history_pos
|
|
| 797 |
+ size = len(history)
|
|
| 798 |
+ scroll = False if pos == size else True # are we scrolling?
|
|
| 799 |
+ # we don't want size of the buffer to grow indefinately
|
|
| 800 |
+ max_size = gajim.config.get('key_up_lines')
|
|
| 801 |
+ for i in range(size - max_size + 1):
|
|
| 802 |
+ if pos == 0:
|
|
| 803 |
+ break
|
|
| 804 |
+ history.pop(0)
|
|
| 805 |
+ pos -= 1
|
|
| 806 |
+ history.append(message)
|
|
| 807 |
+ if not scroll or msg_type == 'sent':
|
|
| 808 |
+ pos = len(history)
|
|
| 809 |
+ if msg_type == 'sent':
|
|
| 810 |
+ self.sent_history_pos = pos
|
|
| 811 |
+ self.orig_msg = None
|
|
| 812 |
+ else:
|
|
| 813 |
+ self.received_history_pos = pos
|
|
| 814 |
+ |
|
| 815 |
+ def print_conversation_line(self, text, kind, name, tim,
|
|
| 816 |
+ other_tags_for_name=[], other_tags_for_time=[], other_tags_for_text=[],
|
|
| 817 |
+ count_as_new=True, subject=None, old_kind=None, xhtml=None, simple=False,
|
|
| 818 |
+ xep0184_id=None, graphics=True, displaymarking=None, msg_log_id=None,
|
|
| 819 |
+ msg_stanza_id=None, correct_id=None, additional_data={}):
|
|
| 820 |
+ """
|
|
| 821 |
+ Print 'chat' type messages
|
|
| 822 |
+ correct_id = (message_id, correct_id)
|
|
| 823 |
+ """
|
|
| 824 |
+ jid = self.contact.jid
|
|
| 825 |
+ full_jid = self.get_full_jid()
|
|
| 826 |
+ textview = self.conv_textview
|
|
| 827 |
+ end = False
|
|
| 828 |
+ if self.was_at_the_end or kind == 'outgoing':
|
|
| 829 |
+ end = True
|
|
| 830 |
+ |
|
| 831 |
+ textview.print_conversation_line(text, jid, kind, name, tim,
|
|
| 832 |
+ other_tags_for_name, other_tags_for_time, other_tags_for_text,
|
|
| 833 |
+ subject, old_kind, xhtml, simple=simple, graphics=graphics,
|
|
| 834 |
+ displaymarking=displaymarking, msg_stanza_id=msg_stanza_id,
|
|
| 835 |
+ correct_id=correct_id, additional_data=additional_data)
|
|
| 836 |
+ |
|
| 837 |
+ if xep0184_id is not None:
|
|
| 838 |
+ textview.show_xep0184_warning(xep0184_id)
|
|
| 839 |
+ |
|
| 840 |
+ if not count_as_new:
|
|
| 841 |
+ return
|
|
| 842 |
+ if kind in ('incoming', 'incoming_queue', 'outgoing'):
|
|
| 843 |
+ self.last_received_txt[name] = text
|
|
| 844 |
+ if correct_id:
|
|
| 845 |
+ self.last_received_id[name] = correct_id[0]
|
|
| 846 |
+ if kind == 'incoming':
|
|
| 847 |
+ if not self.type_id == message_control.TYPE_GC or \
|
|
| 848 |
+ gajim.config.get('notify_on_all_muc_messages') or \
|
|
| 849 |
+ 'marked' in other_tags_for_text:
|
|
| 850 |
+ # it's a normal message, or a muc message with want to be
|
|
| 851 |
+ # notified about if quitting just after
|
|
| 852 |
+ # other_tags_for_text == ['marked'] --> highlighted gc message
|
|
| 853 |
+ gajim.last_message_time[self.account][full_jid] = time.time()
|
|
| 854 |
+ |
|
| 855 |
+ if kind in ('incoming', 'incoming_queue'):
|
|
| 856 |
+ # Record the history of received messages
|
|
| 857 |
+ self.save_message(text, 'received')
|
|
| 858 |
+ |
|
| 859 |
+ if kind in ('incoming', 'incoming_queue', 'error'):
|
|
| 860 |
+ gc_message = False
|
|
| 861 |
+ if self.type_id == message_control.TYPE_GC:
|
|
| 862 |
+ gc_message = True
|
|
| 863 |
+ |
|
| 864 |
+ if ((self.parent_win and (not self.parent_win.get_active_control() or \
|
|
| 865 |
+ self != self.parent_win.get_active_control() or \
|
|
| 866 |
+ not self.parent_win.is_active() or not end)) or \
|
|
| 867 |
+ (gc_message and \
|
|
| 868 |
+ jid in gajim.interface.minimized_controls[self.account])) and \
|
|
| 869 |
+ kind in ('incoming', 'incoming_queue', 'error'):
|
|
| 870 |
+ # we want to have save this message in events list
|
|
| 871 |
+ # other_tags_for_text == ['marked'] --> highlighted gc message
|
|
| 872 |
+ if gc_message:
|
|
| 873 |
+ if 'marked' in other_tags_for_text:
|
|
| 874 |
+ event_type = events.PrintedMarkedGcMsgEvent
|
|
| 875 |
+ else:
|
|
| 876 |
+ event_type = events.PrintedGcMsgEvent
|
|
| 877 |
+ event = 'gc_message_received'
|
|
| 878 |
+ else:
|
|
| 879 |
+ if self.type_id == message_control.TYPE_CHAT:
|
|
| 880 |
+ event_type = events.PrintedChatEvent
|
|
| 881 |
+ else:
|
|
| 882 |
+ event_type = events.PrintedPmEvent
|
|
| 883 |
+ event = 'message_received'
|
|
| 884 |
+ show_in_roster = notify.get_show_in_roster(event,
|
|
| 885 |
+ self.account, self.contact, self.session)
|
|
| 886 |
+ show_in_systray = notify.get_show_in_systray(event,
|
|
| 887 |
+ self.account, self.contact, event_type.type_)
|
|
| 888 |
+ |
|
| 889 |
+ event = event_type(text, subject, self, msg_log_id,
|
|
| 890 |
+ show_in_roster=show_in_roster,
|
|
| 891 |
+ show_in_systray=show_in_systray)
|
|
| 892 |
+ gajim.events.add_event(self.account, full_jid, event)
|
|
| 893 |
+ # We need to redraw contact if we show in roster
|
|
| 894 |
+ if show_in_roster:
|
|
| 895 |
+ gajim.interface.roster.draw_contact(self.contact.jid,
|
|
| 896 |
+ self.account)
|
|
| 897 |
+ |
|
| 898 |
+ if not self.parent_win:
|
|
| 899 |
+ return
|
|
| 900 |
+ |
|
| 901 |
+ if (not self.parent_win.get_active_control() or \
|
|
| 902 |
+ self != self.parent_win.get_active_control() or \
|
|
| 903 |
+ not self.parent_win.is_active() or not end) and \
|
|
| 904 |
+ kind in ('incoming', 'incoming_queue', 'error'):
|
|
| 905 |
+ self.parent_win.redraw_tab(self)
|
|
| 906 |
+ if not self.parent_win.is_active():
|
|
| 907 |
+ self.parent_win.show_title(True, self) # Enabled Urgent hint
|
|
| 908 |
+ else:
|
|
| 909 |
+ self.parent_win.show_title(False, self) # Disabled Urgent hint
|
|
| 910 |
+ |
|
| 911 |
+ def toggle_emoticons(self):
|
|
| 912 |
+ """
|
|
| 913 |
+ Hide show emoticons_button and make sure emoticons_menu is always there
|
|
| 914 |
+ when needed
|
|
| 915 |
+ """
|
|
| 916 |
+ emoticons_button = self.xml.get_object('emoticons_button')
|
|
| 917 |
+ if gajim.config.get('emoticons_theme'):
|
|
| 918 |
+ emoticons_button.show()
|
|
| 919 |
+ emoticons_button.set_no_show_all(False)
|
|
| 920 |
+ else:
|
|
| 921 |
+ emoticons_button.hide()
|
|
| 922 |
+ emoticons_button.set_no_show_all(True)
|
|
| 923 |
+ |
|
| 924 |
+ def append_emoticon(self, str_):
|
|
| 925 |
+ buffer_ = self.msg_textview.get_buffer()
|
|
| 926 |
+ if buffer_.get_char_count():
|
|
| 927 |
+ buffer_.insert_at_cursor(' %s ' % str_)
|
|
| 928 |
+ else: # we are the beginning of buffer
|
|
| 929 |
+ buffer_.insert_at_cursor('%s ' % str_)
|
|
| 930 |
+ self.msg_textview.grab_focus()
|
|
| 931 |
+ |
|
| 932 |
+ def on_emoticons_button_clicked(self, widget):
|
|
| 933 |
+ """
|
|
| 934 |
+ Popup emoticons menu
|
|
| 935 |
+ """
|
|
| 936 |
+ gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
|
|
| 937 |
+ gajim.interface.popup_emoticons_under_button(widget, self.parent_win)
|
|
| 938 |
+ |
|
| 939 |
+ def on_color_menuitem_activate(self, widget):
|
|
| 940 |
+ color_dialog = Gtk.ColorChooserDialog(None, self.parent_win.window)
|
|
| 941 |
+ color_dialog.set_use_alpha(False)
|
|
| 942 |
+ color_dialog.connect('response', self.msg_textview.color_set)
|
|
| 943 |
+ color_dialog.show_all()
|
|
| 944 |
+ |
|
| 945 |
+ def on_font_menuitem_activate(self, widget):
|
|
| 946 |
+ font_dialog = Gtk.FontChooserDialog(None, self.parent_win.window)
|
|
| 947 |
+ start, finish = self.msg_textview.get_active_iters()
|
|
| 948 |
+ font_dialog.connect('response', self.msg_textview.font_set, start, finish)
|
|
| 949 |
+ font_dialog.show_all()
|
|
| 950 |
+ |
|
| 951 |
+ def on_formatting_menuitem_activate(self, widget):
|
|
| 952 |
+ tag = widget.get_name()
|
|
| 953 |
+ self.msg_textview.set_tag(tag)
|
|
| 954 |
+ |
|
| 955 |
+ def on_clear_formatting_menuitem_activate(self, widget):
|
|
| 956 |
+ self.msg_textview.clear_tags()
|
|
| 957 |
+ |
|
| 958 |
+ def on_actions_button_clicked(self, widget):
|
|
| 959 |
+ """
|
|
| 960 |
+ Popup action menu
|
|
| 961 |
+ """
|
|
| 962 |
+ menu = self.prepare_context_menu(hide_buttonbar_items=True)
|
|
| 963 |
+ menu.show_all()
|
|
| 964 |
+ menu.attach_to_widget(widget, None)
|
|
| 965 |
+ gtkgui_helpers.popup_emoticons_under_button(menu, widget,
|
|
| 966 |
+ self.parent_win)
|
|
| 967 |
+ |
|
| 968 |
+ def update_font(self):
|
|
| 969 |
+ font = Pango.FontDescription(gajim.config.get('conversation_font'))
|
|
| 970 |
+ self.conv_textview.tv.override_font(font)
|
|
| 971 |
+ self.msg_textview.override_font(font)
|
|
| 972 |
+ |
|
| 973 |
+ def update_tags(self):
|
|
| 974 |
+ self.conv_textview.update_tags()
|
|
| 975 |
+ |
|
| 976 |
+ def clear(self, tv):
|
|
| 977 |
+ buffer_ = tv.get_buffer()
|
|
| 978 |
+ start, end = buffer_.get_bounds()
|
|
| 979 |
+ buffer_.delete(start, end)
|
|
| 980 |
+ |
|
| 981 |
+ def _on_history_menuitem_activate(self, widget=None, jid=None):
|
|
| 982 |
+ """
|
|
| 983 |
+ When history menuitem is pressed: call history window
|
|
| 984 |
+ """
|
|
| 985 |
+ if not jid:
|
|
| 986 |
+ jid = self.contact.jid
|
|
| 987 |
+ |
|
| 988 |
+ if 'logs' in gajim.interface.instances:
|
|
| 989 |
+ gajim.interface.instances['logs'].window.present()
|
|
| 990 |
+ gajim.interface.instances['logs'].open_history(jid, self.account)
|
|
| 991 |
+ else:
|
|
| 992 |
+ gajim.interface.instances['logs'] = \
|
|
| 993 |
+ history_window.HistoryWindow(jid, self.account)
|
|
| 994 |
+ |
|
| 995 |
+ def _on_send_file(self, gc_contact=None):
|
|
| 996 |
+ """
|
|
| 997 |
+ gc_contact can be set when we are in a groupchat control
|
|
| 998 |
+ """
|
|
| 999 |
+ def _on_ok(c):
|
|
| 1000 |
+ gajim.interface.instances['file_transfers'].show_file_send_request(
|
|
| 1001 |
+ self.account, c)
|
|
| 1002 |
+ if self.TYPE_ID == message_control.TYPE_PM:
|
|
| 1003 |
+ gc_contact = self.gc_contact
|
|
| 1004 |
+ if gc_contact:
|
|
| 1005 |
+ # gc or pm
|
|
| 1006 |
+ gc_control = gajim.interface.msg_win_mgr.get_gc_control(
|
|
| 1007 |
+ gc_contact.room_jid, self.account)
|
|
| 1008 |
+ self_contact = gajim.contacts.get_gc_contact(self.account,
|
|
| 1009 |
+ gc_control.room_jid, gc_control.nick)
|
|
| 1010 |
+ if gc_control.is_anonymous and gc_contact.affiliation not in ['admin',
|
|
| 1011 |
+ 'owner'] and self_contact.affiliation in ['admin', 'owner']:
|
|
| 1012 |
+ contact = gajim.contacts.get_contact(self.account, gc_contact.jid)
|
|
| 1013 |
+ if not contact or contact.sub not in ('both', 'to'):
|
|
| 1014 |
+ prim_text = _('Really send file?')
|
|
| 1015 |
+ sec_text = _('If you send a file to %s, he/she will know your '
|
|
| 1016 |
+ 'real Jabber ID.') % gc_contact.name
|
|
| 1017 |
+ dialog = dialogs.NonModalConfirmationDialog(prim_text,
|
|
| 1018 |
+ sec_text, on_response_ok=(_on_ok, gc_contact))
|
|
| 1019 |
+ dialog.popup()
|
|
| 1020 |
+ return
|
|
| 1021 |
+ _on_ok(gc_contact)
|
|
| 1022 |
+ return
|
|
| 1023 |
+ _on_ok(self.contact)
|
|
| 1024 |
+ |
|
| 1025 |
+ def on_minimize_menuitem_toggled(self, widget):
|
|
| 1026 |
+ """
|
|
| 1027 |
+ When a grouchat is minimized, unparent the tab, put it in roster etc
|
|
| 1028 |
+ """
|
|
| 1029 |
+ old_value = True
|
|
| 1030 |
+ non_minimized_gc = gajim.config.get_per('accounts', self.account,
|
|
| 1031 |
+ 'non_minimized_gc').split()
|
|
| 1032 |
+ if self.contact.jid in non_minimized_gc:
|
|
| 1033 |
+ old_value = False
|
|
| 1034 |
+ minimize = widget.get_active()
|
|
| 1035 |
+ if not minimize and not self.contact.jid in non_minimized_gc:
|
|
| 1036 |
+ non_minimized_gc.append(self.contact.jid)
|
|
| 1037 |
+ if minimize and self.contact.jid in non_minimized_gc:
|
|
| 1038 |
+ non_minimized_gc.remove(self.contact.jid)
|
|
| 1039 |
+ if old_value != minimize:
|
|
| 1040 |
+ gajim.config.set_per('accounts', self.account, 'non_minimized_gc',
|
|
| 1041 |
+ ' '.join(non_minimized_gc))
|
|
| 1042 |
+ |
|
| 1043 |
+ def set_control_active(self, state):
|
|
| 1044 |
+ if state:
|
|
| 1045 |
+ jid = self.contact.jid
|
|
| 1046 |
+ if self.was_at_the_end:
|
|
| 1047 |
+ # we are at the end
|
|
| 1048 |
+ type_ = ['printed_' + self.type_id]
|
|
| 1049 |
+ if self.type_id == message_control.TYPE_GC:
|
|
| 1050 |
+ type_ = ['printed_gc_msg', 'printed_marked_gc_msg']
|
|
| 1051 |
+ if not gajim.events.remove_events(self.account, self.get_full_jid(),
|
|
| 1052 |
+ types=type_):
|
|
| 1053 |
+ # There were events to remove
|
|
| 1054 |
+ self.redraw_after_event_removed(jid)
|
|
| 1055 |
+ |
|
| 1056 |
+ def bring_scroll_to_end(self, textview, diff_y=0):
|
|
| 1057 |
+ """
|
|
| 1058 |
+ Scroll to the end of textview if end is not visible
|
|
| 1059 |
+ """
|
|
| 1060 |
+ if self.scroll_to_end_id:
|
|
| 1061 |
+ # a scroll is already planned
|
|
| 1062 |
+ return
|
|
| 1063 |
+ buffer_ = textview.get_buffer()
|
|
| 1064 |
+ end_iter = buffer_.get_end_iter()
|
|
| 1065 |
+ end_rect = textview.get_iter_location(end_iter)
|
|
| 1066 |
+ visible_rect = textview.get_visible_rect()
|
|
| 1067 |
+ # scroll only if expected end is not visible
|
|
| 1068 |
+ if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
|
|
| 1069 |
+ self.scroll_to_end_id = GLib.idle_add(self.scroll_to_end_iter,
|
|
| 1070 |
+ textview)
|
|
| 1071 |
+ |
|
| 1072 |
+ def scroll_to_end_iter(self, textview):
|
|
| 1073 |
+ buffer_ = textview.get_buffer()
|
|
| 1074 |
+ end_iter = buffer_.get_end_iter()
|
|
| 1075 |
+ textview.scroll_to_iter(end_iter, 0, False, 1, 1)
|
|
| 1076 |
+ self.scroll_to_end_id = None
|
|
| 1077 |
+ return False
|
|
| 1078 |
+ |
|
| 1079 |
+ def on_configure_event(self, msg_textview, event):
|
|
| 1080 |
+ """
|
|
| 1081 |
+ When message_textview changes its size: if the new height will enlarge
|
|
| 1082 |
+ the window, enable the scrollbar automatic policy. Also enable scrollbar
|
|
| 1083 |
+ automatic policy for horizontal scrollbar if message we have in
|
|
| 1084 |
+ message_textview is too big
|
|
| 1085 |
+ """
|
|
| 1086 |
+ if msg_textview.get_window() is None:
|
|
| 1087 |
+ return
|
|
| 1088 |
+ |
|
| 1089 |
+ min_height = self.conv_scrolledwindow.get_property('height-request')
|
|
| 1090 |
+ conversation_height = self.conv_textview.tv.get_window().get_size()[1]
|
|
| 1091 |
+ message_height = msg_textview.get_window().get_size()[1]
|
|
| 1092 |
+ message_width = msg_textview.get_window().get_size()[0]
|
|
| 1093 |
+ # new tab is not exposed yet
|
|
| 1094 |
+ if conversation_height < 2:
|
|
| 1095 |
+ return
|
|
| 1096 |
+ |
|
| 1097 |
+ if conversation_height < min_height:
|
|
| 1098 |
+ min_height = conversation_height
|
|
| 1099 |
+ |
|
| 1100 |
+ # we don't want to always resize in height the message_textview
|
|
| 1101 |
+ # so we have minimum on conversation_textview's scrolled window
|
|
| 1102 |
+ # but we also want to avoid window resizing so if we reach that
|
|
| 1103 |
+ # minimum for conversation_textview and maximum for message_textview
|
|
| 1104 |
+ # we set to automatic the scrollbar policy
|
|
| 1105 |
+ diff_y = message_height - event.height
|
|
| 1106 |
+ if diff_y != 0:
|
|
| 1107 |
+ if conversation_height + diff_y < min_height:
|
|
| 1108 |
+ if message_height + conversation_height - min_height > min_height:
|
|
| 1109 |
+ policy = self.msg_scrolledwindow.get_property(
|
|
| 1110 |
+ 'vscrollbar-policy')
|
|
| 1111 |
+ if policy != Gtk.PolicyType.AUTOMATIC:
|
|
| 1112 |
+ self.msg_scrolledwindow.set_property('vscrollbar-policy',
|
|
| 1113 |
+ Gtk.PolicyType.AUTOMATIC)
|
|
| 1114 |
+ self.msg_scrolledwindow.set_property('height-request',
|
|
| 1115 |
+ message_height + conversation_height - min_height)
|
|
| 1116 |
+ else:
|
|
| 1117 |
+ self.msg_scrolledwindow.set_property('vscrollbar-policy',
|
|
| 1118 |
+ Gtk.PolicyType.NEVER)
|
|
| 1119 |
+ self.msg_scrolledwindow.set_property('height-request', -1)
|
|
| 1120 |
+ |
|
| 1121 |
+ self.smooth = True # reinit the flag
|
|
| 1122 |
+ # enable scrollbar automatic policy for horizontal scrollbar
|
|
| 1123 |
+ # if message we have in message_textview is too big
|
|
| 1124 |
+ if event.width > message_width:
|
|
| 1125 |
+ self.msg_scrolledwindow.set_property('hscrollbar-policy',
|
|
| 1126 |
+ Gtk.PolicyType.AUTOMATIC)
|
|
| 1127 |
+ else:
|
|
| 1128 |
+ self.msg_scrolledwindow.set_property('hscrollbar-policy',
|
|
| 1129 |
+ Gtk.PolicyType.NEVER)
|
|
| 1130 |
+ |
|
| 1131 |
+ return True
|
|
| 1132 |
+ |
|
| 1133 |
+ def on_conversation_vadjustment_changed(self, adjustment):
|
|
| 1134 |
+ # used to stay at the end of the textview when we shrink conversation
|
|
| 1135 |
+ # textview.
|
|
| 1136 |
+ if self.was_at_the_end:
|
|
| 1137 |
+ if self.conv_textview.at_the_end():
|
|
| 1138 |
+ # we are at the end
|
|
| 1139 |
+ self.conv_textview.bring_scroll_to_end(-18)
|
|
| 1140 |
+ else:
|
|
| 1141 |
+ self.conv_textview.bring_scroll_to_end(-18, use_smooth=False)
|
|
| 1142 |
+ self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value()\
|
|
| 1143 |
+ - adjustment.get_page_size()) < 18
|
|
| 1144 |
+ |
|
| 1145 |
+ def on_conversation_vadjustment_value_changed(self, adjustment):
|
|
| 1146 |
+ # stop automatic scroll when we manually scroll
|
|
| 1147 |
+ if not self.conv_textview.auto_scrolling:
|
|
| 1148 |
+ self.conv_textview.stop_scrolling()
|
|
| 1149 |
+ self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value() \
|
|
| 1150 |
+ - adjustment.get_page_size()) < 18
|
|
| 1151 |
+ if self.resource:
|
|
| 1152 |
+ jid = self.contact.get_full_jid()
|
|
| 1153 |
+ else:
|
|
| 1154 |
+ jid = self.contact.jid
|
|
| 1155 |
+ types_list = []
|
|
| 1156 |
+ type_ = self.type_id
|
|
| 1157 |
+ if type_ == message_control.TYPE_GC:
|
|
| 1158 |
+ type_ = 'gc_msg'
|
|
| 1159 |
+ types_list = ['printed_' + type_, type_, 'printed_marked_gc_msg']
|
|
| 1160 |
+ else: # Not a GC
|
|
| 1161 |
+ types_list = ['printed_' + type_, type_]
|
|
| 1162 |
+ |
|
| 1163 |
+ if not len(gajim.events.get_events(self.account, jid, types_list)):
|
|
| 1164 |
+ return
|
|
| 1165 |
+ if not self.parent_win:
|
|
| 1166 |
+ return
|
|
| 1167 |
+ if self.conv_textview.at_the_end() and \
|
|
| 1168 |
+ self.parent_win.get_active_control() == self and \
|
|
| 1169 |
+ self.parent_win.window.is_active():
|
|
| 1170 |
+ # we are at the end
|
|
| 1171 |
+ if self.type_id == message_control.TYPE_GC:
|
|
| 1172 |
+ if not gajim.events.remove_events(self.account, jid,
|
|
| 1173 |
+ types=types_list):
|
|
| 1174 |
+ self.redraw_after_event_removed(jid)
|
|
| 1175 |
+ elif self.session and self.session.remove_events(types_list):
|
|
| 1176 |
+ # There were events to remove
|
|
| 1177 |
+ self.redraw_after_event_removed(jid)
|
|
| 1178 |
+ |
|
| 1179 |
+ def redraw_after_event_removed(self, jid):
|
|
| 1180 |
+ """
|
|
| 1181 |
+ We just removed a 'printed_*' event, redraw contact in roster or
|
|
| 1182 |
+ gc_roster and titles in roster and msg_win
|
|
| 1183 |
+ """
|
|
| 1184 |
+ self.parent_win.redraw_tab(self)
|
|
| 1185 |
+ self.parent_win.show_title()
|
|
| 1186 |
+ # TODO : get the contact and check notify.get_show_in_roster()
|
|
| 1187 |
+ if self.type_id == message_control.TYPE_PM:
|
|
| 1188 |
+ room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
|
|
| 1189 |
+ groupchat_control = gajim.interface.msg_win_mgr.get_gc_control(
|
|
| 1190 |
+ room_jid, self.account)
|
|
| 1191 |
+ if room_jid in gajim.interface.minimized_controls[self.account]:
|
|
| 1192 |
+ groupchat_control = \
|
|
| 1193 |
+ gajim.interface.minimized_controls[self.account][room_jid]
|
|
| 1194 |
+ contact = gajim.contacts.get_contact_with_highest_priority(
|
|
| 1195 |
+ self.account, room_jid)
|
|
| 1196 |
+ if contact:
|
|
| 1197 |
+ gajim.interface.roster.draw_contact(room_jid, self.account)
|
|
| 1198 |
+ if groupchat_control:
|
|
| 1199 |
+ groupchat_control.draw_contact(nick)
|
|
| 1200 |
+ if groupchat_control.parent_win:
|
|
| 1201 |
+ groupchat_control.parent_win.redraw_tab(groupchat_control)
|
|
| 1202 |
+ else:
|
|
| 1203 |
+ gajim.interface.roster.draw_contact(jid, self.account)
|
|
| 1204 |
+ gajim.interface.roster.show_title()
|
|
| 1205 |
+ |
|
| 1206 |
+ def scroll_messages(self, direction, msg_buf, msg_type):
|
|
| 1207 |
+ if msg_type == 'sent':
|
|
| 1208 |
+ history = self.sent_history
|
|
| 1209 |
+ pos = self.sent_history_pos
|
|
| 1210 |
+ self.received_history_pos = len(self.received_history)
|
|
| 1211 |
+ else:
|
|
| 1212 |
+ history = self.received_history
|
|
| 1213 |
+ pos = self.received_history_pos
|
|
| 1214 |
+ self.sent_history_pos = len(self.sent_history)
|
|
| 1215 |
+ size = len(history)
|
|
| 1216 |
+ if self.orig_msg is None:
|
|
| 1217 |
+ # user was typing something and then went into history, so save
|
|
| 1218 |
+ # whatever is already typed
|
|
| 1219 |
+ start_iter = msg_buf.get_start_iter()
|
|
| 1220 |
+ end_iter = msg_buf.get_end_iter()
|
|
| 1221 |
+ self.orig_msg = msg_buf.get_text(start_iter, end_iter, False)
|
|
| 1222 |
+ if pos == size and size > 0 and direction == 'up' and \
|
|
| 1223 |
+ msg_type == 'sent' and not self.correcting and (not \
|
|
| 1224 |
+ history[pos - 1].startswith('/') or history[pos - 1].startswith('/me')):
|
|
| 1225 |
+ self.correcting = True
|
|
| 1226 |
+ context = self.msg_textview.get_style_context()
|
|
| 1227 |
+ state = Gtk.StateFlags.NORMAL
|
|
| 1228 |
+ self.old_message_tv_color = context.get_background_color(state)
|
|
| 1229 |
+ color = Gdk.RGBA()
|
|
| 1230 |
+ Gdk.RGBA.parse(color, 'PaleGoldenrod')
|
|
| 1231 |
+ self.msg_textview.override_background_color(Gtk.StateType.NORMAL,
|
|
| 1232 |
+ color)
|
|
| 1233 |
+ message = history[pos - 1]
|
|
| 1234 |
+ msg_buf.set_text(message)
|
|
| 1235 |
+ return
|
|
| 1236 |
+ if self.correcting:
|
|
| 1237 |
+ # We were previously correcting
|
|
| 1238 |
+ self.msg_textview.override_background_color(Gtk.StateType.NORMAL,
|
|
| 1239 |
+ self.old_message_tv_color)
|
|
| 1240 |
+ self.correcting = False
|
|
| 1241 |
+ pos += -1 if direction == 'up' else +1
|
|
| 1242 |
+ if pos == -1:
|
|
| 1243 |
+ return
|
|
| 1244 |
+ if pos >= size:
|
|
| 1245 |
+ pos = size
|
|
| 1246 |
+ message = self.orig_msg
|
|
| 1247 |
+ self.orig_msg = None
|
|
| 1248 |
+ else:
|
|
| 1249 |
+ message = history[pos]
|
|
| 1250 |
+ if msg_type == 'sent':
|
|
| 1251 |
+ self.sent_history_pos = pos
|
|
| 1252 |
+ else:
|
|
| 1253 |
+ self.received_history_pos = pos
|
|
| 1254 |
+ if self.orig_msg is not None:
|
|
| 1255 |
+ message = '> %s\n' % message.replace('\n', '\n> ')
|
|
| 1256 |
+ msg_buf.set_text(message)
|
|
| 1257 |
+ |
|
| 1258 |
+ def lighten_color(self, color):
|
|
| 1259 |
+ p = 0.4
|
|
| 1260 |
+ mask = 0
|
|
| 1261 |
+ color.red = int((color.red * p) + (mask * (1 - p)))
|
|
| 1262 |
+ color.green = int((color.green * p) + (mask * (1 - p)))
|
|
| 1263 |
+ color.blue = int((color.blue * p) + (mask * (1 - p)))
|
|
| 1264 |
+ return color
|
|
| 1265 |
+ |
|
| 1266 |
+ def widget_set_visible(self, widget, state):
|
|
| 1267 |
+ """
|
|
| 1268 |
+ Show or hide a widget
|
|
| 1269 |
+ """
|
|
| 1270 |
+ # make the last message visible, when changing to "full view"
|
|
| 1271 |
+ if not state:
|
|
| 1272 |
+ GLib.idle_add(self.conv_textview.scroll_to_end_iter)
|
|
| 1273 |
+ |
|
| 1274 |
+ widget.set_no_show_all(state)
|
|
| 1275 |
+ if state:
|
|
| 1276 |
+ widget.hide()
|
|
| 1277 |
+ else:
|
|
| 1278 |
+ widget.show_all()
|
|
| 1279 |
+ |
|
| 1280 |
+ def chat_buttons_set_visible(self, state):
|
|
| 1281 |
+ """
|
|
| 1282 |
+ Toggle chat buttons
|
|
| 1283 |
+ """
|
|
| 1284 |
+ MessageControl.chat_buttons_set_visible(self, state)
|
|
| 1285 |
+ self.widget_set_visible(self.xml.get_object('actions_hbox'), state)
|
|
| 1286 |
+ |
|
| 1287 |
+ def got_connected(self):
|
|
| 1288 |
+ self.msg_textview.set_sensitive(True)
|
|
| 1289 |
+ self.msg_textview.set_editable(True)
|
|
| 1290 |
+ self.update_toolbar()
|
|
| 1291 |
+ |
|
| 1292 |
+ def got_disconnected(self):
|
|
| 1293 |
+ self.msg_textview.set_sensitive(False)
|
|
| 1294 |
+ self.msg_textview.set_editable(False)
|
|
| 1295 |
+ self.conv_textview.tv.grab_focus()
|
|
| 1296 |
+ |
|
| 1297 |
+ self.no_autonegotiation = False
|
|
| 1298 |
+ self.update_toolbar()
|
| ... | ... | @@ -45,7 +45,7 @@ import gtkgui_helpers |
| 45 | 45 |
import dialogs
|
| 46 | 46 |
import cell_renderer_image
|
| 47 | 47 |
import message_control
|
| 48 |
-import chat_control
|
|
| 48 |
+from chat_control_base import ChatControlBase
|
|
| 49 | 49 |
import dataforms_widget
|
| 50 | 50 |
import profile_window
|
| 51 | 51 |
|
| ... | ... | @@ -690,7 +690,7 @@ class PreferencesWindow: |
| 690 | 690 |
|
| 691 | 691 |
def apply_speller(self):
|
| 692 | 692 |
for ctrl in self._get_all_controls():
|
| 693 |
- if isinstance(ctrl, chat_control.ChatControlBase):
|
|
| 693 |
+ if isinstance(ctrl, ChatControlBase):
|
|
| 694 | 694 |
try:
|
| 695 | 695 |
spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
|
| 696 | 696 |
except (TypeError, RuntimeError, OSError):
|
| ... | ... | @@ -701,7 +701,7 @@ class PreferencesWindow: |
| 701 | 701 |
|
| 702 | 702 |
def remove_speller(self):
|
| 703 | 703 |
for ctrl in self._get_all_controls():
|
| 704 |
- if isinstance(ctrl, chat_control.ChatControlBase):
|
|
| 704 |
+ if isinstance(ctrl, ChatControlBase):
|
|
| 705 | 705 |
try:
|
| 706 | 706 |
spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
|
| 707 | 707 |
except (TypeError, RuntimeError):
|
| ... | ... | @@ -54,7 +54,7 @@ from common import ged |
| 54 | 54 |
from common import i18n
|
| 55 | 55 |
|
| 56 | 56 |
from chat_control import ChatControl
|
| 57 |
-from chat_control import ChatControlBase
|
|
| 57 |
+from chat_control_base import ChatControlBase
|
|
| 58 | 58 |
from common.exceptions import GajimGeneralException
|
| 59 | 59 |
|
| 60 | 60 |
from command_system.implementation.hosts import PrivateChatCommands
|
| ... | ... | @@ -61,7 +61,7 @@ import dialogs |
| 61 | 61 |
import notify
|
| 62 | 62 |
import message_control
|
| 63 | 63 |
|
| 64 |
-from chat_control import ChatControlBase
|
|
| 64 |
+from chat_control_base import ChatControlBase
|
|
| 65 | 65 |
from chat_control import ChatControl
|
| 66 | 66 |
from groupchat_control import GroupchatControl
|
| 67 | 67 |
from groupchat_control import PrivateChatControl
|
| ... | ... | @@ -38,7 +38,7 @@ import common |
| 38 | 38 |
import gtkgui_helpers
|
| 39 | 39 |
import message_control
|
| 40 | 40 |
import dialogs
|
| 41 |
-from chat_control import ChatControlBase
|
|
| 41 |
+from chat_control_base import ChatControlBase
|
|
| 42 | 42 |
|
| 43 | 43 |
from common import gajim
|
| 44 | 44 |
|