Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 41 additions & 5 deletions pyface/i_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@


from traits.api import Any, Bool, HasTraits, Interface
from traits.trait_base import not_none


class IWidget(Interface):
Expand Down Expand Up @@ -90,11 +91,21 @@ def _remove_event_listeners(self):
""" Remove toolkit-specific bindings for events """


class MWidget(object):
class MWidget(HasTraits):
""" The mixin class that contains common code for toolkit specific
implementations of the IWidget interface.
"""

def destroy(self):
""" Destroy the control if it exists.

Subclasses of this mixin should ensure that they call super()
if they override this method.
"""
if self.control is not None:
self._remove_event_listeners()
self.control = None

# ------------------------------------------------------------------------
# Protected 'IWidget' interface.
# ------------------------------------------------------------------------
Expand All @@ -104,13 +115,18 @@ def _create(self):

This method should create the control and assign it to the
:py:attr:``control`` trait.

Subclasses of this mixin should ensure that they call super()
if they override this method.
"""
self.control = self._create_control(self.parent)
self._add_event_listeners()

def _create_control(self, parent):
""" Create toolkit specific control that represents the widget.

Subclasses of this mixin should implement this method.

Parameters
----------
parent : toolkit control
Expand All @@ -125,9 +141,29 @@ def _create_control(self, parent):
raise NotImplementedError()

def _add_event_listeners(self):
""" Set up toolkit-specific bindings for events """
pass
""" Set up toolkit-specific bindings for events and trait observers

The default implementation sets up observers for all traits with
the `widget_observer` metadata.

Subclasses should override with additional listeners, but must call
super() to ensure superclass listeners are also connected.
"""
for name, trait in self.traits(widget_observer=not_none).items():
observer = getattr(self, trait.widget_observer)
self.observe(observer, name, dispatch='ui')

def _remove_event_listeners(self):
""" Remove toolkit-specific bindings for events """
pass
""" Remove toolkit-specific bindings for events and trait observers

The default implementation removes up observers for all traits with
the `widget_observer` metadata.

Subclasses should override with additional listeners, but must call
super() to ensure superclass listeners are also removed.
"""
for name, trait in self.traits(widget_observer=not_none).items():
observer = trait.widget_observer
if isinstance(observer, str):
observer = getattr(self, observer)
self.observe(observer, name, dispatch='ui', remove=True)
39 changes: 39 additions & 0 deletions pyface/tests/test_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@

import unittest

from traits.api import Any, Bool
from traits.testing.unittest_tools import UnittestTools

from ..application_window import ApplicationWindow
from ..toolkit import toolkit_object
from ..widget import Widget

Expand All @@ -21,6 +23,10 @@


class ConcreteWidget(Widget):

# a trait for testing connection of observeres
flag = Bool(widget_observer='_flag_updated')

def _create_control(self, parent):
if toolkit_object.toolkit == "wx":
import wx
Expand All @@ -38,6 +44,9 @@ def _create_control(self, parent):
control = None
return control

def _flag_updated(self, event):
self.flag_event = event


class TestWidget(unittest.TestCase, UnittestTools):
def setUp(self):
Expand Down Expand Up @@ -83,6 +92,36 @@ def test_enabled(self):

self.assertFalse(self.widget.enabled)

def test_widget_observers(self):
self.window = ApplicationWindow()
self.window._create()

self.widget = ConcreteWidget(parent=self.window.control)
self.widget._create()

try:
with self.assertTraitChanges(self.widget, "flag", count=1):
self.widget.flag = True

flag_event = getattr(self.widget, 'flag_event', None)
self.assertIsNotNone(flag_event)
self.assertEqual(flag_event.object, self.widget)
self.assertEqual(flag_event.name, "flag")
self.assertEqual(flag_event.old, False)
self.assertEqual(flag_event.new, True)

self.widget.flag_event = None

self.widget.destroy()

with self.assertTraitChanges(self.widget, "flag", count=1):
self.widget.flag = False

self.assertIsNone(self.widget.flag_event)

finally:
self.window.destroy()


@unittest.skipIf(no_gui_test_assistant, "No GuiTestAssistant")
class TestConcreteWidget(unittest.TestCase, GuiTestAssistant):
Expand Down
5 changes: 3 additions & 2 deletions pyface/ui/qt4/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,21 @@ def enable(self, enabled):
self.control.setEnabled(enabled)

def destroy(self):
self._remove_event_listeners()
if self.control is not None:
self.control.hide()
self.control.deleteLater()
self.control = None
super().destroy()

def _add_event_listeners(self):
super()._add_event_listeners()
self.control.installEventFilter(self._event_filter)

def _remove_event_listeners(self):
if self._event_filter is not None:
if self.control is not None:
self.control.removeEventFilter(self._event_filter)
self._event_filter = None
super()._remove_event_listeners()

# Trait change handlers --------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion pyface/ui/wx/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,4 @@ def enable(self, enabled):
def destroy(self):
if self.control is not None:
self.control.Destroy()
self.control = None
super().destroy()