diff options
author | Jesús Eduardo <heckyel@hyperbola.info> | 2017-05-31 18:08:31 -0500 |
---|---|---|
committer | Jesús Eduardo <heckyel@hyperbola.info> | 2017-05-31 18:08:31 -0500 |
commit | e1180428ed3e7634fe1596103511fbb1da05f228 (patch) | |
tree | 13de9592bcde7050b089b9644839668024c518b3 /mvc/widgets/tablescroll.py | |
download | librevideoconverter-e1180428ed3e7634fe1596103511fbb1da05f228.tar.lz librevideoconverter-e1180428ed3e7634fe1596103511fbb1da05f228.tar.xz librevideoconverter-e1180428ed3e7634fe1596103511fbb1da05f228.zip |
first commit
Diffstat (limited to 'mvc/widgets/tablescroll.py')
-rw-r--r-- | mvc/widgets/tablescroll.py | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/mvc/widgets/tablescroll.py b/mvc/widgets/tablescroll.py new file mode 100644 index 0000000..841e62c --- /dev/null +++ b/mvc/widgets/tablescroll.py @@ -0,0 +1,154 @@ +# @Base: Miro - an RSS based video player application +# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 +# Participatory Culture Foundation +# +# 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; either version 2 of the License, or +# (at your option) any later version. +# +# 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. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +"""tablescroll.py -- High-level scroll management. This ensures that behavior +like scroll_to_item works the same way across platforms. +""" + +from mvc.errors import WidgetActionError + + +class ScrollbarOwnerMixin(object): + """Scrollbar management for TableView. + + External methods have undecorated names; internal methods start with _. + + External methods: + - handle failure themselves (e.g. return None or retry later) + - return basic data types (e.g. (x, y) tuples) + - use "tree" coordinates + + Internal methods (intended to be used by ScrollbarOwnerMixin and the + platform implementations): + - raise WidgetActionError subclasses on failure + - use Rect/Point structs + - also use "tree" coordinates + """ + def __init__(self, _work_around_17153=False): + self.__work_around_17153 = _work_around_17153 + self._scroll_to_iter_callback = None + self.create_signal('scroll-range-changed') + + def scroll_to_iter(self, iter_, manual=True, recenter=False): + """Scroll the given item into view. + + manual: scroll even if we were not following the playing item + recenter: scroll even if item is in top half of view + """ + try: + item = self._get_item_area(iter_) + visible = self._get_visible_area() + manually_scrolled = self._manually_scrolled + except WidgetActionError: + if self._scroll_to_iter_callback: + # We just retried and failed. Do nothing; we will retry again + # next time scrollable range changes. + return + # We just tried and failed; schedule a retry when the scrollable + # range changes. + self._scroll_to_iter_callback = self.connect('scroll-range-changed', + lambda *a: self.scroll_to_iter(iter_, manual, recenter)) + return + # If the above succeeded, we know the iter's position; this means we can + # set_scroll_position to that position. That may work now or be + # postponed until later, but either way we're done with scroll_to_iter. + if self._scroll_to_iter_callback: + self.disconnect(self._scroll_to_iter_callback) + self._scroll_to_iter_callback = None + visible_bottom = visible.y + visible.height + visible_middle = visible.y + visible.height // 2 + item_bottom = item.y + item.height + item_middle = item.y + item.height // 2 + in_top = item_bottom >= visible.y and item.y <= visible_middle + in_bottom = item_bottom >= visible_middle and item.y <= visible_bottom + if self._should_scroll( + manual, in_top, in_bottom, recenter, manually_scrolled): + destination = item_middle - visible.height // 2 + self._set_vertical_scroll(destination) + # set_scroll_position will take care of scroll to the position when + # possible; this may or may not be now, but our work here is done. + + def set_scroll_position(self, position, restore_only=False, + _hack_for_17153=False): + """Scroll the top left corner to the given (x, y) offset from the origin + of the view. + + restore_only: set the value only if no other value has been set yet + """ + if _hack_for_17153 and not self.__work_around_17153: + return + if not restore_only or not self._position_set: + self._set_scroll_position(position) + + @classmethod + def _should_scroll(cls, + manual, in_top, in_bottom, recenter, manually_scrolled): + if not manual and manually_scrolled: + # The user has moved the scrollbars since we last autoscrolled, and + # we're deciding whether we should resume autoscrolling. + # We want to do that when the currently-playing item catches up to + # the center of the screen i.e. is part above the center, part below + return in_top and in_bottom + # This is a manual scroll, or we're already autoscrolling - so we no + # longer need to worry about either manual or manually_scrolled + if in_top: + # The item is in the top half; let playback catch up with the + # current scroll position, unless recentering has been requested + return recenter + if in_bottom: + # We land here when: + # - playback has begun with an item in the bottom half of the screen + # - scroll is following sequential playback + # Either way we want to jump down to the item. + return True + # We're scrolling to an item that's not in view because: + # - playback has begun with an item that is out of sight + # - we're autoscrolling on shuffle + # Either way we want to show the item. + return True + + def reset_scroll(self): + """To scroll back to the origin; platform code might want to do + something special to forget the current position when this happens. + """ + self.set_scroll_position((0, 0)) + + def get_scroll_position(self): + """Returns the current scroll position, or None if not ready.""" + try: + return tuple(self._get_scroll_position()) + except WidgetActionError: + return None + + def _set_vertical_scroll(self, pos): + """Helper to set our vertical position without affecting our horizontal + position. + """ + # FIXME: shouldn't reset horizontal position + self.set_scroll_position((0, pos)) |