Fix TextView scrolling

Fixes #8813
This commit is contained in:
Philipp Hörist 2018-03-20 22:19:30 +01:00
parent 25e123a379
commit abdca0eaac
8 changed files with 42 additions and 56 deletions

View File

@ -305,13 +305,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
'conversation_scrolledwindow') 'conversation_scrolledwindow')
self.conv_scrolledwindow.add(self.conv_textview.tv) self.conv_scrolledwindow.add(self.conv_textview.tv)
widget = self.conv_scrolledwindow.get_vadjustment() widget = self.conv_scrolledwindow.get_vadjustment()
id_ = widget.connect('value-changed',
self.on_conversation_vadjustment_value_changed)
self.handlers[id_] = widget
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.was_at_the_end = True
self.correcting = False self.correcting = False
self.last_sent_msg = None self.last_sent_msg = None
@ -965,7 +961,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
full_jid = self.get_full_jid() full_jid = self.get_full_jid()
textview = self.conv_textview textview = self.conv_textview
end = False end = False
if self.was_at_the_end or kind == 'outgoing': if self.conv_textview.autoscroll or kind == 'outgoing':
end = True end = True
if other_tags_for_name is None: if other_tags_for_name is None:
@ -1201,7 +1197,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
if state: if state:
self.set_emoticon_popover() self.set_emoticon_popover()
jid = self.contact.jid jid = self.contact.jid
if self.was_at_the_end: if self.conv_textview.autoscroll:
# we are at the end # we are at the end
type_ = ['printed_' + self.type_id] type_ = ['printed_' + self.type_id]
if self.type_id == message_control.TYPE_GC: if self.type_id == message_control.TYPE_GC:
@ -1221,21 +1217,14 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
else: else:
self.send_chatstate('inactive', self.contact) self.send_chatstate('inactive', self.contact)
def scroll_to_end_iter(self): def scroll_to_end(self, force=False):
self.conv_textview.scroll_to_end_iter() self.conv_textview.scroll_to_end(force)
return False
def on_conversation_vadjustment_changed(self, adjustment): def _on_edge_reached(self, scrolledwindow, pos):
# used to stay at the end of the textview when we shrink conversation if pos != Gtk.PositionType.BOTTOM:
# textview. return
if self.was_at_the_end: # Remove all events and set autoscroll True
self.scroll_to_end_iter() self.conv_textview.autoscroll = True
self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value()\
- adjustment.get_page_size()) < 18
def on_conversation_vadjustment_value_changed(self, adjustment):
self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value() \
- adjustment.get_page_size()) < 18
if self.resource: if self.resource:
jid = self.contact.get_full_jid() jid = self.contact.get_full_jid()
else: else:
@ -1252,8 +1241,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
return return
if not self.parent_win: if not self.parent_win:
return return
if self.conv_textview.at_the_end() and \ if self.parent_win.get_active_control() == self and \
self.parent_win.get_active_control() == self and \
self.parent_win.window.is_active(): self.parent_win.window.is_active():
# we are at the end # we are at the end
if self.type_id == message_control.TYPE_GC: if self.type_id == message_control.TYPE_GC:
@ -1264,6 +1252,18 @@ 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 _on_scroll(self, widget, event):
# On scrolliung UP disable autoscroll
has_direction, direction = event.get_scroll_direction()
if has_direction and direction == Gdk.ScrollDirection.UP:
# Check if we have a Scrollbar
adjustment = self.conv_scrolledwindow.get_vadjustment()
if adjustment.get_upper() != adjustment.get_page_size():
self.conv_textview.autoscroll = False
def on_conversation_vadjustment_changed(self, adjustment):
self.scroll_to_end()
def redraw_after_event_removed(self, jid): def redraw_after_event_removed(self, jid):
""" """
We just removed a 'printed_*' event, redraw contact in roster or We just removed a 'printed_*' event, redraw contact in roster or
@ -1344,7 +1344,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
""" """
# make the last message visible, when changing to "full view" # make the last message visible, when changing to "full view"
if not state: if not state:
GLib.idle_add(self.conv_textview.scroll_to_end_iter) self.scroll_to_end()
widget.set_no_show_all(state) widget.set_no_show_all(state)
if state: if state:

View File

@ -206,7 +206,7 @@ class ConversationTextview(GObject.GObject):
self.last_sent_message_id = None self.last_sent_message_id = None
# 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 = {}
self.autoscroll = True
# 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
@ -370,16 +370,9 @@ class ConversationTextview(GObject.GObject):
self.tv.tagXMPP.set_property('foreground', color) self.tv.tagXMPP.set_property('foreground', color)
self.tv.tagSthAtSth.set_property('foreground', color) self.tv.tagSthAtSth.set_property('foreground', color)
def at_the_end(self): def scroll_to_end(self, force=False):
return gtkgui_helpers.at_the_end(self.tv.get_parent()) if self.autoscroll or force:
gtkgui_helpers.scroll_to_end(self.tv.get_parent())
def scroll_to_end_iter(self):
buffer_ = self.tv.get_buffer()
end_iter = buffer_.get_end_iter()
if not end_iter:
return False
self.tv.scroll_to_iter(end_iter, 0, False, 1, 1)
return False # when called in an idle_add, just do it once
def correct_message(self, correct_id, kind, name): def correct_message(self, correct_id, kind, name):
allowed = True allowed = True
@ -449,7 +442,7 @@ class ConversationTextview(GObject.GObject):
buffer_.end_user_action() buffer_.end_user_action()
del self.xep0184_marks[id_] del self.xep0184_marks[id_]
def show_focus_out_line(self, scroll=True): def show_focus_out_line(self):
if not self.allow_focus_out_line: if not self.allow_focus_out_line:
# if room did not receive focus-in from the last time we added # if room did not receive focus-in from the last time we added
# --- line then do not readd # --- line then do not readd
@ -505,11 +498,7 @@ class ConversationTextview(GObject.GObject):
buffer_.get_end_iter(), left_gravity=True) buffer_.get_end_iter(), left_gravity=True)
buffer_.end_user_action() buffer_.end_user_action()
self.scroll_to_end()
if scroll:
# scroll to the end (via idle in case the scrollbar has
# appeared)
GLib.idle_add(self.scroll_to_end_iter)
def clear(self, tv = None): def clear(self, tv = None):
""" """
@ -1231,9 +1220,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 self.at_the_end() or kind == 'outgoing': if self.autoscroll or kind == 'outgoing':
# we are at the end or we are sending something # we are at the end or we are sending something
GLib.idle_add(self.scroll_to_end_iter) self.scroll_to_end(force=True)
self.just_cleared = False self.just_cleared = False
buffer_.end_user_action() buffer_.end_user_action()

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.2 --> <!-- Generated with glade 3.21.0 -->
<interface> <interface>
<requires lib="gtk+" version="3.20"/> <requires lib="gtk+" version="3.20"/>
<object class="GtkAdjustment" id="adjustment1"> <object class="GtkAdjustment" id="adjustment1">
@ -594,6 +594,8 @@
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="shadow_type">in</property> <property name="shadow_type">in</property>
<property name="overlay_scrolling">False</property> <property name="overlay_scrolling">False</property>
<signal name="edge-reached" handler="_on_edge_reached" swapped="no"/>
<signal name="scroll-event" handler="_on_scroll" swapped="no"/>
<child> <child>
<placeholder/> <placeholder/>
</child> </child>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.2 --> <!-- Generated with glade 3.21.0 -->
<interface> <interface>
<requires lib="gtk+" version="3.20"/> <requires lib="gtk+" version="3.20"/>
<object class="GtkMenu" id="formattings_menu"> <object class="GtkMenu" id="formattings_menu">
@ -187,6 +187,8 @@
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="shadow_type">in</property> <property name="shadow_type">in</property>
<property name="overlay_scrolling">False</property> <property name="overlay_scrolling">False</property>
<signal name="edge-reached" handler="_on_edge_reached" swapped="no"/>
<signal name="scroll-event" handler="_on_scroll" swapped="no"/>
<child> <child>
<placeholder/> <placeholder/>
</child> </child>

View File

@ -1380,8 +1380,7 @@ class GroupchatControl(ChatControlBase):
# we have full focus (we are reading it!) # we have full focus (we are reading it!)
return return
at_the_end = self.conv_textview.at_the_end() self.conv_textview.show_focus_out_line()
self.conv_textview.show_focus_out_line(scroll=at_the_end)
def needs_visual_notification(self, text): def needs_visual_notification(self, text):
""" """

View File

@ -1814,8 +1814,8 @@ class Interface:
w.window.present() w.window.present()
# 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 ctrl.scroll_to_end()
tv.scroll_to_end_iter()
def join_gc_minimal(self, account, room_jid, password=None): def join_gc_minimal(self, account, room_jid, password=None):
if account is not None: if account is not None:

View File

@ -292,16 +292,10 @@ class MessageWindow(object):
self._controls[control.account][fjid] = control self._controls[control.account][fjid] = control
if self.get_num_controls() == 2: if self.get_num_controls() == 2:
# is first conversation_textview scrolled down ?
scrolled = False
first_widget = self.notebook.get_nth_page(0) first_widget = self.notebook.get_nth_page(0)
ctrl = self._widget_to_control(first_widget) ctrl = self._widget_to_control(first_widget)
conv_textview = ctrl.conv_textview
if conv_textview.at_the_end():
scrolled = True
self.notebook.set_show_tabs(True) self.notebook.set_show_tabs(True)
if scrolled: ctrl.scroll_to_end()
GLib.idle_add(conv_textview.scroll_to_end_iter)
# Add notebook page and connect up to the tab's close button # Add notebook page and connect up to the tab's close button
xml = gtkgui_helpers.get_gtk_builder('message_window.ui', 'chat_tab_ebox') xml = gtkgui_helpers.get_gtk_builder('message_window.ui', 'chat_tab_ebox')

View File

@ -2723,7 +2723,7 @@ class RosterWindow:
xep0184_id=xep0184_id, additional_data=obj.additional_data) xep0184_id=xep0184_id, additional_data=obj.additional_data)
if obj.msg_log_id: if obj.msg_log_id:
pw = obj.session.control.parent_win pw = obj.session.control.parent_win
end = obj.session.control.was_at_the_end end = obj.session.control.conv_textview.autoscroll
if not pw or (pw.get_active_control() and obj.session.control \ if not pw or (pw.get_active_control() and obj.session.control \
== pw.get_active_control() and pw.is_active() and end): == pw.get_active_control() and pw.is_active() and end):
app.logger.set_read_messages([obj.msg_log_id]) app.logger.set_read_messages([obj.msg_log_id])