2019-09-10 00:54:28 +00:00
|
|
|
# Created By: Virgil Dupras
|
|
|
|
# Created On: 2011-09-06
|
|
|
|
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
2020-01-01 02:16:27 +00:00
|
|
|
#
|
|
|
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
|
|
# which should be included with this package. The terms are also available at
|
2019-09-10 00:54:28 +00:00
|
|
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
|
|
|
2020-06-26 04:26:48 +00:00
|
|
|
from collections.abc import Sequence, MutableSequence
|
2019-09-10 00:54:28 +00:00
|
|
|
|
|
|
|
from .base import GUIObject
|
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
class Selectable(Sequence):
|
|
|
|
"""Mix-in for a ``Sequence`` that manages its selection status.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
When mixed in with a ``Sequence``, we enable it to manage its selection status. The selection
|
|
|
|
is held as a list of ``int`` indexes. Multiple selection is supported.
|
|
|
|
"""
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def __init__(self):
|
|
|
|
self._selected_indexes = []
|
2020-01-01 02:16:27 +00:00
|
|
|
|
|
|
|
# --- Private
|
2019-09-10 00:54:28 +00:00
|
|
|
def _check_selection_range(self):
|
|
|
|
if not self:
|
|
|
|
self._selected_indexes = []
|
|
|
|
if not self._selected_indexes:
|
|
|
|
return
|
2020-01-01 02:16:27 +00:00
|
|
|
self._selected_indexes = [
|
|
|
|
index for index in self._selected_indexes if index < len(self)
|
|
|
|
]
|
2019-09-10 00:54:28 +00:00
|
|
|
if not self._selected_indexes:
|
|
|
|
self._selected_indexes = [len(self) - 1]
|
2020-01-01 02:16:27 +00:00
|
|
|
|
|
|
|
# --- Virtual
|
2019-09-10 00:54:28 +00:00
|
|
|
def _update_selection(self):
|
|
|
|
"""(Virtual) Updates the model's selection appropriately.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
Called after selection has been updated. Takes the table's selection and does appropriates
|
|
|
|
updates on the view and/or model. Common sense would dictate that when the selection doesn't
|
|
|
|
change, we don't update anything (and thus don't call ``_update_selection()`` at all), but
|
|
|
|
there are cases where it's false. For example, if our list updates its items but doesn't
|
|
|
|
change its selection, we probably want to update the model's selection.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
By default, does nothing.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
Important note: This is only called on :meth:`select`, not on changes to
|
|
|
|
:attr:`selected_indexes`.
|
|
|
|
"""
|
|
|
|
# A redesign of how this whole thing works is probably in order, but not now, there's too
|
|
|
|
# much breakage at once involved.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
|
|
|
# --- Public
|
2019-09-10 00:54:28 +00:00
|
|
|
def select(self, indexes):
|
|
|
|
"""Update selection to ``indexes``.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
:meth:`_update_selection` is called afterwards.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
:param list indexes: List of ``int`` that is to become the new selection.
|
|
|
|
"""
|
|
|
|
if isinstance(indexes, int):
|
|
|
|
indexes = [indexes]
|
|
|
|
self.selected_indexes = indexes
|
|
|
|
self._update_selection()
|
2020-01-01 02:16:27 +00:00
|
|
|
|
|
|
|
# --- Properties
|
2019-09-10 00:54:28 +00:00
|
|
|
@property
|
|
|
|
def selected_index(self):
|
|
|
|
"""Points to the first selected index.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
|
|
|
*int*. *get/set*.
|
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
Thin wrapper around :attr:`selected_indexes`. ``None`` if selection is empty. Using this
|
|
|
|
property only makes sense if your selectable sequence supports single selection only.
|
|
|
|
"""
|
|
|
|
return self._selected_indexes[0] if self._selected_indexes else None
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
@selected_index.setter
|
|
|
|
def selected_index(self, value):
|
|
|
|
self.selected_indexes = [value]
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
@property
|
|
|
|
def selected_indexes(self):
|
|
|
|
"""List of selected indexes.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
*list of int*. *get/set*.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
When setting the value, automatically removes out-of-bounds indexes. The list is kept
|
|
|
|
sorted.
|
|
|
|
"""
|
|
|
|
return self._selected_indexes
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
@selected_indexes.setter
|
|
|
|
def selected_indexes(self, value):
|
|
|
|
self._selected_indexes = value
|
|
|
|
self._selected_indexes.sort()
|
|
|
|
self._check_selection_range()
|
|
|
|
|
|
|
|
|
|
|
|
class SelectableList(MutableSequence, Selectable):
|
|
|
|
"""A list that can manage selection of its items.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
Subclasses :class:`Selectable`. Behaves like a ``list``.
|
|
|
|
"""
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def __init__(self, items=None):
|
|
|
|
Selectable.__init__(self)
|
|
|
|
if items:
|
|
|
|
self._items = list(items)
|
|
|
|
else:
|
|
|
|
self._items = []
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def __delitem__(self, key):
|
|
|
|
self._items.__delitem__(key)
|
|
|
|
self._check_selection_range()
|
|
|
|
self._on_change()
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def __getitem__(self, key):
|
|
|
|
return self._items.__getitem__(key)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def __len__(self):
|
|
|
|
return len(self._items)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def __setitem__(self, key, value):
|
|
|
|
self._items.__setitem__(key, value)
|
|
|
|
self._on_change()
|
2020-01-01 02:16:27 +00:00
|
|
|
|
|
|
|
# --- Override
|
2019-09-10 00:54:28 +00:00
|
|
|
def append(self, item):
|
|
|
|
self._items.append(item)
|
|
|
|
self._on_change()
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def insert(self, index, item):
|
|
|
|
self._items.insert(index, item)
|
|
|
|
self._on_change()
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def remove(self, row):
|
|
|
|
self._items.remove(row)
|
|
|
|
self._check_selection_range()
|
|
|
|
self._on_change()
|
2020-01-01 02:16:27 +00:00
|
|
|
|
|
|
|
# --- Virtual
|
2019-09-10 00:54:28 +00:00
|
|
|
def _on_change(self):
|
|
|
|
"""(Virtual) Called whenever the contents of the list changes.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
By default, does nothing.
|
|
|
|
"""
|
2020-01-01 02:16:27 +00:00
|
|
|
|
|
|
|
# --- Public
|
2019-09-10 00:54:28 +00:00
|
|
|
def search_by_prefix(self, prefix):
|
|
|
|
# XXX Why the heck is this method here?
|
|
|
|
prefix = prefix.lower()
|
|
|
|
for index, s in enumerate(self):
|
|
|
|
if s.lower().startswith(prefix):
|
|
|
|
return index
|
|
|
|
return -1
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
|
|
|
|
class GUISelectableListView:
|
|
|
|
"""Expected interface for :class:`GUISelectableList`'s view.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
*Not actually used in the code. For documentation purposes only.*
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
Our view, some kind of list view or combobox, is expected to sync with the list's contents by
|
|
|
|
appropriately behave to all callbacks in this interface.
|
|
|
|
"""
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def refresh(self):
|
|
|
|
"""Refreshes the contents of the list widget.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
Ensures that the contents of the list widget is synced with the model.
|
|
|
|
"""
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def update_selection(self):
|
|
|
|
"""Update selection status.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
Ensures that the list widget's selection is in sync with the model.
|
|
|
|
"""
|
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
class GUISelectableList(SelectableList, GUIObject):
|
|
|
|
"""Cross-toolkit GUI-enabled list view.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
Represents a UI element presenting the user with a selectable list of items.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
Subclasses :class:`SelectableList` and :class:`.GUIObject`. Expected view:
|
|
|
|
:class:`GUISelectableListView`.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
:param iterable items: If specified, items to fill the list with initially.
|
|
|
|
"""
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def __init__(self, items=None):
|
|
|
|
SelectableList.__init__(self, items)
|
|
|
|
GUIObject.__init__(self)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def _view_updated(self):
|
|
|
|
"""Refreshes the view contents with :meth:`GUISelectableListView.refresh`.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
Overrides :meth:`~hscommon.gui.base.GUIObject._view_updated`.
|
|
|
|
"""
|
|
|
|
self.view.refresh()
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def _update_selection(self):
|
|
|
|
"""Refreshes the view selection with :meth:`GUISelectableListView.update_selection`.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
Overrides :meth:`Selectable._update_selection`.
|
|
|
|
"""
|
|
|
|
self.view.update_selection()
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def _on_change(self):
|
|
|
|
"""Refreshes the view contents with :meth:`GUISelectableListView.refresh`.
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
Overrides :meth:`SelectableList._on_change`.
|
|
|
|
"""
|
|
|
|
self.view.refresh()
|