aboutsummaryrefslogtreecommitdiffstats
path: root/lvc/widgets/tableselection.py
diff options
context:
space:
mode:
Diffstat (limited to 'lvc/widgets/tableselection.py')
-rw-r--r--lvc/widgets/tableselection.py220
1 files changed, 220 insertions, 0 deletions
diff --git a/lvc/widgets/tableselection.py b/lvc/widgets/tableselection.py
new file mode 100644
index 0000000..ee6472c
--- /dev/null
+++ b/lvc/widgets/tableselection.py
@@ -0,0 +1,220 @@
+# @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.
+
+"""tableselection.py -- High-level selection management. Subclasses defined in
+the platform tableview modules provide the platform-specific methods used here.
+"""
+
+from contextlib import contextmanager
+
+from lvc.errors import WidgetActionError, WidgetUsageError
+
+class SelectionOwnerMixin(object):
+ """Encapsulates the selection functionality of a TableView, for
+ consistent behavior across platforms.
+
+ Emits:
+ :signal selection-changed: the selection has been changed
+ :signal selection-invalid: the item selected can no longer be selected
+ :signal deselected: all items have been deselected
+ """
+ def __init__(self):
+ self._ignore_selection_changed = 0
+ self._allow_multiple_select = None
+ self.create_signal('selection-changed')
+ self.create_signal('selection-invalid')
+ self.create_signal('deselected')
+
+ @property
+ def allow_multiple_select(self):
+ """Return whether the widget allows multiple selection."""
+ if self._allow_multiple_select is None:
+ self._allow_multiple_select = self._get_allow_multiple_select()
+ return self._allow_multiple_select
+
+ @allow_multiple_select.setter
+ def allow_multiple_select(self, allow):
+ """Set whether to allow multiple selection; this method is expected
+ always to work.
+ """
+ if self._allow_multiple_select != allow:
+ self._set_allow_multiple_select(allow)
+ self._allow_multiple_select = allow
+
+ @property
+ def num_rows_selected(self):
+ """Override on platforms with a way to count rows without having to
+ retrieve them.
+ """
+ if self.allow_multiple_select:
+ return len(self._get_selected_iters())
+ else:
+ return int(self._get_selected_iter() is not None)
+
+ def select(self, iter_, signal=False):
+ """Try to select an iter.
+
+ :raises WidgetActionError: iter does not exist or is not selectable
+ """
+ self.select_iters((iter_,), signal)
+
+ def select_iters(self, iters, signal=False):
+ """Try to select multiple iters (signaling at most once).
+
+ :raises WidgetActionError: iter does not exist or is not selectable
+ """
+ with self._ignoring_changes(not signal):
+ for iter_ in iters:
+ self._select(iter_)
+ if not all(self._is_selected(iter_) for iter_ in iters):
+ raise WidgetActionError("the specified iter cannot be selected")
+
+ def is_selected(self, iter_):
+ """Test if an iter is selected"""
+ return self._is_selected(iter_)
+
+ def unselect(self, iter_):
+ """Unselect an Iter. Fails silently if the Iter is not selected.
+ """
+ self._validate_iter(iter_)
+ with self._ignoring_changes():
+ self._unselect(iter_)
+
+ def unselect_iters(self, iters):
+ """Unselect iters. Fails silently if the iters are not selected."""
+ with self._ignoring_changes():
+ for iter_ in iters:
+ self.unselect(iter_)
+
+ def unselect_all(self, signal=True):
+ """Unselect all. emits only the 'deselected' signal."""
+ with self._ignoring_changes():
+ self._unselect_all()
+ if signal:
+ self.emit('deselected')
+
+ def on_selection_changed(self, _widget_or_notification):
+ """When we receive a selection-changed signal, we forward it if we're
+ not in a 'with _ignoring_changes' block. Selection-changed
+ handlers are run in an ignoring block, and anything that changes the
+ selection to reflect the current state.
+ """
+ # don't bother sending out a second selection-changed signal if
+ # the handler changes the selection (#15767)
+ if not self._ignore_selection_changed:
+ with self._ignoring_changes():
+ self.emit('selection-changed')
+
+ def get_selection_as_strings(self):
+ """Returns the current selection as a list of strings.
+ """
+ return [self._iter_to_string(iter_) for iter_ in self.get_selection()]
+
+ def set_selection_as_strings(self, selected):
+ """Given a list of selection strings, selects each Iter represented by
+ the strings.
+
+ Raises WidgetActionError upon failure.
+ """
+ # iter may not be destringable (yet) - bounds error
+ # destringed iter not selectable if parent isn't open (yet)
+ self.set_selection(self._iter_from_string(sel) for sel in selected)
+
+ def get_cursor(self):
+ """Get the location of the keyboard cursor for the tableview.
+
+ Returns a string that represents the row that the keyboard cursor is
+ on.
+ """
+
+ def set_cursor(self, location):
+ """Set the location of the keyboard cursor for the tableview.
+
+ :param location: return value from a call to get_cursor()
+
+ Raises WidgetActionError upon failure.
+ """
+
+ def get_selection(self):
+ """Returns a list of GTK Iters. Works regardless of whether multiple
+ selection is enabled.
+ """
+ return self._get_selected_iters()
+
+ def get_selected(self):
+ """Return the single selected item.
+
+ :raises WidgetUsageError: multiple selection is enabled
+ """
+ if self.allow_multiple_select:
+ raise WidgetUsageError("table allows multiple selection")
+ return self._get_selected_iter()
+
+ def _validate_iter(self, iter_):
+ """Check whether an iter is valid.
+
+ :raises WidgetDomainError: the iter is not valid
+ :raises WidgetActionError: there is no model right now
+ """
+
+ @contextmanager
+ def _ignoring_changes(self, ignoring=True):
+ """Use this with with to prevent sending signals when we're changing
+ our own selection; that way, when we get a signal, we know it's
+ something important.
+ """
+ if ignoring:
+ self._ignore_selection_changed += 1
+ try:
+ yield
+ finally:
+ if ignoring:
+ self._ignore_selection_changed -= 1
+
+ @contextmanager
+ def preserving_selection(self):
+ """Prevent selection changes in a block from having any effect or
+ sticking - no signals will be sent, and the selection will be restored
+ to its original value when the block exits.
+ """
+ iters = self._get_selected_iters()
+ with self._ignoring_changes():
+ try:
+ yield
+ finally:
+ self.set_selection(iters)
+
+ def set_selection(self, iters, signal=False):
+ """Set the selection to the given iters, replacing any previous
+ selection and signaling at most once.
+ """
+ self.unselect_all(signal=False)
+ for iter_ in iters:
+ self.select(iter_, signal=False)
+ if signal: self.emit('selection-changed')