Merge branch 'scrolling' into 'master'

Improve Scrolling

See merge request !65
This commit is contained in:
Philipp Hörist 2017-03-11 22:18:37 +01:00
commit 55561b3dd0
5 changed files with 10 additions and 134 deletions

View File

@ -39,6 +39,7 @@ import gui_menu_builder
import message_control import message_control
import dialogs import dialogs
from common import logger
from common import gajim from common import gajim
from common import helpers from common import helpers
from common import exceptions from common import exceptions
@ -1626,10 +1627,9 @@ class ChatControl(ChatControlBase):
rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many, rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many,
pending_how_many, timeout, self.account) pending_how_many, timeout, self.account)
except exceptions.DatabaseMalformed: except exceptions.DatabaseMalformed:
import common.logger
dialogs.ErrorDialog(_('Database Error'), dialogs.ErrorDialog(_('Database Error'),
_('The database file (%s) cannot be read. Try to repair it or ' _('The database file (%s) cannot be read. Try to repair it or '
'remove it (all history will be lost).') % common.logger.LOG_DB_PATH) 'remove it (all history will be lost).') % logger.LOG_DB_PATH)
rows = [] rows = []
local_old_kind = None local_old_kind = None
self.conv_textview.just_cleared = True self.conv_textview.just_cleared = True

View File

@ -323,7 +323,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
id_ = widget.connect('changed', id_ = widget.connect('changed',
self.on_conversation_vadjustment_changed) self.on_conversation_vadjustment_changed)
self.handlers[id_] = widget self.handlers[id_] = widget
self.scroll_to_end_id = None
self.was_at_the_end = True self.was_at_the_end = True
self.correcting = False self.correcting = False
self.last_sent_msg = None self.last_sent_msg = None
@ -984,27 +983,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
# There were events to remove # There were events to remove
self.redraw_after_event_removed(jid) self.redraw_after_event_removed(jid)
def bring_scroll_to_end(self, textview, diff_y=0): def scroll_to_end_iter(self):
""" self.conv_textview.scroll_to_end_iter()
Scroll to the end of textview if end is not visible
"""
if self.scroll_to_end_id:
# a scroll is already planned
return
buffer_ = textview.get_buffer()
end_iter = buffer_.get_end_iter()
end_rect = textview.get_iter_location(end_iter)
visible_rect = textview.get_visible_rect()
# scroll only if expected end is not visible
if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
self.scroll_to_end_id = GLib.idle_add(self.scroll_to_end_iter,
textview)
def scroll_to_end_iter(self, textview):
buffer_ = textview.get_buffer()
end_iter = buffer_.get_end_iter()
textview.scroll_to_iter(end_iter, 0, False, 1, 1)
self.scroll_to_end_id = None
return False return False
def on_configure_event(self, msg_textview, event): def on_configure_event(self, msg_textview, event):
@ -1065,18 +1045,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
# used to stay at the end of the textview when we shrink conversation # used to stay at the end of the textview when we shrink conversation
# textview. # textview.
if self.was_at_the_end: if self.was_at_the_end:
if self.conv_textview.at_the_end(): self.scroll_to_end_iter()
# we are at the end
self.conv_textview.bring_scroll_to_end(-18)
else:
self.conv_textview.bring_scroll_to_end(-18, use_smooth=False)
self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value()\ self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value()\
- adjustment.get_page_size()) < 18 - adjustment.get_page_size()) < 18
def on_conversation_vadjustment_value_changed(self, adjustment): def on_conversation_vadjustment_value_changed(self, adjustment):
# stop automatic scroll when we manually scroll
if not self.conv_textview.auto_scrolling:
self.conv_textview.stop_scrolling()
self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value() \ self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value() \
- adjustment.get_page_size()) < 18 - adjustment.get_page_size()) < 18
if self.resource: if self.resource:

View File

@ -279,7 +279,6 @@ 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, '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', _('List of colors, separated by ":", that will be used to color nicknames in group chats.'), True ], 'gc_nicknames_colors': [ opt_str, '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', _('List of colors, separated by ":", 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.')],

View File

@ -216,9 +216,6 @@ class ConversationTextview(GObject.GObject):
# last_received_message_id[name] = (msg_stanza_id, line_start_mark) # last_received_message_id[name] = (msg_stanza_id, line_start_mark)
self.last_received_message_id = {} self.last_received_message_id = {}
# It's True when we scroll in the code, so we can detect scroll from user
self.auto_scrolling = False
# connect signals # connect signals
id_ = self.tv.connect('populate_popup', self.on_textview_populate_popup) id_ = self.tv.connect('populate_popup', self.on_textview_populate_popup)
self.handlers[id_] = self.tv self.handlers[id_] = self.tv
@ -325,7 +322,6 @@ class ConversationTextview(GObject.GObject):
# holds a mark at the end of --- line # holds a mark at the end of --- line
self.focus_out_end_mark = None self.focus_out_end_mark = None
self.smooth_id = None
self.just_cleared = False self.just_cleared = False
def query_tooltip(self, widget, x_pos, y_pos, keyboard_mode, tooltip): def query_tooltip(self, widget, x_pos, y_pos, keyboard_mode, tooltip):
@ -398,87 +394,7 @@ class ConversationTextview(GObject.GObject):
self.tv.tagSthAtSth.set_property('foreground', color) self.tv.tagSthAtSth.set_property('foreground', color)
def at_the_end(self): def at_the_end(self):
buffer_ = self.tv.get_buffer() return gtkgui_helpers.at_the_end(self.tv.get_parent())
end_iter = buffer_.get_end_iter()
end_rect = self.tv.get_iter_location(end_iter)
visible_rect = self.tv.get_visible_rect()
if end_rect.y <= (visible_rect.y + visible_rect.height):
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.get_upper() - vadj.get_page_size() + 1
cur_val = vadj.get_value()
# scroll by 1/3rd of remaining distance
onethird = cur_val + ((max_val - cur_val) / 3.0)
self.auto_scrolling = True
vadj.set_value(onethird)
self.auto_scrolling = False
if max_val - onethird < 0.01:
self.smooth_id = None
self.smooth_scroll_timer.cancel()
return False
return True
def smooth_scroll_timeout(self):
GLib.idle_add(self.do_smooth_scroll_timeout)
return
def do_smooth_scroll_timeout(self):
if not self.smooth_id:
# we finished scrolling
return
GLib.source_remove(self.smooth_id)
self.smooth_id = None
parent = self.tv.get_parent()
if parent:
vadj = parent.get_vadjustment()
self.auto_scrolling = True
vadj.set_value(vadj.get_upper() - vadj.get_page_size() + 1)
self.auto_scrolling = False
def smooth_scroll_to_end(self):
if self.smooth_id is not None: # already scrolling
return False
self.smooth_id = GLib.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()
end_mark = buffer_.get_mark('end')
if not end_mark:
return False
self.auto_scrolling = True
self.tv.scroll_to_mark(end_mark, 0, True, 0, 1)
adjustment = parent.get_hadjustment()
adjustment.set_value(0)
self.auto_scrolling = False
return False # when called in an idle_add, just do it once
def bring_scroll_to_end(self, diff_y=0, use_smooth=None):
''' scrolls to the end of textview if end is not visible '''
if use_smooth is None:
use_smooth = gajim.config.get('use_smooth_scrolling')
buffer_ = self.tv.get_buffer()
end_iter = buffer_.get_end_iter()
end_rect = self.tv.get_iter_location(end_iter)
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):
if use_smooth:
GLib.idle_add(self.smooth_scroll_to_end)
else:
GLib.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()
@ -488,12 +404,6 @@ class ConversationTextview(GObject.GObject):
self.tv.scroll_to_iter(end_iter, 0, False, 1, 1) self.tv.scroll_to_iter(end_iter, 0, False, 1, 1)
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 stop_scrolling(self):
if self.smooth_id:
GLib.source_remove(self.smooth_id)
self.smooth_id = None
self.smooth_scroll_timer.cancel()
def correct_message(self, correct_id, kind, name): def correct_message(self, correct_id, kind, name):
allowed = True allowed = True
if kind == 'incoming': if kind == 'incoming':
@ -649,7 +559,7 @@ class ConversationTextview(GObject.GObject):
if scroll: if scroll:
# scroll to the end (via idle in case the scrollbar has # scroll to the end (via idle in case the scrollbar has
# appeared) # appeared)
GLib.idle_add(self.scroll_to_end) GLib.idle_add(self.scroll_to_end_iter)
def on_textview_draw(self, widget, ctx): def on_textview_draw(self, widget, ctx):
return return
@ -1283,8 +1193,6 @@ class ConversationTextview(GObject.GObject):
# even if we insert directly at the mark iter # even if we insert directly at the mark iter
temp_mark = buffer_.create_mark('temp', iter_, left_gravity=True) temp_mark = buffer_.create_mark('temp', iter_, left_gravity=True)
at_the_end = self.at_the_end()
if text.startswith('/me '): if text.startswith('/me '):
direction_mark = i18n.paragraph_direction_mark(str(text[3:])) direction_mark = i18n.paragraph_direction_mark(str(text[3:]))
else: else:
@ -1369,13 +1277,9 @@ class ConversationTextview(GObject.GObject):
self.last_sent_message_id = (msg_stanza_id, new_mark) self.last_sent_message_id = (msg_stanza_id, new_mark)
if not insert_mark: if not insert_mark:
if at_the_end or kind == 'outgoing': if self.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) GLib.idle_add(self.scroll_to_end_iter)
if gajim.config.get('use_smooth_scrolling'):
GLib.idle_add(self.smooth_scroll_to_end)
else:
GLib.idle_add(self.scroll_to_end)
self.just_cleared = False self.just_cleared = False
buffer_.end_user_action() buffer_.end_user_action()

View File

@ -1831,7 +1831,7 @@ class Interface:
# Using isinstance here because we want to catch all derived types # Using isinstance here because we want to catch all derived types
if isinstance(ctrl, ChatControlBase): if isinstance(ctrl, ChatControlBase):
tv = ctrl.conv_textview tv = ctrl.conv_textview
tv.scroll_to_end() tv.scroll_to_end_iter()
################################################################################ ################################################################################
### Methods dealing with emoticons ### Methods dealing with emoticons