diff --git a/spyder/api/plugins/new_api.py b/spyder/api/plugins/new_api.py index 3df12509ffe..508bf0ac5c1 100644 --- a/spyder/api/plugins/new_api.py +++ b/spyder/api/plugins/new_api.py @@ -159,6 +159,13 @@ class SpyderPluginV2(QObject, SpyderActionMixin, SpyderConfigurationObserver, # disabled. Default: True CAN_BE_DISABLED = True + # Qt Web Widgets may be a heavy dependency for many packagers + # (e.g. conda-forge) + # We thus ask plugins to declare whether or not they need + # web widgets to enhance the distribution of Spyder to users + # https://github.com/spyder-ide/spyder/pull/22196#issuecomment-2189377043 + REQUIRE_WEB_WIDGETS = False + # --- API: Signals ------------------------------------------------------- # ------------------------------------------------------------------------ # Signals here are automatically connected by the Spyder main window and diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py index bde289b3973..b3346592e33 100644 --- a/spyder/app/mainwindow.py +++ b/spyder/app/mainwindow.py @@ -54,7 +54,10 @@ from qtpy import QtSvg # analysis:ignore # Avoid a bug in Qt: https://bugreports.qt.io/browse/QTBUG-46720 -from qtpy import QtWebEngineWidgets # analysis:ignore +try: + from qtpy.QtWebEngineWidgets import WEBENGINE +except ImportError: + WEBENGINE = False from qtawesome.iconic_font import FontError @@ -759,12 +762,6 @@ def setup(self): registry_external_plugins = {} for plugin in all_plugins.values(): plugin_name = plugin.NAME - # Disable panes that use web widgets (currently Help and Online - # Help) if the user asks for it. - # See spyder-ide/spyder#16518 - if self._cli_options.no_web_widgets: - if "help" in plugin_name: - continue plugin_main_attribute_name = ( self._INTERNAL_PLUGINS_MAPPING[plugin_name] if plugin_name in self._INTERNAL_PLUGINS_MAPPING @@ -791,6 +788,18 @@ def setup(self): if plugin_name in enabled_plugins: PluginClass = internal_plugins[plugin_name] if issubclass(PluginClass, SpyderPluginV2): + # Disable plugins that use web widgets (currently Help and + # Online Help) if the user asks for it. + # See spyder-ide/spyder#16518 + # The plugins that require QtWebengine must declare + # themselves as needing that dependency + # https://github.com/spyder-ide/spyder/pull/22196#issuecomment-2189377043 + if PluginClass.REQUIRE_WEB_WIDGETS and ( + not WEBENGINE or + self._cli_options.no_web_widgets + ): + continue + PLUGIN_REGISTRY.register_plugin(self, PluginClass, external=False) diff --git a/spyder/plugins/help/plugin.py b/spyder/plugins/help/plugin.py index c713db65a77..00f38f89b13 100644 --- a/spyder/plugins/help/plugin.py +++ b/spyder/plugins/help/plugin.py @@ -46,6 +46,7 @@ class Help(SpyderDockablePlugin): LOG_PATH = get_conf_path(CONF_SECTION) FONT_SIZE_DELTA = DEFAULT_SMALL_DELTA DISABLE_ACTIONS_WHEN_HIDDEN = False + REQUIRE_WEB_WIDGETS = True # Signals sig_focus_changed = Signal() # TODO: What triggers this? diff --git a/spyder/plugins/help/widgets.py b/spyder/plugins/help/widgets.py index 94e3c58af10..c8916629488 100644 --- a/spyder/plugins/help/widgets.py +++ b/spyder/plugins/help/widgets.py @@ -17,7 +17,6 @@ from qtpy import PYQT5 from qtpy.QtCore import Qt, QUrl, Signal, Slot, QPoint from qtpy.QtGui import QColor -from qtpy.QtWebEngineWidgets import WEBENGINE, QWebEnginePage from qtpy.QtWidgets import (QActionGroup, QComboBox, QLabel, QLineEdit, QMessageBox, QSizePolicy, QStackedLayout, QVBoxLayout, QWidget) @@ -36,11 +35,16 @@ from spyder.utils.image_path_manager import get_image_path from spyder.utils.palette import QStylePalette from spyder.utils.qthelpers import start_file -from spyder.widgets.browser import FrameWebView from spyder.widgets.comboboxes import EditableComboBox from spyder.widgets.findreplace import FindReplace from spyder.widgets.simplecodeeditor import SimpleCodeEditor +# In case WebEngine is not available (e.g. in Conda-forge) +try: + from qtpy.QtWebEngineWidgets import WEBENGINE +except ImportError: + WEBENGINE = False + # --- Constants # ---------------------------------------------------------------------------- @@ -157,6 +161,9 @@ def __init__(self, parent): QWidget.__init__(self, parent) SpyderWidgetMixin.__init__(self, class_parent=parent) + from qtpy.QtWebEngineWidgets import QWebEnginePage + from spyder.widgets.browser import FrameWebView + self.webview = FrameWebView(self) self.webview.setup() diff --git a/spyder/plugins/ipythonconsole/widgets/main_widget.py b/spyder/plugins/ipythonconsole/widgets/main_widget.py index 238ca4e6ecf..eaeb9aa46c4 100644 --- a/spyder/plugins/ipythonconsole/widgets/main_widget.py +++ b/spyder/plugins/ipythonconsole/widgets/main_widget.py @@ -23,7 +23,6 @@ from qtconsole.client import QtKernelClient from qtpy.QtCore import Signal, Slot from qtpy.QtGui import QColor -from qtpy.QtWebEngineWidgets import WEBENGINE from qtpy.QtWidgets import ( QApplication, QHBoxLayout, QLabel, QMessageBox, QVBoxLayout, QWidget) from traitlets.config.loader import Config, load_pyconfig_files @@ -46,11 +45,16 @@ from spyder.utils import encoding, programs, sourcecode from spyder.utils.misc import get_error_match, remove_backslashes from spyder.utils.palette import QStylePalette -from spyder.widgets.browser import FrameWebView from spyder.widgets.findreplace import FindReplace from spyder.widgets.tabs import Tabs from spyder.plugins.ipythonconsole.utils.stdfile import StdFile +# In case WebEngine is not available (e.g. in Conda-forge) +try: + from qtpy.QtWebEngineWidgets import WEBENGINE +except ImportError: + WEBENGINE = False + # Logging logger = logging.getLogger(__name__) @@ -308,7 +312,7 @@ def __init__(self, name=None, plugin=None, parent=None): self.enable_infowidget = True if plugin: cli_options = plugin.get_command_line_options() - if cli_options.no_web_widgets: + if cli_options.no_web_widgets or not WEBENGINE: self.enable_infowidget = False # Attrs for testing @@ -351,6 +355,8 @@ def __init__(self, name=None, plugin=None, parent=None): # Info widget if self.enable_infowidget: + from spyder.widgets.browser import FrameWebView + self.infowidget = FrameWebView(self) if WEBENGINE: self.infowidget.page().setBackgroundColor( diff --git a/spyder/plugins/onlinehelp/plugin.py b/spyder/plugins/onlinehelp/plugin.py index b27fcf4db9c..90d0d27ef06 100644 --- a/spyder/plugins/onlinehelp/plugin.py +++ b/spyder/plugins/onlinehelp/plugin.py @@ -32,6 +32,7 @@ class OnlineHelp(SpyderDockablePlugin): CONF_FILE = False WIDGET_CLASS = PydocBrowser LOG_PATH = get_conf_path(NAME) + REQUIRE_WEB_WIDGETS = True # --- Signals # ------------------------------------------------------------------------ diff --git a/spyder/plugins/onlinehelp/widgets.py b/spyder/plugins/onlinehelp/widgets.py index ba5d9bb3e37..7dc0edfb14a 100644 --- a/spyder/plugins/onlinehelp/widgets.py +++ b/spyder/plugins/onlinehelp/widgets.py @@ -17,17 +17,21 @@ # Third party imports from qtpy.QtCore import Qt, QThread, QUrl, Signal, Slot from qtpy.QtGui import QCursor -from qtpy.QtWebEngineWidgets import WEBENGINE from qtpy.QtWidgets import QApplication, QLabel, QVBoxLayout # Local imports from spyder.api.translations import _ from spyder.api.widgets.main_widget import PluginMainWidget from spyder.plugins.onlinehelp.pydoc_patch import _start_server, _url_handler -from spyder.widgets.browser import FrameWebView, WebViewActions from spyder.widgets.comboboxes import UrlComboBox from spyder.widgets.findreplace import FindReplace +# In case WebEngine is not available (e.g. in Conda-forge) +try: + from qtpy.QtWebEngineWidgets import WEBENGINE +except ImportError: + WEBENGINE = False + # --- Constants # ---------------------------------------------------------------------------- @@ -139,6 +143,8 @@ class PydocBrowser(PluginMainWidget): """ def __init__(self, name=None, plugin=None, parent=None): + from spyder.widgets.browser import FrameWebView + super().__init__(name, plugin, parent=parent) self._is_running = False @@ -194,6 +200,8 @@ def get_focus_widget(self): return self.url_combo def setup(self): + from spyder.widgets.browser import WebViewActions + # Actions home_action = self.create_action( PydocBrowserActions.Home, @@ -244,6 +252,8 @@ def setup(self): self.sig_toggle_view_changed.connect(self.initialize) def update_actions(self): + from spyder.widgets.browser import WebViewActions + stop_action = self.get_action(WebViewActions.Stop) refresh_action = self.get_action(WebViewActions.Refresh) @@ -272,6 +282,8 @@ def _continue_initialization(self): def _handle_url_combo_activation(self): """Load URL from combo box first item.""" + from spyder.widgets.browser import WebViewActions + if not self._is_running: text = str(self.url_combo.currentText()) self.go_to(self.text_to_url(text))