Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Add support for custom status bar widgets #14872

Merged
merged 4 commits into from
Mar 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 111 additions & 36 deletions spyder/api/widgets/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,34 @@
from qtpy.QtWidgets import QHBoxLayout, QLabel, QWidget

# Local imports
from spyder.api.exceptions import SpyderAPIError
from spyder.api.widgets.mixins import SpyderWidgetMixin
from spyder.config.gui import is_dark_interface
from spyder.utils.qthelpers import create_waitspinner


class StatusBarWidget(QWidget, SpyderWidgetMixin):
"""Status bar widget base."""
"""
Base class for status bar widgets.

These widgets consist by default of an icon, a label and a spinner,
which are organized from left to right on that order.

You can also add any other QWidget to this layout by setting the
CUSTOM_WIDGET_CLASS class attribute. It'll be put between the label
and the spinner.
"""

ID = None
"""
Unique string widget identifier.
"""

CUSTOM_WIDGET_CLASS = None
"""
Custom widget class to add to the default layout.
"""

sig_option_changed = Signal(str, object)
"""
This signal is required when widgets need to change options in
Expand All @@ -37,69 +52,127 @@ class StatusBarWidget(QWidget, SpyderWidgetMixin):
This signal is emmitted when the widget is clicked.
"""

def __init__(self, parent=None, spinner=False):
"""Status bar widget base."""
def __init__(self, parent=None, show_icon=True, show_label=True,
show_spinner=False):
"""
Base class for status bar widgets.

These are composed of the following widgets, which are arranged
in a QHBoxLayout from left to right:

* Icon
* Label
* Custom QWidget
* Spinner

Parameters
----------
show_icon: bool
Show an icon in the widget.
show_label: bool
Show a label in the widget.
show_spinner: bool
Show a spinner.

Notes
-----
1. To use an icon, you need to redefine the ``get_icon`` method.
2. To use a label, you need to call ``set_value``.
"""
super().__init__(parent)

# Variables
self.value = None
self._parent = parent

# Widget
self._icon = self.get_icon()
self._pixmap = None
self._icon_size = QSize(16, 16) # Should this be adjustable?
self.label_icon = QLabel()
self.label_value = QLabel()
self.show_icon = show_icon
self.show_label = show_label
self.show_spinner = show_spinner

self.value = None
self.label_icon = None
self.label_value = None
self.spinner = None
if spinner:
self.custom_widget = None

self.set_layout()

def set_layout(self):
"""Set layout for default widgets."""
# Icon
if self.show_icon:
self._icon = self.get_icon()
self._pixmap = None
self._icon_size = QSize(16, 16) # Should this be adjustable?
self.label_icon = QLabel()
self.set_icon()

# Label
if self.show_label:
self.label_value = QLabel()
self.set_value('')

# See spyder-ide/spyder#9044.
self.text_font = QFont(QFont().defaultFamily(),
weight=QFont.Normal)
self.label_value.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
self.label_value.setFont(self.text_font)

# Custom widget
if self.CUSTOM_WIDGET_CLASS:
if not issubclass(self.CUSTOM_WIDGET_CLASS, QWidget):
raise SpyderAPIError(
'Any custom status widget must subclass QWidget!'
)
self.custom_widget = self.CUSTOM_WIDGET_CLASS(self._parent)

# Spinner
if self.show_spinner:
self.spinner = create_waitspinner(size=14, parent=self)
self.spinner.hide()

# Layout setup
layout = QHBoxLayout(self)
layout.setSpacing(0) # Reduce space between icon and label
layout.addWidget(self.label_icon)
layout.addWidget(self.label_value)
if spinner:
if self.show_icon:
layout.addWidget(self.label_icon)
if self.show_label:
layout.addWidget(self.label_value)
if self.custom_widget:
layout.addWidget(self.custom_widget)
if self.show_spinner:
layout.addWidget(self.spinner)
self.spinner.hide()

if is_dark_interface():
layout.addSpacing(0)
else:
layout.addSpacing(10)
layout.setContentsMargins(0, 0, 0, 0)

# Widget setup
self.set_icon()

# See spyder-ide/spyder#9044.
self.text_font = QFont(QFont().defaultFamily(), weight=QFont.Normal)
self.label_value.setAlignment(Qt.AlignRight)
self.label_value.setFont(self.text_font)
layout.setContentsMargins(0, 0, 0, 0)
layout.setAlignment(Qt.AlignVCenter)

# Setup
self.set_value('')
self.update_tooltip()

# ---- Status bar widget API
def set_icon(self):
"""Set the icon for the status bar widget."""
icon = self._icon
self.label_icon.setVisible(icon is not None)
if icon is not None and isinstance(icon, QIcon):
self._pixmap = icon.pixmap(self._icon_size)
self.label_icon.setPixmap(self._pixmap)
if self.label_icon:
icon = self._icon
self.label_icon.setVisible(icon is not None)
if icon is not None and isinstance(icon, QIcon):
self._pixmap = icon.pixmap(self._icon_size)
self.label_icon.setPixmap(self._pixmap)

def set_value(self, value):
"""Set formatted text value."""
self.value = value
self.label_value.setText(value)
if self.label_value:
self.value = value
self.label_value.setText(value)

def update_tooltip(self):
"""Update tooltip for widget."""
tooltip = self.get_tooltip()
if tooltip:
self.label_value.setToolTip(tooltip)
if self.label_value:
self.label_value.setToolTip(tooltip)
if self.label_icon:
self.label_icon.setToolTip(tooltip)
self.setToolTip(tooltip)
Expand All @@ -120,10 +193,12 @@ def get_icon(self):


class BaseTimerStatus(StatusBarWidget):
"""Status bar widget base for widgets that update based on timers."""
"""
Base class for status bar widgets that update based on timers.
"""

def __init__(self, parent=None):
"""Status bar widget base for widgets that update based on timers."""
"""Base class for status bar widgets that update based on timers."""
self.timer = None # Needs to come before parent call
super().__init__(parent)
self._interval = 2000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class LSPStatusWidget(StatusBarWidget):

def __init__(self, parent, provider):
self.tooltip = self.BASE_TOOLTIP
super().__init__(parent, spinner=True)
super().__init__(parent, show_spinner=True)

self.provider = provider
self.current_language = None
Expand Down
29 changes: 28 additions & 1 deletion spyder/plugins/statusbar/widgets/tests/test_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

# Thrid party imports
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QMainWindow
from qtpy.QtWidgets import QComboBox, QMainWindow
import pytest

# Local imports
Expand Down Expand Up @@ -59,6 +59,20 @@ def get_icon(self):
return 'icon'


class MyComboBox(QComboBox):
def __init__(self, parent):
super().__init__(parent)
self.addItems(['foo', 'bar'])


class CustomStatusBarWidget(StatusBarWidget):
ID = 'custom_status'
CUSTOM_WIDGET_CLASS = MyComboBox

def get_icon(self):
return self.create_icon('environment')


def test_status_bar_widget_signal(status_bar, qtbot):
plugin, window = status_bar

Expand All @@ -76,5 +90,18 @@ def test_status_bar_widget_signal(status_bar, qtbot):
assert w.get_icon() == 'icon'


def test_custom_widget(status_bar, qtbot):
plugin, window = status_bar

# Add widget to status bar
w = CustomStatusBarWidget(window)
w.set_value('Options: ')
plugin.add_status_widget(w)
# qtbot.stop()

# We create three widgets by default
assert len(plugin.STATUS_WIDGETS) == 4


if __name__ == "__main__":
pytest.main()