diff --git a/.github/workflows/ets-from-source.yml b/.github/workflows/ets-from-source.yml index 86e504687..dacf565da 100644 --- a/.github/workflows/ets-from-source.yml +++ b/.github/workflows/ets-from-source.yml @@ -13,7 +13,7 @@ jobs: test-with-edm: strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-18.04, macos-latest, windows-latest] toolkit: ['wx', 'pyqt5', 'pyside2'] runs-on: ${{ matrix.os }} env: @@ -42,6 +42,7 @@ jobs: - name: Install Wx dependencies for Linux run: | sudo apt-get update + sudo apt-get install libsdl-image1.2 sudo apt-get install libsdl2-2.0-0 shell: bash if: startsWith(matrix.os, 'ubuntu') && matrix.toolkit == 'wx' diff --git a/.github/workflows/test-with-edm.yml b/.github/workflows/test-with-edm.yml index 3a6295b4b..14e5627dd 100644 --- a/.github/workflows/test-with-edm.yml +++ b/.github/workflows/test-with-edm.yml @@ -15,7 +15,7 @@ jobs: test-with-edm: strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-18.04, macos-latest, windows-latest] toolkit: ['wx', 'pyqt5', 'pyside2'] runs-on: ${{ matrix.os }} env: @@ -44,6 +44,7 @@ jobs: - name: Install Wx dependencies for Linux run: | sudo apt-get update + sudo apt-get install libsdl-image1.2 sudo apt-get install libsdl2-2.0-0 shell: bash if: startsWith(matrix.os, 'ubuntu') && matrix.toolkit == 'wx' diff --git a/etstool.py b/etstool.py index a22c2b2b4..6f9492705 100644 --- a/etstool.py +++ b/etstool.py @@ -210,7 +210,7 @@ def install(edm, runtime, toolkit, environment, editable, source): elif sys.platform == "linux": # XXX this is mainly for TravisCI workers; need a generic solution commands.append( - "{edm} run -e {environment} -- pip install -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-16.04/ wxPython<4.1" # noqa: E501 + "{edm} run -e {environment} -- pip install -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04/ wxPython<4.1" # noqa: E501 ) else: commands.append( diff --git a/pyface/dock/dock_window.py b/pyface/dock/dock_window.py index 3c820a01c..ac5c8bbf9 100644 --- a/pyface/dock/dock_window.py +++ b/pyface/dock/dock_window.py @@ -35,6 +35,7 @@ Str, List, Bool, + observe, ) from traits.trait_base import traits_home from traitsui.api import View, HGroup, VGroup, Item, Handler, error @@ -343,7 +344,9 @@ def _theme_default(self): # -- Trait Event Handlers --------------------------------------------------- - def _theme_changed(self, theme): + @observe("theme") + def _update_background_color_and_layout(self, event): + theme = event.new if self.control is not None: if theme.use_theme_color: color = theme.tab.image_slice.bg_color diff --git a/pyface/dock/dock_window_feature.py b/pyface/dock/dock_window_feature.py index 7a16c3238..80d2fa9a2 100644 --- a/pyface/dock/dock_window_feature.py +++ b/pyface/dock/dock_window_feature.py @@ -24,7 +24,9 @@ from weakref import ref -from traits.api import HasPrivateTraits, Instance, Int, Str, Bool, Property +from traits.api import ( + HasPrivateTraits, Instance, Int, Str, Bool, Property, observe +) from traitsui.menu import Menu, Action from pyface.timer.api import do_later @@ -785,7 +787,8 @@ def toggle_feature(cls, event): # Handles the 'image' trait being changed: # --------------------------------------------------------------------------- - def _image_changed(self): + @observe('image') + def _reset_bitmap(self, event): self._bitmap = None # -- Property Implementations --------------------------------------------------- diff --git a/pyface/tasks/task_window.py b/pyface/tasks/task_window.py index e15be5c2b..57ccf8eaa 100644 --- a/pyface/tasks/task_window.py +++ b/pyface/tasks/task_window.py @@ -416,7 +416,9 @@ def _set_title(self, title): # Trait change handlers ------------------------------------------------ - def __active_state_changed(self, state): + @observe("_active_state") + def _update_traits_given_new_active_state(self, event): + state = event.new if state is None: self.active_task = self.central_pane = None self.dock_panes = [] diff --git a/pyface/tests/test_widget.py b/pyface/tests/test_widget.py index 955929465..88ee30d1b 100644 --- a/pyface/tests/test_widget.py +++ b/pyface/tests/test_widget.py @@ -24,6 +24,7 @@ is_qt = (toolkit_object.toolkit in {"qt4", "qt"}) is_linux = (sys.platform == "linux") +is_mac = (sys.platform == "darwin") class ConcreteWidget(Widget): @@ -320,9 +321,9 @@ def test_enabled(self): self.assertFalse(self.widget.control.isEnabled()) - @unittest.skipIf( - is_linux, - "Linux keyboard focus is False unless window is active", + @unittest.skipUnless( + is_mac, + "Broken on Linux and Windows", ) def test_focus(self): with self.event_loop(): diff --git a/pyface/tree/node_manager.py b/pyface/tree/node_manager.py index 12d2c84bd..9acc3ff7d 100644 --- a/pyface/tree/node_manager.py +++ b/pyface/tree/node_manager.py @@ -14,7 +14,7 @@ import logging -from traits.api import HasPrivateTraits, List +from traits.api import HasPrivateTraits, List, observe from .node_type import NodeType @@ -122,9 +122,10 @@ def get_key(self, node): # Private interface. # ------------------------------------------------------------------------ - def _node_types_changed(self, new): + @observe("node_types") + def _update_node_manager_on_new_node_types(self, event): """ Called when the entire list of node types has been changed. """ - + new = event.new for node_type in new: node_type.node_manager = self diff --git a/pyface/tree/node_tree.py b/pyface/tree/node_tree.py index 5d48169f9..15dc92bb4 100644 --- a/pyface/tree/node_tree.py +++ b/pyface/tree/node_tree.py @@ -16,7 +16,7 @@ from pyface.action.api import ActionEvent -from traits.api import Instance, List, Property +from traits.api import Instance, List, observe, Property from .node_manager import NodeManager @@ -79,16 +79,18 @@ def _set_node_types(self, node_types): # Trait event handlers ------------------------------------------------- - def _node_activated_changed(self, obj): + @observe("node_activated") + def _perform_default_action_on_activated_node(self, event): """ Called when a node has been activated (i.e., double-clicked). """ - + obj = event.new default_action = self.model.get_default_action(obj) if default_action is not None: self._perform_default_action(default_action, obj) - def _node_right_clicked_changed(self, obj_point): + @observe("node_right_clicked") + def _show_menu_on_right_clicked_object(self, event): """ Called when the right mouse button is clicked on the tree. """ - + obj_point = event.new obj, point = obj_point # Add the node that the right-click occurred on to the selection. self.select(obj) diff --git a/pyface/ui/qt4/gui.py b/pyface/ui/qt4/gui.py index 9749ad627..ea2a69bd3 100644 --- a/pyface/ui/qt4/gui.py +++ b/pyface/ui/qt4/gui.py @@ -18,7 +18,7 @@ from pyface.qt import QtCore, QtGui -from traits.api import Bool, HasTraits, provides, Str +from traits.api import Bool, HasTraits, observe, provides, Str from pyface.util.guisupport import start_event_loop_qt4 @@ -120,9 +120,10 @@ def _state_location_default(self): return self._default_state_location() - def _busy_changed(self, new): + @observe("busy") + def _update_busy_state(self, event): """ The busy trait change handler. """ - + new = event.new if new: QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) else: diff --git a/pyface/ui/qt4/heading_text.py b/pyface/ui/qt4/heading_text.py index 227594578..ba9fc2085 100644 --- a/pyface/ui/qt4/heading_text.py +++ b/pyface/ui/qt4/heading_text.py @@ -15,7 +15,7 @@ from pyface.qt import QtGui -from traits.api import Int, provides, Str +from traits.api import Int, observe, provides, Str from pyface.i_heading_text import IHeadingText, MHeadingText @@ -71,8 +71,9 @@ def _set_text(self, text): # Trait event handlers ------------------------------------------------- - def _text_changed(self, new): + @observe("text") + def _update_text(self, event): """ Called when the text is changed. """ - + new = event.new if self.control is not None: self._set_text(new) diff --git a/pyface/ui/qt4/tasks/advanced_editor_area_pane.py b/pyface/ui/qt4/tasks/advanced_editor_area_pane.py index e8dcf6184..b307fdd3b 100644 --- a/pyface/ui/qt4/tasks/advanced_editor_area_pane.py +++ b/pyface/ui/qt4/tasks/advanced_editor_area_pane.py @@ -280,7 +280,7 @@ def __init__(self, editor_area, parent=None): break # Monitor focus changes so we can set the active editor. - QtGui.QApplication.instance().focusChanged.connect(self._focus_changed) + QtGui.QApplication.instance().focusChanged.connect(self._update_active_editor) # Configure the QMainWindow. # FIXME: Currently animation is not supported. @@ -296,7 +296,7 @@ def __init__(self, editor_area, parent=None): def _remove_event_listeners(self): """ Disconnects focusChanged signal of the application """ app = QtGui.QApplication.instance() - app.focusChanged.disconnect(self._focus_changed) + app.focusChanged.disconnect(self._update_active_editor) def add_editor_widget(self, editor_widget): """ Adds a dock widget to the editor area. @@ -458,7 +458,7 @@ def childEvent(self, event): # Use UniqueConnections since Qt recycles the tab bars. child.installEventFilter(self) child.currentChanged.connect( - self._tab_index_changed, QtCore.Qt.UniqueConnection + self._update_editor_in_focus, QtCore.Qt.UniqueConnection ) child.setTabsClosable(True) child.setUsesScrollButtons(True) @@ -575,7 +575,7 @@ def focusInEvent(self, event): # Signal handlers. # ------------------------------------------------------------------------ - def _focus_changed(self, old, new): + def _update_active_editor(self, old, new): """ Handle an application-level focus change. """ if new is not None: @@ -588,7 +588,7 @@ def _focus_changed(self, old, new): if not self.editor_area.editors: self.editor_area.active_editor = None - def _tab_index_changed(self, index): + def _update_editor_in_focus(self, index): """ Handle a tab selection. """ widgets = self.get_dock_widgets_for_bar(self.sender()) diff --git a/pyface/undo/undo_manager.py b/pyface/undo/undo_manager.py index 3815d077b..cb382a93d 100644 --- a/pyface/undo/undo_manager.py +++ b/pyface/undo/undo_manager.py @@ -32,6 +32,7 @@ Property, Str, provides, + observe, ) # Local imports. @@ -95,9 +96,10 @@ def undo(self): # Private interface. ########################################################################### - def _active_stack_changed(self, new): + @observe("active_stack") + def _update_stack_updated(self, event): """ Handle a different stack becoming active. """ - + new = event.new # Pretend that the stack contents have changed. self.stack_updated = new diff --git a/pyface/wizard/chained_wizard.py b/pyface/wizard/chained_wizard.py index 6d0753927..788a18f9b 100644 --- a/pyface/wizard/chained_wizard.py +++ b/pyface/wizard/chained_wizard.py @@ -11,7 +11,7 @@ """ A wizard model that can be chained with other wizards. """ -from traits.api import Instance +from traits.api import Instance, observe from .i_wizard import IWizard @@ -43,9 +43,10 @@ def _controller_default(self): # Static ---- - def _next_wizard_changed(self, old, new): + @observe("next_wizard") + def _reset_next_controller_and_update(self, event): """ Handle the next wizard being changed. """ - + old, new = event.old, event.new if new is not None: self.controller.next_controller = new.controller @@ -58,9 +59,10 @@ def _next_wizard_changed(self, old, new): # self._create_buttons(self.control) self._update() - def _controller_changed(self, old, new): + @observe("controller") + def _reset_traits_on_controller_and_update(self, event): """ handle the controller being changed. """ - + old, new = event.old, event.new if new is not None and self.next_wizard is not None: self.controller.next_controller = self.next_wizard.controller diff --git a/pyface/wizard/chained_wizard_controller.py b/pyface/wizard/chained_wizard_controller.py index cdb17a9fc..19682684c 100644 --- a/pyface/wizard/chained_wizard_controller.py +++ b/pyface/wizard/chained_wizard_controller.py @@ -11,7 +11,7 @@ """ A wizard controller that can be chained with others. """ -from traits.api import Instance +from traits.api import Instance, observe from .i_wizard_controller import IWizardController @@ -168,9 +168,10 @@ def _update(self): # Static ---- - def _current_page_changed(self, old, new): + @observe("current_paage") + def _reset_observers_on_current_page_and_update(self, event): """ Called when the current page is changed. """ - + old, new = event.old, event.new if old is not None: old.observe( self._on_page_complete, "complete", remove=True @@ -184,9 +185,10 @@ def _current_page_changed(self, old, new): self._update() - def _next_controller_changed(self, old, new): + @observe("next_controller") + def _reset_observers_on_next_controller_and_update(self, event): """ Called when the next controller is changed. """ - + old, new = event.old, event.new if old is not None: old.observe( self._on_controller_complete, "complete", remove=True diff --git a/pyface/wizard/wizard_controller.py b/pyface/wizard/wizard_controller.py index b8f0afd5c..ce3d3fc6f 100644 --- a/pyface/wizard/wizard_controller.py +++ b/pyface/wizard/wizard_controller.py @@ -11,7 +11,9 @@ """ A wizard controller that has a static list of pages. """ -from traits.api import Bool, HasTraits, Instance, List, Property, provides +from traits.api import ( + Bool, HasTraits, Instance, List, Property, provides, observe +) from .i_wizard_controller import IWizardController @@ -153,9 +155,10 @@ def _update(self): # Static ---- - def _current_page_changed(self, old, new): + @observe("current_page") + def _reset_observers_on_current_page_and_update(self, event): """ Called when the current page is changed. """ - + old, new = event.old, event.new if old is not None: old.observe( self._on_page_complete, "complete", remove=True