diff --git a/docs/source/index.rst b/docs/source/index.rst index c42f242eb..21281763e 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -43,7 +43,7 @@ that signifies the GUI toolkit. The supported values of **ETSConfig.toolkit** are: -* 'qt4' or 'qt': PySide2, PySide6 or `PyQt `_, +* 'qt' or 'qt4': PySide2, PySide6 or `PyQt `_, which provide Python bindings for the `Qt `_ framework. * 'wx': `wxPython 4 `_, which provides Python bindings for the `wxWidgets `_ toolkit. diff --git a/docs/source/overview.rst b/docs/source/overview.rst index 3059fead7..274db2d87 100644 --- a/docs/source/overview.rst +++ b/docs/source/overview.rst @@ -45,7 +45,7 @@ interface, which in turn inherits from :py:class:`.IDialog`, is the combination of these. The :py:class:`.MFileDialog` class provides some mix-in capabilities around the expression of filters for file types. And then there are two concrete implementations, :py:class:`pyface.ui.wx.file_dialog.FileDialog` -for wxPython and :py:class:`pyface.ui.qt4.file_dialog.FileDialog` for the Qt +for wxPython and :py:class:`pyface.ui.qt.file_dialog.FileDialog` for the Qt backends. These toolkit classes in turn inherit from the appropriate toolkit's :py:class:`Dialog`, :py:class:`Window`, and :py:class:`Widget` classes, as well as the :py:class:`.MFileDialog` mixin. diff --git a/docs/source/toolkits.rst b/docs/source/toolkits.rst index c7ccabe7b..5bc91eaa2 100644 --- a/docs/source/toolkits.rst +++ b/docs/source/toolkits.rst @@ -21,7 +21,7 @@ ways: attribute appropriately:: from tratis.etsconfig.api import ETSConfig - ETSConfig.toolkit = 'qt4' + ETSConfig.toolkit = 'qt' This must be done _before_ any widget imports in your application, including importing :py:mod:`pyface.api`. Precisely, this must be set before the @@ -30,7 +30,7 @@ ways: If for some reason Pyface can't load a deliberately specified toolkit, then it will raise an exception. -If the toolkit is not specified, Pyface will try to load the ``qt4`` or ``wx`` +If the toolkit is not specified, Pyface will try to load the ``qt`` or ``wx`` toolkits, in that order, and then any other toolkits that it knows about other than ``null``. If all of those fail, then it will try to load the ``null`` toolkit. @@ -57,8 +57,8 @@ The API module for the new widget class typically looks something like this:: The base toolkits use the identifier to select which module to import the toolkit object by constructing a full module path from the partial path and -importing the object. For example the ``qt4`` backend will look for the -concrete implementation in :py:mod:`pyface.ui.qt4.my_package.my_widget` +importing the object. For example the ``qt`` backend will look for the +concrete implementation in :py:mod:`pyface.ui.qt.my_package.my_widget` while the ``wx`` backend will look for :py:mod:`pyface.ui.wx.my_package.my_widget`. @@ -73,6 +73,34 @@ second trait provides a hook where an application can insert other packages into the search path to override the default implementations of a toolkit's widgets, if needed. +The "qt4" Toolkit +----------------- + +The "qt4" toolkit is the same as the "qt" toolkit in almost all respects: +in older versions of Pyface it was the standard name for all the Qt-based +toolkits whether or not they were actually using Qt4. + +However it does trigger some backwards-compatibility code that may be useful +for legacy applications. In particular it installs import hooks that makes the +``pyface.ui.qt4.*`` package namespace an alias for ``pyface.ui.qt.*`` modules. + +This backwards-compatibility code can also be invoked by setting the +``ETS_QT4_IMPORTS`` environment variable to any non-empty value, or adding +an instance of the :py:class:`pyface.ui.ShadowedModuleFinder` module finder +to :py:attr:`sys.meta_path` list. + +.. warning:: + + Library code which imports from ``pyface.ui.qt4.*`` should not use this + compatibility code. Instead it should be updated to import from + ``pyface.ui.qt.*`` as soon as practical. Backwards-compatibility can be + achieved fairly easily by using :py:attr:`pyface.toolkit.toolkit` to access + objects rather than direct imports. + +This backwards-compatibility code will be removed in Pyface 9, and applications +which rely on the particulars of the implementation are encouraged to +migrate to the newer import locations as soon as practical. + Toolkit Entrypoints =================== diff --git a/etstool.py b/etstool.py index 12bded99d..6d661394f 100644 --- a/etstool.py +++ b/etstool.py @@ -141,7 +141,10 @@ doc_ignore = { "pyface/wx/*", "pyface/qt/*", - "pyface/ui/*", + "pyface/ui/null/*", + "pyface/ui/qt/*", + "pyface/ui/qt4/*", + "pyface/ui/wx/*", "pyface/dock/*", "pyface/util/fix_introspect_bug.py", "pyface/grid/*", diff --git a/examples/python_editor.py b/examples/python_editor.py index 5af8aca2f..47f0d1d8f 100644 --- a/examples/python_editor.py +++ b/examples/python_editor.py @@ -68,7 +68,7 @@ def __init__(self, **traits): ) ) - # Add a tool bar if we are using qt4 - wx has layout issues + # Add a tool bar if we are using qt - wx has layout issues if toolkit_object.toolkit.startswith("qt"): from pygments.styles import STYLE_MAP diff --git a/examples/tasks/advanced/python_editor_qt4.py b/examples/tasks/advanced/python_editor_qt4.py index 943ac90bb..7522e2c9a 100644 --- a/examples/tasks/advanced/python_editor_qt4.py +++ b/examples/tasks/advanced/python_editor_qt4.py @@ -126,7 +126,7 @@ def _show_line_numbers_updated(self, event=None): def _create_control(self, parent): """ Creates the toolkit-specific control for the widget. """ - from pyface.ui.qt4.code_editor.code_widget import AdvancedCodeWidget + from pyface.ui.qt.code_editor.code_widget import AdvancedCodeWidget self.control = control = AdvancedCodeWidget(parent) self._show_line_numbers_updated() diff --git a/examples/tasks/advanced/run.py b/examples/tasks/advanced/run.py index 4466e420e..03e90ed14 100644 --- a/examples/tasks/advanced/run.py +++ b/examples/tasks/advanced/run.py @@ -3,7 +3,7 @@ components. Note: Run it with -$ ETS_TOOLKIT='qt4' python run.py +$ ETS_TOOLKIT='qt' python run.py as the wx backend is not supported yet for the TaskWindow. """ diff --git a/examples/tasks/basic/run.py b/examples/tasks/basic/run.py index 4466e420e..03e90ed14 100644 --- a/examples/tasks/basic/run.py +++ b/examples/tasks/basic/run.py @@ -3,7 +3,7 @@ components. Note: Run it with -$ ETS_TOOLKIT='qt4' python run.py +$ ETS_TOOLKIT='qt' python run.py as the wx backend is not supported yet for the TaskWindow. """ diff --git a/pyface/base_toolkit.py b/pyface/base_toolkit.py index 1a08b9514..ad53b1591 100644 --- a/pyface/base_toolkit.py +++ b/pyface/base_toolkit.py @@ -57,7 +57,7 @@ - after that, we try every 'pyface.toolkit' plugin we can find. If one succeeds, we consider ourselves good, and set the ETSConfig.toolkit appropriately. The order is configurable, and by default will try to load - the `qt4` toolkit first, `wx` next, then all others in arbitrary order, + the `qt` toolkit first, `wx` next, then all others in arbitrary order, and `null` last. - finally, if all else fails, we try to load the null toolkit. @@ -81,7 +81,7 @@ logger = logging.getLogger(__name__) -TOOLKIT_PRIORITIES = {"qt4": -2, "wx": -1, "null": float("inf")} +TOOLKIT_PRIORITIES = {"qt": -2, "wx": -1, "null": float("inf")} default_priorities = lambda plugin: TOOLKIT_PRIORITIES.get(plugin.name, 0) diff --git a/pyface/mimedata.py b/pyface/mimedata.py index 517f81da3..adbb8d90b 100644 --- a/pyface/mimedata.py +++ b/pyface/mimedata.py @@ -10,6 +10,6 @@ # Import the toolkit specific version. from pyface.toolkit import toolkit_object -# WIP: Currently only supports qt4 backend. API might change without +# WIP: Currently only supports qt backend. API might change without # prior notification PyMimeData = toolkit_object("mimedata:PyMimeData") diff --git a/pyface/tasks/tests/test_dock_pane_toggle_group.py b/pyface/tasks/tests/test_dock_pane_toggle_group.py index 6ed3c5aa2..676272d0f 100644 --- a/pyface/tasks/tests/test_dock_pane_toggle_group.py +++ b/pyface/tasks/tests/test_dock_pane_toggle_group.py @@ -19,7 +19,7 @@ from traits.etsconfig.api import ETSConfig -USING_WX = ETSConfig.toolkit not in ["", "qt4"] +USING_WX = ETSConfig.toolkit not in {"", "qt", "qt4"} class BogusTask(Task): diff --git a/pyface/tasks/tests/test_editor_area_pane.py b/pyface/tasks/tests/test_editor_area_pane.py index c2ead0905..1ba73031b 100644 --- a/pyface/tasks/tests/test_editor_area_pane.py +++ b/pyface/tasks/tests/test_editor_area_pane.py @@ -18,7 +18,7 @@ GuiTestAssistant = toolkit_object("util.gui_test_assistant:GuiTestAssistant") no_gui_test_assistant = GuiTestAssistant.__name__ == "Unimplemented" -USING_WX = ETSConfig.toolkit not in ["", "qt4"] +USING_WX = ETSConfig.toolkit not in {"", "qt", "qt4"} class EditorAreaPaneTestCase(unittest.TestCase): diff --git a/pyface/tasks/tests/test_enaml_dock_pane.py b/pyface/tasks/tests/test_enaml_dock_pane.py index 67a9c1760..acc8c9736 100644 --- a/pyface/tasks/tests/test_enaml_dock_pane.py +++ b/pyface/tasks/tests/test_enaml_dock_pane.py @@ -13,7 +13,7 @@ # Skip tests if Enaml is not installed or we're using the wx backend. SKIP_REASON = None -if ETSConfig.toolkit not in ["", "qt4"]: +if ETSConfig.toolkit not in {"", "qt", "qt4"}: SKIP_REASON = "Enaml does not support WX" else: try: diff --git a/pyface/tasks/tests/test_enaml_editor.py b/pyface/tasks/tests/test_enaml_editor.py index 0e0de2a5c..e0bd6efca 100644 --- a/pyface/tasks/tests/test_enaml_editor.py +++ b/pyface/tasks/tests/test_enaml_editor.py @@ -13,7 +13,7 @@ # Skip tests if Enaml is not installed or we're using the wx backend. SKIP_REASON = None -if ETSConfig.toolkit not in ["", "qt4"]: +if ETSConfig.toolkit not in {"", "qt", "qt4"}: SKIP_REASON = "Enaml does not support WX" else: try: diff --git a/pyface/tasks/tests/test_enaml_task_pane.py b/pyface/tasks/tests/test_enaml_task_pane.py index 016b11647..fd1a7fcb2 100644 --- a/pyface/tasks/tests/test_enaml_task_pane.py +++ b/pyface/tasks/tests/test_enaml_task_pane.py @@ -13,7 +13,7 @@ # Skip tests if Enaml is not installed or we're using the wx backend. SKIP_REASON = None -if ETSConfig.toolkit not in ["", "qt4"]: +if ETSConfig.toolkit not in {"", "qt", "qt4"}: SKIP_REASON = "Enaml does not support WX" else: try: diff --git a/pyface/tests/test_base_toolkit.py b/pyface/tests/test_base_toolkit.py index 8c1a486bb..b9c3ca435 100644 --- a/pyface/tests/test_base_toolkit.py +++ b/pyface/tests/test_base_toolkit.py @@ -27,12 +27,17 @@ def test_missing_toolkit(self): def test_find_current_toolkit_no_etsconfig(self): old_etsconfig_toolkit = ETSConfig._toolkit + expected_toolkit = ( + "qt" + if old_etsconfig_toolkit == "qt4" + else old_etsconfig_toolkit + ) ETSConfig._toolkit = "" try: toolkit = find_toolkit("pyface.toolkits", old_etsconfig_toolkit) self.assertEqual(toolkit.package, "pyface") - self.assertEqual(toolkit.toolkit, old_etsconfig_toolkit) - self.assertEqual(ETSConfig.toolkit, old_etsconfig_toolkit) + self.assertEqual(toolkit.toolkit, expected_toolkit) + self.assertEqual(ETSConfig.toolkit, expected_toolkit) finally: ETSConfig._toolkit = old_etsconfig_toolkit diff --git a/pyface/toolkit.py b/pyface/toolkit.py index c853ccfb5..afe854860 100644 --- a/pyface/toolkit.py +++ b/pyface/toolkit.py @@ -19,5 +19,5 @@ from .base_toolkit import find_toolkit -# The toolkit function. +#: The callable toolkit object. toolkit = toolkit_object = find_toolkit("pyface.toolkits") diff --git a/pyface/ui/__init__.py b/pyface/ui/__init__.py index e69de29bb..5cc66c880 100644 --- a/pyface/ui/__init__.py +++ b/pyface/ui/__init__.py @@ -0,0 +1,113 @@ +# (C) Copyright 2005-2023 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + +from importlib import import_module +from importlib.abc import MetaPathFinder, Loader +from importlib.machinery import ModuleSpec +from importlib.util import find_spec +import sys + + +# Import hooks for loading pyface.ui.qt.* in place of a pyface.ui.qt4.* +# This is just the implementation, it is not connected in this module, but +# is available for applications which want to install it themselves. +# It is here rather than in pyface.ui.qt4 so it can be imported and used +# without generating the warnings from pyface.ui.qt4 +# +# To use manually: +# +# import sys +# sys.meta_path.append(ShadowedModuleFinder()) + +class ShadowedModuleLoader(Loader): + """This loads another module into sys.modules with a given name. + + Parameters + ---------- + fullname : str + The full name of the module we're trying to import. + Eg. "pyface.ui.qt4.foo" + new_name : str + The full name of the corresponding "real" module. + Eg. "pyface.ui.qt.foo" + new_spec : ModuleSpec instance + The spec object for the corresponding "real" module. + """ + + def __init__(self, fullname, new_name, new_spec): + self.fullname = fullname + self.new_name = new_name + self.new_spec = new_spec + + def create_module(self, spec): + """Create the module object. + + This doesn't create the module object directly, rather it gets the + underlying "real" module's object, importing it if needed. This object + is then returned as the "new" module. + """ + if self.new_name not in sys.modules: + import_module(self.new_name) + return sys.modules[self.new_name] + + def exec_module(self, module): + """Execute code for the module. + + This is given a module which has already been executed, so we don't + need to execute anything. However we do need to remove the __spec__ + that the importlibs machinery has injected into the module and + replace it with the original spec for the underlying "real" module. + """ + # patch up the __spec__ with the true module's original __spec__ + if self.new_spec: + module.__spec__ = self.new_spec + self.new_spec = None + + +class ShadowedModuleFinder(MetaPathFinder): + """MetaPathFinder for shadowing modules in a package + + This finds modules with names that match a package but arranges loading + from a different package. By default this is matches imports from any + path starting with pyface.ui.qt4. and returns a loader which will instead + load from pyface.ui.qt.* + + The end result is that sys.modules has two entries for pointing to the + same module object. + + This may be hooked up by code in pyface.ui.qt4, but it can also be + installed manually with:: + + import sys + sys.meta_path.append(ShadowedModuleFinder()) + + Parameters + ---------- + package : str + The prefix of the "shadow" package. + true_package : str + The prefix of the "real" package which contains the actual code. + """ + + def __init__(self, package="pyface.ui.qt4.", true_package="pyface.ui.qt."): + self.package = package + self.true_package = true_package + + def find_spec(self, fullname, path, target=None): + if fullname.startswith(self.package): + new_name = fullname.replace(self.package, self.true_package, 1) + new_spec = find_spec(new_name) + if new_spec is None: + return None + return ModuleSpec( + name=fullname, + loader=ShadowedModuleLoader(fullname, new_name, new_spec), + is_package=(new_spec.submodule_search_locations is not None), + ) diff --git a/pyface/ui/qt4/action/__init__.py b/pyface/ui/qt/__init__.py similarity index 100% rename from pyface/ui/qt4/action/__init__.py rename to pyface/ui/qt/__init__.py diff --git a/pyface/ui/qt4/about_dialog.py b/pyface/ui/qt/about_dialog.py similarity index 100% rename from pyface/ui/qt4/about_dialog.py rename to pyface/ui/qt/about_dialog.py diff --git a/pyface/ui/qt4/code_editor/__init__.py b/pyface/ui/qt/action/__init__.py similarity index 100% rename from pyface/ui/qt4/code_editor/__init__.py rename to pyface/ui/qt/action/__init__.py diff --git a/pyface/ui/qt4/action/action_item.py b/pyface/ui/qt/action/action_item.py similarity index 100% rename from pyface/ui/qt4/action/action_item.py rename to pyface/ui/qt/action/action_item.py diff --git a/pyface/ui/qt4/action/menu_bar_manager.py b/pyface/ui/qt/action/menu_bar_manager.py similarity index 100% rename from pyface/ui/qt4/action/menu_bar_manager.py rename to pyface/ui/qt/action/menu_bar_manager.py diff --git a/pyface/ui/qt4/action/menu_manager.py b/pyface/ui/qt/action/menu_manager.py similarity index 100% rename from pyface/ui/qt4/action/menu_manager.py rename to pyface/ui/qt/action/menu_manager.py diff --git a/pyface/ui/qt4/action/status_bar_manager.py b/pyface/ui/qt/action/status_bar_manager.py similarity index 100% rename from pyface/ui/qt4/action/status_bar_manager.py rename to pyface/ui/qt/action/status_bar_manager.py diff --git a/pyface/ui/qt4/action/tool_bar_manager.py b/pyface/ui/qt/action/tool_bar_manager.py similarity index 100% rename from pyface/ui/qt4/action/tool_bar_manager.py rename to pyface/ui/qt/action/tool_bar_manager.py diff --git a/pyface/ui/qt4/application_window.py b/pyface/ui/qt/application_window.py similarity index 100% rename from pyface/ui/qt4/application_window.py rename to pyface/ui/qt/application_window.py diff --git a/pyface/ui/qt4/beep.py b/pyface/ui/qt/beep.py similarity index 100% rename from pyface/ui/qt4/beep.py rename to pyface/ui/qt/beep.py diff --git a/pyface/ui/qt4/clipboard.py b/pyface/ui/qt/clipboard.py similarity index 100% rename from pyface/ui/qt4/clipboard.py rename to pyface/ui/qt/clipboard.py diff --git a/pyface/ui/qt4/timer/__init__.py b/pyface/ui/qt/code_editor/__init__.py similarity index 100% rename from pyface/ui/qt4/timer/__init__.py rename to pyface/ui/qt/code_editor/__init__.py diff --git a/pyface/ui/qt4/code_editor/code_widget.py b/pyface/ui/qt/code_editor/code_widget.py similarity index 100% rename from pyface/ui/qt4/code_editor/code_widget.py rename to pyface/ui/qt/code_editor/code_widget.py diff --git a/pyface/ui/qt4/code_editor/find_widget.py b/pyface/ui/qt/code_editor/find_widget.py similarity index 100% rename from pyface/ui/qt4/code_editor/find_widget.py rename to pyface/ui/qt/code_editor/find_widget.py diff --git a/pyface/ui/qt4/code_editor/gutters.py b/pyface/ui/qt/code_editor/gutters.py similarity index 100% rename from pyface/ui/qt4/code_editor/gutters.py rename to pyface/ui/qt/code_editor/gutters.py diff --git a/pyface/ui/qt4/code_editor/pygments_highlighter.py b/pyface/ui/qt/code_editor/pygments_highlighter.py similarity index 100% rename from pyface/ui/qt4/code_editor/pygments_highlighter.py rename to pyface/ui/qt/code_editor/pygments_highlighter.py diff --git a/pyface/ui/qt4/code_editor/replace_widget.py b/pyface/ui/qt/code_editor/replace_widget.py similarity index 100% rename from pyface/ui/qt4/code_editor/replace_widget.py rename to pyface/ui/qt/code_editor/replace_widget.py diff --git a/pyface/ui/qt4/code_editor/tests/__init__.py b/pyface/ui/qt/code_editor/tests/__init__.py similarity index 100% rename from pyface/ui/qt4/code_editor/tests/__init__.py rename to pyface/ui/qt/code_editor/tests/__init__.py diff --git a/pyface/ui/qt4/code_editor/tests/test_code_widget.py b/pyface/ui/qt/code_editor/tests/test_code_widget.py similarity index 97% rename from pyface/ui/qt4/code_editor/tests/test_code_widget.py rename to pyface/ui/qt/code_editor/tests/test_code_widget.py index b12e348b4..be9e8f734 100644 --- a/pyface/ui/qt4/code_editor/tests/test_code_widget.py +++ b/pyface/ui/qt/code_editor/tests/test_code_widget.py @@ -16,7 +16,7 @@ from pyface.qt.QtTest import QTest -from pyface.ui.qt4.code_editor.code_widget import ( +from pyface.ui.qt.code_editor.code_widget import ( CodeWidget, AdvancedCodeWidget, ) diff --git a/pyface/ui/qt4/color.py b/pyface/ui/qt/color.py similarity index 100% rename from pyface/ui/qt4/color.py rename to pyface/ui/qt/color.py diff --git a/pyface/ui/qt4/color_dialog.py b/pyface/ui/qt/color_dialog.py similarity index 100% rename from pyface/ui/qt4/color_dialog.py rename to pyface/ui/qt/color_dialog.py diff --git a/pyface/ui/qt4/confirmation_dialog.py b/pyface/ui/qt/confirmation_dialog.py similarity index 100% rename from pyface/ui/qt4/confirmation_dialog.py rename to pyface/ui/qt/confirmation_dialog.py diff --git a/pyface/ui/qt4/console/__init__.py b/pyface/ui/qt/console/__init__.py similarity index 100% rename from pyface/ui/qt4/console/__init__.py rename to pyface/ui/qt/console/__init__.py diff --git a/pyface/ui/qt4/console/api.py b/pyface/ui/qt/console/api.py similarity index 100% rename from pyface/ui/qt4/console/api.py rename to pyface/ui/qt/console/api.py diff --git a/pyface/ui/qt4/console/bracket_matcher.py b/pyface/ui/qt/console/bracket_matcher.py similarity index 100% rename from pyface/ui/qt4/console/bracket_matcher.py rename to pyface/ui/qt/console/bracket_matcher.py diff --git a/pyface/ui/qt4/console/call_tip_widget.py b/pyface/ui/qt/console/call_tip_widget.py similarity index 100% rename from pyface/ui/qt4/console/call_tip_widget.py rename to pyface/ui/qt/console/call_tip_widget.py diff --git a/pyface/ui/qt4/console/completion_lexer.py b/pyface/ui/qt/console/completion_lexer.py similarity index 100% rename from pyface/ui/qt4/console/completion_lexer.py rename to pyface/ui/qt/console/completion_lexer.py diff --git a/pyface/ui/qt4/console/console_widget.py b/pyface/ui/qt/console/console_widget.py similarity index 100% rename from pyface/ui/qt4/console/console_widget.py rename to pyface/ui/qt/console/console_widget.py diff --git a/pyface/ui/qt4/console/history_console_widget.py b/pyface/ui/qt/console/history_console_widget.py similarity index 100% rename from pyface/ui/qt4/console/history_console_widget.py rename to pyface/ui/qt/console/history_console_widget.py diff --git a/pyface/ui/qt4/console/tests/__init__.py b/pyface/ui/qt/console/tests/__init__.py similarity index 100% rename from pyface/ui/qt4/console/tests/__init__.py rename to pyface/ui/qt/console/tests/__init__.py diff --git a/pyface/ui/qt4/console/tests/test_console_widget.py b/pyface/ui/qt/console/tests/test_console_widget.py similarity index 100% rename from pyface/ui/qt4/console/tests/test_console_widget.py rename to pyface/ui/qt/console/tests/test_console_widget.py diff --git a/pyface/ui/qt4/data_view/__init__.py b/pyface/ui/qt/data_view/__init__.py similarity index 100% rename from pyface/ui/qt4/data_view/__init__.py rename to pyface/ui/qt/data_view/__init__.py diff --git a/pyface/ui/qt4/data_view/data_view_item_model.py b/pyface/ui/qt/data_view/data_view_item_model.py similarity index 100% rename from pyface/ui/qt4/data_view/data_view_item_model.py rename to pyface/ui/qt/data_view/data_view_item_model.py diff --git a/pyface/ui/qt4/data_view/data_view_widget.py b/pyface/ui/qt/data_view/data_view_widget.py similarity index 99% rename from pyface/ui/qt4/data_view/data_view_widget.py rename to pyface/ui/qt/data_view/data_view_widget.py index 6cd96b9f8..a240e8a03 100644 --- a/pyface/ui/qt4/data_view/data_view_widget.py +++ b/pyface/ui/qt/data_view/data_view_widget.py @@ -19,7 +19,7 @@ from pyface.data_view.i_data_view_widget import ( IDataViewWidget, MDataViewWidget ) -from pyface.ui.qt4.layout_widget import LayoutWidget +from pyface.ui.qt.layout_widget import LayoutWidget from .data_view_item_model import DataViewItemModel # XXX This file is scaffolding and may need to be rewritten diff --git a/pyface/ui/qt4/data_view/data_wrapper.py b/pyface/ui/qt/data_view/data_wrapper.py similarity index 100% rename from pyface/ui/qt4/data_view/data_wrapper.py rename to pyface/ui/qt/data_view/data_wrapper.py diff --git a/pyface/ui/qt4/data_view/tests/__init__.py b/pyface/ui/qt/data_view/tests/__init__.py similarity index 100% rename from pyface/ui/qt4/data_view/tests/__init__.py rename to pyface/ui/qt/data_view/tests/__init__.py diff --git a/pyface/ui/qt4/data_view/tests/test_data_view_item_model.py b/pyface/ui/qt/data_view/tests/test_data_view_item_model.py similarity index 97% rename from pyface/ui/qt4/data_view/tests/test_data_view_item_model.py rename to pyface/ui/qt/data_view/tests/test_data_view_item_model.py index e952e66a7..57c70c883 100644 --- a/pyface/ui/qt4/data_view/tests/test_data_view_item_model.py +++ b/pyface/ui/qt/data_view/tests/test_data_view_item_model.py @@ -20,7 +20,7 @@ from pyface.data_view.exporters.row_exporter import RowExporter from pyface.data_view.data_formats import table_format from pyface.data_view.value_types.api import FloatValue -from pyface.ui.qt4.data_view.data_view_item_model import DataViewItemModel +from pyface.ui.qt.data_view.data_view_item_model import DataViewItemModel @requires_numpy diff --git a/pyface/ui/qt4/data_view/tests/test_data_wrapper.py b/pyface/ui/qt/data_view/tests/test_data_wrapper.py similarity index 95% rename from pyface/ui/qt4/data_view/tests/test_data_wrapper.py rename to pyface/ui/qt/data_view/tests/test_data_wrapper.py index a173c3de2..5ce827048 100644 --- a/pyface/ui/qt4/data_view/tests/test_data_wrapper.py +++ b/pyface/ui/qt/data_view/tests/test_data_wrapper.py @@ -11,7 +11,7 @@ from unittest import TestCase from pyface.qt.QtCore import QMimeData -from pyface.ui.qt4.data_view.data_wrapper import DataWrapper +from pyface.ui.qt.data_view.data_wrapper import DataWrapper class TestDataWrapper(TestCase): diff --git a/pyface/ui/qt4/dialog.py b/pyface/ui/qt/dialog.py similarity index 100% rename from pyface/ui/qt4/dialog.py rename to pyface/ui/qt/dialog.py diff --git a/pyface/ui/qt4/directory_dialog.py b/pyface/ui/qt/directory_dialog.py similarity index 100% rename from pyface/ui/qt4/directory_dialog.py rename to pyface/ui/qt/directory_dialog.py diff --git a/pyface/ui/qt4/fields/__init__.py b/pyface/ui/qt/fields/__init__.py similarity index 100% rename from pyface/ui/qt4/fields/__init__.py rename to pyface/ui/qt/fields/__init__.py diff --git a/pyface/ui/qt4/fields/combo_field.py b/pyface/ui/qt/fields/combo_field.py similarity index 100% rename from pyface/ui/qt4/fields/combo_field.py rename to pyface/ui/qt/fields/combo_field.py diff --git a/pyface/ui/qt4/fields/field.py b/pyface/ui/qt/fields/field.py similarity index 93% rename from pyface/ui/qt4/fields/field.py rename to pyface/ui/qt/fields/field.py index 91936035c..dc3b00aa2 100644 --- a/pyface/ui/qt4/fields/field.py +++ b/pyface/ui/qt/fields/field.py @@ -14,7 +14,7 @@ from traits.api import Any, provides from pyface.fields.i_field import IField, MField -from pyface.ui.qt4.layout_widget import LayoutWidget +from pyface.ui.qt.layout_widget import LayoutWidget @provides(IField) diff --git a/pyface/ui/qt4/fields/spin_field.py b/pyface/ui/qt/fields/spin_field.py similarity index 100% rename from pyface/ui/qt4/fields/spin_field.py rename to pyface/ui/qt/fields/spin_field.py diff --git a/pyface/ui/qt4/fields/text_field.py b/pyface/ui/qt/fields/text_field.py similarity index 100% rename from pyface/ui/qt4/fields/text_field.py rename to pyface/ui/qt/fields/text_field.py diff --git a/pyface/ui/qt4/fields/time_field.py b/pyface/ui/qt/fields/time_field.py similarity index 96% rename from pyface/ui/qt4/fields/time_field.py rename to pyface/ui/qt/fields/time_field.py index 359dcb189..0823e9fd2 100644 --- a/pyface/ui/qt4/fields/time_field.py +++ b/pyface/ui/qt/fields/time_field.py @@ -16,7 +16,7 @@ from pyface.qt.QtGui import QTimeEdit from pyface.fields.i_time_field import ITimeField, MTimeField -from pyface.ui.qt4.util.datetime import pytime_to_qtime, qtime_to_pytime +from pyface.ui.qt.util.datetime import pytime_to_qtime, qtime_to_pytime from .field import Field diff --git a/pyface/ui/qt4/fields/toggle_field.py b/pyface/ui/qt/fields/toggle_field.py similarity index 100% rename from pyface/ui/qt4/fields/toggle_field.py rename to pyface/ui/qt/fields/toggle_field.py diff --git a/pyface/ui/qt4/file_dialog.py b/pyface/ui/qt/file_dialog.py similarity index 100% rename from pyface/ui/qt4/file_dialog.py rename to pyface/ui/qt/file_dialog.py diff --git a/pyface/ui/qt4/font.py b/pyface/ui/qt/font.py similarity index 100% rename from pyface/ui/qt4/font.py rename to pyface/ui/qt/font.py diff --git a/pyface/ui/qt4/font_dialog.py b/pyface/ui/qt/font_dialog.py similarity index 100% rename from pyface/ui/qt4/font_dialog.py rename to pyface/ui/qt/font_dialog.py diff --git a/pyface/ui/qt4/gui.py b/pyface/ui/qt/gui.py similarity index 100% rename from pyface/ui/qt4/gui.py rename to pyface/ui/qt/gui.py diff --git a/pyface/ui/qt4/heading_text.py b/pyface/ui/qt/heading_text.py similarity index 100% rename from pyface/ui/qt4/heading_text.py rename to pyface/ui/qt/heading_text.py diff --git a/pyface/ui/qt4/image_cache.py b/pyface/ui/qt/image_cache.py similarity index 100% rename from pyface/ui/qt4/image_cache.py rename to pyface/ui/qt/image_cache.py diff --git a/pyface/ui/qt4/image_resource.py b/pyface/ui/qt/image_resource.py similarity index 100% rename from pyface/ui/qt4/image_resource.py rename to pyface/ui/qt/image_resource.py diff --git a/pyface/ui/qt4/images/application.png b/pyface/ui/qt/images/application.png similarity index 100% rename from pyface/ui/qt4/images/application.png rename to pyface/ui/qt/images/application.png diff --git a/pyface/ui/qt4/images/heading_level_1.png b/pyface/ui/qt/images/heading_level_1.png similarity index 100% rename from pyface/ui/qt4/images/heading_level_1.png rename to pyface/ui/qt/images/heading_level_1.png diff --git a/pyface/ui/qt4/init.py b/pyface/ui/qt/init.py similarity index 96% rename from pyface/ui/qt4/init.py rename to pyface/ui/qt/init.py index a5f024ba7..0389d37dc 100644 --- a/pyface/ui/qt4/init.py +++ b/pyface/ui/qt/init.py @@ -37,7 +37,7 @@ # create the toolkit object -toolkit_object = Toolkit("pyface", "qt", "pyface.ui.qt4") +toolkit_object = Toolkit("pyface", "qt", "pyface.ui.qt") # ensure that Traits has a UI handler appropriate for the toolkit. diff --git a/pyface/ui/qt4/layered_panel.py b/pyface/ui/qt/layered_panel.py similarity index 100% rename from pyface/ui/qt4/layered_panel.py rename to pyface/ui/qt/layered_panel.py diff --git a/pyface/ui/qt4/layout_widget.py b/pyface/ui/qt/layout_widget.py similarity index 99% rename from pyface/ui/qt4/layout_widget.py rename to pyface/ui/qt/layout_widget.py index edc0a3675..02ab6831f 100644 --- a/pyface/ui/qt4/layout_widget.py +++ b/pyface/ui/qt/layout_widget.py @@ -15,7 +15,7 @@ from pyface.qt import QtGui from pyface.i_layout_item import DEFAULT_SIZE from pyface.i_layout_widget import ILayoutWidget, MLayoutWidget -from pyface.ui.qt4.widget import Widget +from pyface.ui.qt.widget import Widget #: Maximum widget size (some versions of PyQt don't export it) diff --git a/pyface/ui/qt4/message_dialog.py b/pyface/ui/qt/message_dialog.py similarity index 100% rename from pyface/ui/qt4/message_dialog.py rename to pyface/ui/qt/message_dialog.py diff --git a/pyface/ui/qt4/mimedata.py b/pyface/ui/qt/mimedata.py similarity index 100% rename from pyface/ui/qt4/mimedata.py rename to pyface/ui/qt/mimedata.py diff --git a/pyface/ui/qt4/pil_image.py b/pyface/ui/qt/pil_image.py similarity index 96% rename from pyface/ui/qt4/pil_image.py rename to pyface/ui/qt/pil_image.py index b6383e5dd..46686ff59 100644 --- a/pyface/ui/qt4/pil_image.py +++ b/pyface/ui/qt/pil_image.py @@ -11,7 +11,7 @@ from traits.api import provides from pyface.i_pil_image import IPILImage, MPILImage -from pyface.ui.qt4.util.image_helpers import resize_image +from pyface.ui.qt.util.image_helpers import resize_image @provides(IPILImage) diff --git a/pyface/ui/qt4/progress_dialog.py b/pyface/ui/qt/progress_dialog.py similarity index 100% rename from pyface/ui/qt4/progress_dialog.py rename to pyface/ui/qt/progress_dialog.py diff --git a/pyface/ui/qt4/python_editor.py b/pyface/ui/qt/python_editor.py similarity index 98% rename from pyface/ui/qt4/python_editor.py rename to pyface/ui/qt/python_editor.py index 042bc2e28..25c98e717 100644 --- a/pyface/ui/qt4/python_editor.py +++ b/pyface/ui/qt/python_editor.py @@ -19,8 +19,8 @@ from pyface.i_python_editor import IPythonEditor, MPythonEditor from pyface.key_pressed_event import KeyPressedEvent -from pyface.ui.qt4.layout_widget import LayoutWidget -from pyface.ui.qt4.code_editor.code_widget import AdvancedCodeWidget +from pyface.ui.qt.layout_widget import LayoutWidget +from pyface.ui.qt.code_editor.code_widget import AdvancedCodeWidget @provides(IPythonEditor) diff --git a/pyface/ui/qt4/python_shell.py b/pyface/ui/qt/python_shell.py similarity index 100% rename from pyface/ui/qt4/python_shell.py rename to pyface/ui/qt/python_shell.py diff --git a/pyface/ui/qt4/resource_manager.py b/pyface/ui/qt/resource_manager.py similarity index 100% rename from pyface/ui/qt4/resource_manager.py rename to pyface/ui/qt/resource_manager.py diff --git a/pyface/ui/qt4/single_choice_dialog.py b/pyface/ui/qt/single_choice_dialog.py similarity index 100% rename from pyface/ui/qt4/single_choice_dialog.py rename to pyface/ui/qt/single_choice_dialog.py diff --git a/pyface/ui/qt4/splash_screen.py b/pyface/ui/qt/splash_screen.py similarity index 100% rename from pyface/ui/qt4/splash_screen.py rename to pyface/ui/qt/splash_screen.py diff --git a/pyface/ui/qt4/split_widget.py b/pyface/ui/qt/split_widget.py similarity index 100% rename from pyface/ui/qt4/split_widget.py rename to pyface/ui/qt/split_widget.py diff --git a/pyface/ui/qt4/system_metrics.py b/pyface/ui/qt/system_metrics.py similarity index 98% rename from pyface/ui/qt4/system_metrics.py rename to pyface/ui/qt/system_metrics.py index 6ac01aa01..4f230453a 100644 --- a/pyface/ui/qt4/system_metrics.py +++ b/pyface/ui/qt/system_metrics.py @@ -41,7 +41,7 @@ class SystemMetrics(MSystemMetrics, HasTraits): def _get_screen_width(self): # QDesktopWidget.screenGeometry() is deprecated and Qt docs - # suggest using screens() instead, but screens in not available in qt4 + # suggest using screens() instead, but screens in not available in qt # see issue: enthought/pyface#721 if not is_qt4: return QtGui.QApplication.instance().screens()[0].availableGeometry().width() @@ -50,7 +50,7 @@ def _get_screen_width(self): def _get_screen_height(self): # QDesktopWidget.screenGeometry(int screen) is deprecated and Qt docs - # suggest using screens() instead, but screens in not available in qt4 + # suggest using screens() instead, but screens in not available in qt # see issue: enthought/pyface#721 if not is_qt4: return ( diff --git a/pyface/ui/qt4/tasks/__init__.py b/pyface/ui/qt/tasks/__init__.py similarity index 100% rename from pyface/ui/qt4/tasks/__init__.py rename to pyface/ui/qt/tasks/__init__.py diff --git a/pyface/ui/qt4/tasks/advanced_editor_area_pane.py b/pyface/ui/qt/tasks/advanced_editor_area_pane.py similarity index 100% rename from pyface/ui/qt4/tasks/advanced_editor_area_pane.py rename to pyface/ui/qt/tasks/advanced_editor_area_pane.py diff --git a/pyface/ui/qt4/tasks/dock_pane.py b/pyface/ui/qt/tasks/dock_pane.py similarity index 100% rename from pyface/ui/qt4/tasks/dock_pane.py rename to pyface/ui/qt/tasks/dock_pane.py diff --git a/pyface/ui/qt4/tasks/editor.py b/pyface/ui/qt/tasks/editor.py similarity index 100% rename from pyface/ui/qt4/tasks/editor.py rename to pyface/ui/qt/tasks/editor.py diff --git a/pyface/ui/qt4/tasks/editor_area_pane.py b/pyface/ui/qt/tasks/editor_area_pane.py similarity index 100% rename from pyface/ui/qt4/tasks/editor_area_pane.py rename to pyface/ui/qt/tasks/editor_area_pane.py diff --git a/pyface/ui/qt4/tasks/main_window_layout.py b/pyface/ui/qt/tasks/main_window_layout.py similarity index 100% rename from pyface/ui/qt4/tasks/main_window_layout.py rename to pyface/ui/qt/tasks/main_window_layout.py diff --git a/pyface/ui/qt4/tasks/split_editor_area_pane.py b/pyface/ui/qt/tasks/split_editor_area_pane.py similarity index 100% rename from pyface/ui/qt4/tasks/split_editor_area_pane.py rename to pyface/ui/qt/tasks/split_editor_area_pane.py diff --git a/pyface/ui/qt4/tasks/task_pane.py b/pyface/ui/qt/tasks/task_pane.py similarity index 100% rename from pyface/ui/qt4/tasks/task_pane.py rename to pyface/ui/qt/tasks/task_pane.py diff --git a/pyface/ui/qt4/tasks/task_window_backend.py b/pyface/ui/qt/tasks/task_window_backend.py similarity index 100% rename from pyface/ui/qt4/tasks/task_window_backend.py rename to pyface/ui/qt/tasks/task_window_backend.py diff --git a/pyface/ui/qt4/tasks/tests/__init__.py b/pyface/ui/qt/tasks/tests/__init__.py similarity index 100% rename from pyface/ui/qt4/tasks/tests/__init__.py rename to pyface/ui/qt/tasks/tests/__init__.py diff --git a/pyface/ui/qt4/tasks/tests/test_dock_pane.py b/pyface/ui/qt/tasks/tests/test_dock_pane.py similarity index 100% rename from pyface/ui/qt4/tasks/tests/test_dock_pane.py rename to pyface/ui/qt/tasks/tests/test_dock_pane.py diff --git a/pyface/ui/qt4/tasks/tests/test_main_window_layout.py b/pyface/ui/qt/tasks/tests/test_main_window_layout.py similarity index 97% rename from pyface/ui/qt4/tasks/tests/test_main_window_layout.py rename to pyface/ui/qt/tasks/tests/test_main_window_layout.py index 09378932f..4125ffb7e 100644 --- a/pyface/ui/qt4/tasks/tests/test_main_window_layout.py +++ b/pyface/ui/qt/tasks/tests/test_main_window_layout.py @@ -17,7 +17,7 @@ try: from pyface.qt import QtGui - from pyface.ui.qt4.tasks.main_window_layout import MainWindowLayout + from pyface.ui.qt.tasks.main_window_layout import MainWindowLayout except ImportError: if toolkit_object.toolkit.startswith("qt"): raise @@ -44,7 +44,7 @@ def create_dummy_dock_widget(parent): @unittest.skipIf( - toolkit_object.toolkit != "qt4", + toolkit_object.toolkit != "qt", "This test targets Qt specific MainWindowLayout. " "Current toolkit is not Qt." ) diff --git a/pyface/ui/qt4/tasks/tests/test_split_editor_area_pane.py b/pyface/ui/qt/tasks/tests/test_split_editor_area_pane.py similarity index 99% rename from pyface/ui/qt4/tasks/tests/test_split_editor_area_pane.py rename to pyface/ui/qt/tasks/tests/test_split_editor_area_pane.py index 57ea478af..ba0bf1be5 100644 --- a/pyface/ui/qt4/tasks/tests/test_split_editor_area_pane.py +++ b/pyface/ui/qt/tasks/tests/test_split_editor_area_pane.py @@ -29,7 +29,7 @@ TaskWindow, ) from pyface.util.guisupport import get_app_qt4 -from pyface.ui.qt4.util.testing import event_loop +from pyface.ui.qt.util.testing import event_loop from pyface.util.gui_test_assistant import GuiTestAssistant diff --git a/pyface/ui/qt4/tasks/util.py b/pyface/ui/qt/tasks/util.py similarity index 100% rename from pyface/ui/qt4/tasks/util.py rename to pyface/ui/qt/tasks/util.py diff --git a/pyface/ui/qt4/tests/__init__.py b/pyface/ui/qt/tests/__init__.py similarity index 100% rename from pyface/ui/qt4/tests/__init__.py rename to pyface/ui/qt/tests/__init__.py diff --git a/pyface/ui/qt4/tests/bad_import.py b/pyface/ui/qt/tests/bad_import.py similarity index 100% rename from pyface/ui/qt4/tests/bad_import.py rename to pyface/ui/qt/tests/bad_import.py diff --git a/pyface/ui/qt/tests/good_package/__init__.py b/pyface/ui/qt/tests/good_package/__init__.py new file mode 100644 index 000000000..fe97580a1 --- /dev/null +++ b/pyface/ui/qt/tests/good_package/__init__.py @@ -0,0 +1,13 @@ +# (C) Copyright 2005-2023 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! +# This is used to test what happens when there is an unrelated import error +# when importing a toolkit object + +"""Dummy package for testing toolkit imports""" diff --git a/pyface/ui/qt/tests/good_package/good_import.py b/pyface/ui/qt/tests/good_package/good_import.py new file mode 100644 index 000000000..fab1fed0a --- /dev/null +++ b/pyface/ui/qt/tests/good_package/good_import.py @@ -0,0 +1,13 @@ +# (C) Copyright 2005-2023 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! +# This is used to test what happens when there is an unrelated import error +# when importing a toolkit object + +"""Dummy module for testing toolkit imports""" diff --git a/pyface/ui/qt/tests/good_package/has_bad_import.py b/pyface/ui/qt/tests/good_package/has_bad_import.py new file mode 100644 index 000000000..3c41d0a9f --- /dev/null +++ b/pyface/ui/qt/tests/good_package/has_bad_import.py @@ -0,0 +1,16 @@ +# (C) Copyright 2005-2023 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! +# This is used to test what happens when there is an unrelated import error +# when importing a toolkit object + +"""Dummy module for testing toolkit imports""" + +# raise a module not found error from somewhere else +import nonexistent_module # noqa F401 diff --git a/pyface/ui/qt4/tests/test_gui.py b/pyface/ui/qt/tests/test_gui.py similarity index 100% rename from pyface/ui/qt4/tests/test_gui.py rename to pyface/ui/qt/tests/test_gui.py diff --git a/pyface/ui/qt4/tests/test_message_dialog.py b/pyface/ui/qt/tests/test_message_dialog.py similarity index 97% rename from pyface/ui/qt4/tests/test_message_dialog.py rename to pyface/ui/qt/tests/test_message_dialog.py index f7e32990b..ac359024c 100644 --- a/pyface/ui/qt4/tests/test_message_dialog.py +++ b/pyface/ui/qt/tests/test_message_dialog.py @@ -17,7 +17,7 @@ from pyface.api import MessageDialog from pyface.qt import QtCore, QtGui -from pyface.ui.qt4.util.gui_test_assistant import GuiTestAssistant +from pyface.ui.qt.util.gui_test_assistant import GuiTestAssistant class TestMessageDialog(GuiTestAssistant, unittest.TestCase): diff --git a/pyface/ui/qt4/tests/test_mimedata.py b/pyface/ui/qt/tests/test_mimedata.py similarity index 100% rename from pyface/ui/qt4/tests/test_mimedata.py rename to pyface/ui/qt/tests/test_mimedata.py diff --git a/pyface/ui/qt4/tests/test_progress_dialog.py b/pyface/ui/qt/tests/test_progress_dialog.py similarity index 100% rename from pyface/ui/qt4/tests/test_progress_dialog.py rename to pyface/ui/qt/tests/test_progress_dialog.py diff --git a/pyface/ui/qt/tests/test_qt4_import_hooks.py b/pyface/ui/qt/tests/test_qt4_import_hooks.py new file mode 100644 index 000000000..5aa949f0e --- /dev/null +++ b/pyface/ui/qt/tests/test_qt4_import_hooks.py @@ -0,0 +1,236 @@ +# (C) Copyright 2005-2023 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + +from contextlib import contextmanager +import os +import sys +import unittest + +from traits.etsconfig.api import ETSConfig + +from pyface.ui import ShadowedModuleFinder + + +class TestQt4ImportHooks(unittest.TestCase): + + def test_qt4_import_no_hook(self): + with self._unload_modules([ + "pyface.ui.qt4", + "pyface.ui.qt4.tests", + "pyface.ui.qt4.tests.good_package", + "pyface.ui.qt4.tests.good_package.good_import", + ]): + with self._clean_meta_path(): + with self.assertWarns(FutureWarning): + with self.assertRaises(ModuleNotFoundError) as cm: + import pyface.ui.qt4.tests.good_package.good_import # noqa F401 + + self.assertEqual(cm.exception.name, "pyface.ui.qt4.tests") + + def test_qt4_import_with_hook(self): + with self._unload_modules([ + "pyface.ui.qt4", + "pyface.ui.qt4.tests", + "pyface.ui.qt4.tests.good_package", + "pyface.ui.qt4.tests.good_package.good_import", + ]): + with self._clean_meta_path(): + sys.meta_path.append(ShadowedModuleFinder()) + + with self.assertWarns(DeprecationWarning): + import pyface.ui.qt4.tests.good_package.good_import # noqa F401 + + import pyface.ui.qt.tests.good_package.good_import + + self.assertIs( + pyface.ui.qt4.tests.good_package.good_import, + pyface.ui.qt.tests.good_package.good_import, + ) + + self.assertIs( + pyface.ui.qt4.tests.good_package, + pyface.ui.qt.tests.good_package, + ) + + self.assertIs( + pyface.ui.qt4.tests, + pyface.ui.qt.tests, + ) + + def test_qt4_import_with_hook_no_module(self): + with self._unload_modules([ + "pyface.ui.qt4", + "pyface.ui.qt4.tests", + "pyface.ui.qt4.tests.good_package", + ]): + with self._clean_meta_path(): + sys.meta_path.append(ShadowedModuleFinder()) + + with self.assertWarns(DeprecationWarning): + with self.assertRaises(ModuleNotFoundError) as cm: + import pyface.ui.qt4.tests.good_package.no_module # noqa F401 + + self.assertEqual( + cm.exception.name, + "pyface.ui.qt4.tests.good_package.no_module", + ) + + def test_qt4_import_with_hook_module_has_bad_import(self): + with self._unload_modules([ + "pyface.ui.qt4", + "pyface.ui.qt4.tests", + "pyface.ui.qt4.tests.good_package", + ]): + with self._clean_meta_path(): + sys.meta_path.append(ShadowedModuleFinder()) + + with self.assertWarns(DeprecationWarning): + with self.assertRaises(ModuleNotFoundError) as cm: + import pyface.ui.qt4.tests.good_package.has_bad_import # noqa F401 + + self.assertEqual(cm.exception.name, "nonexistent_module") + + def test_qt4_import_with_ets_qt4_imports(self): + with self._unload_modules([ + "pyface.ui.qt4", + "pyface.ui.qt4.tests", + "pyface.ui.qt4.tests.good_package", + "pyface.ui.qt4.tests.good_package.good_import", + ]): + with self._clean_meta_path(): + with self._set_environment("ETS_QT4_IMPORTS", "1"): + + with self.assertWarns(DeprecationWarning): + import pyface.ui.qt4.tests.good_package.good_import + + import pyface.ui.qt.tests.good_package.good_import + + self.assertIs( + pyface.ui.qt4.tests.good_package.good_import, + pyface.ui.qt.tests.good_package.good_import, + ) + + def test_qt4_import_with_ets_toolkit_qt4(self): + with self._unload_modules([ + "pyface.ui.qt4", + "pyface.ui.qt4.tests", + "pyface.ui.qt4.tests.good_package", + "pyface.ui.qt4.tests.good_package.good_import", + ]): + with self._clean_meta_path(): + with self._set_environment("ETS_TOOLKIT", "qt4"): + + with self.assertWarns(DeprecationWarning): + import pyface.ui.qt4.tests.good_package.good_import + + import pyface.ui.qt.tests.good_package.good_import + + self.assertIs( + pyface.ui.qt4.tests.good_package.good_import, + pyface.ui.qt.tests.good_package.good_import, + ) + + def test_qt4_import_with_ets_toolkit_qt(self): + with self._unload_modules([ + "pyface.ui.qt4", + "pyface.ui.qt4.tests", + "pyface.ui.qt4.tests.good_package", + "pyface.ui.qt4.tests.good_package.good_import", + ]): + with self._clean_meta_path(): + with self._set_environment("ETS_TOOLKIT", "qt"): + with self.assertWarns(FutureWarning): + with self.assertRaises(ModuleNotFoundError): + import pyface.ui.qt4.tests.good_package.good_import # noqa F401 + + def test_qt4_import_with_etsconfig_toolkit_qt4(self): + with self._unload_modules([ + "pyface.ui.qt4", + "pyface.ui.qt4.tests", + "pyface.ui.qt4.tests.good_package", + "pyface.ui.qt4.tests.good_package.good_import", + ]): + with self._clean_meta_path(): + with self._set_etsconfig_toolkit("qt4"): + + with self.assertWarns(DeprecationWarning): + import pyface.ui.qt4.tests.good_package.good_import + + import pyface.ui.qt.tests.good_package.good_import + + self.assertIs( + pyface.ui.qt4.tests.good_package.good_import, + pyface.ui.qt.tests.good_package.good_import, + ) + + def test_qt4_import_with_etsconfig_toolkit_qt(self): + with self._unload_modules([ + "pyface.ui.qt4", + "pyface.ui.qt4.tests", + "pyface.ui.qt4.tests.good_package", + "pyface.ui.qt4.tests.good_package.good_import", + ]): + with self._clean_meta_path(): + with self._set_etsconfig_toolkit("qt"): + with self.assertWarns(FutureWarning): + with self.assertRaises(ModuleNotFoundError): + import pyface.ui.qt4.tests.good_package.good_import # noqa F401 + + @contextmanager + def _clean_meta_path(self): + """Temporarily remove ShadowedModuleFinder instances from sys.meta_path""" + old_meta_path = sys.meta_path[:] + sys.meta_path[:] = [ + finder for finder in sys.meta_path + if not isinstance(finder, ShadowedModuleFinder) + ] + try: + yield + finally: + sys.meta_path[:] = old_meta_path + + @contextmanager + def _unload_modules(self, names): + """Temporarily remove listed modules from sys.modules""" + old_modules = { + name: sys.modules.pop(name, None) + for name in names + } + try: + yield + finally: + for name, old_module in old_modules.items(): + if old_module: + sys.modules[name] = old_module + else: + sys.modules.pop(name, None) + + @contextmanager + def _set_environment(self, name, value): + """Temporarily set an environment variable to a value""" + old_value = os.environ.get(name, None) + os.environ[name] = value + try: + yield + finally: + if old_value is None: + del os.environ[name] + else: + os.environ[name] = old_value + + @contextmanager + def _set_etsconfig_toolkit(self, value): + """Temporarily set ETSConfig.toolkit to a value""" + old_value = ETSConfig._toolkit + ETSConfig._toolkit = value + try: + yield + finally: + ETSConfig._toolkit = old_value diff --git a/pyface/ui/qt4/tests/test_qt_imports.py b/pyface/ui/qt/tests/test_qt_imports.py similarity index 99% rename from pyface/ui/qt4/tests/test_qt_imports.py rename to pyface/ui/qt/tests/test_qt_imports.py index 07a89732b..3327c61a6 100644 --- a/pyface/ui/qt4/tests/test_qt_imports.py +++ b/pyface/ui/qt/tests/test_qt_imports.py @@ -14,6 +14,7 @@ class TestPyfaceQtImports(unittest.TestCase): + def test_imports(self): # check that all Qt API imports work import pyface.qt.QtCore # noqa: F401 diff --git a/pyface/ui/qt4/tests/test_window.py b/pyface/ui/qt/tests/test_window.py similarity index 95% rename from pyface/ui/qt4/tests/test_window.py rename to pyface/ui/qt/tests/test_window.py index bc172d0fc..4320d88c9 100644 --- a/pyface/ui/qt4/tests/test_window.py +++ b/pyface/ui/qt/tests/test_window.py @@ -16,7 +16,7 @@ from pyface.api import Window from pyface.qt import QtGui -from pyface.ui.qt4.util.gui_test_assistant import GuiTestAssistant +from pyface.ui.qt.util.gui_test_assistant import GuiTestAssistant class TestWindow(GuiTestAssistant, unittest.TestCase): diff --git a/pyface/ui/qt4/wizard/__init__.py b/pyface/ui/qt/timer/__init__.py similarity index 100% rename from pyface/ui/qt4/wizard/__init__.py rename to pyface/ui/qt/timer/__init__.py diff --git a/pyface/ui/qt4/timer/timer.py b/pyface/ui/qt/timer/timer.py similarity index 100% rename from pyface/ui/qt4/timer/timer.py rename to pyface/ui/qt/timer/timer.py diff --git a/pyface/ui/qt4/util/__init__.py b/pyface/ui/qt/util/__init__.py similarity index 100% rename from pyface/ui/qt4/util/__init__.py rename to pyface/ui/qt/util/__init__.py diff --git a/pyface/ui/qt4/util/datetime.py b/pyface/ui/qt/util/datetime.py similarity index 100% rename from pyface/ui/qt4/util/datetime.py rename to pyface/ui/qt/util/datetime.py diff --git a/pyface/ui/qt4/util/event_loop_helper.py b/pyface/ui/qt/util/event_loop_helper.py similarity index 100% rename from pyface/ui/qt4/util/event_loop_helper.py rename to pyface/ui/qt/util/event_loop_helper.py diff --git a/pyface/ui/qt4/util/gui_test_assistant.py b/pyface/ui/qt/util/gui_test_assistant.py similarity index 96% rename from pyface/ui/qt4/util/gui_test_assistant.py rename to pyface/ui/qt/util/gui_test_assistant.py index 1668a8a8a..12e3bdcec 100644 --- a/pyface/ui/qt4/util/gui_test_assistant.py +++ b/pyface/ui/qt/util/gui_test_assistant.py @@ -16,7 +16,7 @@ import unittest.mock as mock from pyface.qt.QtGui import QApplication -from pyface.ui.qt4.gui import GUI +from pyface.ui.qt.gui import GUI from traits.testing.api import UnittestTools from traits.testing.unittest_tools import ( _TraitsChangeCollector as TraitsChangeCollector, @@ -44,16 +44,22 @@ def setUp(self): except ImportError: self.traitsui_raise_patch = None else: - self.traitsui_raise_patch = mock.patch( - "traitsui.qt4.ui_base._StickyDialog.raise_" - ) + try: + import traitsui.qt # noqa: F401 + self.traitsui_raise_patch = mock.patch( + "traitsui.qt.ui_base._StickyDialog.raise_" + ) + except ModuleNotFoundError: + self.traitsui_raise_patch = mock.patch( + "traitsui.qt4.ui_base._StickyDialog.raise_" + ) self.traitsui_raise_patch.start() def new_activate(self): self.control.activateWindow() self.pyface_raise_patch = mock.patch( - "pyface.ui.qt4.window.Window.activate", + "pyface.ui.qt.window.Window.activate", new_callable=lambda: new_activate, ) self.pyface_raise_patch.start() diff --git a/pyface/ui/qt4/util/image_helpers.py b/pyface/ui/qt/util/image_helpers.py similarity index 100% rename from pyface/ui/qt4/util/image_helpers.py rename to pyface/ui/qt/util/image_helpers.py diff --git a/pyface/ui/qt4/util/modal_dialog_tester.py b/pyface/ui/qt/util/modal_dialog_tester.py similarity index 100% rename from pyface/ui/qt4/util/modal_dialog_tester.py rename to pyface/ui/qt/util/modal_dialog_tester.py diff --git a/pyface/ui/qt4/util/testing.py b/pyface/ui/qt/util/testing.py similarity index 100% rename from pyface/ui/qt4/util/testing.py rename to pyface/ui/qt/util/testing.py diff --git a/pyface/ui/qt4/util/tests/__init__.py b/pyface/ui/qt/util/tests/__init__.py similarity index 100% rename from pyface/ui/qt4/util/tests/__init__.py rename to pyface/ui/qt/util/tests/__init__.py diff --git a/pyface/ui/qt4/util/tests/test_datetime.py b/pyface/ui/qt/util/tests/test_datetime.py similarity index 100% rename from pyface/ui/qt4/util/tests/test_datetime.py rename to pyface/ui/qt/util/tests/test_datetime.py diff --git a/pyface/ui/qt4/util/tests/test_event_loop_helper.py b/pyface/ui/qt/util/tests/test_event_loop_helper.py similarity index 92% rename from pyface/ui/qt4/util/tests/test_event_loop_helper.py rename to pyface/ui/qt/util/tests/test_event_loop_helper.py index f3786f068..949ceff0e 100644 --- a/pyface/ui/qt4/util/tests/test_event_loop_helper.py +++ b/pyface/ui/qt/util/tests/test_event_loop_helper.py @@ -11,7 +11,7 @@ from traits.api import HasTraits, provides from pyface.i_gui import IGUI -from pyface.ui.qt4.util.event_loop_helper import EventLoopHelper +from pyface.ui.qt.util.event_loop_helper import EventLoopHelper @provides(IGUI) diff --git a/pyface/ui/qt4/util/tests/test_gui_test_assistant.py b/pyface/ui/qt/util/tests/test_gui_test_assistant.py similarity index 97% rename from pyface/ui/qt4/util/tests/test_gui_test_assistant.py rename to pyface/ui/qt/util/tests/test_gui_test_assistant.py index 2e04fa453..65d40cad8 100644 --- a/pyface/ui/qt4/util/tests/test_gui_test_assistant.py +++ b/pyface/ui/qt/util/tests/test_gui_test_assistant.py @@ -11,8 +11,8 @@ import unittest from pyface.timer.api import CallbackTimer -from pyface.ui.qt4.util.gui_test_assistant import GuiTestAssistant -from pyface.ui.qt4.util.event_loop_helper import ConditionTimeoutError +from pyface.ui.qt.util.gui_test_assistant import GuiTestAssistant +from pyface.ui.qt.util.event_loop_helper import ConditionTimeoutError from traits.api import Event, HasStrictTraits diff --git a/pyface/ui/qt4/util/tests/test_image_helpers.py b/pyface/ui/qt/util/tests/test_image_helpers.py similarity index 100% rename from pyface/ui/qt4/util/tests/test_image_helpers.py rename to pyface/ui/qt/util/tests/test_image_helpers.py diff --git a/pyface/ui/qt4/util/tests/test_modal_dialog_tester.py b/pyface/ui/qt/util/tests/test_modal_dialog_tester.py similarity index 96% rename from pyface/ui/qt4/util/tests/test_modal_dialog_tester.py rename to pyface/ui/qt/util/tests/test_modal_dialog_tester.py index 7cd275206..7b443effa 100644 --- a/pyface/ui/qt4/util/tests/test_modal_dialog_tester.py +++ b/pyface/ui/qt/util/tests/test_modal_dialog_tester.py @@ -18,9 +18,9 @@ from pyface.api import Dialog, MessageDialog, OK, CANCEL from traits.api import HasStrictTraits -from pyface.ui.qt4.util.testing import silence_output -from pyface.ui.qt4.util.gui_test_assistant import GuiTestAssistant -from pyface.ui.qt4.util.modal_dialog_tester import ModalDialogTester +from pyface.ui.qt.util.testing import silence_output +from pyface.ui.qt.util.gui_test_assistant import GuiTestAssistant +from pyface.ui.qt.util.modal_dialog_tester import ModalDialogTester from pyface.util.testing import skip_if_no_traitsui diff --git a/pyface/ui/qt4/widget.py b/pyface/ui/qt/widget.py similarity index 100% rename from pyface/ui/qt4/widget.py rename to pyface/ui/qt/widget.py diff --git a/pyface/ui/qt4/window.py b/pyface/ui/qt/window.py similarity index 100% rename from pyface/ui/qt4/window.py rename to pyface/ui/qt/window.py diff --git a/pyface/ui/qt4/workbench/__init__.py b/pyface/ui/qt/wizard/__init__.py similarity index 100% rename from pyface/ui/qt4/workbench/__init__.py rename to pyface/ui/qt/wizard/__init__.py diff --git a/pyface/ui/qt4/wizard/wizard.py b/pyface/ui/qt/wizard/wizard.py similarity index 100% rename from pyface/ui/qt4/wizard/wizard.py rename to pyface/ui/qt/wizard/wizard.py diff --git a/pyface/ui/qt4/wizard/wizard_page.py b/pyface/ui/qt/wizard/wizard_page.py similarity index 100% rename from pyface/ui/qt4/wizard/wizard_page.py rename to pyface/ui/qt/wizard/wizard_page.py diff --git a/pyface/ui/qt/workbench/__init__.py b/pyface/ui/qt/workbench/__init__.py new file mode 100644 index 000000000..aa2218ef6 --- /dev/null +++ b/pyface/ui/qt/workbench/__init__.py @@ -0,0 +1,9 @@ +# (C) Copyright 2005-2023 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! diff --git a/pyface/ui/qt4/workbench/editor.py b/pyface/ui/qt/workbench/editor.py similarity index 100% rename from pyface/ui/qt4/workbench/editor.py rename to pyface/ui/qt/workbench/editor.py diff --git a/pyface/ui/qt4/workbench/images/spinner.gif b/pyface/ui/qt/workbench/images/spinner.gif similarity index 100% rename from pyface/ui/qt4/workbench/images/spinner.gif rename to pyface/ui/qt/workbench/images/spinner.gif diff --git a/pyface/ui/qt4/workbench/split_tab_widget.py b/pyface/ui/qt/workbench/split_tab_widget.py similarity index 100% rename from pyface/ui/qt4/workbench/split_tab_widget.py rename to pyface/ui/qt/workbench/split_tab_widget.py diff --git a/pyface/ui/qt4/workbench/tests/__init__.py b/pyface/ui/qt/workbench/tests/__init__.py similarity index 100% rename from pyface/ui/qt4/workbench/tests/__init__.py rename to pyface/ui/qt/workbench/tests/__init__.py diff --git a/pyface/ui/qt4/workbench/tests/test_split_tab_widget.py b/pyface/ui/qt/workbench/tests/test_split_tab_widget.py similarity index 93% rename from pyface/ui/qt4/workbench/tests/test_split_tab_widget.py rename to pyface/ui/qt/workbench/tests/test_split_tab_widget.py index d302d8ab3..1fce9ebc7 100644 --- a/pyface/ui/qt4/workbench/tests/test_split_tab_widget.py +++ b/pyface/ui/qt/workbench/tests/test_split_tab_widget.py @@ -12,7 +12,7 @@ import unittest from pyface.qt import QtCore, QtGui -from pyface.ui.qt4.workbench.split_tab_widget import _DragableTabBar +from pyface.ui.qt.workbench.split_tab_widget import _DragableTabBar class TestSplitTabWidget(unittest.TestCase): diff --git a/pyface/ui/qt4/workbench/tests/test_workbench_window_layout.py b/pyface/ui/qt/workbench/tests/test_workbench_window_layout.py similarity index 90% rename from pyface/ui/qt4/workbench/tests/test_workbench_window_layout.py rename to pyface/ui/qt/workbench/tests/test_workbench_window_layout.py index bdd26b7f4..770c40926 100644 --- a/pyface/ui/qt4/workbench/tests/test_workbench_window_layout.py +++ b/pyface/ui/qt/workbench/tests/test_workbench_window_layout.py @@ -12,8 +12,8 @@ import unittest from unittest import mock -from pyface.ui.qt4.workbench.split_tab_widget import SplitTabWidget -from pyface.ui.qt4.workbench.workbench_window_layout import ( +from pyface.ui.qt.workbench.split_tab_widget import SplitTabWidget +from pyface.ui.qt.workbench.workbench_window_layout import ( WorkbenchWindowLayout, ) diff --git a/pyface/ui/qt4/workbench/view.py b/pyface/ui/qt/workbench/view.py similarity index 100% rename from pyface/ui/qt4/workbench/view.py rename to pyface/ui/qt/workbench/view.py diff --git a/pyface/ui/qt4/workbench/workbench_window_layout.py b/pyface/ui/qt/workbench/workbench_window_layout.py similarity index 100% rename from pyface/ui/qt4/workbench/workbench_window_layout.py rename to pyface/ui/qt/workbench/workbench_window_layout.py diff --git a/pyface/ui/qt4/__init__.py b/pyface/ui/qt4/__init__.py index aa2218ef6..d272a0490 100644 --- a/pyface/ui/qt4/__init__.py +++ b/pyface/ui/qt4/__init__.py @@ -7,3 +7,62 @@ # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! + + +import os +import sys +import warnings + +from traits.etsconfig.api import ETSConfig + +from pyface.ui import ShadowedModuleFinder + + +if any(isinstance(finder, ShadowedModuleFinder) for finder in sys.meta_path): + # Importing from pyface.ui.qt4.* is deprecated + # Already have loader registered. + warnings.warn( + """The pyface.ui.qt4.* modules have moved to pyface.ui.qt.* + +Backward compatibility import hooks are in place. They will be removed in a +future release of Pyface. +""", + DeprecationWarning, + stacklevel=2, + ) +elif ( + os.environ.get('ETS_QT4_IMPORTS', None) # environment says we want this + or os.environ.get('ETS_TOOLKIT', None) == "qt4" # environment says old qt4 + or ETSConfig.toolkit == "qt4" # the ETSConfig toolkit says old qt4 +): + # Register our loader. This is messing with global state that we do not own + # so we only do it when we have other global state telling us to. + + sys.meta_path.append(ShadowedModuleFinder()) + + # Importing from pyface.ui.qt4.* is deprecated + warnings.warn( + """The pyface.ui.qt4.* modules have moved to pyface.ui.qt.* + +Backward compatibility import hooks have been automatically applied. +They will be removed in a future release of Pyface. +""", + DeprecationWarning, + stacklevel=2, + ) +else: + # Don't import from this module, use a future warning as we want end-users + # of ETS apps to see the hints about environment variables. + warnings.warn( + """The pyface.ui.qt4.* modules have moved to pyface.ui.qt.*. + +Applications which require backwards compatibility can either: + +- set the ETS_QT4_IMPORTS environment variable +- set the ETS_TOOLKIT environment variable to "qt4", +- the ETSConfig.toolkit to "qt4" +- install pyface.ui.ShadowedModuleFinder() into sys.meta_path +""", + FutureWarning, + stacklevel=2, + ) diff --git a/pyface/util/guisupport.py b/pyface/util/guisupport.py index 6d4c0ebad..5bbc6e62f 100644 --- a/pyface/util/guisupport.py +++ b/pyface/util/guisupport.py @@ -119,12 +119,12 @@ def start_event_loop_wx(app=None): # ----------------------------------------------------------------------------- -# qt4 +# qt # ----------------------------------------------------------------------------- def get_app_qt4(*args, **kwargs): - """Create a new qt4 app or return an existing one.""" + """Create a new qt app or return an existing one.""" from pyface.qt import QtGui app = QtGui.QApplication.instance() @@ -136,18 +136,18 @@ def get_app_qt4(*args, **kwargs): def is_event_loop_running_qt4(app=None): - """Is the qt4 event loop running.""" + """Is the qt event loop running.""" if app is None: app = get_app_qt4([""]) if hasattr(app, "_in_event_loop"): return app._in_event_loop else: - # Does qt4 provide a other way to detect this? + # Does qt provide a other way to detect this? return False def start_event_loop_qt4(app=None): - """Start the qt4 event loop in a consistent manner.""" + """Start the qt event loop in a consistent manner.""" if app is None: app = get_app_qt4([""]) if not is_event_loop_running_qt4(app): diff --git a/pyproject.toml b/pyproject.toml index 3fb2f044f..b0687a01f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,8 +21,8 @@ version = '8.0.0.dev0' license = {file = "LICENSE.txt"} [project.entry-points.'pyface.toolkits'] -qt = 'pyface.ui.qt4.init:toolkit_object' -qt4 = 'pyface.ui.qt4.init:toolkit_object' +qt = 'pyface.ui.qt.init:toolkit_object' +qt4 = 'pyface.ui.qt.init:toolkit_object' wx = 'pyface.ui.wx.init:toolkit_object' null = 'pyface.ui.null.init:toolkit_object'