Skip to content

Commit

Permalink
Merge pull request #21134 from ccordoba12/empty-panes-improvements
Browse files Browse the repository at this point in the history
PR: Improve UI of `PaneEmptyWidget`, show message on panes connected to dead consoles and improve About dialog UI
  • Loading branch information
ccordoba12 authored Jul 28, 2023
2 parents 68cdfdf + 83551f4 commit 2540935
Show file tree
Hide file tree
Showing 18 changed files with 368 additions and 123 deletions.
69 changes: 48 additions & 21 deletions spyder/api/shellconnect/main_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# Local imports
from spyder.api.translations import _
from spyder.api.widgets.main_widget import PluginMainWidget
from spyder.widgets.helperwidgets import PaneEmptyWidget


class ShellConnectMainWidget(PluginMainWidget):
Expand All @@ -40,6 +41,24 @@ def __init__(self, *args, **kwargs):
layout.addWidget(self._stack)
self.setLayout(layout)

# ---- PluginMainWidget API
# ------------------------------------------------------------------------
def current_widget(self):
"""
Return the current widget in the stack.
Returns
-------
QWidget
The current widget.
"""
return self._stack.currentWidget()

def get_focus_widget(self):
return self.current_widget()

# ---- SpyderWidgetMixin API
# ------------------------------------------------------------------------
def update_style(self):
self._stack.setStyleSheet("QStackedWidget {padding: 0px; border: 0px}")

Expand All @@ -56,20 +75,6 @@ def count(self):
"""
return self._stack.count()

def current_widget(self):
"""
Return the current figure browser widget in the stack.
Returns
-------
QWidget
The current widget.
"""
return self._stack.currentWidget()

def get_focus_widget(self):
return self.current_widget()

def get_widget_for_shellwidget(self, shellwidget):
"""return widget corresponding to shellwidget."""
shellwidget_id = id(shellwidget)
Expand All @@ -80,10 +85,7 @@ def get_widget_for_shellwidget(self, shellwidget):
# ---- Public API
# ------------------------------------------------------------------------
def add_shellwidget(self, shellwidget):
"""
Create a new widget in the stack and associate it to
shellwidget.
"""
"""Create a new widget in the stack and associate it to shellwidget."""
shellwidget_id = id(shellwidget)
if shellwidget_id not in self._shellwidgets:
widget = self.create_new_widget(shellwidget)
Expand All @@ -109,17 +111,38 @@ def remove_shellwidget(self, shellwidget):
self.update_actions()

def set_shellwidget(self, shellwidget):
"""
Set widget associated with shellwidget as the current widget.
"""
"""Set widget associated with shellwidget as the current widget."""
old_widget = self.current_widget()
widget = self.get_widget_for_shellwidget(shellwidget)
if widget is None:
return

self._stack.setCurrentWidget(widget)
self.switch_widget(widget, old_widget)
self.update_actions()

def add_errored_shellwidget(self, shellwidget):
"""
Create a new PaneEmptyWidget in the stack and associate it to
shellwidget.
This is necessary to show a meaningful message when switching to
consoles with dead kernels.
"""
shellwidget_id = id(shellwidget)
if shellwidget_id not in self._shellwidgets:
widget = PaneEmptyWidget(
self,
"variable-explorer", # TODO: Use custom icon here
_("No connected console"),
_("The current console failed to start, so there is no "
"content to show here.")
)

self._stack.addWidget(widget)
self._shellwidgets[shellwidget_id] = widget
self.set_shellwidget(shellwidget)

def create_new_widget(self, shellwidget):
"""Create a widget to communicate with shellwidget."""
raise NotImplementedError
Expand All @@ -137,3 +160,7 @@ def refresh(self):
if self.count():
widget = self.current_widget()
widget.refresh()

def is_current_widget_empty(self):
"""Check if the current widget is a PaneEmptyWidget."""
return isinstance(self.current_widget(), PaneEmptyWidget)
75 changes: 58 additions & 17 deletions spyder/api/shellconnect/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,54 @@

class ShellConnectMixin:
"""
Mixin to connect a plugin composed of stacked widgets to the shell
widgets in the IPython console.
Mixin to connect any widget or object to the shell widgets in the IPython
console.
"""

# ---- Connection to the IPython console
# -------------------------------------------------------------------------
def register_ipythonconsole(self, ipyconsole):
"""Register signals from the console."""
ipyconsole.sig_shellwidget_changed.connect(self.set_shellwidget)
ipyconsole.sig_shellwidget_created.connect(self.add_shellwidget)
ipyconsole.sig_shellwidget_deleted.connect(self.remove_shellwidget)
ipyconsole.sig_shellwidget_errored.connect(
self.add_errored_shellwidget)

def unregister_ipythonconsole(self, ipyconsole):
"""Unregister signals from the console."""
ipyconsole.sig_shellwidget_changed.disconnect(self.set_shellwidget)
ipyconsole.sig_shellwidget_created.disconnect(self.add_shellwidget)
ipyconsole.sig_shellwidget_deleted.disconnect(self.remove_shellwidget)
ipyconsole.sig_shellwidget_errored.disconnect(
self.add_errored_shellwidget)

# ---- Public API
# -------------------------------------------------------------------------
def set_shellwidget(self, shellwidget):
"""Update the current shellwidget."""
raise NotImplementedError

def add_shellwidget(self, shellwidget):
"""Add a new shellwidget to be registered."""
raise NotImplementedError

def remove_shellwidget(self, shellwidget):
"""Remove a registered shellwidget."""
raise NotImplementedError

def add_errored_shellwidget(self, shellwidget):
"""Register a new shellwidget whose kernel failed to start."""
raise NotImplementedError


class ShellConnectPluginMixin(ShellConnectMixin):
"""
Mixin to connect a plugin composed of stacked widgets to the shell widgets
in the IPython console.
It is assumed that self.get_widget() returns an instance of
ShellConnectMainWidget
ShellConnectMainWidget.
"""

# ---- Connection to the IPython console
Expand All @@ -30,25 +73,12 @@ def on_ipython_console_available(self):
ipyconsole = self.get_plugin(Plugins.IPythonConsole)
self.register_ipythonconsole(ipyconsole)

def register_ipythonconsole(self, ipyconsole):
"""Register the console."""
ipyconsole.sig_shellwidget_changed.connect(self.set_shellwidget)
ipyconsole.sig_shellwidget_created.connect(self.add_shellwidget)
ipyconsole.sig_shellwidget_deleted.connect(self.remove_shellwidget)

@on_plugin_teardown(plugin=Plugins.IPythonConsole)
def on_ipython_console_teardown(self):
"""Disconnect from the IPython console."""
ipyconsole = self.get_plugin(Plugins.IPythonConsole)
self.unregister_ipythonconsole(ipyconsole)

def unregister_ipythonconsole(self, ipyconsole):
"""Unregister the console."""

ipyconsole.sig_shellwidget_changed.disconnect(self.set_shellwidget)
ipyconsole.sig_shellwidget_created.disconnect(self.add_shellwidget)
ipyconsole.sig_shellwidget_deleted.disconnect(self.remove_shellwidget)

# ---- Public API
# -------------------------------------------------------------------------
def set_shellwidget(self, shellwidget):
Expand Down Expand Up @@ -78,7 +108,7 @@ def add_shellwidget(self, shellwidget):

def remove_shellwidget(self, shellwidget):
"""
Remove the registered shellwidget.
Remove a registered shellwidget.
Parameters
----------
Expand All @@ -87,6 +117,17 @@ def remove_shellwidget(self, shellwidget):
"""
self.get_widget().remove_shellwidget(shellwidget)

def add_errored_shellwidget(self, shellwidget):
"""
Add a new shellwidget whose kernel failed to start.
Parameters
----------
shellwidget: spyder.plugins.ipyconsole.widgets.shell.ShellWidget
The shell widget.
"""
self.get_widget().add_errored_shellwidget(shellwidget)

def current_widget(self):
"""
Return the current widget displayed at the moment.
Expand Down
18 changes: 14 additions & 4 deletions spyder/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@

def get_class_values(cls):
"""
Get the attribute values for the class enumerations used in our
API.
Get the attribute values for the class enumerations used in our API.
Idea from:
https://stackoverflow.com/a/17249228/438386
Idea from: https://stackoverflow.com/a/17249228/438386
"""
return [v for (k, v) in cls.__dict__.items() if k[:1] != '_']

Expand Down Expand Up @@ -54,3 +52,15 @@ def __iter__(self):
child = self.children[key]
for prefix in child:
yield prefix


class classproperty(property):
"""
Decorator to declare class constants as properties that require additional
computation.
Taken from: https://stackoverflow.com/a/7864317/438386
"""

def __get__(self, cls, owner):
return classmethod(self.fget).__get__(None, owner)()
11 changes: 5 additions & 6 deletions spyder/app/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,11 +666,11 @@ def setup(self):
logger.info("Applying theme configuration...")
ui_theme = self.get_conf('ui_theme', section='appearance')
color_scheme = self.get_conf('selected', section='appearance')
qapp = QApplication.instance()

if ui_theme == 'dark':
if not running_under_pytest():
# Set style proxy to fix combobox popup on mac and qdark
qapp = QApplication.instance()
qapp.setStyle(self._proxy_style)
dark_qss = str(APP_STYLESHEET)
self.setStyleSheet(dark_qss)
Expand All @@ -680,7 +680,6 @@ def setup(self):
elif ui_theme == 'light':
if not running_under_pytest():
# Set style proxy to fix combobox popup on mac and qdark
qapp = QApplication.instance()
qapp.setStyle(self._proxy_style)
light_qss = str(APP_STYLESHEET)
self.setStyleSheet(light_qss)
Expand All @@ -691,7 +690,6 @@ def setup(self):
if not is_dark_font_color(color_scheme):
if not running_under_pytest():
# Set style proxy to fix combobox popup on mac and qdark
qapp = QApplication.instance()
qapp.setStyle(self._proxy_style)
dark_qss = str(APP_STYLESHEET)
self.setStyleSheet(dark_qss)
Expand All @@ -703,6 +701,10 @@ def setup(self):
self.statusBar().setStyleSheet(light_qss)
css_path = CSS_PATH

# This needs to done after applying the stylesheet to the window
logger.info("Set color for links in Qt widgets")
set_links_color(qapp)

# Set css_path as a configuration to be used by the plugins
self.set_conf('css_path', css_path, section='appearance')

Expand Down Expand Up @@ -1456,9 +1458,6 @@ def main(options, args):
pass
CONF.set('main', 'previous_crash', previous_crash)

# **** Set color for links ****
set_links_color(app)

# **** Create main window ****
mainwindow = None
try:
Expand Down
4 changes: 2 additions & 2 deletions spyder/plugins/debugger/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from spyder.api.plugins import Plugins, SpyderDockablePlugin
from spyder.api.plugin_registration.decorators import (
on_plugin_available, on_plugin_teardown)
from spyder.api.shellconnect.mixins import ShellConnectMixin
from spyder.api.shellconnect.mixins import ShellConnectPluginMixin
from spyder.api.translations import _
from spyder.config.manager import CONF
from spyder.plugins.debugger.confpage import DebuggerConfigPage
Expand All @@ -37,7 +37,7 @@
from spyder.plugins.editor.api.run import CellRun, SelectionRun


class Debugger(SpyderDockablePlugin, ShellConnectMixin, RunExecutor):
class Debugger(SpyderDockablePlugin, ShellConnectPluginMixin, RunExecutor):
"""Debugger plugin."""

NAME = 'debugger'
Expand Down
Loading

0 comments on commit 2540935

Please sign in to comment.