Index: src/conversation_textview.py
===================================================================
--- src/conversation_textview.py	(révision 8490)
+++ src/conversation_textview.py	(copie de travail)
@@ -10,13 +10,14 @@
 ##
 ## 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
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
 ## GNU General Public License for more details.
 ##
 
 import random
 from tempfile import gettempdir
 from subprocess import Popen
+from threading import Timer # for smooth scrolling
 
 import gtk
 import pango
@@ -44,6 +45,10 @@
 	path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png')
 	FOCUS_OUT_LINE_PIXBUF = gtk.gdk.pixbuf_new_from_file(path_to_file)
 
+	# smooth scroll constants
+	MAX_SCROLL_TIME = 0.4 # seconds
+	SCROLL_DELAY = 33 # milliseconds
+
 	def __init__(self, account, used_in_history_window = False):
 		'''if used_in_history_window is True, then we do not show
 		Clear menuitem in context menu'''
@@ -154,6 +159,7 @@
 		self.line_tooltip = tooltips.BaseTooltip()
 		# use it for hr too
 		self.tv.focus_out_line_pixbuf = ConversationTextview.FOCUS_OUT_LINE_PIXBUF
+		self.smooth_id = None
 
 	def del_handlers(self):
 		for i in self.handlers.keys():
@@ -181,6 +187,41 @@
 			return True
 		return False
 
+	# Smooth scrolling inspired by Pidgin code
+	def smooth_scroll(self):
+		parent = self.tv.get_parent()
+		if not parent:
+			return False
+		vadj = parent.get_vadjustment()
+		max_val = vadj.upper - vadj.page_size + 1
+		cur_val = vadj.get_value()
+		# scroll by 1/3rd of remaining distance
+		onethird = cur_val + ((max_val - cur_val) / 3.0)
+		vadj.set_value(onethird)
+		if max_val - onethird < 0.01:
+			self.smooth_id = None
+			self.smooth_scroll_timer.cancel()
+			return False
+		return True		   
+
+	def smooth_scroll_timeout(self):
+		gobject.source_remove(self.smooth_id)
+		self.smooth_id = None
+		parent = self.tv.get_parent()
+		if parent:
+			vadj = parent.get_vadjustment()
+			vadj.set_value(vadj.upper - vadj.page_size + 1)
+
+	def smooth_scroll_to_end(self):
+		if None != self.smooth_id: # already scrolling
+			return False
+		self.smooth_id = gobject.timeout_add(self.SCROLL_DELAY,
+											 self.smooth_scroll)
+		self.smooth_scroll_timer = Timer(self.MAX_SCROLL_TIME,
+										 self.smooth_scroll_timeout)
+		self.smooth_scroll_timer.start()
+		return False
+
 	def scroll_to_end(self):
 		parent = self.tv.get_parent()
 		buffer = self.tv.get_buffer()
@@ -192,7 +233,9 @@
 		adjustment.set_value(0)
 		return False # when called in an idle_add, just do it once
 
-	def bring_scroll_to_end(self, diff_y = 0):
+	def bring_scroll_to_end(self, diff_y = 0,\
+							use_smooth =\
+							gajim.config.get('use_smooth_scrolling')):
 		''' scrolls to the end of textview if end is not visible '''
 		buffer = self.tv.get_buffer()
 		end_iter = buffer.get_end_iter()
@@ -200,7 +243,10 @@
 		visible_rect = self.tv.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)
+			if use_smooth:
+				gobject.idle_add(self.smooth_scroll_to_end)
+			else:
+				gobject.idle_add(self.scroll_to_end_iter)
 
 	def scroll_to_end_iter(self):
 		buffer = self.tv.get_buffer()
@@ -407,7 +453,7 @@
 				item.set_property('sensitive', False)
 			else:
 				item = gtk.MenuItem(_('Web _Search for it'))
-				link =  search_link % self.selected_phrase
+				link =	search_link % self.selected_phrase
 				id = item.connect('activate', self.visit_url_from_menuitem, link)
 				self.handlers[id] = item
 			submenu.append(item)
@@ -632,7 +678,7 @@
 				cwd=gettempdir())
 			exitcode = p.wait()
 
-		if exitcode == 0:	    
+		if exitcode == 0:		
 			p = Popen(['dvips', '-E', '-o', tmpfile + '.ps', tmpfile + '.dvi'],
 				cwd=gettempdir())
 			exitcode = p.wait()
@@ -857,7 +903,10 @@
 		if at_the_end or kind == 'outgoing':
 			# we are at the end or we are sending something
 			# scroll to the end (via idle in case the scrollbar has appeared)
-			gobject.idle_add(self.scroll_to_end)
+			if gajim.config.get('use_smooth_scrolling'):
+				gobject.idle_add(self.smooth_scroll_to_end)
+			else:
+				gobject.idle_add(self.scroll_to_end)
 
 		buffer.end_user_action()
 
Index: src/common/config.py
===================================================================
--- src/common/config.py	(révision 8490)
+++ src/common/config.py	(copie de travail)
@@ -218,6 +218,7 @@
 		'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat occupants list in group chat window.')],
 		'chat_merge_consecutive_nickname': [opt_bool, False, _('In a chat, show the nickname at the beginning of a line only when it\'s not the same person talking than in previous message.')],
 		'chat_merge_consecutive_nickname_indent': [opt_str, '  ', _('Indentation when using merge consecutive nickname.')],
+        'use_smooth_scrolling': [opt_bool, True, _('Smooth scroll message in conversation window')],
 		'gc_nicknames_colors': [ opt_str, '#a34526:#c000ff:#0012ff:#388a99:#045723:#7c7c7c:#ff8a00:#94452d:#244b5a:#32645a', _('List of colors that will be used to color nicknames in group chats.'), True ],
 		'ctrl_tab_go_to_next_composing': [opt_bool, True, _('Ctrl-Tab go to next composing tab when none is unread.')],
 		'confirm_metacontacts': [ opt_str, '', _('Should we show the confirm metacontacts creation dialog or not? Empty string means we never show the dialog.')],
Index: src/chat_control.py
===================================================================
--- src/chat_control.py	(révision 8490)
+++ src/chat_control.py	(copie de travail)
@@ -253,6 +253,8 @@
 		# For JEP-0172
 		self.user_nick = None
 
+		self.smooth = True
+
 	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):
@@ -812,9 +814,10 @@
 				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)
-		
+			self.conv_textview.bring_scroll_to_end(diff_y - 18, False)
+		else:
+			self.conv_textview.bring_scroll_to_end(diff_y - 18, self.smooth)
+		self.smooth = True # reinit the flag
 		# enable scrollbar automatic policy for horizontal scrollbar
 		# if message we have in message_textview is too big
 		if requisition.width > message_width:
@@ -895,6 +898,7 @@
 			if self.sent_history_pos == 0:
 				return
 			self.sent_history_pos = self.sent_history_pos - 1
+			self.smooth = False
 			conv_buf.set_text(self.sent_history[self.sent_history_pos])
 		elif direction == 'down':
 			if self.sent_history_pos >= size - 1:
@@ -904,6 +908,7 @@
 				return
 
 			self.sent_history_pos = self.sent_history_pos + 1
+			self.smooth = False
 			conv_buf.set_text(self.sent_history[self.sent_history_pos])
 
 	def lighten_color(self, color):
