g13gui: Make observers use setProperty and properties

This makes them behave in a much more pythonic way.
This commit is contained in:
June Tate-Gans 2021-05-02 12:50:29 -05:00
parent 6daf662698
commit d2881a0e6e
3 changed files with 79 additions and 31 deletions

View File

@ -41,13 +41,5 @@ class GtkObserver(Observer):
overridden. overridden.
""" """
(subject, changeType, key, data) = self._observerQueue.get() (subject, changeType, key, data) = self._observerQueue.get()
self.gtkSubjectChanged(subject, changeType, key, data) Observer.onSubjectChanged(self, subject, changeType, key, data)
self._observerQueue.task_done() self._observerQueue.task_done()
def gtkSubjectChanged(self, subject, changeType, key, data=None):
"""Subject notification handler.
Runs on the UI thread, and must be overridden by subclasses.
"""
raise NotImplementedError(
"%s did not override Observer#gtkSubjectChanged" % (type(self)))

View File

@ -11,26 +11,6 @@ class ChangeType(Enum):
MODIFY = 2 MODIFY = 2
class Observer(object):
def onSubjectChanged(self, subject, changeType, key, data=None):
"""Event handler for observer notifications.
Each subclass of Observer MUST override this method. There is no default
method for handling events of this nature.
subject[object]: the subject object that sent the event notifying something
changed in its data model.
changeType[ChangeType]: the type of change that occurred.
key[string]: a required name for what field changed.
data[object]: an optional context-dependent object, dict, or
None, specifying what changed. In the case of an ADD or MODIFY,
whatChanged should be the new data. In the case of a DELETE, it should
be the old data (or None).
"""
raise NotImplementedError(
"%s did not override Observer#onSubjectChanged!" % (type(self)))
class Subject(object): class Subject(object):
"""Simple class to handle the subject-side of the Observer pattern.""" """Simple class to handle the subject-side of the Observer pattern."""
@ -89,13 +69,78 @@ class Subject(object):
self._changes = [] self._changes = []
def setProperty(self, propertyName, value, notify=True):
propertyName = '_' + propertyName
self.__dict__[propertyName] = value
self.addChange(ChangeType.MODIFY, propertyName, value)
if notify:
self.notifyChanged()
class Observer(object):
def _makeChangeTriggerKeys(self, changeType, keys):
result = []
if keys != Subject.AllKeys:
for key in keys:
result.append((changeType, key))
else:
result.append((changeType, keys))
return result
def changeTrigger(self, callback,
changeType=None, keys=Subject.AllKeys):
if '_changeTriggers' not in self.__dict__:
self._changeTriggers = {}
for key in self._makeChangeTriggerKeys(changeType, keys):
self._changeTriggers[key] = callback
def onSubjectChanged(self, subject, changeType, key, data=None):
"""Generic event handler dispatcher for observer notifications.
Each subclass of Observer MUST call setChangeTrigger to register a
callback method in its __init__.
subject[object]: the subject object that sent the event notifying
something changed in its data model.
changeType[ChangeType]: the type of change that occurred.
key[string]: a required name for what field changed.
data[object]: an optional context-dependent object, dict, or
None, specifying what changed. In the case of an ADD or MODIFY,
whatChanged should be the new data. In the case of a DELETE, it
should be the old data (or None).
"""
if '_changeTriggers' not in self.__dict__:
raise NotImplementedError(
'onSubjectChanged(%s, %s, %s, %s) fired with no '
'listeners registered!' %
(subject, changeType, key, data))
found = False
triggers = (
self._changeTriggers.get((None, Subject.AllKeys)),
self._changeTriggers.get((changeType, Subject.AllKeys)),
self._changeTriggers.get((None, key)),
self._changeTriggers.get((changeType, key)))
for trigger in triggers:
if trigger:
found = True
trigger(subject, changeType, key, data)
if not found:
raise NotImplementedError(
'onSubjectChanged(%s, %s, %s, %s) fired without a listener!'
% (subject, changeType, key, data))
class ObserverTestCase(Observer, unittest.TestCase): class ObserverTestCase(Observer, unittest.TestCase):
def __init__(self, methodName): def __init__(self, methodName):
unittest.TestCase.__init__(self, methodName) unittest.TestCase.__init__(self, methodName)
self.changeTrigger(self.changed)
self.clearChanges() self.clearChanges()
def onSubjectChanged(self, subject, type, key, data=None): def changed(self, subject, type, key, data=None):
self.changes.insert(0, { self.changes.insert(0, {
'subject': subject, 'subject': subject,
'type': type, 'type': type,

View File

@ -3,6 +3,8 @@
import unittest import unittest
import observer import observer
from builtins import property
class TestIncorrectObserver(observer.Observer): class TestIncorrectObserver(observer.Observer):
def __hash__(self): def __hash__(self):
@ -10,7 +12,16 @@ class TestIncorrectObserver(observer.Observer):
class TestSubject(observer.Subject): class TestSubject(observer.Subject):
pass def __init__(self):
self._value = None
@property
def value(self):
return self._value
@value.setter
def value(self, value):
self.setProperty('value', value)
class ObserverTestCase(observer.ObserverTestCase): class ObserverTestCase(observer.ObserverTestCase):