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: Improve UI of PaneEmptyWidget, show message on panes connected to dead consoles and improve About dialog UI #21134

Merged
merged 14 commits into from
Jul 28, 2023
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
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
dalthviz marked this conversation as resolved.
Show resolved Hide resolved
_("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 @@ -1439,9 +1441,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