mirror of
https://github.com/arsenetar/dupeguru.git
synced 2025-03-10 05:34:36 +00:00
Improved hscommon.gui docs
This commit is contained in:
parent
0b00171655
commit
da06ef8cad
8
help/en/developer/hscommon/gui/base.rst
Normal file
8
help/en/developer/hscommon/gui/base.rst
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
hscommon.gui.base
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. automodule:: hscommon.gui.base
|
||||||
|
|
||||||
|
.. autoclass:: GUIObject
|
||||||
|
:members:
|
||||||
|
:private-members:
|
19
help/en/developer/hscommon/gui/selectable_list.rst
Normal file
19
help/en/developer/hscommon/gui/selectable_list.rst
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
hscommon.gui.selectable_list
|
||||||
|
============================
|
||||||
|
|
||||||
|
.. automodule:: hscommon.gui.selectable_list
|
||||||
|
|
||||||
|
.. autoclass:: Selectable
|
||||||
|
:members:
|
||||||
|
:private-members:
|
||||||
|
|
||||||
|
.. autoclass:: SelectableList
|
||||||
|
:members:
|
||||||
|
:private-members:
|
||||||
|
|
||||||
|
.. autoclass:: GUISelectableList
|
||||||
|
:members:
|
||||||
|
:private-members:
|
||||||
|
|
||||||
|
.. autoclass:: GUISelectableListView
|
||||||
|
:members:
|
11
help/en/developer/hscommon/gui/text_field.rst
Normal file
11
help/en/developer/hscommon/gui/text_field.rst
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
hscommon.gui.text_field
|
||||||
|
=======================
|
||||||
|
|
||||||
|
.. automodule:: hscommon.gui.text_field
|
||||||
|
|
||||||
|
.. autoclass:: TextField
|
||||||
|
:members:
|
||||||
|
:private-members:
|
||||||
|
|
||||||
|
.. autoclass:: TextFieldView
|
||||||
|
:members:
|
@ -10,3 +10,6 @@ hscommon
|
|||||||
notify
|
notify
|
||||||
path
|
path
|
||||||
util
|
util
|
||||||
|
gui/base
|
||||||
|
gui/text_field
|
||||||
|
gui/selectable_list
|
||||||
|
@ -13,23 +13,42 @@ class NoopGUI:
|
|||||||
def __getattr__(self, func_name):
|
def __getattr__(self, func_name):
|
||||||
return noop
|
return noop
|
||||||
|
|
||||||
# A GUIObject is a cross-toolkit "model" representation of a GUI layer object, for example, a table.
|
|
||||||
# It acts as a cross-toolkit interface to multiple what we call here a "view". That view is a
|
|
||||||
# toolkit-specific controller to the actual view (an NSTableView, a QTableView, etc.).
|
|
||||||
# In our GUIObject, we need a reference to that toolkit-specific controller because some actions,
|
|
||||||
# have effects on it (for example, prompting it to refresh its data). The GUIObject is typically
|
|
||||||
# instantiated before its "view", that is why we set it as None on init. However, the GUI
|
|
||||||
# layer is supposed to set the view as soon as its toolkit-specific controller is instantiated.
|
|
||||||
|
|
||||||
# When you subclass GUIObject, you will likely want to update its view on instantiation. That
|
|
||||||
# is why we call self.view.refresh() in _view_updated(). If you need another type of action on
|
|
||||||
# view instantiation, just override the method.
|
|
||||||
class GUIObject:
|
class GUIObject:
|
||||||
|
"""Cross-toolkit "model" representation of a GUI layer object.
|
||||||
|
|
||||||
|
A ``GUIObject`` is a cross-toolkit "model" representation of a GUI layer object, for example, a
|
||||||
|
table. It acts as a cross-toolkit interface to multiple what we call here a :attr:`view`. That
|
||||||
|
view is a toolkit-specific controller to the actual view (an ``NSTableView``, a ``QTableView``,
|
||||||
|
etc.). In our GUIObject, we need a reference to that toolkit-specific controller because some
|
||||||
|
actions, have effects on it (for example, prompting it to refresh its data). The ``GUIObject``
|
||||||
|
is typically instantiated before its :attr:`view`, that is why we set it as ``None`` on init.
|
||||||
|
However, the GUI layer is supposed to set the view as soon as its toolkit-specific controller is
|
||||||
|
instantiated.
|
||||||
|
|
||||||
|
When you subclass ``GUIObject``, you will likely want to update its view on instantiation. That
|
||||||
|
is why we call ``self.view.refresh()`` in :meth:`_view_updated`. If you need another type of
|
||||||
|
action on view instantiation, just override the method.
|
||||||
|
|
||||||
|
.. attribute:: view
|
||||||
|
|
||||||
|
A reference to our toolkit-specific view controller. This view stats as ``None`` and has to
|
||||||
|
be set "manually". There's two times at which we set the view property: On initialization,
|
||||||
|
where we set the view that we'll use for our lifetime, and just before the view is
|
||||||
|
deallocated. We need to unset our view at that time to avoid calls to a deallocated instance
|
||||||
|
(which means a crash).
|
||||||
|
|
||||||
|
To unset our view, we simple assign it to ``None``.
|
||||||
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._view = None
|
self._view = None
|
||||||
|
|
||||||
def _view_updated(self):
|
def _view_updated(self):
|
||||||
pass #virtual
|
"""(Virtual) Called after :attr:`view` has been set.
|
||||||
|
|
||||||
|
Doing nothing by default, this method is called after :attr:`view` has been set (it isn't
|
||||||
|
called when it's unset, however). Use this for initialization code that requires a view
|
||||||
|
(which is often the whole of the initialization code).
|
||||||
|
"""
|
||||||
|
|
||||||
def has_view(self):
|
def has_view(self):
|
||||||
return (self._view is not None) and (not isinstance(self._view, NoopGUI))
|
return (self._view is not None) and (not isinstance(self._view, NoopGUI))
|
||||||
@ -40,10 +59,6 @@ class GUIObject:
|
|||||||
|
|
||||||
@view.setter
|
@view.setter
|
||||||
def view(self, value):
|
def view(self, value):
|
||||||
# There's two times at which we set the view property: On initialization, where we set the
|
|
||||||
# view that we'll use for your lifetime, and just before the view is deallocated. We need
|
|
||||||
# to unset our view at that time to avoid calls to a deallocated instance (which means a
|
|
||||||
# crash).
|
|
||||||
if self._view is None:
|
if self._view is None:
|
||||||
# Initial view assignment
|
# Initial view assignment
|
||||||
if value is None:
|
if value is None:
|
||||||
|
@ -11,6 +11,22 @@ from collections import Sequence, MutableSequence
|
|||||||
from .base import GUIObject
|
from .base import GUIObject
|
||||||
|
|
||||||
class Selectable(Sequence):
|
class Selectable(Sequence):
|
||||||
|
"""Mix-in for a ``Sequence`` that manages its selection status.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. attribute:: selected_index
|
||||||
|
|
||||||
|
*int*. *get/set*. Thin wrapper around :attr:`selected_indexes`. Points to the first selected
|
||||||
|
index or ``None`` if it's empty. Using this property only makes sense if your selectable
|
||||||
|
sequence supports single selection only.
|
||||||
|
|
||||||
|
.. attribute:: selected_indexes
|
||||||
|
|
||||||
|
*list*. *get/set*. List of selected indexes. When setting the value, automatically removes
|
||||||
|
out-of-bounds indexes. The list is kept sorted.
|
||||||
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._selected_indexes = []
|
self._selected_indexes = []
|
||||||
|
|
||||||
@ -26,16 +42,30 @@ class Selectable(Sequence):
|
|||||||
|
|
||||||
#--- Virtual
|
#--- Virtual
|
||||||
def _update_selection(self):
|
def _update_selection(self):
|
||||||
# Takes the table's selection and does appropriates updates on the view and/or model, when
|
"""(Virtual) Updates the model's selection appropriately.
|
||||||
# appropriate. 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
|
Called after selection has been updated. Takes the table's selection and does appropriates
|
||||||
# where it's false. For example, if our list updates its items but doesn't change its
|
updates on the view and/or model. Common sense would dictate that when the selection doesn't
|
||||||
# selection, we probably want to update the model's selection. A redesign of how this whole
|
change, we don't update anything (and thus don't call ``_update_selection()`` at all), but
|
||||||
# thing works is probably in order, but not now, there's too much breakage at once involved.
|
there are cases where it's false. For example, if our list updates its items but doesn't
|
||||||
pass
|
change its selection, we probably want to update the model's selection.
|
||||||
|
|
||||||
|
By default, does nothing.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
#--- Public
|
#--- Public
|
||||||
def select(self, indexes):
|
def select(self, indexes):
|
||||||
|
"""Update selection to ``indexes``.
|
||||||
|
|
||||||
|
:meth:`_update_selection` is called afterwards.
|
||||||
|
|
||||||
|
:param list indexes: List of ``int`` that is to become the new selection.
|
||||||
|
"""
|
||||||
if isinstance(indexes, int):
|
if isinstance(indexes, int):
|
||||||
indexes = [indexes]
|
indexes = [indexes]
|
||||||
self.selected_indexes = indexes
|
self.selected_indexes = indexes
|
||||||
@ -62,6 +92,10 @@ class Selectable(Sequence):
|
|||||||
|
|
||||||
|
|
||||||
class SelectableList(MutableSequence, Selectable):
|
class SelectableList(MutableSequence, Selectable):
|
||||||
|
"""A list that can manage selection of its items.
|
||||||
|
|
||||||
|
Subclasses :class:`Selectable`. Behaves like a ``list``.
|
||||||
|
"""
|
||||||
def __init__(self, items=None):
|
def __init__(self, items=None):
|
||||||
Selectable.__init__(self)
|
Selectable.__init__(self)
|
||||||
if items:
|
if items:
|
||||||
@ -100,10 +134,14 @@ class SelectableList(MutableSequence, Selectable):
|
|||||||
|
|
||||||
#--- Virtual
|
#--- Virtual
|
||||||
def _on_change(self):
|
def _on_change(self):
|
||||||
pass
|
"""(Virtual) Called whenever the contents of the list changes.
|
||||||
|
|
||||||
|
By default, does nothing.
|
||||||
|
"""
|
||||||
|
|
||||||
#--- Public
|
#--- Public
|
||||||
def search_by_prefix(self, prefix):
|
def search_by_prefix(self, prefix):
|
||||||
|
# XXX Why the heck is this method here?
|
||||||
prefix = prefix.lower()
|
prefix = prefix.lower()
|
||||||
for index, s in enumerate(self):
|
for index, s in enumerate(self):
|
||||||
if s.lower().startswith(prefix):
|
if s.lower().startswith(prefix):
|
||||||
@ -111,21 +149,57 @@ class SelectableList(MutableSequence, Selectable):
|
|||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
|
||||||
class GUISelectableList(SelectableList, GUIObject):
|
class GUISelectableListView:
|
||||||
#--- View interface
|
"""Expected interface for :class:`GUISelectableList`'s view.
|
||||||
# refresh()
|
|
||||||
# update_selection()
|
|
||||||
#
|
|
||||||
|
|
||||||
|
*Not actually used in the code. For documentation purposes only.*
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
def refresh(self):
|
||||||
|
"""Refreshes the contents of the list widget.
|
||||||
|
|
||||||
|
Ensures that the contents of the list widget is synced with the model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def update_selection(self):
|
||||||
|
"""Update selection status.
|
||||||
|
|
||||||
|
Ensures that the list widget's selection is in sync with the model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class GUISelectableList(SelectableList, GUIObject):
|
||||||
|
"""Cross-toolkit list view.
|
||||||
|
|
||||||
|
Represents a UI element presenting the user with a selectable list of items.
|
||||||
|
|
||||||
|
Subclasses :class:`SelectableList` and :class:`~hscommon.gui.base.GUIObject`. Expected view:
|
||||||
|
:class:`GUISelectableListView`.
|
||||||
|
|
||||||
|
:param iterable items: If specified, items to fill the list with initially.
|
||||||
|
"""
|
||||||
def __init__(self, items=None):
|
def __init__(self, items=None):
|
||||||
SelectableList.__init__(self, items)
|
SelectableList.__init__(self, items)
|
||||||
GUIObject.__init__(self)
|
GUIObject.__init__(self)
|
||||||
|
|
||||||
def _view_updated(self):
|
def _view_updated(self):
|
||||||
|
"""Refreshes the view contents with :meth:`GUISelectableListView.refresh`.
|
||||||
|
|
||||||
|
Overrides :meth:`~hscommon.gui.base.GUIObject._view_updated`.
|
||||||
|
"""
|
||||||
self.view.refresh()
|
self.view.refresh()
|
||||||
|
|
||||||
def _update_selection(self):
|
def _update_selection(self):
|
||||||
|
"""Refreshes the view selection with :meth:`GUISelectableListView.update_selection`.
|
||||||
|
|
||||||
|
Overrides :meth:`Selectable._update_selection`.
|
||||||
|
"""
|
||||||
self.view.update_selection()
|
self.view.update_selection()
|
||||||
|
|
||||||
def _on_change(self):
|
def _on_change(self):
|
||||||
|
"""Refreshes the view contents with :meth:`GUISelectableListView.refresh`.
|
||||||
|
|
||||||
|
Overrides :meth:`SelectableList._on_change`.
|
||||||
|
"""
|
||||||
self.view.refresh()
|
self.view.refresh()
|
||||||
|
@ -8,7 +8,44 @@
|
|||||||
from .base import GUIObject
|
from .base import GUIObject
|
||||||
from ..util import nonone
|
from ..util import nonone
|
||||||
|
|
||||||
|
class TextFieldView:
|
||||||
|
"""Expected interface for :class:`TextField`'s view.
|
||||||
|
|
||||||
|
*Not actually used in the code. For documentation purposes only.*
|
||||||
|
|
||||||
|
Our view is expected to sync with :attr:`TextField.text` "both ways", that is, update the
|
||||||
|
model's text when the user types something, but also update the text field when :meth:`refresh`
|
||||||
|
is called.
|
||||||
|
"""
|
||||||
|
def refresh(self):
|
||||||
|
"""Refreshes the contents of the input widget.
|
||||||
|
|
||||||
|
Ensures that the contents of the input widget is actually :attr:`TextField.text`.
|
||||||
|
"""
|
||||||
|
|
||||||
class TextField(GUIObject):
|
class TextField(GUIObject):
|
||||||
|
"""Cross-toolkit text field.
|
||||||
|
|
||||||
|
Represents a UI element allowing the user to input a text value. Its main attribute is
|
||||||
|
:attr:`text` which acts as the store of the said value.
|
||||||
|
|
||||||
|
When our model value isn't a string, we have a built-in parsing/formatting mechanism allowing
|
||||||
|
us to directly retrieve/set our non-string value through :attr:`value`.
|
||||||
|
|
||||||
|
Subclasses :class:`hscommon.gui.base.GUIObject`. Expected view: :class:`TextFieldView`.
|
||||||
|
|
||||||
|
.. attribute:: text
|
||||||
|
|
||||||
|
*str*. The text that is currently displayed in the widget. This property can be set. When it
|
||||||
|
is, :meth:`refresh` is called and the view is synced with our value. Always in sync with
|
||||||
|
:attr:`value`.
|
||||||
|
|
||||||
|
.. attribute:: value
|
||||||
|
|
||||||
|
The "parsed" representation of :attr:`text`. By default, it's a mirror of :attr:`text`, but
|
||||||
|
a subclass can override :meth:`_parse` and :meth:`_format` to have anything else. Always in
|
||||||
|
sync with :attr:`text`.
|
||||||
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
GUIObject.__init__(self)
|
GUIObject.__init__(self)
|
||||||
self._text = ''
|
self._text = ''
|
||||||
@ -16,13 +53,25 @@ class TextField(GUIObject):
|
|||||||
|
|
||||||
#--- Virtual
|
#--- Virtual
|
||||||
def _parse(self, text):
|
def _parse(self, text):
|
||||||
|
"""(Virtual) Parses ``text`` to put into :attr:`value`.
|
||||||
|
|
||||||
|
Returns the parsed version of ``text``. Called whenever :attr:`text` changes.
|
||||||
|
"""
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def _format(self, value):
|
def _format(self, value):
|
||||||
|
"""(Virtual) Formats ``value`` to put into :attr:`text`.
|
||||||
|
|
||||||
|
Returns the formatted version of ``value``. Called whenever :attr:`value` changes.
|
||||||
|
"""
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def _update(self, newvalue):
|
def _update(self, newvalue):
|
||||||
pass
|
"""(Virtual) Called whenever we have a new value.
|
||||||
|
|
||||||
|
Whenever our text/value store changes to a new value (different from the old one), this
|
||||||
|
method is called. By default, it does nothing but you can override it if you want.
|
||||||
|
"""
|
||||||
|
|
||||||
#--- Override
|
#--- Override
|
||||||
def _view_updated(self):
|
def _view_updated(self):
|
||||||
@ -30,6 +79,8 @@ class TextField(GUIObject):
|
|||||||
|
|
||||||
#--- Public
|
#--- Public
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
|
"""Triggers a view :meth:`~TextFieldView.refresh`.
|
||||||
|
"""
|
||||||
self.view.refresh()
|
self.view.refresh()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
Loading…
x
Reference in New Issue
Block a user