chat_control.py 64.5 KB
Newer Older
nicfit's avatar
added  
nicfit committed
1 2
##	chat_control.py
##
nkour's avatar
nkour committed
3 4
## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>
## Copyright (C) 2006 Nikos Kouremenos <kourem@gmail.com>
nicfit's avatar
nicfit committed
5
## Copyright (C) 2006 Travis Shirk <travis@pobox.com>
6
## Copyright (C) 2006 Dimitur Kirov <dkirov@gmail.com>
nicfit's avatar
added  
nicfit committed
7 8 9 10 11 12 13 14 15 16 17
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 2 only.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##

nkour's avatar
nkour committed
18
import os
nicfit's avatar
nicfit committed
19
import time
nicfit's avatar
added  
nicfit committed
20 21 22 23
import gtk
import pango
import gobject
import gtkgui_helpers
24
import message_control
nicfit's avatar
nicfit committed
25
import dialogs
26
import history_window
sb's avatar
sb committed
27
import notify
nicfit's avatar
added  
nicfit committed
28 29

from common import gajim
nicfit's avatar
nicfit committed
30
from common import helpers
31
from message_control import MessageControl
nicfit's avatar
added  
nicfit committed
32 33
from conversation_textview import ConversationTextview
from message_textview import MessageTextView
34
from common.contacts import GC_Contact
35 36
from common.logger import Constants
constants = Constants()
nicfit's avatar
added  
nicfit committed
37

nicfit's avatar
nicfit committed
38 39 40 41 42 43
try:
	import gtkspell
	HAS_GTK_SPELL = True
except:
	HAS_GTK_SPELL = False

sb's avatar
sb committed
44 45 46 47 48 49 50 51

# the next script, executed in the "po" directory,
# generates the following list.
##!/bin/sh
#LANG=$(for i in *.po; do  j=${i/.po/}; echo -n "_('"$j"')":" '"$j"', " ; done)
#echo "{_('en'):'en'",$LANG"}"
langs = {_('English'): 'en', _('Bulgarian'): 'bg', _('Briton'): 'br', _('Czech'): 'cs', _('German'): 'de', _('Greek'): 'el', _('Esperanto'): 'eo', _('Spanish'): 'es', _('Basc'): 'eu', _('French'): 'fr', _('Croatian'): 'hr', _('Italian'): 'it', _('Norvegian b'): 'nb', _('Dutch'): 'nl', _('Norvegian'): 'no', _('Polish'): 'pl', _('Portuguese'): 'pt', _('Brazilian Portuguese'): 'pt_BR', _('Russian'): 'ru', _('Slovak'): 'sk', _('Swedish'): 'sv', _('Chinese (Ch)'): 'zh_CN'}

nicfit's avatar
added  
nicfit committed
52

53
################################################################################
54
class ChatControlBase(MessageControl):
nicfit's avatar
nicfit committed
55
	'''A base class containing a banner, ConversationTextview, MessageTextView
56
	'''
57 58 59 60 61
	def get_font_attrs(self):
		''' get pango font  attributes for banner from theme settings '''
		theme = gajim.config.get('roster_theme')
		bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
		bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs')
sb's avatar
sb committed
62

63 64 65 66 67 68 69 70 71 72
		if bannerfont:
			font = pango.FontDescription(bannerfont)
		else:
			font = pango.FontDescription('Normal')
		if bannerfontattrs:
			# B attribute is set by default
			if 'B' in bannerfontattrs:
				font.set_weight(pango.WEIGHT_HEAVY)
			if 'I' in bannerfontattrs:
				font.set_style(pango.STYLE_ITALIC)
sb's avatar
sb committed
73

74
		font_attrs = 'font_desc="%s"' % font.to_string()
sb's avatar
sb committed
75

76 77 78 79
		# in case there is no font specified we use x-large font size
		if font.get_size() == 0:
			font_attrs = '%s size="x-large"' % font_attrs
		font.set_weight(pango.WEIGHT_NORMAL)
80
		font_attrs_small = 'font_desc="%s" size="small"' % font.to_string()
81
		return (font_attrs, font_attrs_small)
sb's avatar
sb committed
82 83 84 85 86 87 88 89 90

	def get_nb_unread(self):
		jid = self.contact.jid
		if self.resource:
			jid += '/' + self.resource
		type_ = self.type_id
		return len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
			type_]))

91 92 93 94
	def draw_banner(self):
		self._paint_banner()
		self._update_banner_state_image()
		# Derived types SHOULD implement this
95

96
	def update_ui(self):
97 98
		self.draw_banner()
		# Derived types SHOULD implement this
99

100 101
	def repaint_themed_widgets(self):
		self.draw_banner()
nicfit's avatar
nicfit committed
102
		# Derived classes MAY implement this
103

104 105 106
	def _update_banner_state_image(self):
		pass # Derived types MAY implement this

107 108
	def handle_message_textview_mykey_press(self, widget, event_keyval,
	event_keymod):
nicfit's avatar
nicfit committed
109 110
		pass # Derived should implement this rather than connecting to the event itself.

sb's avatar
sb committed
111 112
	def __init__(self, type_id, parent_win, widget_name, display_names, contact,
	acct, resource = None):
113 114
		MessageControl.__init__(self, type_id, parent_win, widget_name,
			display_names,	contact, acct, resource = resource);
115 116 117 118
		# when/if we do XHTML we will but formatting buttons back
		widget = self.xml.get_widget('emoticons_button')
		id = widget.connect('clicked', self.on_emoticons_button_clicked)
		self.handlers[id] = widget
sb's avatar
sb committed
119

120 121
		id = self.widget.connect('key_press_event', self._on_keypress_event)
		self.handlers[id] = self.widget
122 123 124 125 126

		widget = self.xml.get_widget('banner_eventbox')
		id = widget.connect('button-press-event',
			self._on_banner_eventbox_button_press_event)
		self.handlers[id] = widget
sb's avatar
sb committed
127

128
		# Create textviews and connect signals
nicfit's avatar
nicfit committed
129
		self.conv_textview = ConversationTextview(self.account)
sb's avatar
sb committed
130

nkour's avatar
nkour committed
131 132
		self.conv_scrolledwindow = self.xml.get_widget(
			'conversation_scrolledwindow')
133
		self.conv_scrolledwindow.add(self.conv_textview.tv)
134 135
		widget = self.conv_scrolledwindow.get_vadjustment()
		id = widget.connect('value-changed',
nicfit's avatar
nicfit committed
136
			self.on_conversation_vadjustment_value_changed)
137
		self.handlers[id] = widget
138
		# add MessageTextView to UI and connect signals
nicfit's avatar
nicfit committed
139
		self.msg_scrolledwindow = self.xml.get_widget('message_scrolledwindow')
140
		self.msg_textview = MessageTextView()
141
		id = self.msg_textview.connect('mykeypress',
sb's avatar
sb committed
142
			self._on_message_textview_mykeypress_event)
143
		self.handlers[id] = self.msg_textview
nicfit's avatar
nicfit committed
144
		self.msg_scrolledwindow.add(self.msg_textview)
145
		id = self.msg_textview.connect('key_press_event',
sb's avatar
sb committed
146
			self._on_message_textview_key_press_event)
147 148 149
		self.handlers[id] = self.msg_textview
		id = self.msg_textview.connect('size-request', self.size_request)
		self.handlers[id] = self.msg_textview
sb's avatar
sb committed
150 151 152 153
		id = self.msg_textview.connect('populate_popup',
			self.on_msg_textview_populate_popup)
		self.handlers[id] = self.msg_textview
	
nicfit's avatar
nicfit committed
154
		self.update_font()
155

nicfit's avatar
nicfit committed
156
		# Hook up send button
157
		widget = self.xml.get_widget('send_button')
sb's avatar
sb committed
158
		id = widget.connect('clicked', self._on_send_button_clicked)
159
		self.handlers[id] = widget
160

161 162
		# the following vars are used to keep history of user's messages
		self.sent_history = []
163
		self.sent_history_pos = 0
164 165 166
		self.typing_new = False
		self.orig_msg = ''

167 168 169 170
		# Emoticons menu
		# set image no matter if user wants at this time emoticons or not
		# (so toggle works ok)
		img = self.xml.get_widget('emoticons_button_image')
171 172
		img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
			'smile.png'))
173 174
		self.toggle_emoticons()

nicfit's avatar
nicfit committed
175 176 177
		# Attach speller
		if gajim.config.get('use_speller') and HAS_GTK_SPELL:
			try:
sb's avatar
sb committed
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
				spell = gtkspell.Spell(self.msg_textview)
				# loop removing non-existant dictionaries
				# iterating on a copy
				for lang in dict(langs):
					try: 
						spell.set_language(langs[lang])
					except:
						del langs[lang]
				# now set the one the user selected
				per_type = 'contacts'
				if self.type_id == message_control.TYPE_GC:
					per_type = 'rooms'
				lang = gajim.config.get_per(per_type, self.contact.jid,
					'speller_language')
				if not lang:
					# use the default one
					lang = gajim.config.get('speller_language')
				if lang:
					self.msg_textview.lang = lang
					spell.set_language(lang)
			except (gobject.GError, RuntimeError), msg:
nkour's avatar
nkour committed
199 200 201 202 203
				dialogs.ErrorDialog(unicode(msg), _('If that is not your language '
					'for which you want to highlight misspelled words, then please '
					'set your $LANG as appropriate. Eg. for French do export '
					'LANG=fr_FR or export LANG=fr_FR.UTF-8 in ~/.bash_profile or to '
					'make it global in /etc/profile.\n\nHighlighting misspelled '
204
					'words feature will not be used'))
nicfit's avatar
nicfit committed
205 206
				gajim.config.set('use_speller', False)

207
		self.style_event_id = 0
208
		self.conv_textview.tv.show()
sb's avatar
sb committed
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250

		# For JEP-0172
		self.user_nick = None

	def on_msg_textview_populate_popup(self, textview, menu):
		'''we override the default context menu and we prepend an option to switch languages'''
		def _on_select_dictionary(widget, lang):
			per_type = 'contacts'
			if self.type_id == message_control.TYPE_GC:
				per_type = 'rooms'
			if not gajim.config.get_per(per_type, self.contact.jid):
				gajim.config.add_per(per_type, self.contact.jid)
			gajim.config.set_per(per_type, self.contact.jid, 'speller_language',
				lang)
			spell = gtkspell.get_from_text_view(self.msg_textview)
			self.msg_textview.lang = lang
			spell.set_language(lang)
			widget.set_active(True)

		item = gtk.SeparatorMenuItem()
		menu.prepend(item)

		if gajim.config.get('use_speller') and HAS_GTK_SPELL:
			item = gtk.MenuItem(_('Spelling language'))
			menu.prepend(item)
			submenu = gtk.Menu()
			item.set_submenu(submenu)
			for lang in sorted(langs):
				item = gtk.CheckMenuItem(lang)
				if langs[lang] == self.msg_textview.lang:
					item.set_active(True)
				submenu.append(item)
				id = item.connect('activate', _on_select_dictionary, langs[lang])
				self.handlers[id] = item

		item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
		menu.prepend(item)
		id = item.connect('activate', self.msg_textview.clear)
		self.handlers[id] = item

		menu.show_all()

251 252 253 254 255
	# moved from ChatControl 
	def _on_banner_eventbox_button_press_event(self, widget, event):
		'''If right-clicked, show popup'''
		if event.button == 3: # right click
			self.parent_win.popup_menu(event)
256

nicfit's avatar
nicfit committed
257 258
	def _on_send_button_clicked(self, widget):
		'''When send button is pressed: send the current message'''
259 260 261 262
		if gajim.connections[self.account].connected < 2: # we are not connected
			dialog = dialogs.ErrorDialog(_('A connection is not available'),
				_('Your message can not be sent until you are connected.'))
			return
nicfit's avatar
nicfit committed
263 264 265 266 267 268 269 270
		message_buffer = self.msg_textview.get_buffer()
		start_iter = message_buffer.get_start_iter()
		end_iter = message_buffer.get_end_iter()
		message = message_buffer.get_text(start_iter, end_iter, 0).decode('utf-8')

		# send the message
		self.send_message(message)

271 272 273 274 275 276 277 278 279
	def _paint_banner(self):
		'''Repaint banner with theme color'''
		theme = gajim.config.get('roster_theme')
		bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
		textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
		# the backgrounds are colored by using an eventbox by
		# setting the bg color of the eventbox and the fg of the name_label
		banner_eventbox = self.xml.get_widget('banner_eventbox')
		banner_name_label = self.xml.get_widget('banner_name_label')
280
		self.disconnect_style_event(banner_name_label)
281 282 283
		if bgcolor:
			banner_eventbox.modify_bg(gtk.STATE_NORMAL, 
				gtk.gdk.color_parse(bgcolor))
284
			default_bg = False
285
		else:
286
			default_bg = True
287 288 289
		if textcolor:
			banner_name_label.modify_fg(gtk.STATE_NORMAL,
				gtk.gdk.color_parse(textcolor))
290
			default_fg = False
291
		else:
292 293
			default_fg = True
		if default_bg or default_fg:
nkour's avatar
nkour committed
294 295
			self._on_style_set_event(banner_name_label, None, default_fg,
				default_bg)
296 297 298 299
	
	def disconnect_style_event(self, widget):
		if self.style_event_id:
			widget.disconnect(self.style_event_id)
300 301
			del self.handlers[self.style_event_id]
			self.style_event_id = 0	
302 303 304
	
	def connect_style_event(self, widget, set_fg = False, set_bg = False):
		self.disconnect_style_event(widget)
nkour's avatar
nkour committed
305 306
		self.style_event_id = widget.connect('style-set',
			self._on_style_set_event, set_fg, set_bg)
307
		self.handlers[self.style_event_id] = widget
308 309
	
	def _on_style_set_event(self, widget, style, *opts):
nkour's avatar
nkour committed
310
		'''set style of widget from style class *.Frame.Eventbox 
311
			opts[0] == True -> set fg color
nkour's avatar
nkour committed
312
			opts[1] == True -> set bg color'''
313 314 315 316 317 318 319 320 321 322
		banner_eventbox = self.xml.get_widget('banner_eventbox')
		self.disconnect_style_event(widget)
		if opts[1]:
			bg_color = widget.style.bg[gtk.STATE_SELECTED]
			banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color)
		if opts[0]:
			fg_color = widget.style.fg[gtk.STATE_SELECTED]
			widget.modify_fg(gtk.STATE_NORMAL, fg_color)
		self.connect_style_event(widget, opts[0], opts[1])
	
323
	def _on_keypress_event(self, widget, event):
324
		if event.state & gtk.gdk.CONTROL_MASK:
325
			# CTRL + l|L: clear conv_textview
326
			if event.keyval == gtk.keysyms.l or event.keyval == gtk.keysyms.L:
sb's avatar
sb committed
327
				self.conv_textview.clear()
328 329
				return True
			# CTRL + v: Paste into msg_textview
330 331 332
			elif event.keyval == gtk.keysyms.v:
				if not self.msg_textview.is_focus():
					self.msg_textview.grab_focus()
333
				# Paste into the msg textview
334
				self.msg_textview.emit('key_press_event', event)
335
			# CTRL + u: emacs style clear line
336 337
			elif event.keyval == gtk.keysyms.u:
				self.clear(self.msg_textview) # clear message textview too
338 339 340 341 342 343
			elif event.keyval == gtk.keysyms.ISO_Left_Tab: # CTRL + SHIFT + TAB
				self.parent_win.move_to_next_unread_tab(False)
				return True
			elif event.keyval == gtk.keysyms.Tab: # CTRL + TAB
				self.parent_win.move_to_next_unread_tab(True)
				return True
344 345 346 347 348
			# CTRL + PAGE_[UP|DOWN]: send to parent notebook
			elif event.keyval == gtk.keysyms.Page_Down or \
					event.keyval == gtk.keysyms.Page_Up:
				self.parent_win.notebook.emit('key_press_event', event)
				return True
349 350
		elif event.keyval == gtk.keysyms.m and \
			(event.state & gtk.gdk.MOD1_MASK): # alt + m opens emoticons menu
351
			if gajim.config.get('emoticons_theme'):
352 353 354 355 356 357 358 359 360 361 362 363 364 365
				msg_tv = self.msg_textview
				def set_emoticons_menu_position(w, msg_tv = self.msg_textview):
					window = msg_tv.get_window(gtk.TEXT_WINDOW_WIDGET)
					# get the window position
					origin = window.get_origin()
					size = window.get_size()
					buf = msg_tv.get_buffer()
					# get the cursor position
					cursor = msg_tv.get_iter_location(buf.get_iter_at_mark(
						buf.get_insert()))
					cursor =  msg_tv.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT,
						cursor.x, cursor.y)
					x = origin[0] + cursor[0]
					y = origin[1] + size[1]
366 367
					menu_width, menu_height = \
						gajim.interface.emoticons_menu.size_request()
368 369 370 371 372 373 374 375 376 377 378 379 380
					#FIXME: get_line_count is not so good
					#get the iter of cursor, then tv.get_line_yrange
					# so we know in which y we are typing (not how many lines we have
					# then go show just above the current cursor line for up
					# or just below the current cursor line for down
					#TEST with having 3 lines and writing in the 2nd
					if y + menu_height > gtk.gdk.screen_height():
						# move menu just above cursor
						y -= menu_height +\
							(msg_tv.allocation.height / buf.get_line_count())
					#else: # move menu just below cursor
					#	y -= (msg_tv.allocation.height / buf.get_line_count())
					return (x, y, True) # push_in True
381 382 383
				gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
				gajim.interface.emoticons_menu.popup(None, None,
					set_emoticons_menu_position, 1, 0)
384
		return False
385

386
	def _on_message_textview_key_press_event(self, widget, event):
387 388 389
		if self.widget_name == 'muc_child_vbox':
			if event.keyval not in (gtk.keysyms.ISO_Left_Tab, gtk.keysyms.Tab):
				self.last_key_tabs = False
390
		if event.state & gtk.gdk.SHIFT_MASK:
391 392 393 394 395
			# CTRL + SHIFT + TAB
			if event.state & gtk.gdk.CONTROL_MASK and \
					event.keyval == gtk.keysyms.ISO_Left_Tab:
				self.parent_win.move_to_next_unread_tab(False)
				return True
396
			# SHIFT + PAGE_[UP|DOWN]: send to conv_textview
397
			elif event.keyval == gtk.keysyms.Page_Down or \
398
					event.keyval == gtk.keysyms.Page_Up:
399
				self.conv_textview.tv.emit('key_press_event', event)
400
				return True
401 402 403 404
		elif event.state & gtk.gdk.CONTROL_MASK:
			if event.keyval == gtk.keysyms.Tab: # CTRL + TAB
				self.parent_win.move_to_next_unread_tab(True)
				return True
405 406 407 408 409
			# CTRL + PAGE_[UP|DOWN]: send to parent notebook
			elif event.keyval == gtk.keysyms.Page_Down or \
					event.keyval == gtk.keysyms.Page_Up:
				self.parent_win.notebook.emit('key_press_event', event)
				return True
410 411 412
			# we pressed a control key or ctrl+sth: we don't block
			# the event in order to let ctrl+c (copy text) and
			# others do their default work
413
			self.conv_textview.tv.emit('key_press_event', event)
414
		return False
415

416
	def _on_message_textview_mykeypress_event(self, widget, event_keyval,
nkour's avatar
nkour committed
417
		event_keymod):
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
		'''When a key is pressed:
		if enter is pressed without the shift key, message (if not empty) is sent
		and printed in the conversation'''

		# NOTE: handles mykeypress which is custom signal connected to this
		# CB in new_tab(). for this singal see message_textview.py
		message_textview = widget
		message_buffer = message_textview.get_buffer()
		start_iter, end_iter = message_buffer.get_bounds()
		message = message_buffer.get_text(start_iter, end_iter, False).decode('utf-8')

		# construct event instance from binding
		event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
		event.keyval = event_keyval
		event.state = event_keymod
		event.time = 0 # assign current time

		if event.keyval == gtk.keysyms.Up:
			if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+UP
nicfit's avatar
nicfit committed
437
				self.sent_messages_scroll('up', widget.get_buffer())
438 439
		elif event.keyval == gtk.keysyms.Down:
			if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+Down
nicfit's avatar
nicfit committed
440
				self.sent_messages_scroll('down', widget.get_buffer())
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
		elif event.keyval == gtk.keysyms.Return or \
			event.keyval == gtk.keysyms.KP_Enter: # ENTER
			# NOTE: SHIFT + ENTER is not needed to be emulated as it is not
			# binding at all (textview's default action is newline)

			if gajim.config.get('send_on_ctrl_enter'):
				# here, we emulate GTK default action on ENTER (add new line)
				# normally I would add in keypress but it gets way to complex
				# to get instant result on changing this advanced setting
				if event.state == 0: # no ctrl, no shift just ENTER add newline
					end_iter = message_buffer.get_end_iter()
					message_buffer.insert_at_cursor('\n')
					send_message = False
				elif event.state & gtk.gdk.CONTROL_MASK: # CTRL + ENTER
					send_message = True
			else: # send on Enter, do newline on Ctrl Enter
				if event.state & gtk.gdk.CONTROL_MASK: # Ctrl + ENTER
					end_iter = message_buffer.get_end_iter()
					message_buffer.insert_at_cursor('\n')
					send_message = False
				else: # ENTER
					send_message = True
				
			if gajim.connections[self.account].connected < 2: # we are not connected
nicfit's avatar
nicfit committed
465 466
				dialog = dialogs.ErrorDialog(_('A connection is not available'),
					_('Your message can not be sent until you are connected.'))
467 468 469 470
				send_message = False

			if send_message:
				self.send_message(message) # send the message
nicfit's avatar
nicfit committed
471 472 473
		else:
			# Give the control itself a chance to process
			self.handle_message_textview_mykey_press(widget, event_keyval, event_keymod)
474 475 476 477

	def _process_command(self, message):
		if not message:
			return False
nicfit's avatar
nicfit committed
478

479 480 481 482 483 484 485
		message = message[1:]
		message_array = message.split(' ', 1)
		command = message_array.pop(0).lower()
		if message_array == ['']:
			message_array = []

		if command == 'clear' and not len(message_array):
486
			self.conv_textview.clear() # clear conversation
487 488
			self.clear(self.msg_textview) # clear message textview too
			return True
489
		elif message == 'compact' and not len(message_array):
490
			self.chat_buttons_set_visible(not self.hide_chat_buttons_current)
491 492
			self.clear(self.msg_textview)
			return True
nicfit's avatar
nicfit committed
493
		return False
494

495 496
	def send_message(self, message, keyID = '', type = 'chat', chatstate = None,
	msg_id = None, composing_jep = None, resource = None):
497 498 499 500
		'''Send the given message to the active tab'''
		if not message or message == '\n':
			return

sb's avatar
sb committed
501

502
		if not self._process_command(message):
503
			MessageControl.send_message(self, message, keyID, type = type,
504
				chatstate = chatstate, msg_id = msg_id,
sb's avatar
sb committed
505 506
				composing_jep = composing_jep, resource = resource,
				user_nick = self.user_nick)
507 508 509
			# Record message history
			self.save_sent_message(message)

sb's avatar
sb committed
510 511 512
			# Be sure to send user nickname only once according to JEP-0172
			self.user_nick = None

513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
		# Clear msg input
		message_buffer = self.msg_textview.get_buffer()
		message_buffer.set_text('') # clear message buffer (and tv of course)

	def save_sent_message(self, message):
		#save the message, so user can scroll though the list with key up/down
		size = len(self.sent_history)
		#we don't want size of the buffer to grow indefinately
		max_size = gajim.config.get('key_up_lines')
		if size >= max_size:
			for i in xrange(0, size - 1): 
				self.sent_history[i] = self.sent_history[i + 1]
			self.sent_history[max_size - 1] = message
		else:
			self.sent_history.append(message)
			self.sent_history_pos = size + 1

		self.typing_new = True
		self.orig_msg = ''

	def print_conversation_line(self, text, kind, name, tim,
nkour's avatar
nkour committed
534
		other_tags_for_name = [], other_tags_for_time = [], 
535 536
		other_tags_for_text = [], count_as_new = True,
		subject = None, old_kind = None):
537 538
		'''prints 'chat' type messages'''
		jid = self.contact.jid
539
		full_jid = self.get_full_jid()
540 541 542 543 544
		textview = self.conv_textview
		end = False
		if textview.at_the_end() or kind == 'outgoing':
			end = True
		textview.print_conversation_line(text, jid, kind, name, tim,
545 546
			other_tags_for_name, other_tags_for_time, other_tags_for_text,
			subject, old_kind)
547 548 549

		if not count_as_new:
			return
550
		if kind == 'incoming':
551
			gajim.last_message_time[self.account][full_jid] = time.time()
552
		urgent = True
nicfit's avatar
nicfit committed
553
		if (not self.parent_win.get_active_jid() or \
sb's avatar
sb committed
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
		full_jid != self.parent_win.get_active_jid() or \
		not self.parent_win.is_active() or not end) and \
		kind in ('incoming', 'incoming_queue'):
			if self.notify_on_new_messages():
				type_ = 'printed_' + self.type_id
				if self.type_id == message_control.TYPE_GC:
					type_ = 'printed_gc_msg'
				show_in_roster = notify.get_show_in_roster('message_received',
					self.account, self.contact)
				show_in_systray = notify.get_show_in_systray('message_received',
					self.account, self.contact)
				event = gajim.events.create_event(type_, None,
					show_in_roster = show_in_roster,
					show_in_systray = show_in_systray)
				gajim.events.add_event(self.account, full_jid, event)
				# We need to redraw contact if we show in roster
				if show_in_roster:
					gajim.interface.roster.draw_contact(self.contact.jid,
						self.account)
573
			self.parent_win.redraw_tab(self)
nicfit's avatar
nicfit committed
574
			if not self.parent_win.is_active():
575
				ctrl = gajim.interface.msg_win_mgr.get_control(full_jid,
576 577
					self.account)
				self.parent_win.show_title(urgent, ctrl)
578

579 580 581 582
	def toggle_emoticons(self):
		'''hide show emoticons_button and make sure emoticons_menu is always there
		when needed'''
		emoticons_button = self.xml.get_widget('emoticons_button')
583
		if gajim.config.get('emoticons_theme'):
584 585 586 587 588 589
			emoticons_button.show()
			emoticons_button.set_no_show_all(False)
		else:
			emoticons_button.hide()
			emoticons_button.set_no_show_all(True)

590 591 592 593 594 595 596
	def append_emoticon(self, str_):
		buffer = self.msg_textview.get_buffer()
		if buffer.get_char_count():
			buffer.insert_at_cursor(' %s ' % str_)
		else: # we are the beginning of buffer
			buffer.insert_at_cursor('%s ' % str_)
		self.msg_textview.grab_focus()
sb's avatar
sb committed
597

598 599
	def on_emoticons_button_clicked(self, widget):
		'''popup emoticons menu'''
600
		gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
601
		gajim.interface.popup_emoticons_under_button(widget, self.parent_win)
602

nicfit's avatar
nicfit committed
603 604 605 606
	def on_actions_button_clicked(self, widget):
		'''popup action menu'''
		menu = self.prepare_context_menu()
		menu.show_all()
sb's avatar
sb committed
607 608
		gtkgui_helpers.popup_emoticons_under_button(menu, widget,
			self.parent_win)
nicfit's avatar
nicfit committed
609

610 611
	def update_font(self):
		font = pango.FontDescription(gajim.config.get('conversation_font'))
612
		self.conv_textview.tv.modify_font(font)
613 614 615 616 617
		self.msg_textview.modify_font(font)

	def update_tags(self):
		self.conv_textview.update_tags()

nicfit's avatar
nicfit committed
618 619 620 621
	def clear(self, tv):
		buffer = tv.get_buffer()
		start, end = buffer.get_bounds()
		buffer.delete(start, end)
622

623
	def _on_history_menuitem_activate(self, widget = None, jid = None):
nkour's avatar
nkour committed
624
		'''When history menuitem is pressed: call history window'''
625 626
		if not jid:
			jid = self.contact.jid
627

628 629 630
		if gajim.interface.instances['logs'].has_key(jid):
			gajim.interface.instances['logs'][jid].window.present()
		else:
631 632 633 634 635
			gajim.interface.instances['logs'][jid] = \
				history_window.HistoryWindow(jid, self.account)

	def _on_compact_view_menuitem_activate(self, widget):
		isactive = widget.get_active()
636
		self.chat_buttons_set_visible(isactive)
637

nicfit's avatar
nicfit committed
638 639 640 641
	def set_control_active(self, state):
		if state:
			jid = self.contact.jid
			if self.conv_textview.at_the_end():
sb's avatar
sb committed
642 643 644 645 646 647 648
				# we are at the end
				type_ = 'printed_' + self.type_id
				if self.type_id == message_control.TYPE_GC:
					type_ = 'printed_gc_msg'
				if not gajim.events.remove_events(self.account, self.get_full_jid(),
				types = [type_]):
					# There were events to remove
649
					self.parent_win.redraw_tab(self)
nicfit's avatar
nicfit committed
650
					self.parent_win.show_title()
sb's avatar
sb committed
651 652 653 654 655 656 657 658 659
					# redraw roster
					if self.type_id == message_control.TYPE_PM:
						room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
						groupchat_control = gajim.interface.msg_win_mgr.get_control(
							room_jid, self.account)
						groupchat_control.draw_contact(nick)
					else:
						gajim.interface.roster.draw_contact(jid, self.account)
						gajim.interface.roster.show_title()
660
			self.msg_textview.grab_focus()
nicfit's avatar
nicfit committed
661
			# Note, we send None chatstate to preserve current
662
			self.parent_win.redraw_tab(self)
nicfit's avatar
nicfit committed
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679

	def bring_scroll_to_end(self, textview, diff_y = 0):
		''' scrolls to the end of textview if end is not visible '''
		buffer = textview.get_buffer()
		end_iter = buffer.get_end_iter()
		end_rect = textview.get_iter_location(end_iter)
		visible_rect = textview.get_visible_rect()
		# scroll only if expected end is not visible
		if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
			gobject.idle_add(self.scroll_to_end_iter, textview)

	def scroll_to_end_iter(self, textview):
		buffer = textview.get_buffer()
		end_iter = buffer.get_end_iter()
		textview.scroll_to_iter(end_iter, 0, False, 1, 1)
		return False

dkirov's avatar
dkirov committed
680
	def size_request(self, msg_textview , requisition):
nicfit's avatar
nicfit committed
681 682 683 684 685 686 687 688
		''' When message_textview changes its size. If the new height
		will enlarge the window, enable the scrollbar automatic policy
		Also enable scrollbar automatic policy for horizontal scrollbar
		if message we have in message_textview is too big'''
		if msg_textview.window is None:
			return

		min_height = self.conv_scrolledwindow.get_property('height-request')
689
		conversation_height = self.conv_textview.tv.window.get_size()[1]
nicfit's avatar
nicfit committed
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731
		message_height = msg_textview.window.get_size()[1]
		message_width = msg_textview.window.get_size()[0]
		# new tab is not exposed yet
		if conversation_height < 2:
			return

		if conversation_height < min_height:
			min_height = conversation_height

		# we don't want to always resize in height the message_textview
		# so we have minimum on conversation_textview's scrolled window
		# but we also want to avoid window resizing so if we reach that 
		# minimum for conversation_textview and maximum for message_textview
		# we set to automatic the scrollbar policy
		diff_y =  message_height - requisition.height
		if diff_y != 0:
			if conversation_height + diff_y < min_height:
				if message_height + conversation_height - min_height > min_height:
					self.msg_scrolledwindow.set_property('vscrollbar-policy', 
						gtk.POLICY_AUTOMATIC)
					self.msg_scrolledwindow.set_property('height-request', 
						message_height + conversation_height - min_height)
					self.bring_scroll_to_end(msg_textview)
			else:
				self.msg_scrolledwindow.set_property('vscrollbar-policy', 
					gtk.POLICY_NEVER)
				self.msg_scrolledwindow.set_property('height-request', -1)

		self.conv_textview.bring_scroll_to_end(diff_y - 18)
		
		# enable scrollbar automatic policy for horizontal scrollbar
		# if message we have in message_textview is too big
		if requisition.width > message_width:
			self.msg_scrolledwindow.set_property('hscrollbar-policy', 
				gtk.POLICY_AUTOMATIC)
		else:
			self.msg_scrolledwindow.set_property('hscrollbar-policy', 
				gtk.POLICY_NEVER)

		return True

	def on_conversation_vadjustment_value_changed(self, widget):
sb's avatar
sb committed
732 733 734 735 736 737 738 739 740
		if self.resource:
			jid = self.contact.get_full_jid()
		else:
			jid = self.contact.jid
		type_ = self.type_id
		if type_ == message_control.TYPE_GC:
			type_ = 'gc_msg'
		if not len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
		type_])):
nicfit's avatar
nicfit committed
741
			return
nicfit's avatar
nicfit committed
742 743 744
		if self.conv_textview.at_the_end() and \
				self.parent_win.get_active_control() == self and \
				self.parent_win.window.is_active():
sb's avatar
sb committed
745 746 747 748 749 750 751 752 753
			# we are at the end
			type_ = self.type_id
			if type_ == message_control.TYPE_GC:
				type_ = 'gc_msg'
			if not gajim.events.remove_events(self.account, self.get_full_jid(),
			types = ['printed_' + type_, type_]):
				# There were events to remove
				self.parent_win.redraw_tab(self)
				self.parent_win.show_title()
nicfit's avatar
nicfit committed
754

nicfit's avatar
nicfit committed
755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778
	def sent_messages_scroll(self, direction, conv_buf):
		size = len(self.sent_history) 
		if self.typing_new:
			#user was typing something and then went into history, so save
			#whatever is already typed
			start_iter = conv_buf.get_start_iter()
			end_iter = conv_buf.get_end_iter()
			self.orig_msg = conv_buf.get_text(start_iter, end_iter, 0).decode('utf-8')
			self.typing_new = False
		if direction == 'up':
			if self.sent_history_pos == 0:
				return
			self.sent_history_pos = self.sent_history_pos - 1
			conv_buf.set_text(self.sent_history[self.sent_history_pos])
		elif direction == 'down':
			if self.sent_history_pos >= size - 1:
				conv_buf.set_text(self.orig_msg);
				self.typing_new = True
				self.sent_history_pos = size
				return

			self.sent_history_pos = self.sent_history_pos + 1
			conv_buf.set_text(self.sent_history[self.sent_history_pos])

nicfit's avatar
nicfit committed
779 780 781 782 783 784 785 786
	def lighten_color(self, color):
		p = 0.4
		mask = 0
		color.red = int((color.red * p) + (mask * (1 - p)))
		color.green = int((color.green * p) + (mask * (1 - p)))
		color.blue = int((color.blue * p) + (mask * (1 - p)))
		return color

787 788
	def widget_set_visible(self, widget, state):
		'''Show or hide a widget. state is bool'''
nicfit's avatar
nicfit committed
789 790 791
		# make the last message visible, when changing to "full view"
		if not state:
			gobject.idle_add(self.conv_textview.scroll_to_end_iter)
792 793 794 795

		widget.set_no_show_all(state)
		if state:
			widget.hide()
nicfit's avatar
nicfit committed
796
		else:
797 798 799 800 801 802
			widget.show_all()

	def chat_buttons_set_visible(self, state):
		'''Toggle chat buttons. state is bool'''
		MessageControl.chat_buttons_set_visible(self, state)
		self.widget_set_visible(self.xml.get_widget('actions_hbox'), state)
nicfit's avatar
nicfit committed
803

nicfit's avatar
nicfit committed
804 805
	def got_connected(self):
		self.msg_textview.set_sensitive(True)
806
		self.msg_textview.set_editable(True)
nicfit's avatar
nicfit committed
807 808 809 810
		self.xml.get_widget('send_button').set_sensitive(True)

	def got_disconnected(self):
		self.msg_textview.set_sensitive(False)
811
		self.msg_textview.set_editable(False)
sb's avatar
sb committed
812
		self.conv_textview.tv.grab_focus()
nicfit's avatar
nicfit committed
813 814
		self.xml.get_widget('send_button').set_sensitive(False)

815
################################################################################
816 817
class ChatControl(ChatControlBase):
	'''A control for standard 1-1 chat'''
818
	TYPE_ID = message_control.TYPE_CHAT
819 820
	old_msg_kind = None # last kind of the printed message
	
821
	def __init__(self, parent_win, contact, acct, resource = None):
822
		ChatControlBase.__init__(self, self.TYPE_ID, parent_win, 'chat_child_vbox',
nkour's avatar
nkour committed
823
			(_('Chat'), _('Chats')), contact, acct, resource)
824 825 826 827 828 829 830
			
		# for muc use:
		# widget = self.xml.get_widget('muc_window_actions_button')
		widget = self.xml.get_widget('message_window_actions_button')
		id = widget.connect('clicked', self.on_actions_button_clicked)
		self.handlers[id] = widget

831 832 833
		self.hide_chat_buttons_always = gajim.config.get('always_hide_chat_buttons')
		self.chat_buttons_set_visible(self.hide_chat_buttons_always)
		self.widget_set_visible(self.xml.get_widget('banner_eventbox'), gajim.config.get('hide_chat_banner'))
834
		# Initialize drag-n-drop
nicfit's avatar
nicfit committed
835 836
		self.TARGET_TYPE_URI_LIST = 80
		self.dnd_list = [ ( 'text/uri-list', 0, self.TARGET_TYPE_URI_LIST ) ]
837 838
		id = self.widget.connect('drag_data_received', self._on_drag_data_received)
		self.handlers[id] = self.widget
nkour's avatar
nkour committed
839 840 841 842
		self.widget.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
			gtk.DEST_DEFAULT_HIGHLIGHT |
			gtk.DEST_DEFAULT_DROP,
			self.dnd_list, gtk.gdk.ACTION_COPY)
nicfit's avatar
nicfit committed
843 844 845 846 847 848 849 850 851 852

		# keep timeout id and window obj for possible big avatar
		# it is on enter-notify and leave-notify so no need to be per jid
		self.show_bigger_avatar_timeout_id = None
		self.bigger_avatar_window = None
		self.show_avatar(self.contact.resource)			

		# chatstate timers and state
		self.reset_kbd_mouse_timeout_vars()
		self._schedule_activity_timers()
853 854

		# Hook up signals
855
		id = self.parent_win.window.connect('motion-notify-event',
nkour's avatar
nkour committed
856
			self._on_window_motion_notify)
857
		self.handlers[id] = self.parent_win.window
858
		message_tv_buffer = self.msg_textview.get_buffer()
859 860 861 862 863 864
		id = message_tv_buffer.connect('changed', self._on_message_tv_buffer_changed)
		self.handlers[id] = message_tv_buffer
		
		widget = self.xml.get_widget('avatar_eventbox')
		id = widget.connect('enter-notify-event', self.on_avatar_eventbox_enter_notify_event)
		self.handlers[id] = widget
nkour's avatar
nkour committed
865

866 867 868
		id = widget.connect('leave-notify-event', self.on_avatar_eventbox_leave_notify_event)
		self.handlers[id] = widget

sb's avatar
sb committed
869 870 871
		id = widget.connect('button-press-event', self.on_avatar_eventbox_button_press_event)
		self.handlers[id] = widget

872 873 874
		widget = self.xml.get_widget('gpg_togglebutton')
		id = widget.connect('clicked', self.on_toggle_gpg_togglebutton)
		self.handlers[id] = widget
875 876 877

		if self.contact.jid in gajim.encrypted_chats[self.account]:
			self.xml.get_widget('gpg_togglebutton').set_active(True)
878 879
		
		self.status_tooltip = gtk.Tooltips()
880
		self.update_ui()
881 882
		# restore previous conversation
		self.restore_conversation()
sb's avatar
sb committed
883 884
		# is account displayed after nick in banner ?
		self.account_displayed= False
885

886 887
	def notify_on_new_messages(self):
		return gajim.config.get('trayicon_notification_on_new_messages')
dkirov's avatar
dkirov committed
888
	
nicfit's avatar
nicfit committed
889
	def on_avatar_eventbox_enter_notify_event(self, widget, event):
890 891 892
		'''we enter the eventbox area so we under conditions add a timeout
		to show a bigger avatar after 0.5 sec'''
		jid = self.contact.jid
893 894 895 896 897
		is_fake = False
		if self.type_id == message_control.TYPE_PM:
			is_fake = True
		avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid,
			is_fake)
898 899 900 901 902
		if avatar_pixbuf in ('ask', None):
			return
		avatar_w = avatar_pixbuf.get_width()
		avatar_h = avatar_pixbuf.get_height()
		
903
		scaled_buf = self.xml.get_widget('avatar_image').get_pixbuf()
904 905 906 907 908 909 910 911 912
		scaled_buf_w = scaled_buf.get_width()
		scaled_buf_h = scaled_buf.get_height()
		
		# do we have something bigger to show?
		if avatar_w > scaled_buf_w or avatar_h > scaled_buf_h:
			# wait for 0.5 sec in case we leave earlier
			self.show_bigger_avatar_timeout_id = gobject.timeout_add(500,
				self.show_bigger_avatar, widget)
		
nicfit's avatar
nicfit committed
913
	def on_avatar_eventbox_leave_notify_event(self, widget, event):
914 915 916 917
		'''we left the eventbox area that holds the avatar img'''
		# did we add a timeout? if yes remove it
		if self.show_bigger_avatar_timeout_id is not None:
			gobject.source_remove(self.show_bigger_avatar_timeout_id)
nicfit's avatar
nicfit committed
918

sb's avatar
sb committed
919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935
	def on_avatar_eventbox_button_press_event(self, widget, event):
		'''If right-clicked, show popup'''
		if event.button == 3: # right click
			menu = gtk.Menu()
			menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
			id = menuitem.connect('activate', 
				gtkgui_helpers.on_avatar_save_as_menuitem_activate,
				self.contact.jid, self.account, self.contact.name + '.jpeg')
			self.handlers[id] = menuitem
			menu.append(menuitem)
			menu.show_all()
			menu.connect('selection-done', lambda w:w.destroy())	
			# show the menu
			menu.show_all()
			menu.popup(None, None, None, event.button, event.time)
		return True

nicfit's avatar
nicfit committed
936 937
	def _on_window_motion_notify(self, widget, event):
		'''it gets called no matter if it is the active window or not'''
nicfit's avatar
nicfit committed
938
		if self.parent_win.get_active_jid() == self.contact.jid:
939
			# if window is the active one, change vars assisting chatstate
nicfit's avatar
nicfit committed
940 941 942
			self.mouse_over_in_last_5_secs = True
			self.mouse_over_in_last_30_secs = True

943 944
	def _schedule_activity_timers(self):
		self.possible_paused_timeout_id = gobject.timeout_add(5000,
945
			self.check_for_possible_paused_chatstate, None)
946
		self.possible_inactive_timeout_id = gobject.timeout_add(30000,
947
			self.check_for_possible_inactive_chatstate, None)
948

949
	def update_ui(self):
950
		# The name banner is drawn here
951
		ChatControlBase.update_ui(self)
952 953

	def _update_banner_state_image(self):
954
		contact = gajim.contacts.get_contact_with_highest_priority(self.account,
955
			self.contact.jid)
956
		if not contact or self.resource:
nicfit's avatar
nicfit committed
957 958
			# For transient contacts
			contact = self.contact
959 960 961 962 963
		show = contact.show
		jid = contact.jid

		# Set banner image
		img_32 = gajim.interface.roster.get_appropriate_state_images(jid,
964 965 966
			size = '32', icon_name = show)
		img_16 = gajim.interface.roster.get_appropriate_state_images(jid,
			icon_name = show)
967 968 969 970 971 972 973 974 975 976 977 978 979
		if img_32.has_key(show) and img_32[show].get_pixbuf():
			# we have 32x32! use it!
			banner_image = img_32[show]
			use_size_32 = True
		else:
			banner_image = img_16[show]
			use_size_32 = False

		banner_status_img = self.xml.get_widget('banner_status_image')
		if banner_image.get_storage_type() == gtk.IMAGE_ANIMATION:
			banner_status_img.set_from_animation(banner_image.get_animation())
		else:
			pix = banner_image.get_pixbuf()
980 981 982 983 984 985 986
			if pix is not None:
				if use_size_32:
					banner_status_img.set_from_pixbuf(pix)
				else: # we need to scale 16x16 to 32x32
					scaled_pix = pix.scale_simple(32, 32,
									gtk.gdk.INTERP_BILINEAR)
					banner_status_img.set_from_pixbuf(scaled_pix)
987 988 989

		self._update_gpg()

990
	def draw_banner(self, chatstate = None):
991
		'''Draw the fat line at the top of the window that 
dkirov's avatar
dkirov committed
992
		houses the status icon, name, jid.  The chatstate arg should
993 994 995
		only be used if the control's chatstate member is NOT to be use, such as
		composing, paused, etc.
		'''
996 997 998 999 1000 1001
		ChatControlBase.draw_banner(self)

		contact = self.contact
		jid = contact.jid

		banner_name_label = self.xml.get_widget('banner_name_label')
1002
		name = contact.get_shown_name()
1003
		avoid_showing_account_too = False
1004 1005
		if self.resource:
			name += '/' + self.resource
1006
			avoid_showing_account_too = True
sb's avatar
sb committed
1007 1008 1009 1010 1011
		if self.TYPE_ID == message_control.TYPE_PM:
			room_jid = self.contact.jid.split('/')[0]
			room_ctrl = gajim.interface.msg_win_mgr.get_control(room_jid,
				self.account)
			name = _('%s from room %s') % (name, room_ctrl.name)
1012
		name = gtkgui_helpers.escape_for_pango_markup(name)
nicfit's avatar
nicfit committed
1013 1014

		# We know our contacts nick, but if there are any other controls 
1015 1016
		# with the same nick we need to also display the account
		# except if we are talking to two different resources of the same contact
nicfit's avatar
nicfit committed
1017
		acct_info = ''
sb's avatar
sb committed
1018
		self.account_displayed = False
nicfit's avatar
nicfit committed
1019
		for ctrl in self.parent_win.controls():
dkirov's avatar
dkirov committed
1020
			if ctrl == self or ctrl.type_id == 'gc':
nicfit's avatar
nicfit committed
1021
				continue
1022 1023
			if self.contact.get_shown_name() == ctrl.contact.get_shown_name()\
			and not avoid_showing_account_too:
sb's avatar
sb committed
1024 1025 1026 1027
				self.account_displayed = True
				if not ctrl.account_displayed:
					# do that after this instance exists
					gobject.idle_add(ctrl.draw_banner)
nicfit's avatar
nicfit committed
1028 1029 1030
				acct_info = ' (%s)' % \
						gtkgui_helpers.escape_for_pango_markup(self.account)
				break
1031

1032 1033 1034
		status = contact.status
		if status is not None:
			banner_name_label.set_ellipsize(pango.ELLIPSIZE_END)
nkour's avatar
nkour committed
1035
			status = gtkgui_helpers.reduce_chars_newlines(status, max_lines = 2)
1036
		status_escaped = gtkgui_helpers.escape_for_pango_markup(status)
1037

1038
		font_attrs, font_attrs_small = self.get_font_attrs()
1039 1040 1041 1042 1043
		st = gajim.config.get('chat_state_notifications')
		cs = contact.chatstate
		if cs and st in ('composing_only', 'all'):
			if contact.show == 'offline':
				chatstate = ''
sb's avatar
sb committed
1044
			elif contact.composing_jep == 'JEP-0085':
1045
				chatstate = helpers.get_uf_chatstate(cs)
sb's avatar
sb committed
1046
			elif contact.composing_jep == 'JEP-0022':
1047
				if cs in ('composing', 'paused'):
1048 1049 1050 1051
					# only print composing, paused
					chatstate = helpers.get_uf_chatstate(cs)
				else:
					chatstate = ''
1052 1053
			elif chatstate is None:
				chatstate = helpers.get_uf_chatstate(cs)
sb's avatar
sb committed
1054

1055
			label_text = '<span %s>%s</span><span %s>%s %s</span>' % \
sb's avatar
sb committed
1056
				(font_attrs, name, font_attrs_small, acct_info, chatstate)
1057
		else:
dkirov's avatar
dkirov committed
1058
			# weight="heavy" size="x-large"
1059
			label_text = '<span %s>%s</span><span %s>%s</span>' % \
sb's avatar
sb committed
1060
				(font_attrs, name, font_attrs_small, acct_info)
1061
		if status_escaped:
1062
			label_text += '\n<span %s>%s</span>' %\
sb's avatar
sb committed
1063
				(font_attrs_small, status_escaped)
1064 1065
			banner_eventbox = self.xml.get_widget('banner_eventbox')
			self.status_tooltip.set_tip(banner_eventbox, status)
dkirov's avatar
typo  
dkirov committed
1066
			self.status_tooltip.enable()
1067 1068
		else:
			self.status_tooltip.disable()
1069 1070
		# setup the label that holds name and jid
		banner_name_label.set_markup(label_text)
sb's avatar
sb committed
1071

Yann Leboulanger's avatar
Yann Leboulanger committed
1072 1073 1074
	def on_toggle_gpg_togglebutton(self, widget):
		gajim.config.set_per('contacts', self.contact.get_full_jid(),
			'gpg_enabled', widget.get_active())
1075

1076 1077
	def _update_gpg(self):
		tb = self.xml.get_widget('gpg_togglebutton')
1078 1079 1080
		# we can do gpg
		# if self.contact is our own contact info (transports), 
		# don't enable pgp
1081
		if self.contact.keyID and not gajim.jid_is_transport(self.contact.jid): 
1082 1083
			tb.set_sensitive(True)
			tt = _('OpenPGP Encryption')
Yann Leboulanger's avatar
Yann Leboulanger committed
1084 1085

			# restore gpg pref
sb's avatar
sb committed
1086 1087
			gpg_pref = gajim.config.get_per('contacts', self.contact.jid,
				'gpg_enabled')
Yann Leboulanger's avatar
Yann Leboulanger committed
1088
			if gpg_pref == None:
sb's avatar
sb committed
1089 1090 1091
				gajim.config.add_per('contacts', self.contact.jid)
				gpg_pref = gajim.config.get_per('contacts', self.contact.jid,
					'gpg_enabled')
Yann Leboulanger's avatar
Yann Leboulanger committed
1092 1093
			tb.set_active(gpg_pref)

1094 1095 1096
		else:
			tb.set_sensitive(False)
			#we talk about a contact here
1097
			tt = _('%s has not broadcast an OpenPGP key, nor has one been assigned') %\
1098
					self.contact.get_shown_name()
1099 1100
		gtk.Tooltips().set_tip(self.xml.get_widget('gpg_eventbox'), tt)

1101 1102
	def send_message(self, message, keyID = '', chatstate = None):
		'''Send a message to contact'''
1103
		if message in ('', None, '\n') or self._process_command(message):
1104 1105
			return

nicfit's avatar
nicfit committed
1106 1107 1108
		# refresh timers
		self.reset_kbd_mouse_timeout_vars()

1109 1110
		contact = self.contact
		jid = self.contact.jid
1111

1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
		keyID = ''
		encrypted = False
		if self.xml.get_widget('gpg_togglebutton').get_active():
			keyID = contact.keyID
			encrypted = True


		chatstates_on = gajim.config.get('chat_state_notifications') != 'disabled'
		chatstate_to_send = None
		if chatstates_on and contact is not None:
1122
			if contact.composing_jep is None:
1123 1124 1125 1126 1127 1128 1129 1130 1131
				# no info about peer
				# send active to discover chat state capabilities
				# this is here (and not in send_chatstate)
				# because we want it sent with REAL message
				# (not standlone) eg. one that has body
				chatstate_to_send = 'active'
				contact.our_chatstate = 'ask' # pseudo state
			# if peer supports jep85 and we are not 'ask', send 'active'
			# NOTE: first active and 'ask' is set in gajim.py
1132
			elif contact.composing_jep is not False:
1133 1134 1135 1136 1137 1138 1139 1140
				#send active chatstate on every message (as JEP says)
				chatstate_to_send = 'active'
				contact.our_chatstate = 'active'

				gobject.source_remove(self.possible_paused_timeout_id)
				gobject.source_remove(self.possible_inactive_timeout_id)
				self._schedule_activity_timers()
				
1141
		ChatControlBase.send_message(self, message, keyID, type = 'chat',
1142 1143
			chatstate = chatstate_to_send,
			composing_jep = contact.composing_jep)
1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188
		self.print_conversation(message, self.contact.jid, encrypted = encrypted)

	def check_for_possible_paused_chatstate(self, arg):
		''' did we move mouse of that window or write something in message
		textview in the last 5 seconds?
		if yes we go active for mouse, composing for kbd
		if no we go paused if we were previously composing '''
		contact = self.contact
		jid = contact.jid
		current_state = contact.our_chatstate
		if current_state is False: # jid doesn't support chatstates
			return False # stop looping

		message_buffer = self.msg_textview.get_buffer()
		if self.kbd_activity_in_last_5_secs and message_buffer.get_char_count():
			# Only composing if the keyboard activity was in text entry
			self.send_chatstate('composing')
		elif self.mouse_over_in_last_5_secs and\
			jid == self.parent_win.get_active_jid():
			self.send_chatstate('active')
		else:
			if current_state == 'composing':
				self.send_chatstate('paused') # pause composing

		# assume no activity and let the motion-notify or 'insert-text' make them True
		# refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds!
		self.reset_kbd_mouse_timeout_vars()
		return True # loop forever		

	def check_for_possible_inactive_chatstate(self, arg):
		''' did we move mouse over that window or wrote something in message
		textview in the last 30 seconds?
		if yes we go active
		if no we go inactive '''
		contact = self.contact
		jid = contact.jid

		current_state = contact.our_chatstate
		if current_state is False: # jid doesn't support chatstates
			return False # stop looping

		if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs:
			return True # loop forever

		if not self.mouse_over_in_last_30_secs or self.kbd_activity_in_last_30_secs:
1189
			self.send_chatstate('inactive', contact)
1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202

		# assume no activity and let the motion-notify or 'insert-text' make them True
		# refresh 30 seconds too or else it's 30 - 5 = 25 seconds!
		self.reset_kbd_mouse_timeout_vars()
		return True # loop forever

	def reset_kbd_mouse_timeout_vars(self):
		self.kbd_activity_in_last_5_secs = False
		self.mouse_over_in_last_5_secs = False
		self.mouse_over_in_last_30_secs = False
		self.kbd_activity_in_last_30_secs = False

	def print_conversation(self, text, frm = '', tim = None,
1203
		encrypted = False, subject = None):
1204 1205 1206 1207 1208