Skip to content

PR: Fixes to make the app work with PySide6 #23732

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

Merged
merged 12 commits into from
Mar 19, 2025
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
24 changes: 20 additions & 4 deletions spyder/api/config/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
from typing import Any, Callable, Optional, Union
import warnings

# Third-party imports
from qtpy import PYSIDE6

# Local imports
from spyder.config.manager import CONF
from spyder.config.types import ConfigurationKey
Expand Down Expand Up @@ -239,10 +242,13 @@ def __init__(self):
section = self.CONF_SECTION if section is None else section
observed_options = self._configuration_listeners[section]
for option in observed_options:
logger.debug(
f'{self} is observing option "{option}" in section '
f'"{section}"'
)
# Avoid a crash at startup due to MRO
if not PYSIDE6:
logger.debug(
f'{self} is observing option "{option}" in section '
f'"{section}"'
)

CONF.observe_configuration(self, section, option)

def __del__(self):
Expand All @@ -252,6 +258,16 @@ def __del__(self):
def _gather_observers(self):
"""Gather all the methods decorated with `on_conf_change`."""
for method_name in dir(self):
# Avoid crash at startup due to MRO
if PYSIDE6 and method_name in {
# PySide seems to require that the class is instantiated to
# access this method
"painters",
# Method is debounced
"restart_kernel",
}:
continue

method = getattr(self, method_name, None)
if hasattr(method, '_conf_listen'):
info = method._conf_listen
Expand Down
19 changes: 6 additions & 13 deletions spyder/api/widgets/comboboxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

# Third-party imports
import qstylizer.style
from qtpy import PYQT5, PYQT6
from qtpy.QtCore import QSize, Qt, Signal
from qtpy.QtGui import QColor, QPainter, QFontMetrics
from qtpy.QtWidgets import (
Expand Down Expand Up @@ -256,7 +255,7 @@ def _generate_stylesheet(self):
return css


class SpyderComboBox(QComboBox, _SpyderComboBoxMixin):
class SpyderComboBox(_SpyderComboBoxMixin, QComboBox):
"""Default combobox widget for Spyder."""

def __init__(self, parent=None, items_elide_mode=None):
Expand All @@ -270,11 +269,8 @@ def __init__(self, parent=None, items_elide_mode=None):
items_elide_mode: Qt.TextElideMode, optional
Elide mode for the combobox items.
"""
if PYQT5 or PYQT6:
super().__init__(parent)
else:
QComboBox.__init__(self, parent)
_SpyderComboBoxMixin.__init__(self)
QComboBox.__init__(self, parent)
_SpyderComboBoxMixin.__init__(self)

self.is_editable = None
self._is_shown = False
Expand Down Expand Up @@ -375,14 +371,11 @@ def __init__(self, parent=None, items_elide_mode=None):
self.setStyleSheet(self._css.toString())


class SpyderFontComboBox(QFontComboBox, _SpyderComboBoxMixin):
class SpyderFontComboBox(_SpyderComboBoxMixin, QFontComboBox):

def __init__(self, parent=None):
if PYQT5 or PYQT6:
super().__init__(parent)
else:
QFontComboBox.__init__(self, parent)
_SpyderComboBoxMixin.__init__(self)
QFontComboBox.__init__(self, parent)
_SpyderComboBoxMixin.__init__(self)

# Avoid font name eliding because it confuses users.
# Fixes spyder-ide/spyder#22683
Expand Down
4 changes: 2 additions & 2 deletions spyder/api/widgets/main_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
status bar widgets or toolbars.
"""

from qtpy import PYQT5, PYQT6
from qtpy import PYSIDE2
from qtpy.QtCore import Signal
from qtpy.QtWidgets import QWidget

Expand Down Expand Up @@ -113,7 +113,7 @@ class PluginMainContainer(QWidget, SpyderWidgetMixin):
"""

def __init__(self, name, plugin, parent=None):
if PYQT5 or PYQT6:
if not PYSIDE2:
super().__init__(parent=parent, class_parent=plugin)
else:
QWidget.__init__(self, parent)
Expand Down
4 changes: 2 additions & 2 deletions spyder/api/widgets/main_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from typing import Optional

# Third party imports
from qtpy import PYQT5, PYQT6
from qtpy import PYSIDE2
from qtpy.QtCore import QByteArray, QSize, Qt, Signal, Slot
from qtpy.QtGui import QFocusEvent, QIcon
from qtpy.QtWidgets import (QApplication, QHBoxLayout, QSizePolicy,
Expand Down Expand Up @@ -206,7 +206,7 @@ class PluginMainWidget(QWidget, SpyderWidgetMixin):
"""

def __init__(self, name, plugin, parent=None):
if PYQT5 or PYQT6:
if not PYSIDE2:
super().__init__(parent=parent, class_parent=plugin)
else:
QWidget.__init__(self, parent)
Expand Down
2 changes: 1 addition & 1 deletion spyder/api/widgets/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ class SpyderWidgetMixin(
# Context name used to store actions, toolbars, toolbuttons and menus
CONTEXT_NAME = None

def __init__(self, class_parent=None):
def __init__(self, class_parent=None, parent=None):
for attr in ['CONF_SECTION', 'PLUGIN_NAME']:
if getattr(self, attr, None) is None:
if hasattr(class_parent, attr):
Expand Down
4 changes: 2 additions & 2 deletions spyder/api/widgets/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

# Third party imports
import qstylizer.parser
from qtpy import PYQT5, PYQT6
from qtpy import PYSIDE2
from qtpy.QtCore import Qt, QSize, QTimer, Signal
from qtpy.QtGui import QIcon
from qtpy.QtWidgets import QHBoxLayout, QLabel, QWidget
Expand Down Expand Up @@ -82,7 +82,7 @@ def __init__(self, parent=None, show_icon=True, show_label=True,
1. To use an icon, you need to redefine the ``get_icon`` method.
2. To use a label, you need to call ``set_value``.
"""
if PYQT5 or PYQT6:
if not PYSIDE2:
super().__init__(parent, class_parent=parent)
else:
QWidget.__init__(self, parent)
Expand Down
2 changes: 1 addition & 1 deletion spyder/app/tests/test_mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5414,7 +5414,7 @@ def test_goto_find(main_window, qtbot, tmpdir):
for i in range(5):
item = file_item.child(i)
findinfiles.result_browser.setCurrentItem(item)
findinfiles.result_browser.activated(item)
findinfiles.result_browser.on_item_activated(item)
cursor = code_editor.textCursor()
position = (cursor.selectionStart(), cursor.selectionEnd())
assert position == match_positions[i]
Expand Down
8 changes: 8 additions & 0 deletions spyder/plugins/completion/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from typing import Any, Optional, Tuple, Union

# Third party imports
from qtpy import PYSIDE6
from qtpy.QtCore import Signal, QObject, Slot, Qt

# Local imports
Expand Down Expand Up @@ -675,6 +676,13 @@ class CompletionConfigurationObserver(SpyderConfigurationObserver):
def _gather_observers(self):
"""Gather all the methods decorated with `on_conf_change`."""
for method_name in dir(self):
# Avoid crash at startup due to MRO
if PYSIDE6 and method_name in {
# Method is debounced
"interpreter_changed"
}:
continue

method = getattr(self, method_name, None)
if hasattr(method, '_conf_listen'):
info = method._conf_listen
Expand Down
4 changes: 2 additions & 2 deletions spyder/plugins/debugger/widgets/breakpoint_table_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

# Third party imports
import qstylizer.style
from qtpy import PYQT5, PYQT6
from qtpy import PYSIDE2
from qtpy.compat import to_qvariant
from qtpy.QtCore import QAbstractTableModel, QModelIndex, Qt, Signal
from qtpy.QtWidgets import QAbstractItemView, QTableView
Expand Down Expand Up @@ -175,7 +175,7 @@ class BreakpointTableView(QTableView, SpyderWidgetMixin):
sig_conditional_breakpoint_requested = Signal()

def __init__(self, parent, data):
if PYQT5 or PYQT6:
if not PYSIDE2:
super().__init__(parent, class_parent=parent)
else:
QTableView.__init__(self, parent)
Expand Down
6 changes: 3 additions & 3 deletions spyder/plugins/debugger/widgets/framesbrowser.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,13 +492,13 @@ def __init__(self, parent):

# Signals
self.header().sectionClicked.connect(self.sort_section)
self.itemActivated.connect(self.activated)
self.itemClicked.connect(self.activated)
self.itemActivated.connect(self.on_item_activated)
self.itemClicked.connect(self.on_item_activated)

def set_title(self, title):
self.setHeaderLabels([title])

def activated(self, item):
def on_item_activated(self, item):
"""Double-click event."""
itemdata = self.data.get(id(self.currentItem()))
if itemdata is not None:
Expand Down
29 changes: 20 additions & 9 deletions spyder/plugins/editor/api/decoration.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,23 @@ def __init__(self, cursor_or_bloc_or_doc, start_pos=None, end_pos=None,
if start_pos is not None:
self.cursor.setPosition(start_pos)
if end_pos is not None:
self.cursor.setPosition(end_pos, QTextCursor.KeepAnchor)
self.cursor.setPosition(end_pos, QTextCursor.MoveMode.KeepAnchor)
if start_line is not None:
self.cursor.movePosition(self.cursor.Start, self.cursor.MoveAnchor)
self.cursor.movePosition(self.cursor.Down, self.cursor.MoveAnchor,
start_line)
self.cursor.movePosition(
QTextCursor.MoveOperation.Start,
QTextCursor.MoveMode.MoveAnchor
)
self.cursor.movePosition(
QTextCursor.MoveOperation.Down,
QTextCursor.MoveMode.MoveAnchor,
start_line
)
if end_line is not None:
self.cursor.movePosition(self.cursor.Down, self.cursor.KeepAnchor,
end_line - start_line)
self.cursor.movePosition(
QTextCursor.MoveOperation.Down,
QTextCursor.MoveMode.KeepAnchor,
end_line - start_line
)
if font is not None:
self.format.setFont(font)

Expand Down Expand Up @@ -173,12 +182,14 @@ def select_line(self):
and stops at the non-whitespace character.
:return:
"""
self.cursor.movePosition(self.cursor.StartOfBlock)
self.cursor.movePosition(QTextCursor.MoveOperation.StartOfBlock)
text = self.cursor.block().text()
lindent = len(text) - len(text.lstrip())
self.cursor.setPosition(self.cursor.block().position() + lindent)
self.cursor.movePosition(self.cursor.EndOfBlock,
self.cursor.KeepAnchor)
self.cursor.movePosition(
QTextCursor.MoveOperation.EndOfBlock,
QTextCursor.MoveMode.KeepAnchor
)

def set_full_width(self, flag=True, clear=True):
"""
Expand Down
Loading
Loading