Skip to content

Commit

Permalink
Merge pull request #14872 from ccordoba12/custom-statusbar-widget
Browse files Browse the repository at this point in the history
PR: Add support for custom status bar widgets
  • Loading branch information
ccordoba12 committed Mar 10, 2021
2 parents dc0b204 + ca792c8 commit 1dd460b
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 38 deletions.
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()

0 comments on commit 1dd460b

Please sign in to comment.