Exemple #1
0
    def _register(self):
        """
        Setup and register plugin in Spyder's main window and connect it to
        other plugins.
        """
        # Checks
        # --------------------------------------------------------------------
        if self.NAME is None:
            raise SpyderAPIError('A Spyder Plugin must define a `NAME`!')

        if self.NAME in self._main._PLUGINS:
            raise SpyderAPIError(
                'A Spyder Plugin with NAME="{}" already exists!'.format(
                    self.NAME))

        # Setup configuration
        # --------------------------------------------------------------------
        if self._conf is not None:
            self._conf.register_plugin(self)

        # Signals
        # --------------------------------------------------------------------
        self.is_registered = True

        self.update_font()
Exemple #2
0
    def create_toolbar(self, name, location='top'):
        """
        Create and add an auxiliary toolbar to the top or the bottom of
        the plugin.

        Parameters
        ----------
        location: str
            A string whose value is used to determine where to add the
            toolbar in the plugin interface. The toolbar can be added either
            at the 'top' or at the 'bottom' of the plugin.

        Returns
        -------
        SpyderPluginToolbar
            The auxiliary toolbar that was created and added to the plugin
            interface.
        """
        if name in self._toolbars:
            raise SpyderAPIError('Toolbar "{}" already exists!'.format(name))

        if location not in ['top', 'bottom']:
            raise SpyderAPIError('Invalid location "{}" for toolbar!'.format(
                location))

        toolbar = MainWidgetToolbar(parent=self)
        self._toolbars[name] = toolbar
        self._add_auxiliary_toolbar(toolbar, location)

        return toolbar
Exemple #3
0
    def _check_area(self):
        """
        Check if the current layout added areas cover the entire rectangle.

        Rectangle given by the extreme points for the added areas.
        """
        self._area_rects = []
        height = self._rows + 1
        area_float_rects = []
        delta = 0.0001
        for index, area in enumerate(self._areas):
            # These areas are used with a small delta to ensure if they are
            # next to each other they will not overlap.
            rectf = QRectF()
            rectf.setLeft(area["column"] + delta)
            rectf.setRight(area["column"] + area["col_span"] - delta)
            rectf.setTop(height - area["row"] - delta)
            rectf.setBottom(height - area["row"] - area["row_span"] + delta)
            rectf.index = index
            rectf.plugin_ids = area["plugin_ids"]
            area_float_rects.append(rectf)

            # These areas are used to calculate the actual total area
            rect = QRectF()
            rect.setLeft(area["column"])
            rect.setRight(area["column"] + area["col_span"])
            rect.setTop(height - area["row"])
            rect.setBottom(height - area["row"] - area["row_span"])
            rect.index = index
            rect.plugin_ids = area["plugin_ids"]

            self._area_rects.append(rect)

        # Check if areas are overlapping!
        for rect_1 in area_float_rects:
            for rect_2 in area_float_rects:
                if rect_1.index != rect_2.index:
                    if rect_1.intersects(rect_2):
                        raise SpyderAPIError(
                            "Area with plugins {0} is overlapping area "
                            "with plugins {1}".format(rect_1.plugin_ids,
                                                      rect_2.plugin_ids))

        # Check the total area (using corner points) versus the sum of areas
        total_area = 0
        tops = []
        rights = []
        for index, rect in enumerate(self._area_rects):
            tops.append(rect.top())
            rights.append(rect.right())
            area = abs(rect.width() * rect.height())
            total_area += area
            self._areas[index]["area"] = area

        if total_area != max(rights)*max(tops):
            raise SpyderAPIError(
                "Areas are not covering the entire section!\n"
                "Either an area is missing or col_span/row_span are "
                "not correctly set!"
            )
Exemple #4
0
    def get_plugin(self, plugin_name):
        """
        Return a plugin instance by providing the plugin's NAME.
        """
        # Ensure that this plugin has the plugin corresponding to
        # `plugin_name` listed as required or optional.
        requires = self.REQUIRES or []
        optional = self.OPTIONAL or []
        deps = []

        for dependency in requires + optional:
            deps.append(dependency)

        PLUGINS = self._main._PLUGINS
        if plugin_name in deps:
            for name, plugin_instance in PLUGINS.items():
                if name == plugin_name and name in deps:
                    return plugin_instance
            else:
                if plugin_name in requires:
                    raise SpyderAPIError(
                        'Required Plugin "{}" not found!'.format(plugin_name))
                else:
                    return None
        else:
            raise SpyderAPIError('Plugin "{}" not part of REQUIRES or '
                                 'OPTIONAL requirements!'.format(plugin_name))
Exemple #5
0
    def add_item_to_application_toolbar(self,
                                        item,
                                        toolbar=None,
                                        toolbar_id=None,
                                        section=None,
                                        before=None,
                                        before_section=None):
        """
        Add action or widget `item` to given application toolbar `section`.

        Parameters
        ----------
        item: SpyderAction or QWidget
            The item to add to the `toolbar`.
        toolbar: ApplicationToolbar or None
            Instance of a Spyder application toolbar.
        toolbar_id: str or None
            The application toolbar unique string identifier.
        section: str or None
            The section id in which to insert the `item` on the `toolbar`.
        before: str or None
            Make the item appear before another given item.
        before_section: str or None
            Make the item defined section appear before another given section
            (the section must be already defined).

        Notes
        -----
        Must provide a `toolbar` or a `toolbar_id`.
        """
        if toolbar and toolbar_id:
            raise SpyderAPIError('Must provide only toolbar or toolbar_id!')

        if toolbar is None and toolbar_id is None:
            raise SpyderAPIError(
                'Must provide at least toolbar or toolbar_id!')

        if toolbar and not isinstance(toolbar, ApplicationToolbar):
            raise SpyderAPIError('Not an `ApplicationToolbar`!')

        if toolbar_id and toolbar_id not in self._APPLICATION_TOOLBARS:
            raise SpyderAPIError(
                '{} is not a valid toolbar_id'.format(toolbar_id))

        toolbar_id = toolbar_id if toolbar_id else toolbar.ID
        toolbar = toolbar if toolbar else self.get_application_toolbar(
            toolbar_id)

        toolbar.add_item(item,
                         section=section,
                         before=before,
                         before_section=before_section)
Exemple #6
0
    def create_menu(self, name, text=None):
        """
        Create a menu.

        Parameters
        ----------
        name: str
            Unique str identifier.
        text: str
            Localized text string.

        Return: QMenu
            Return the created menu.
        """
        from spyder.api.widgets.menus import SpyderMenu

        menus = getattr(self, '_menus', None)
        if menus is None:
            self._menus = OrderedDict()

        if name in self._menus:
            raise SpyderAPIError('Menu name "{}" already in use!'.format(name))

        menu = SpyderMenu(parent=self, title=text)
        self._menus[name] = menu
        return menu
Exemple #7
0
    def create_application_toolbar(self, toolbar_id: str,
                                   title: str) -> ApplicationToolbar:
        """
        Create an application toolbar and add it to the main window.

        Parameters
        ----------
        toolbar_id: str
            The toolbar unique identifier string.
        title: str
            The localized toolbar title to be displayed.

        Returns
        -------
        spyder.api.widgets.toolbar.ApplicationToolbar
            The created application toolbar.
        """
        if toolbar_id in self._APPLICATION_TOOLBARS:
            raise SpyderAPIError(
                'Toolbar with ID "{}" already added!'.format(toolbar_id))

        toolbar = ApplicationToolbar(self, title)
        toolbar.ID = toolbar_id
        toolbar.setObjectName(toolbar_id)

        TOOLBAR_REGISTRY.register_reference(toolbar, toolbar_id,
                                            self.PLUGIN_NAME,
                                            self.CONTEXT_NAME)
        self._APPLICATION_TOOLBARS[toolbar_id] = toolbar

        self._add_missing_toolbar_elements(toolbar, toolbar_id)
        return toolbar
Exemple #8
0
    def register_tour(self, tour_id, title, tour_data):
        """
        Register a new interactive tour on spyder.

        Parameters
        ----------
        tour_id: str
            Unique tour string identifier.
        title: str
            Localized tour name.
        tour_data: dict
            The tour steps.
        """
        if tour_id in self._tours:
            raise SpyderAPIError(
                "Tour with id '{}' has already been registered!".format(
                    tour_id))

        self._tours[tour_id] = tour_data
        self._tour_titles[tour_id] = title
        action = self.create_action(
            tour_id,
            text=title,
            triggered=lambda: self.show_tour(tour_id),
        )
        self.add_item_to_menu(action, menu=self.tours_menu)
Exemple #9
0
    def get_plugin(self, plugin_name: str) -> SpyderPluginClass:
        """
        Get a reference to a plugin instance by its name.

        Parameters
        ----------
        plugin_name: str
            Name of the plugin to retrieve.

        Returns
        -------
        plugin: SpyderPluginClass
            The instance of the requested plugin.

        Raises
        ------
        SpyderAPIError
            If the plugin name was not found in the registry.
        """
        if plugin_name in self.plugin_registry:
            plugin_instance = self.plugin_registry[plugin_name]
            return plugin_instance
        else:
            raise SpyderAPIError(f'Plugin {plugin_name} was not found in '
                                 'the registry')
Exemple #10
0
    def create_menu(self, menu_id, title=''):
        """
        Override SpyderMenuMixin method to use a different menu class.

        Parameters
        ----------
        toolbar_id: str
            Unique toolbar string identifier.
        title: str
            Toolbar localized title.

        Returns
        -------
        MainWidgetMenu
            The main widget menu.
        """
        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 = MainWidgetMenu(parent=self, title=title)
        menu.ID = menu_id
        self._menus[menu_id] = menu
        return menu
Exemple #11
0
    def add_application_toolbar(self, toolbar, mainwindow=None):
        """
        Add toolbar to application toolbars.

        Parameters
        ----------
        toolbar: spyder.api.widgets.toolbars.ApplicationToolbar
            The application toolbar to add to the `mainwindow`.
        mainwindow: QMainWindow
            The main application window.
        """
        toolbar_id = toolbar.ID
        toolbar._check_interface()

        if toolbar_id in self._ADDED_TOOLBARS:
            raise SpyderAPIError(
                'Toolbar with ID "{}" already added!'.format(toolbar_id))

        # TODO: Make the icon size adjustable in Preferences later on.
        iconsize = 24
        toolbar.setIconSize(QSize(iconsize, iconsize))
        toolbar.setObjectName(toolbar_id)

        self._ADDED_TOOLBARS[toolbar_id] = toolbar
        self._toolbarslist.append(toolbar)

        if mainwindow:
            mainwindow.addToolBar(toolbar)
Exemple #12
0
    def _set_option(self, option, value, emit):
        """
        Helper method to set/change options with option to emit signal.
        """
        # Check if a togglable action exists with this name and update state
        try:
            action_name = 'toggle_{}_action'.format(option)
            self._update_action_state(action_name, value)
        except SpyderAPIError:
            pass

        self._check_options_dictionary_exist()
        if option in self.DEFAULT_OPTIONS:
            self._options[option] = value

            if emit:
                # This eventually calls on_option_update too.
                self.sig_option_changed.emit(option, value)
            else:
                self.on_option_update(option, value)
        else:
            raise SpyderAPIError(
                'Option "{}" has not been defined in the widget '
                'DEFAULT_OPTIONS attribute!'
                ''.format(option))
Exemple #13
0
    def create_menu(self, menu_id, title='', icon=None):
        """
        Override SpyderMenuMixin method to use a different menu class.

        Parameters
        ----------
        menu_id: str
            Unique toolbar string identifier.
        title: str
            Toolbar localized title.
        icon: QIcon or None
            Icon to use for the menu.

        Returns
        -------
        MainWidgetMenu
            The main widget menu.
        """
        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 = MainWidgetMenu(parent=self, title=title)
        menu.ID = menu_id

        if icon is not None:
            menu.menuAction().setIconVisibleInMenu(True)
            menu.setIcon(icon)

        self._menus[menu_id] = menu
        return menu
Exemple #14
0
    def add_area(self,
                 plugin_ids,
                 row,
                 column,
                 row_span=1,
                 col_span=1,
                 default=False,
                 visible=True,
                 hidden_plugin_ids=[]):
        """
        Add a new area and `plugin_ids` that will populate it to the layout.

        The area will start at row, column spanning row_pan rows and
        column_span columns.

        Parameters
        ----------
        plugin_ids: list
            List of plugin ids that will be in the area
        row: int
            Initial row where the area starts
        column: int
            Initial column where the area starts
        row_span: int, optional
            Number of rows that the area covers
        col_span: int, optional
            Number of columns the area covers
        default: bool, optiona
            Defines an area as the default one, i.e all other plugins that where
            not passed in the `plugins_ids` will be added to the default area.
            By default is False.
        visible: bool, optional
            Defines if the area is visible when setting up the layout.
            Default is True.

        Notes
        -----
        See: https://doc.qt.io/qt-5/qgridlayout.html
        """
        if self._default_added and default:
            raise SpyderAPIError("A default location has already been "
                                 "defined!")

        self._plugin_ids += plugin_ids
        self._rows = max(row, self._rows)
        self._cols = max(column, self._cols)
        self._default_added = default
        self._column_stretchs[column] = 1
        self._row_stretchs[row] = 1
        self._areas.append(
            dict(
                plugin_ids=plugin_ids,
                row=row,
                column=column,
                row_span=row_span,
                col_span=col_span,
                default=default,
                visible=visible,
                hidden_plugin_ids=hidden_plugin_ids,
            ))
Exemple #15
0
def find_external_plugins():
    """
    Find available internal plugins based on setuptools entry points.
    """
    internal_plugins = find_internal_plugins()
    plugins = [
        entry_point
        for entry_point in pkg_resources.iter_entry_points("spyder.plugins")
    ]

    external_plugins = {}
    for entry_point in plugins:
        name = entry_point.name
        if name not in internal_plugins:
            try:
                class_name = entry_point.attrs[0]
                mod = importlib.import_module(entry_point.module_name)
                plugin_class = getattr(mod, class_name, None)

                # To display in dependencies dialog
                plugin_class._spyder_module_name = entry_point.module_name
                plugin_class._spyder_package_name = (
                    entry_point.dist.project_name)
                plugin_class._spyder_version = entry_point.dist.version

                external_plugins[name] = plugin_class
                if name != plugin_class.NAME:
                    raise SpyderAPIError(
                        "Entry point name '{0}' and plugin.NAME '{1}' "
                        "do not match!".format(name, plugin_class.NAME))
            except (ModuleNotFoundError, ImportError) as error:
                print("%s: %s" % (name, str(error)), file=STDERR)
                traceback.print_exc(file=STDERR)

    return external_plugins
Exemple #16
0
    def create_application_toolbar(self, toolbar_id, title):
        """
        Create an application toolbar and add it to the main window.

        Paramaters
        ----------
        toolbar_id: str
            The toolbar unique identifier string.
        title: str
            The localized toolbar title to be displayed.

        Returns
        -------
        spyder.api.widgets.toolbar.ApplicationToolbar
            The created application toolbar.
        """
        if toolbar_id in self._APPLICATION_TOOLBARS:
            raise SpyderAPIError(
                'Toolbar with ID "{}" already added!'.format(toolbar_id))

        toolbar = ApplicationToolbar(self, title)
        toolbar.ID = toolbar_id
        toolbar.setObjectName(toolbar_id)
        self._APPLICATION_TOOLBARS[toolbar_id] = toolbar
        self._toolbarslist.append(toolbar)

        return toolbar
Exemple #17
0
def solve_plugin_dependencies(plugins):
    """
    Return a list of plugins sorted by dependencies.

    Notes
    -----
    * Prune the plugins for which required dependencies are not met
    * Prune the optional dependencies from the remaining plugins based on
        the remaining plugins available.
    * Group the remaining optional dependencies with the required
        dependencies.
    * Sort with toposort algorithm.
    """
    plugin_names = [plugin.NAME for plugin in plugins]

    # Back up dependencies
    for plugin in plugins:
        if plugin.REQUIRES is None:
            plugin.REQUIRES = []

        if plugin.OPTIONAL is None:
            plugin.OPTIONAL = []

        plugin._REQUIRES = plugin.REQUIRES.copy()
        plugin._OPTIONAL = plugin.OPTIONAL.copy()

    plugin_names = {plugin.NAME: plugin for plugin in plugins}
    dependencies_dict = {}

    # Prune plugins based on required dependencies
    remaining_plugins = []
    for plugin in plugins:
        for required in plugin.REQUIRES:
            # Check self references
            if plugin.NAME == required:
                raise SpyderAPIError("Plugin is self referencing!")

            if required not in plugin_names:
                plugin_names.pop(plugin.NAME)
                logger.error("Pruned plugin: " + plugin.NAME)
                break
        else:
            remaining_plugins.append(plugin)

    # Prune optional dependencies from remaining plugins
    for plugin in remaining_plugins:
        for optional in plugin.OPTIONAL:
            if optional not in plugin_names:
                plugin._OPTIONAL.remove(optional)

        plugin._REQUIRES += plugin._OPTIONAL
        dependencies_dict[plugin.NAME] = set(plugin._REQUIRES)

    # Now use toposort with plugin._REQUIRES!
    deps = toposort_flatten(dependencies_dict)

    # Convert back from names to plugins
    plugin_deps = [plugin_names[name] for name in deps]

    return plugin_deps
Exemple #18
0
    def create_menu(self, name, text=None, icon=None):
        """
        Create a menu.

        Parameters
        ----------
        name: str
            Unique str identifier.
        text: str or None
            Localized text string.
        icon: QIcon or None
            Icon to use for the menu.

        Return: QMenu
            Return the created menu.
        """
        from spyder.api.widgets.menus import SpyderMenu

        menus = getattr(self, '_menus', None)
        if menus is None:
            self._menus = OrderedDict()

        if name in self._menus:
            raise SpyderAPIError('Menu name "{}" already in use!'.format(name))

        menu = SpyderMenu(parent=self, title=text)
        if icon is not None:
            menu.menuAction().setIconVisibleInMenu(True)
            menu.setIcon(icon)

        self._menus[name] = menu
        return menu
Exemple #19
0
    def get_conf(self, option, default=NoDefault, section=None):
        """
        Get an option from Spyder configuration system.

        Parameters
        ----------
        option: str
            Name of the option to get its value from.
        default: bool, int, str, tuple, list, dict, NoDefault
            Value to get from the configuration system, passed as a
            Python object.
        section: str
            Section in the configuration system, e.g. `shortcuts`.

        Returns
        -------
        bool, int, str, tuple, list, dict
            Value associated with `option`.
        """
        if self._conf is not None:
            section = self.CONF_SECTION if section is None else section
            if section is None:
                raise SpyderAPIError(
                    'A spyder plugin must define a `CONF_SECTION` class '
                    'attribute!'
                )
            return self._conf.get(section, option, default)
Exemple #20
0
    def set_conf(self, option, value, section=None,
                 recursive_notification=True):
        """
        Set an option in Spyder configuration system.

        Parameters
        ----------
        option: str
            Name of the option (e.g. 'case_sensitive')
        value: bool, int, str, tuple, list, dict
            Value to save in the configuration system, passed as a
            Python object.
        section: str
            Section in the configuration system, e.g. `shortcuts`.
        recursive_notification: bool
            If True, all objects that observe all changes on the
            configuration section and objects that observe partial tuple paths
            are notified. For example if the option `opt` of section `sec`
            changes, then the observers for section `sec` are notified.
            Likewise, if the option `(a, b, c)` changes, then observers for
            `(a, b, c)`, `(a, b)` and a are notified as well.
        """
        if self._conf is not None:
            section = self.CONF_SECTION if section is None else section
            if section is None:
                raise SpyderAPIError(
                    'A spyder plugin must define a `CONF_SECTION` class '
                    'attribute!'
                )

            self._conf.set(section, option, value,
                           recursive_notification=recursive_notification)
            self.apply_conf({option}, False)
Exemple #21
0
    def create_toolbar(self, toolbar_id):
        """
        Create and add an auxiliary toolbar to the top of the plugin.

        Parameters
        ----------
        toolbar_id: str
            Unique toolbar string identifier.

        Returns
        -------
        SpyderPluginToolbar
            The auxiliary toolbar that was created and added to the plugin
            interface.
        """
        if toolbar_id in self._toolbars:
            raise SpyderAPIError(
                'Toolbar "{}" already exists!'.format(toolbar_id))

        toolbar = MainWidgetToolbar(parent=self)
        toolbar.ID = toolbar_id
        self._toolbars[toolbar_id] = toolbar
        self._auxiliary_toolbars[toolbar_id] = toolbar
        self._toolbars_layout.addWidget(toolbar)

        return toolbar
Exemple #22
0
    def _add_auxiliary_toolbar(self, toolbar, location):
        """
        Add the given toolbar to the top or the bottom of the plugin.

        Parameters
        ----------
        toolbar: QToolBar
            The SpyderPluginToolbar that needs to be added to the plugin
            interface.
        location: str
            A string whose value is used to determine where to add the given
            toolbar in the plugin interface. The toolbar can be added either
            to the 'top' or the 'bottom' of the plugin.
        """
        if location == 'top':
            area = Qt.TopToolBarArea
        elif location == 'bottom':
            area = Qt.BottomToolBarArea
        else:
            raise SpyderAPIError('Invalid location "{}"!'.format(location))

        if self._aux_toolbars[area]:
            self.addToolBarBreak(area)

        toolbar.setAllowedAreas(area)
        self.addToolBar(toolbar)
        self._aux_toolbars[area].append(toolbar)
Exemple #23
0
    def create_toolbutton(self,
                          name,
                          text=None,
                          icon=None,
                          tip=None,
                          toggled=None,
                          triggered=None,
                          autoraise=True,
                          text_beside_icon=False):
        """
        Create a Spyder toolbutton.
        """
        toolbuttons = getattr(self, '_toolbuttons', None)
        if toolbuttons is None:
            self._toolbuttons = OrderedDict()

        if name in self._toolbuttons:
            raise SpyderAPIError(
                'Tool button name "{}" already in use!'.format(name))

        toolbutton = create_toolbutton(
            self,
            text=text,
            shortcut=None,
            icon=icon,
            tip=tip,
            toggled=toggled,
            triggered=triggered,
            autoraise=autoraise,
            text_beside_icon=text_beside_icon,
        )
        toolbutton.name = name
        self._toolbuttons[name] = toolbutton
        return toolbutton
Exemple #24
0
    def create_application_menu(self, menu_id, title):
        """
        Create a Spyder application menu.

        Parameters
        ----------
        menu_id: str
            The menu unique identifier string.
        title: str
            The localized menu title to be displayed.
        """
        if menu_id in self._APPLICATION_MENUS:
            raise SpyderAPIError(
                'Menu with id "{}" already added!'.format(menu_id))

        menu = ApplicationMenu(self.main, title)
        menu.menu_id = menu_id
        self._APPLICATION_MENUS[menu_id] = menu
        self.main.menuBar().addMenu(menu)

        # Show and hide shortcuts and icons in menus for macOS
        if sys.platform == 'darwin':
            menu.aboutToShow.connect(
                lambda menu=menu: self._show_shortcuts(menu))
            menu.aboutToHide.connect(
                lambda menu=menu: self._hide_shortcuts(menu))
            menu.aboutToShow.connect(
                lambda menu=menu: set_menu_icons(menu, False))
            menu.aboutToShow.connect(self._hide_options_menus)

        return menu
Exemple #25
0
    def __init__(self):
        self._plugin_listeners = {}
        self._plugin_teardown_listeners = {}
        for method_name in dir(self):
            method = getattr(self, method_name, None)
            if hasattr(method, '_plugin_listen'):
                plugin_listen = method._plugin_listen

                # Check if plugin is listed among REQUIRES and OPTIONAL.
                # Note: We can't do this validation for the Layout plugin
                # because it depends on all plugins through the Plugins.All
                # wildcard.
                if (
                    self.NAME != Plugins.Layout and
                    (plugin_listen not in self.REQUIRES + self.OPTIONAL)
                ):
                    raise SpyderAPIError(
                        f"Method {method_name} of {self} is trying to watch "
                        f"plugin {plugin_listen}, but that plugin is not "
                        f"listed in REQUIRES nor OPTIONAL."
                    )

                logger.debug(
                    f'Method {method_name} is watching plugin {plugin_listen}'
                )
                self._plugin_listeners[plugin_listen] = method_name

            if hasattr(method, '_plugin_teardown'):
                plugin_teardown = method._plugin_teardown

                # Check if plugin is listed among REQUIRES and OPTIONAL.
                # Note: We can't do this validation for the Layout plugin
                # because it depends on all plugins through the Plugins.All
                # wildcard.
                if (
                    self.NAME != Plugins.Layout and
                    (plugin_teardown not in self.REQUIRES + self.OPTIONAL)
                ):
                    raise SpyderAPIError(
                        f"Method {method_name} of {self} is trying to watch "
                        f"plugin {plugin_teardown}, but that plugin is not "
                        f"listed in REQUIRES nor OPTIONAL."
                    )

                logger.debug(f'Method {method_name} will handle plugin '
                             f'teardown for {plugin_teardown}')
                self._plugin_teardown_listeners[plugin_teardown] = method_name
Exemple #26
0
    def get_widget(self):
        """
        Return the plugin main widget.
        """
        if self._widget is None:
            raise SpyderAPIError('Dockable Plugin must have a WIDGET_CLASS!')

        return self._widget
Exemple #27
0
    def add_item_to_menu(self, action_or_menu, menu, section=None,
                         before=None):
        """
        Add a SpyderAction or a QWidget to the menu.
        """
        if not isinstance(menu, SpyderMenu):
            raise SpyderAPIError('Menu must be an instance of SpyderMenu!')

        menu.add_action(action_or_menu, section=section, before=before)
Exemple #28
0
    def add_status_widget(self, widget, position=StatusBarWidgetPosition.Left):
        """
        Add status widget to main application status bar.

        Parameters
        ----------
        widget: StatusBarWidget
            Widget to be added to the status bar.
        position: int
            Position where the widget will be added given the members of the
            StatusBarWidgetPosition enum.
        """
        # Check widget class
        if not isinstance(widget, StatusBarWidget):
            raise SpyderAPIError(
                'Any status widget must subclass StatusBarWidget!')

        # Check ID
        id_ = widget.ID
        if id_ is None:
            raise SpyderAPIError(
                f"Status widget `{repr(widget)}` doesn't have an identifier!")

        # Check it was not added before
        if id_ in self.STATUS_WIDGETS and not running_under_pytest():
            raise SpyderAPIError(f'Status widget `{id_}` already added!')

        if id_ in self.INTERNAL_WIDGETS_IDS:
            self.INTERNAL_WIDGETS[id_] = widget
        elif position == StatusBarWidgetPosition.Right:
            self.EXTERNAL_RIGHT_WIDGETS[id_] = widget
        else:
            self.EXTERNAL_LEFT_WIDGETS[id_] = widget

        self.STATUS_WIDGETS[id_] = widget
        self._statusbar.setStyleSheet('QStatusBar::item {border: None;}')

        if position == StatusBarWidgetPosition.Right:
            self._statusbar.addPermanentWidget(widget)
        else:
            self._statusbar.insertPermanentWidget(StatusBarWidgetPosition.Left,
                                                  widget)
        self._statusbar.layout().setContentsMargins(0, 0, 0, 0)
        self._statusbar.layout().setSpacing(0)
Exemple #29
0
    def get_actions(self, filter_actions=True):
        """
        Return all actions defined by the central widget and all child
        widgets subclassing SpyderWidgetMixin.
        """
        all_children = self._find_children(self, [self])
        actions = OrderedDict()
        actions_blacklist = [
            self.dock_action,
            self.undock_action,
            self.close_action,
            'switch to ' + self._name,
        ]

        for child in all_children:
            get_actions_method = getattr(child, 'get_actions', None)
            _actions = getattr(child, '_actions', None)

            if get_actions_method and _actions:
                for key, action in child._actions.items():
                    # These are actions that we want to skip from exposing to
                    # make things simpler, but avoid creating specific
                    # variables for this
                    if filter_actions:
                        if key not in actions_blacklist:
                            if key in actions:
                                raise SpyderAPIError(
                                    '{} or a child widget has already '
                                    'defined an action "{}"!'.format(self,
                                                                     key)
                                )
                            else:
                                actions[key] = action
                    else:
                        if key in actions:
                            raise SpyderAPIError(
                                '{} or a child widget has already defined an '
                                'action "{}"!'.format(self, key)
                            )
                        else:
                            actions[key] = action

        return actions
Exemple #30
0
    def _check_layout_validity(self):
        """
        Check the current layout is a valid one.
        """
        self._visible_areas = []
        # Check ID
        if self.ID is None:
            raise SpyderAPIError("A Layout must define an `ID` class "
                                 "attribute!")

        # Check name
        self.get_name()

        # All layouts need to add at least 1 area
        if not self._areas:
            raise SpyderAPIError("A Layout must define add least one area!")

        default_areas = []
        area_zero_zero = False

        for area in self._areas:
            default_areas.append(area["default"])
            if area["default"]:
                self._default_area = area

            self._visible_areas.append(area["visible"])

            if area_zero_zero and area["row"] == 0 and area["column"] == 0:
                raise SpyderAPIError(
                    "Multiple areas defined their row and column as 0!")

            if area["row"] == 0 and area["column"] == 0:
                area_zero_zero = True

            if not set(area["hidden_plugin_ids"]) <= set(area["plugin_ids"]):
                raise SpyderAPIError(
                    "At least 1 hidden plugin id is not being specified "
                    "in the area plugin ids list!\n SpyderLayout: {}\n "
                    "hidden_plugin_ids: {}\n"
                    "plugin_ids: {}".format(self.get_name(),
                                            area["hidden_plugin_ids"],
                                            area["plugin_ids"]))

        # Check that there is at least 1 visible!
        if not any(self._visible_areas):
            raise SpyderAPIError("At least 1 area must be `visible`")

        # Check that there is only 1 `default` area!
        if not any(default_areas):
            raise SpyderAPIError("Only 1 area can be the `default`!")

        # Check one area has row zero and column zero
        if not area_zero_zero:
            raise SpyderAPIError(
                "1 area needs to be specified with row 0 and column 0!")

        # Check Area
        self._check_area()