Source code for spyder.api.widgets.mixins
# -----------------------------------------------------------------------------
# Copyright (c) 2013-2016 Colin Duquesnoy and others (see pyqode/AUTHORS.rst)
# Copyright (c) 2016- Spyder Project Contributors (see AUTHORS.txt)
#
# Released under the terms of the MIT License
# (see NOTICE.txt in the Spyder root directory for details)
# -----------------------------------------------------------------------------
"""
Mixin classes for the Spyder widget API.
"""
from __future__ import annotations
# Standard library imports
from collections import OrderedDict
from typing import Any, TYPE_CHECKING
# Third party imports
from qtpy.QtCore import QPoint, Qt
from qtpy.QtGui import QImage, QPainter, QPixmap
from qtpy.QtSvg import QSvgRenderer
from qtpy.QtWidgets import QApplication, QSizePolicy, QWidget
# Local imports
from spyder.api.config.mixins import (
SpyderConfigurationAccessor,
SpyderConfigurationObserver
)
from spyder.api.exceptions import SpyderAPIError
from spyder.api.shortcuts import SpyderShortcutsMixin
from spyder.api.widgets.menus import SpyderMenu
from spyder.api.widgets.toolbars import SpyderToolbar
from spyder.config.manager import CONF
from spyder.utils.icon_manager import ima
from spyder.utils.image_path_manager import get_image_path
from spyder.utils.qthelpers import create_action, create_toolbutton
from spyder.utils.registries import (
ACTION_REGISTRY,
MENU_REGISTRY,
TOOLBAR_REGISTRY,
TOOLBUTTON_REGISTRY,
)
from spyder.utils.stylesheet import PANES_TOOLBAR_STYLESHEET
if TYPE_CHECKING:
from collections.abc import Callable
from qtpy.QtCore import QObject
from qtpy.QtGui import QIcon
from qtpy.QtWidgets import QAction, QMainWindow, QToolButton
import spyder.utils.qthelpers # For SpyderAction
import spyder.config.types # For ConfigurationKey
[docs]
class SpyderToolButtonMixin:
"""
Provide methods to create, add and get toolbar buttons.
"""
[docs]
def create_toolbutton(
self,
name: str,
text: str | None = None,
icon: QIcon | str | None = None,
tip: str | None = None,
toggled: Callable[[Any], None] | bool | None = None,
triggered: Callable[[], None] | None = None,
autoraise: bool = True,
text_beside_icon: bool = False,
section: str | None = None,
option: spyder.config.types.ConfigurationKey | None = None,
register: bool = True,
) -> QToolButton:
"""
Create a new Spyder toolbar button.
Parameters
----------
name: str
Unique identifier for the toolbutton.
text: str | None, optional
Localized text for the toolbutton, displayed in the interface.
If ``None``, the default, no text is shown.
icon: QIcon | str | None, optional
Icon name or object to use in the toolbutton.
If ``None``, the default, no icon is shown.
tip: str | None, optional
Tooltip text for the toolbutton.
If ``None`` (the default), no tooltip is shown.
toggled: Callable[[Any], None] | bool | None, optional
If ``None`` (default) then the button doesn't act like a checkbox.
If ``True``, then the button modifies the configuration ``option``
on the ``section`` specified (or the default
:attr:`~spyder.api.plugins.SpyderPluginV2.CONF_SECTION` if
``section`` is not set). Otherwise, must be a callable,
called when toggling this button. One of ``toggled`` and
``triggered`` must not be ``None``.
triggered: Callable[[], None] | None, optional
If a callable, will be called when triggering this button.
Otherwise, if ``None`` (the default), this will not be a
triggered button and ``toggled`` must be non-``None`` instead.
autoraise : bool, optional
If ``True`` (the default), the button will only draw a 3D frame
when hovered over. If ``False``, will draw the button frame
all the time.
text_beside_icon : bool, optional
If ``True``, the button text will be displayed beside the icon.
If ``False`` (the default), will only show the icon.
section: str | None, optional
Name of the configuration section whose ``option`` is going to be
modified. If ``None`` (the default) and ``option`` is not ``None``,
then it defaults to the class'
:attr:`~spyder.api.plugins.SpyderPluginV2.CONF_SECTION` attribute.
option: spyder.config.types.ConfigurationKey | None, optional
Name of the configuration option whose value is reflected and
affected by the button. If ``None`` (the default), no option
is associated with this button, e.g. for buttons that are
``triggered`` rather than ``toggled``.
register_action: bool, optional
If ``True`` (default) the action will be registered and searchable.
If ``False``, the action will be created but not registered.
Returns
-------
QToolButton
The toolbar button object that was created.
"""
if toggled and not callable(toggled):
toggled = lambda value: None
if toggled is not None:
if section is None and option is not None:
section = self.CONF_SECTION
toolbutton = create_toolbutton(
self,
text=text,
shortcut=None,
icon=icon,
tip=tip,
toggled=toggled,
triggered=triggered,
autoraise=autoraise,
text_beside_icon=text_beside_icon,
section=section,
option=option,
id_=name,
plugin=self.PLUGIN_NAME,
context_name=self.CONTEXT_NAME,
register_toolbutton=register,
)
toolbutton.name = name
if toggled:
if section is not None and option is not None:
value = CONF.get(section, option)
toolbutton.setChecked(value)
return toolbutton
[docs]
def get_toolbutton(
self,
name: str,
context: str | None = None,
plugin: str | None = None,
) -> QToolButton:
"""
Retrieve a toolbar button by name, context and plugin.
Parameters
----------
name: str
Identifier of the toolbutton to retrieve.
context: str | None, optional
Context identifier under which the toolbutton was stored.
If ``None``, the default, then the
:attr:`~SpyderWidgetMixin.CONTEXT_NAME` attribute is used instead.
plugin: str | None, optional
Identifier of the plugin in which the toolbutton was defined.
If ``None``, the default, then the
:attr:`~SpyderWidgetMixin.PLUGIN_NAME` attribute is used instead.
Returns
-------
QToolButton
The corresponding toolbutton widget stored under the given
``name``, ``context`` and ``plugin``.
Raises
------
KeyError
If the combination of ``name``, ``context`` and ``plugin`` keys
does not exist in the toolbutton registry.
"""
plugin = self.PLUGIN_NAME if plugin is None else plugin
context = self.CONTEXT_NAME if context is None else context
return TOOLBUTTON_REGISTRY.get_reference(name, plugin, context)
[docs]
def get_toolbuttons(
self, context: str | None = None, plugin: str | None = None
) -> dict[str, QToolButton]:
"""
Return all toolbar buttons defined by a context on a given plugin.
Parameters
----------
context: str | None, optional
Context identifier under which the toolbuttons were stored.
If ``None``, the default, then the
:attr:`~SpyderWidgetMixin.CONTEXT_NAME` attribute is used instead.
plugin: str | None, optional
Identifier of the plugin in which the toolbuttons were defined.
If ``None``, the default, then the
:attr:`~SpyderWidgetMixin.PLUGIN_NAME` attribute is used instead.
Returns
-------
dict[str, QToolButton]
A dictionary that maps identifier name keys to their corresponding
toolbutton objects.
"""
plugin = self.PLUGIN_NAME if plugin is None else plugin
context = self.CONTEXT_NAME if context is None else context
return TOOLBUTTON_REGISTRY.get_references(plugin, context)
[docs]
class SpyderToolbarMixin:
"""
Provide methods to create, add and get toolbars.
"""
[docs]
def add_item_to_toolbar(
self,
action_or_widget: spyder.utils.qthelpers.SpyderAction | QWidget,
toolbar: SpyderToolbar,
section: str | None = None,
before: str | None = None,
before_section: str | None = None,
) -> None:
"""
Add the given action or widget to this toolbar.
Parameters
----------
action_or_widget : spyder.utils.qthelpers.SpyderAction | QWidget
The action or widget to add to the toolbar.
toolbar : SpyderToolbar
The toolbar object to add ``action_or_widget`` to.
section: str | None, optional
The section id in which to insert the ``action_or_widget``,
or ``None`` (default) for no section.
before: str | None, optional
Make the ``action_or_widget`` appear before the action with the
identifier ``before``. If ``None`` (default), add it to the end.
If ``before`` is not ``None``, ``before_section`` will be ignored.
before_section : str | None, optional
Make the ``section`` appear prior to ``before_section``.
If ``None`` (the default), add the section to the end.
If you provide a ``before`` action, the new action will be placed
before this one, so the section option will be ignored, since the
action will now be placed in the same section as the ``before``
action.
Returns
-------
None
"""
toolbar.add_item(
action_or_widget,
section=section,
before=before,
before_section=before_section,
)
[docs]
def create_stretcher(self, id_: str | None = None) -> QWidget:
"""
Create a stretcher widget to be used in a Spyder toolbar.
Parameters
----------
id_ : str | None, optional
The identifier for the stretcher widget. If ``None`` (the default),
no id will be set.
Returns
-------
QWidget
The created stretcher widget.
"""
stretcher = QWidget(self)
stretcher.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
if id_ is not None:
stretcher.ID = id_
return stretcher
[docs]
def create_toolbar(
self, name: str, register: bool = True
) -> SpyderToolbar:
"""
Create a Spyder toolbar.
Parameters
----------
name: str
Unique string identifier name of the toolbar to create.
register: bool, optional
If ``True`` (default), register the toolbar in the global registry.
If ``False``, don't register it.
Returns
-------
SpyderToolbar
The created toolbar object.
"""
toolbar = SpyderToolbar(self, name)
toolbar.setStyleSheet(str(PANES_TOOLBAR_STYLESHEET))
if register:
TOOLBAR_REGISTRY.register_reference(
toolbar, name, self.PLUGIN_NAME, self.CONTEXT_NAME
)
return toolbar
[docs]
def get_toolbar(
self,
name: str,
context: str | None = None,
plugin: str | None = None,
) -> SpyderToolbar:
"""
Return toolbar by name, context and plugin.
Parameters
----------
name: str
Identifier of the toolbar to retrieve.
context: str | None, optional
Context identifier under which the toolbar was stored.
If ``None``, the default, then the
:attr:`~SpyderWidgetMixin.CONTEXT_NAME` attribute is used instead.
plugin: str | None, optional
Identifier of the plugin in which the toolbar was defined.
If ``None``, the default, then the
:attr:`~SpyderWidgetMixin.PLUGIN_NAME` attribute is used instead.
Returns
-------
SpyderToolbar
The corresponding toolbar widget stored under the given
``name``, ``context`` and ``plugin``.
Raises
------
KeyError
If the combination of ``name``, ``context`` and ``plugin`` keys
does not exist in the toolbar registry.
"""
plugin = self.PLUGIN_NAME if plugin is None else plugin
context = self.CONTEXT_NAME if context is None else context
return TOOLBAR_REGISTRY.get_reference(name, plugin, context)
[docs]
def get_toolbars(
self, context: str | None = None, plugin: str | None = None
) -> dict[str, SpyderToolbar]:
"""
Return all toolbars defined by a context on a given plugin.
Parameters
----------
context: str | None, optional
Context identifier under which the toolbars were stored.
If ``None``, the default, then the
:attr:`~SpyderWidgetMixin.CONTEXT_NAME` attribute is used instead.
plugin: str | None, optional
Identifier of the plugin in which the toolbars were defined.
If ``None``, the default, then the
:attr:`~SpyderWidgetMixin.PLUGIN_NAME` attribute is used instead.
Returns
-------
dict[str, SpyderToolbar]
A dictionary that maps identifier name keys to their corresponding
toolbar objects.
"""
plugin = self.PLUGIN_NAME if plugin is None else plugin
context = self.CONTEXT_NAME if context is None else context
return TOOLBAR_REGISTRY.get_references(plugin, context)
[docs]
class SpyderMenuMixin:
"""
Provide methods to create, add and get menus.
This mixin uses a custom menu object that allows for the creation of
sections in a simple way.
"""
def _create_menu(
self,
menu_id: str,
parent: QWidget | None = None,
title: str | None = None,
icon: QIcon | None = None,
reposition: bool = True,
register: bool = True,
min_width: int | None = None,
MenuClass: type[SpyderMenu] = SpyderMenu,
) -> SpyderMenu:
"""
Create a SpyderMenu or a subclass of it.
Notes
-----
* This method must only be used directly to generate a menu that is a
subclass of SpyderMenu.
* Refer to the documentation for `SpyderMenu` to learn about its args.
"""
if register:
menus = getattr(self, "_menus", None)
if menus is None:
self._menus = OrderedDict()
if menu_id in self._menus:
raise SpyderAPIError(
'Menu name "{}" already in use!'.format(menu_id)
)
menu = MenuClass(
parent=self if parent is None else parent,
menu_id=menu_id,
title=title,
min_width=min_width,
reposition=reposition,
)
if icon is not None:
menu.menuAction().setIconVisibleInMenu(True)
menu.setIcon(icon)
if register:
MENU_REGISTRY.register_reference(
menu, menu_id, self.PLUGIN_NAME, self.CONTEXT_NAME
)
self._menus[menu_id] = menu
return menu
[docs]
class SpyderActionMixin:
"""
Provide methods to create, add and get actions in a unified way.
This mixin uses a custom action object.
"""
def _update_action_state(self, action_name: str, value: bool) -> None:
"""
This allows to update the state of a togglable action without emitting
signals.
This is useful when the global application configuration changes and
we need to propagate the current state of an action based on those
changes
"""
self.blockSignals(True)
try:
self.get_action(action_name).setChecked(value)
except SpyderAPIError:
pass
self.blockSignals(False)
# Comment: The word `context` is used for two different concepts.
# On one side it refers to a Qt widget shortcut context and on the
# other it refers to a section of the configuration (or the widget
# name where it is applied).
[docs]
def create_action(
self,
name: str,
text: str,
icon: QIcon | str | None = None,
icon_text: str = "",
tip: str | None = None,
toggled: Callable[[Any], None] | bool | None = None,
triggered: Callable[[], None] | None = None,
data: Any | None = None,
shortcut: str | None = None,
shortcut_context: str | None = None,
context: Qt.ShortcutContext = Qt.WidgetWithChildrenShortcut,
initial: bool | None = None,
register_shortcut: bool = False,
section: str | None = None,
option: spyder.config.types.ConfigurationKey | None = None,
parent: QWidget | None = None,
register_action: bool = True,
overwrite: bool = False,
context_name: str | None = None,
menurole: QAction.MenuRole | None = None,
) -> spyder.utils.qthelpers.SpyderAction:
"""
Create a new Spyder-specialized :class:`QAction`.
.. note::
There is no need to set a shortcut when creating an action,
unless it's a fixed (non-customizable) one. Otherwise, set the
``register_shortcut`` argument to ``True``. If the shortcut is
found in the ``shortcuts`` config section of your plugin,
then it'll be assigned; if not, it'll be left blank for the
user to define it in Spyder :guilabel:`Preferences`.
Parameters
----------
name: str
Unique identifier for the action.
text: str
Localized text for the action, displayed in the interface.
icon: QIcon | str | None, optional
Icon name or object for the action when used in menus/toolbuttons.
If ``None``, the default, no icon is shown.
icon_text: str, optional
Descriptive text for the action's ``icon``. If a non-empty string
is passed, this will also disable the tooltip on this toolbutton
if part of a toolbar. If the empty string (``""``) (the default),
no icon text is set and the tooltip will not be disabled.
tip: str | None, optional
Tooltip text for the action when used in menus or toolbars.
If ``None`` (the default), no tooltip is shown.
toggled: Callable[[Any], None] | bool | None, optional
If ``None`` (default) then the action doesn't act like a checkbox.
If ``True``, then the action modifies the configuration ``option``
on the ``section`` specified (or the default
:attr:`~spyder.api.plugins.SpyderPluginV2.CONF_SECTION` if
``section`` is not set). Otherwise, must be a callable,
called when toggling this action. One of ``toggled`` and
``triggered`` must not be ``None``.
triggered: Callable[[], None] | None, optional
If a callable, will be called when triggering this action.
Otherwise, if ``None`` (the default), this will not be a
triggered action and ``toggled`` must be non-``None`` instead.
data: Any | None, optional
Arbitrary user data to be set on the action.
If ``None``, the default, no custom data is set.
shortcut: str | None, optional
A fixed (not configurable) keyboard shortcut to use for the action,
in cases where it is not practical for the user to configure the
shortcut. If ``None`` (the default), no fixed shortcut is set.
For a normal configurable shortcut, you instead need to set
``register_shortcut`` to ``True`` and list the shortcut
as one of the plugin's configuration options.
See :mod:`spyder.api.shortcuts` for more details.
shortcut_context: str | None, optional
The context name of the fixed ``shortcut``. ``None`` (the default)
for no context or no shortcut. Use ``"_"`` for global shortcuts (
i.e. that can be used anywhere in the application).
context: Qt.ShortcutContext, optional
Set the context object for the fixed shortcut.
By default, ``Qt.WidgetWithChildrenShortcut``.
initial: bool | None, optional
Set the initial state of a togglable action. This does not emit
the toggled signal. If ``None``, the default, no initial state.
register_shortcut: bool, optional
If ``True``, the main window will expose the shortcut in the
Spyder :guilabel:`Preferences`. If ``False``, the default,
the shortcut will not be registered.
section: str | None, optional
Name of the configuration section whose ``option`` is going to be
modified. If ``None`` (the default) and ``option`` is not ``None``,
then it defaults to the class'
:attr:`~spyder.api.plugins.SpyderPluginV2.CONF_SECTION` attribute.
option: spyder.config.types.ConfigurationKey | None, optional
Name of the configuration option whose value is reflected and
affected by the action. If ``None`` (the default), no option
is associated with this action, e.g. for actions that are
``triggered`` rather than ``toggled``.
parent: QWidget | None, optional
The parent of this widget. If ``None``, the default, uses
this instance object itself as the parent.
register_action: bool, optional
If ``True`` (default) the action will be registered and searchable.
If ``False``, the action will be created but not registered.
overwrite: bool, optional
If ``False`` (the default) and this action overwrites another
with the same ``name`` and ``context_name``, raise a warning.
Set to ``True`` to disable this warning if intentionally
overwriting another action.
context_name: str | None, optional
Name of the context that holds the action when registered.
The combination of ``name`` and ``context_name`` must be unique,
and registering an action with the same ``name`` & `context_name``
will raise a warning by default unless ``overwrite`` is ``True``.
menurole: QAction.MenuRole | None, optional
Menu role for the action. Only has an effect on macOS.
Returns
-------
spyder.utils.qthelpers.SpyderAction
The Spyder action object that was created
(a specialized :class:`QAction`).
Raises
------
SpyderAPIError
If both ``triggered`` and ``toggled`` are ``None``, as at least one
must be provided; or if both ``initial`` is ``True`` and
``triggered`` is not ``None``, as initial values can only apply to
``toggled`` actions.
"""
if triggered is None and toggled is None:
raise SpyderAPIError(
"Action must provide the toggled or triggered parameters!"
)
if parent is None:
parent = self
if toggled and not callable(toggled):
toggled = lambda value: None
if toggled is not None:
if section is None and option is not None:
section = self.CONF_SECTION
action = create_action(
parent,
text=text,
icon=icon,
tip=tip,
toggled=toggled,
triggered=triggered,
data=data,
context=context,
section=section,
shortcut=shortcut,
option=option,
id_=name,
plugin=self.PLUGIN_NAME,
context_name=(
self.CONTEXT_NAME if context_name is None else context_name
),
register_action=register_action,
overwrite=overwrite,
menurole=menurole,
)
action.name = name
if icon_text:
action.setIconText(icon_text)
action.text_beside_icon = bool(icon_text)
action.shortcut_context = shortcut_context
action.register_shortcut = register_shortcut
action.tip = tip
if initial is not None:
if toggled:
action.setChecked(initial)
elif triggered:
raise SpyderAPIError(
"Initial values can only apply to togglable actions!"
)
else:
if toggled:
if section is not None and option is not None:
value = CONF.get(section, option)
action.setChecked(value)
return action
[docs]
def get_action(
self,
name: str,
context: str | None = None,
plugin: str | None = None,
) -> spyder.utils.qthelpers.SpyderAction:
"""
Return an action by name, context and plugin.
Parameters
----------
name: str
Identifier of the action to retrieve.
context: str | None, optional
Context identifier under which the action was stored.
If ``None``, the default, then the
:attr:`~SpyderWidgetMixin.CONTEXT_NAME` attribute is used instead.
plugin: str | None, optional
Identifier of the plugin in which the action was defined.
If ``None``, the default, then the
:attr:`~SpyderWidgetMixin.PLUGIN_NAME` attribute is used instead.
Returns
-------
spyder.utils.qthelpers.SpyderAction
The corresponding action widget stored under the given
``name``, ``context`` and ``plugin``.
Raises
------
KeyError
If the combination of ``name``, ``context`` and ``plugin`` keys
does not exist in the action registry.
"""
plugin = self.PLUGIN_NAME if plugin is None else plugin
context = self.CONTEXT_NAME if context is None else context
return ACTION_REGISTRY.get_reference(name, plugin, context)
[docs]
def get_actions(
self, context: str | None = None, plugin: str | None = None
) -> dict[str, spyder.utils.qthelpers.SpyderAction]:
"""
Return all actions defined by a context on a given plugin.
Parameters
----------
context: str | None, optional
Context identifier under which the actions were stored.
If ``None``, the default, then the
:attr:`~SpyderWidgetMixin.CONTEXT_NAME` attribute is used instead.
plugin: str | None, optional
Identifier of the plugin in which the actions were defined.
If ``None``, the default, then the
:attr:`~SpyderWidgetMixin.PLUGIN_NAME` attribute is used instead.
Returns
-------
dict[str, spyder.utils.qthelpers.SpyderAction]
A dictionary that maps identifier name keys to their corresponding
action objects.
Notes
-----
* Actions should only be created once. Creating new actions on menu
popup is *highly* discouraged.
* Actions can be created directly on a
:class:`~spyder.api.widgets.main_widget.PluginMainWidget` or
:class:`~spyder.api.widgets.main_container.PluginMainContainer`
subclass. Child widgets can also create actions, but they need to
subclass this class or :class:`SpyderWidgetMixin`.
* :class:`~spyder.api.widgets.main_widget.PluginMainWidget` or
:class:`~spyder.api.widgets.main_container.PluginMainContainer`
will collect any actions defined in subwidgets (if defined)
and expose them in the ``get_actions`` method at the plugin level.
* There is no need to override this method.
"""
plugin = self.PLUGIN_NAME if plugin is None else plugin
context = self.CONTEXT_NAME if context is None else context
return ACTION_REGISTRY.get_references(plugin, context)
[docs]
def update_actions(self) -> None:
"""
Update the state of exposed actions.
Exposed actions are actions created by the :meth:`create_action`
method.
Returns
-------
None
Raises
------
NotImplementedError
If this method is not implemented by the plugin, as is required.
"""
raise NotImplementedError("")
[docs]
class SpyderWidgetMixin(
SpyderActionMixin,
SpyderMenuMixin,
SpyderToolbarMixin,
SpyderToolButtonMixin,
SpyderShortcutsMixin,
):
"""
Basic functionality for all Spyder widgets and Qt items.
This provides a simple management of widget options, as well as Qt helpers
for defining the actions a widget provides.
"""
PLUGIN_NAME: str | None = None
"""
Plugin name in the action, toolbar, toolbutton & menu registries.
Usually the same as
:attr:`SpyderPluginV2.NAME <spyder.api.plugins.SpyderPluginV2.NAME>`,
but may be different from :attr:`CONTEXT_NAME`.
"""
CONTEXT_NAME: str | None = None
"""
The name under which to store actions, toolbars, toolbuttons and menus.
This optional attribute defines the context name under which actions,
toolbars, toolbuttons and menus should be registered in the
Spyder global registry.
If those elements belong to the global scope of the plugin, then this
attribute should have a ``None`` value, which will use the plugin's name as
the context scope
"""
[docs]
def __init__(
self,
class_parent: QObject | None = None,
parent: QWidget | None = None,
) -> None:
"""
Add methods and attributes for most Spyder widgets.
Parameters
----------
class_parent : QObject | None, optional
The parent object of this object's class, or ``None`` (default).
Typically the plugin object.
parent : QWidget | None, optional
The parent widget of this one, or ``None`` (default).
Returns
-------
None
"""
for attr in ["CONF_SECTION", "PLUGIN_NAME"]:
if getattr(self, attr, None) is None:
if hasattr(class_parent, attr):
# Inherit class_parent CONF_SECTION/PLUGIN_NAME value
setattr(self, attr, getattr(class_parent, attr))
super().__init__()
[docs]
@staticmethod
def create_icon(name: str) -> QIcon:
"""
Retrieve an icon from Spyder's icon manager.
Parameters
----------
name: str
The name of the icon to retrieve.
Returns
-------
QIcon
The specified icon, as a :class:`QIcon` instance.
"""
return ima.icon(name)
[docs]
def update_style(self) -> None:
"""
Modify the interface styling used by the plugin.
This must be reimplemented by plugins that need to adjust their style.
Changing from the dark to the light interface theme might
require specific styles or stylesheets to be applied. When
the theme is changed by the user through our :guilabel:`Preferences`,
this method will be called for all plugins.
Returns
-------
None
"""
pass
[docs]
def update_translation(self) -> None:
"""
Update localization of the widget.
This method will be called recursively on all widgets on language
change.
Returns
-------
None
"""
pass
[docs]
class SpyderMainWindowMixin:
"""
Mixin with additional functionality for Spyder's :class:`QMainWindow`\\s.
"""
def _is_on_visible_screen(self: QMainWindow) -> bool:
"""Detect if the window is placed on a visible screen."""
x, y = self.geometry().x(), self.geometry().y()
qapp = QApplication.instance()
current_screen = qapp.screenAt(QPoint(x, y))
if current_screen is None:
return False
else:
return True
[docs]
def move_to_primary_screen(self: QMainWindow) -> None:
"""
Move the window to the primary screen if necessary.
Returns
-------
None
"""
if self._is_on_visible_screen():
return
qapp = QApplication.instance()
primary_screen_geometry = qapp.primaryScreen().availableGeometry()
x, y = primary_screen_geometry.x(), primary_screen_geometry.y()
if self.isMaximized():
self.showNormal()
self.move(QPoint(x, y))
# With this we want to maximize only the Spyder main window and not the
# plugin ones, which usually are not maximized.
if not hasattr(self, "is_window_widget"):
self.showMaximized()
[docs]
class SvgToScaledPixmap(SpyderConfigurationAccessor):
"""
Mixin to transform an SVG to a QPixmap.
The resulting :class:`QPixmap` is scaled according to the scale factor
set by users in Spyder's :guilabel:`Preferences`.
"""
[docs]
def svg_to_scaled_pixmap(
self,
svg_file: str,
rescale: float | None = None,
in_package: bool = True,
) -> QPixmap:
"""
Transform an SVG to a QPixmap.
The resulting :class:`QPixmap` is scaled according to the scale factor
set by users in Spyder's :guilabel:`Preferences`.
Parameters
----------
svg_file: str
Name of or path to the SVG file to convert.
rescale: float | None, optional
Rescale pixmap according to a factor between 0 and 1.
If ``None`` (default), will use the default scale factor.
in_package: bool, optional
If ``True`` (the default), get the SVG from the
:file:`images` directory in the installed Spyder package.
If ``False``, retrieve it from the specified file on disk.
Returns
-------
QPixmap
The converted rasterized image.
"""
if in_package:
image_path = get_image_path(svg_file)
if self.get_conf("high_dpi_custom_scale_factor", section="main"):
scale_factors = self.get_conf(
"high_dpi_custom_scale_factors", section="main"
)
scale_factor = float(scale_factors.split(":")[0])
else:
scale_factor = 1
# Get width and height
pm = QPixmap(image_path)
width = pm.width()
height = pm.height()
# Rescale but preserving aspect ratio
if rescale is not None:
aspect_ratio = width / height
width = int(width * rescale)
height = int(width / aspect_ratio)
# Paint image using svg renderer
image = QImage(
int(width * scale_factor),
int(height * scale_factor),
QImage.Format_ARGB32_Premultiplied,
)
image.fill(0)
painter = QPainter(image)
renderer = QSvgRenderer(image_path)
renderer.render(painter)
painter.end()
# This is also necessary to make the image look good for different
# scale factors
if scale_factor > 1.0:
image.setDevicePixelRatio(scale_factor)
# Create pixmap out of image
final_pm = QPixmap.fromImage(image)
final_pm = final_pm.copy(
0, 0, int(width * scale_factor), int(height * scale_factor)
)
return final_pm