[Geobert] Smooth scrolling of conversation textview. see #3358
This commit is contained in:
parent
b60036af8f
commit
7bd952d691
|
@ -253,6 +253,8 @@ class ChatControlBase(MessageControl):
|
||||||
# For JEP-0172
|
# For JEP-0172
|
||||||
self.user_nick = None
|
self.user_nick = None
|
||||||
|
|
||||||
|
self.smooth = True
|
||||||
|
|
||||||
def on_msg_textview_populate_popup(self, textview, menu):
|
def on_msg_textview_populate_popup(self, textview, menu):
|
||||||
'''we override the default context menu and we prepend an option to switch languages'''
|
'''we override the default context menu and we prepend an option to switch languages'''
|
||||||
def _on_select_dictionary(widget, lang):
|
def _on_select_dictionary(widget, lang):
|
||||||
|
@ -812,9 +814,10 @@ class ChatControlBase(MessageControl):
|
||||||
self.msg_scrolledwindow.set_property('vscrollbar-policy',
|
self.msg_scrolledwindow.set_property('vscrollbar-policy',
|
||||||
gtk.POLICY_NEVER)
|
gtk.POLICY_NEVER)
|
||||||
self.msg_scrolledwindow.set_property('height-request', -1)
|
self.msg_scrolledwindow.set_property('height-request', -1)
|
||||||
|
self.conv_textview.bring_scroll_to_end(diff_y - 18, False)
|
||||||
self.conv_textview.bring_scroll_to_end(diff_y - 18)
|
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
|
# enable scrollbar automatic policy for horizontal scrollbar
|
||||||
# if message we have in message_textview is too big
|
# if message we have in message_textview is too big
|
||||||
if requisition.width > message_width:
|
if requisition.width > message_width:
|
||||||
|
@ -895,6 +898,7 @@ class ChatControlBase(MessageControl):
|
||||||
if self.sent_history_pos == 0:
|
if self.sent_history_pos == 0:
|
||||||
return
|
return
|
||||||
self.sent_history_pos = self.sent_history_pos - 1
|
self.sent_history_pos = self.sent_history_pos - 1
|
||||||
|
self.smooth = False
|
||||||
conv_buf.set_text(self.sent_history[self.sent_history_pos])
|
conv_buf.set_text(self.sent_history[self.sent_history_pos])
|
||||||
elif direction == 'down':
|
elif direction == 'down':
|
||||||
if self.sent_history_pos >= size - 1:
|
if self.sent_history_pos >= size - 1:
|
||||||
|
@ -904,6 +908,7 @@ class ChatControlBase(MessageControl):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.sent_history_pos = self.sent_history_pos + 1
|
self.sent_history_pos = self.sent_history_pos + 1
|
||||||
|
self.smooth = False
|
||||||
conv_buf.set_text(self.sent_history[self.sent_history_pos])
|
conv_buf.set_text(self.sent_history[self.sent_history_pos])
|
||||||
|
|
||||||
def lighten_color(self, color):
|
def lighten_color(self, color):
|
||||||
|
|
|
@ -218,6 +218,7 @@ class Config:
|
||||||
'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat occupants list in group chat window.')],
|
'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': [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.')],
|
'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 ],
|
'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.')],
|
'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.')],
|
'confirm_metacontacts': [ opt_str, '', _('Should we show the confirm metacontacts creation dialog or not? Empty string means we never show the dialog.')],
|
||||||
|
|
|
@ -10,13 +10,14 @@
|
||||||
##
|
##
|
||||||
## This program is distributed in the hope that it will be useful,
|
## This program is distributed in the hope that it will be useful,
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
## 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.
|
## GNU General Public License for more details.
|
||||||
##
|
##
|
||||||
|
|
||||||
import random
|
import random
|
||||||
from tempfile import gettempdir
|
from tempfile import gettempdir
|
||||||
from subprocess import Popen
|
from subprocess import Popen
|
||||||
|
from threading import Timer # for smooth scrolling
|
||||||
|
|
||||||
import gtk
|
import gtk
|
||||||
import pango
|
import pango
|
||||||
|
@ -44,6 +45,10 @@ class ConversationTextview:
|
||||||
path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png')
|
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)
|
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):
|
def __init__(self, account, used_in_history_window = False):
|
||||||
'''if used_in_history_window is True, then we do not show
|
'''if used_in_history_window is True, then we do not show
|
||||||
Clear menuitem in context menu'''
|
Clear menuitem in context menu'''
|
||||||
|
@ -154,6 +159,7 @@ class ConversationTextview:
|
||||||
self.line_tooltip = tooltips.BaseTooltip()
|
self.line_tooltip = tooltips.BaseTooltip()
|
||||||
# use it for hr too
|
# use it for hr too
|
||||||
self.tv.focus_out_line_pixbuf = ConversationTextview.FOCUS_OUT_LINE_PIXBUF
|
self.tv.focus_out_line_pixbuf = ConversationTextview.FOCUS_OUT_LINE_PIXBUF
|
||||||
|
self.smooth_id = None
|
||||||
|
|
||||||
def del_handlers(self):
|
def del_handlers(self):
|
||||||
for i in self.handlers.keys():
|
for i in self.handlers.keys():
|
||||||
|
@ -181,6 +187,41 @@ class ConversationTextview:
|
||||||
return True
|
return True
|
||||||
return False
|
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):
|
def scroll_to_end(self):
|
||||||
parent = self.tv.get_parent()
|
parent = self.tv.get_parent()
|
||||||
buffer = self.tv.get_buffer()
|
buffer = self.tv.get_buffer()
|
||||||
|
@ -192,7 +233,9 @@ class ConversationTextview:
|
||||||
adjustment.set_value(0)
|
adjustment.set_value(0)
|
||||||
return False # when called in an idle_add, just do it once
|
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 '''
|
''' scrolls to the end of textview if end is not visible '''
|
||||||
buffer = self.tv.get_buffer()
|
buffer = self.tv.get_buffer()
|
||||||
end_iter = buffer.get_end_iter()
|
end_iter = buffer.get_end_iter()
|
||||||
|
@ -200,7 +243,10 @@ class ConversationTextview:
|
||||||
visible_rect = self.tv.get_visible_rect()
|
visible_rect = self.tv.get_visible_rect()
|
||||||
# scroll only if expected end is not visible
|
# scroll only if expected end is not visible
|
||||||
if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
|
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):
|
def scroll_to_end_iter(self):
|
||||||
buffer = self.tv.get_buffer()
|
buffer = self.tv.get_buffer()
|
||||||
|
@ -407,7 +453,7 @@ class ConversationTextview:
|
||||||
item.set_property('sensitive', False)
|
item.set_property('sensitive', False)
|
||||||
else:
|
else:
|
||||||
item = gtk.MenuItem(_('Web _Search for it'))
|
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)
|
id = item.connect('activate', self.visit_url_from_menuitem, link)
|
||||||
self.handlers[id] = item
|
self.handlers[id] = item
|
||||||
submenu.append(item)
|
submenu.append(item)
|
||||||
|
@ -632,7 +678,7 @@ class ConversationTextview:
|
||||||
cwd=gettempdir())
|
cwd=gettempdir())
|
||||||
exitcode = p.wait()
|
exitcode = p.wait()
|
||||||
|
|
||||||
if exitcode == 0:
|
if exitcode == 0:
|
||||||
p = Popen(['dvips', '-E', '-o', tmpfile + '.ps', tmpfile + '.dvi'],
|
p = Popen(['dvips', '-E', '-o', tmpfile + '.ps', tmpfile + '.dvi'],
|
||||||
cwd=gettempdir())
|
cwd=gettempdir())
|
||||||
exitcode = p.wait()
|
exitcode = p.wait()
|
||||||
|
@ -857,7 +903,10 @@ class ConversationTextview:
|
||||||
if at_the_end or kind == 'outgoing':
|
if at_the_end or kind == 'outgoing':
|
||||||
# we are at the end or we are sending something
|
# we are at the end or we are sending something
|
||||||
# scroll to the end (via idle in case the scrollbar has appeared)
|
# 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()
|
buffer.end_user_action()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue