aboutsummaryrefslogtreecommitdiffstats
path: root/mvc/widgets/tablescroll.py
diff options
context:
space:
mode:
authorJesús Eduardo <heckyel@hyperbola.info>2017-05-31 18:08:31 -0500
committerJesús Eduardo <heckyel@hyperbola.info>2017-05-31 18:08:31 -0500
commite1180428ed3e7634fe1596103511fbb1da05f228 (patch)
tree13de9592bcde7050b089b9644839668024c518b3 /mvc/widgets/tablescroll.py
downloadlibrevideoconverter-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.py154
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))