diff --git a/bootstrap.py b/bootstrap.py index 7ce7cc7c520..169799f200e 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -32,7 +32,8 @@ Type `python bootstrap.py -- --help` to read about Spyder options.""") parser.add_option('--gui', default=None, - help="GUI toolkit: pyqt (for PyQt4) or pyside (for PySide)") + help="GUI toolkit: pyqt5 (for PyQt5), pyqt (for PyQt4) or " + "pyside (for PySide)") parser.add_option('--hide-console', action='store_true', default=False, help="Hide parent console window (Windows only)") parser.add_option('--test', dest="test", action='store_true', default=False, @@ -43,7 +44,7 @@ default=False, help="Run Spyder in debug mode") options, args = parser.parse_args() -assert options.gui in (None, 'pyqt', 'pyside'), \ +assert options.gui in (None, 'pyqt5', 'pyqt', 'pyside'), \ "Invalid GUI toolkit option '%s'" % options.gui # For testing purposes @@ -104,15 +105,20 @@ print(" and %s" % EXTPATH) -# Selecting the GUI toolkit: PySide if installed, otherwise PyQt4 +# Selecting the GUI toolkit: PyQt5 if installed, otherwise PySide or PyQt4 # (Note: PyQt4 is still the officially supported GUI toolkit for Spyder) if options.gui is None: try: - import PySide # analysis:ignore - print("02. PySide is detected, selecting (experimental)") - os.environ['QT_API'] = 'pyside' - except: - print("02. No PySide detected, using PyQt4 if available") + import PyQt5 # analysis:ignore + print("02. PyQt5 is detected, selecting (experimental)") + os.environ['QT_API'] = 'pyqt5' + except ImportError: + try: + import PySide # analysis:ignore + print("02. PySide is detected, selecting") + os.environ['QT_API'] = 'pyside' + except ImportError: + print("02. No PyQt5 or PySide detected, using PyQt4 if available") else: print ("02. Skipping GUI toolkit detection") os.environ['QT_API'] = options.gui diff --git a/spyderlib/ipythonconfig.py b/spyderlib/ipythonconfig.py index a8a9dd3100a..d5637469ced 100644 --- a/spyderlib/ipythonconfig.py +++ b/spyderlib/ipythonconfig.py @@ -8,10 +8,15 @@ IPython configuration variables needed by Spyder """ +import os + from spyderlib.utils import programs from spyderlib import dependencies from spyderlib.baseconfig import _ +# Don't use spyderlib.qt.PYQT5 to avoid importing Qt here +PYQT5 = os.environ.get('QT_API', None) == 'pyqt5' + IPYTHON_REQVER = '>=1.0' ZMQ_REQVER = '>=2.1.11' @@ -21,6 +26,13 @@ required_version=ZMQ_REQVER) def is_qtconsole_installed(): + # Only IPython 3+ is compatible with PyQt5, so this will avoid a + # crash for us + # TODO: Remove this once IPython 3 is released + if programs.is_module_installed('IPython.qt', '<3.0') and PYQT5: + return False + + # Check if pyzmq is installed too, else, what's the point? pyzmq_installed = programs.is_module_installed('zmq', version=ZMQ_REQVER) if programs.is_module_installed('IPython.qt') and pyzmq_installed: return True diff --git a/spyderlib/plugins/__init__.py b/spyderlib/plugins/__init__.py index 8b57240507b..8e2c0e3c341 100644 --- a/spyderlib/plugins/__init__.py +++ b/spyderlib/plugins/__init__.py @@ -18,10 +18,15 @@ # pylint: disable=R0911 # pylint: disable=R0201 +# Qt imports +from spyderlib.qt import PYQT5 from spyderlib.qt.QtGui import (QDockWidget, QWidget, QShortcut, QCursor, QKeySequence, QMainWindow, QApplication) from spyderlib.qt.QtCore import Qt, Signal +# Stdlib imports +import sys + # Local imports from spyderlib.utils.qthelpers import toggle_actions, get_icon, create_action from spyderlib.baseconfig import _ @@ -30,7 +35,6 @@ from spyderlib.guiconfig import get_font, set_font from spyderlib.plugins.configdialog import SpyderConfigPage from spyderlib.py3compat import configparser, is_text_string -import sys class PluginConfigPage(SpyderConfigPage): @@ -154,9 +158,12 @@ class SpyderPluginMixin(object): show_message = None update_plugin_title = None - def __init__(self, main): + def __init__(self, main = None, **kwds): """Bind widget to a QMainWindow instance""" - super(SpyderPluginMixin, self).__init__() + if PYQT5: + super().__init__(**kwds) + else: + super(SpyderPluginMixin, self).__init__() assert self.CONF_SECTION is not None self.main = main self.default_margins = None @@ -178,7 +185,7 @@ def __init__(self, main): # We decided to create our own toggle action instead of using # the one that comes with dockwidget because it's not possible # to raise and focus the plugin with it. - self.toggle_view_action = None + self.toggle_view_action = None def initialize_plugin(self): """Initialize plugin: connect signals, setup actions, ...""" @@ -348,7 +355,7 @@ def get_plugin_font(self, option=None): def set_plugin_font(self, font, option=None): """Set plugin font option""" set_font(font, self.CONF_SECTION, option) - + def __show_message(self, message, timeout=0): """Show message in main window's status bar""" self.main.statusBar().showMessage(message, timeout) @@ -412,10 +419,14 @@ class SpyderPluginWidget(QWidget, SpyderPluginMixin): sig_option_changed = Signal(str, object) show_message = Signal(str, int) update_plugin_title = Signal() - - def __init__(self, parent): - QWidget.__init__(self, parent) - SpyderPluginMixin.__init__(self, parent) + + if PYQT5: + def __init__(self, parent, **kwds): + super().__init__(**kwds) + else: + def __init__(self, parent): + QWidget.__init__(self, parent) + SpyderPluginMixin.__init__(self, parent) def get_plugin_title(self): """ diff --git a/spyderlib/plugins/console.py b/spyderlib/plugins/console.py index a8ec7083571..4b4bc38e123 100644 --- a/spyderlib/plugins/console.py +++ b/spyderlib/plugins/console.py @@ -11,6 +11,7 @@ # pylint: disable=R0911 # pylint: disable=R0201 +from spyderlib.qt import PYQT5 from spyderlib.qt.QtGui import (QVBoxLayout, QFontDialog, QInputDialog, QLineEdit, QMenu) from spyderlib.qt.QtCore import Signal, Slot @@ -34,7 +35,7 @@ from spyderlib.plugins import SpyderPluginWidget from spyderlib.py3compat import to_text_string, getcwd - + class Console(SpyderPluginWidget): """ Console widget @@ -46,7 +47,10 @@ class Console(SpyderPluginWidget): def __init__(self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False): - SpyderPluginWidget.__init__(self, parent) + if PYQT5: + SpyderPluginWidget.__init__(self, parent, main = parent) + else: + SpyderPluginWidget.__init__(self, parent) debug_print(" ..internal console: initializing") self.dialog_manager = DialogManager() @@ -61,13 +65,14 @@ def __init__(self, parent=None, namespace=None, commands=[], message=None, self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0)) self.shell.go_to_error.connect(self.go_to_error) self.shell.focus_changed.connect(lambda: self.focus_changed.emit()) + # Redirecting some signals: self.shell.redirect_stdio.connect(lambda state: self.redirect_stdio.emit(state)) # Initialize plugin self.initialize_plugin() - + # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.shell) diff --git a/spyderlib/plugins/editor.py b/spyderlib/plugins/editor.py index 0d0286220fe..408c1a600eb 100644 --- a/spyderlib/plugins/editor.py +++ b/spyderlib/plugins/editor.py @@ -11,6 +11,7 @@ # pylint: disable=R0911 # pylint: disable=R0201 +from spyderlib.qt import PYQT5 from spyderlib.qt.QtGui import (QVBoxLayout, QPrintDialog, QSplitter, QToolBar, QAction, QApplication, QDialog, QWidget, QPrinter, QActionGroup, QInputDialog, QMenu, @@ -343,7 +344,10 @@ class Editor(SpyderPluginWidget): run_in_current_extconsole = Signal(str, str, str, bool, bool) def __init__(self, parent, ignore_last_opened_files=False): - SpyderPluginWidget.__init__(self, parent) + if PYQT5: + SpyderPluginWidget.__init__(self, parent, main=parent) + else: + SpyderPluginWidget.__init__(self, parent) self.__set_eol_chars = True @@ -1560,7 +1564,8 @@ def change_max_recent_files(self): if valid: self.set_option('max_recent_files', mrf) - @Slot(str, int, str) + + @Slot(str, int, str, object) def load(self, filenames=None, goto=None, word='', editorwindow=None, processevents=True): """ diff --git a/spyderlib/plugins/externalconsole.py b/spyderlib/plugins/externalconsole.py index 900b749e645..3d18f61160b 100644 --- a/spyderlib/plugins/externalconsole.py +++ b/spyderlib/plugins/externalconsole.py @@ -12,6 +12,7 @@ # pylint: disable=R0201 # Qt imports +from spyderlib.qt import PYQT5 from spyderlib.qt.QtGui import (QVBoxLayout, QMessageBox, QInputDialog, QLineEdit, QPushButton, QGroupBox, QLabel, QTabWidget, QFontComboBox, QHBoxLayout, @@ -48,6 +49,7 @@ class ExternalConsoleConfigPage(PluginConfigPage): + def __init__(self, plugin, parent): PluginConfigPage.__init__(self, plugin, parent) self.get_name = lambda: _("Console") @@ -436,7 +438,10 @@ class ExternalConsole(SpyderPluginWidget): redirect_stdio = Signal(bool) def __init__(self, parent, light_mode): - SpyderPluginWidget.__init__(self, parent) + if PYQT5: + SpyderPluginWidget.__init__(self, parent, main = parent) + else: + SpyderPluginWidget.__init__(self, parent) self.light_mode = light_mode self.tabwidget = None self.menu_actions = None diff --git a/spyderlib/plugins/history.py b/spyderlib/plugins/history.py index 5941be44635..a63aa4ee531 100644 --- a/spyderlib/plugins/history.py +++ b/spyderlib/plugins/history.py @@ -6,6 +6,7 @@ """Console History Plugin""" +from spyderlib.qt import PYQT5 from spyderlib.qt.QtGui import (QVBoxLayout, QFontDialog, QInputDialog, QToolButton, QMenu, QFontComboBox, QGroupBox) from spyderlib.qt.QtCore import Signal, Slot @@ -73,7 +74,6 @@ class HistoryLog(SpyderPluginWidget): """ CONF_SECTION = 'historylog' CONFIGWIDGET_CLASS = HistoryConfigPage - # Signals focus_changed = Signal() def __init__(self, parent): @@ -85,8 +85,10 @@ def __init__(self, parent): self.editors = [] self.filenames = [] self.icons = [] - - SpyderPluginWidget.__init__(self, parent) + if PYQT5: + SpyderPluginWidget.__init__(self, parent, main = parent) + else: + SpyderPluginWidget.__init__(self, parent) # Initialize plugin self.initialize_plugin() diff --git a/spyderlib/plugins/inspector.py b/spyderlib/plugins/inspector.py index 3789c944f8e..f8c0783106f 100644 --- a/spyderlib/plugins/inspector.py +++ b/spyderlib/plugins/inspector.py @@ -6,6 +6,7 @@ """Object Inspector Plugin""" +from spyderlib.qt import PYQT5 from spyderlib.qt.QtGui import (QHBoxLayout, QVBoxLayout, QLabel, QSizePolicy, QMenu, QToolButton, QGroupBox, QFontComboBox, QActionGroup, QFontDialog, QWidget, QComboBox, @@ -352,11 +353,13 @@ class ObjectInspector(SpyderPluginWidget): CONF_SECTION = 'inspector' CONFIGWIDGET_CLASS = ObjectInspectorConfigPage LOG_PATH = get_conf_path(CONF_SECTION) - # Signals focus_changed = Signal() def __init__(self, parent): - SpyderPluginWidget.__init__(self, parent) + if PYQT5: + SpyderPluginWidget.__init__(self, parent, main = parent) + else: + SpyderPluginWidget.__init__(self, parent) self.internal_shell = None diff --git a/spyderlib/plugins/ipythonconsole.py b/spyderlib/plugins/ipythonconsole.py index 49a76985fd9..fa429f8f80c 100644 --- a/spyderlib/plugins/ipythonconsole.py +++ b/spyderlib/plugins/ipythonconsole.py @@ -21,6 +21,7 @@ import sys # Qt imports +from spyderlib.qt import PYQT5 from spyderlib.qt.QtGui import (QVBoxLayout, QHBoxLayout, QFormLayout, QMessageBox, QGroupBox, QDialogButtonBox, QDialog, QTabWidget, QFontComboBox, @@ -573,10 +574,12 @@ class IPythonConsole(SpyderPluginWidget): # Signals focus_changed = Signal() edit_goto = Signal(str, int, str) - focus_changed = Signal() def __init__(self, parent): - SpyderPluginWidget.__init__(self, parent) + if PYQT5: + SpyderPluginWidget.__init__(self, parent, main = parent) + else: + SpyderPluginWidget.__init__(self, parent) self.tabwidget = None self.menu_actions = None diff --git a/spyderlib/plugins/onlinehelp.py b/spyderlib/plugins/onlinehelp.py index b07c28d792d..c7732f948e6 100644 --- a/spyderlib/plugins/onlinehelp.py +++ b/spyderlib/plugins/onlinehelp.py @@ -24,6 +24,7 @@ class OnlineHelp(PydocBrowser, SpyderPluginMixin): sig_option_changed = Signal(str, object) CONF_SECTION = 'onlinehelp' LOG_PATH = get_conf_path(CONF_SECTION) + def __init__(self, parent): self.main = parent PydocBrowser.__init__(self, parent) diff --git a/spyderlib/plugins/projectexplorer.py b/spyderlib/plugins/projectexplorer.py index 6ad4ca8cf3e..72e17fc8ae8 100644 --- a/spyderlib/plugins/projectexplorer.py +++ b/spyderlib/plugins/projectexplorer.py @@ -20,6 +20,7 @@ class ProjectExplorer(ProjectExplorerWidget, SpyderPluginMixin): """Project explorer plugin""" CONF_SECTION = 'project_explorer' + open_terminal = Signal(str) open_interpreter = Signal(str) pythonpath_changed = Signal() diff --git a/spyderlib/plugins/shortcuts.py b/spyderlib/plugins/shortcuts.py index add02350726..3c5bec60f6c 100644 --- a/spyderlib/plugins/shortcuts.py +++ b/spyderlib/plugins/shortcuts.py @@ -220,6 +220,10 @@ def setData(self, index, value, role=Qt.EditRole): return True return False + def reset(self): + self.beginResetModel() + self.endResetModel() + class ShortcutsDelegate(QItemDelegate): def __init__(self, parent=None): diff --git a/spyderlib/plugins/variableexplorer.py b/spyderlib/plugins/variableexplorer.py index d77ccbd96f1..4a783b1de57 100644 --- a/spyderlib/plugins/variableexplorer.py +++ b/spyderlib/plugins/variableexplorer.py @@ -82,6 +82,7 @@ class VariableExplorer(QStackedWidget, SpyderPluginMixin): CONF_SECTION = 'variable_explorer' CONFIGWIDGET_CLASS = VariableExplorerConfigPage sig_option_changed = Signal(str, object) + def __init__(self, parent): QStackedWidget.__init__(self, parent) SpyderPluginMixin.__init__(self, parent) diff --git a/spyderlib/plugins/workingdirectory.py b/spyderlib/plugins/workingdirectory.py index 8aca9898a1a..6d7c304a499 100644 --- a/spyderlib/plugins/workingdirectory.py +++ b/spyderlib/plugins/workingdirectory.py @@ -11,6 +11,7 @@ # pylint: disable=R0911 # pylint: disable=R0201 +from spyderlib.qt import PYQT5 from spyderlib.qt.QtGui import (QToolBar, QLabel, QGroupBox, QVBoxLayout, QHBoxLayout, QButtonGroup) from spyderlib.qt.QtCore import Signal, Slot @@ -140,6 +141,7 @@ class WorkingDirectory(QToolBar, SpyderPluginMixin): CONF_SECTION = 'workingdir' CONFIGWIDGET_CLASS = WorkingDirectoryConfigPage LOG_PATH = get_conf_path(CONF_SECTION) + sig_option_changed = Signal(str, object) set_previous_enabled = Signal(bool) set_next_enabled = Signal(bool) @@ -148,9 +150,12 @@ class WorkingDirectory(QToolBar, SpyderPluginMixin): refresh_findinfiles = Signal() set_current_console_wd = Signal(str) - def __init__(self, parent, workdir=None): - QToolBar.__init__(self, parent) - SpyderPluginMixin.__init__(self, parent) + def __init__(self, parent, workdir=None, **kwds): + if PYQT5: + super().__init__(**kwds) + else: + QToolBar.__init__(self, parent) + SpyderPluginMixin.__init__(self, parent) # Initialize plugin self.initialize_plugin() diff --git a/spyderlib/qt/QtCore.py b/spyderlib/qt/QtCore.py index ac23049c8c4..4c0f7126c1e 100644 --- a/spyderlib/qt/QtCore.py +++ b/spyderlib/qt/QtCore.py @@ -6,15 +6,22 @@ import os -if os.environ['QT_API'] == 'pyqt': - from PyQt4.QtCore import * # analysis:ignore - from PyQt4.Qt import QCoreApplication # analysis:ignore - from PyQt4.Qt import Qt # analysis:ignore - from PyQt4.QtCore import pyqtSignal as Signal # analysis:ignore - from PyQt4.QtCore import pyqtSlot as Slot # analysis:ignore - from PyQt4.QtCore import pyqtProperty as Property # analysis:ignore - from PyQt4.QtCore import QT_VERSION_STR as __version__ +if os.environ['QT_API'] == 'pyqt5': + from PyQt5.QtCore import * # analysis:ignore + from PyQt5.QtCore import QCoreApplication + from PyQt5.QtCore import pyqtSignal as Signal + from PyQt5.QtCore import pyqtSlot as Slot + from PyQt5.QtCore import pyqtProperty as Property + from PyQt5.QtCore import QT_VERSION_STR as __version__ +elif os.environ['QT_API'] == 'pyqt': + from PyQt4.QtCore import * # analysis:ignore + from PyQt4.Qt import QCoreApplication # analysis:ignore + from PyQt4.Qt import Qt # analysis:ignore + from PyQt4.QtCore import pyqtSignal as Signal # analysis:ignore + from PyQt4.QtCore import pyqtSlot as Slot # analysis:ignore + from PyQt4.QtCore import pyqtProperty as Property # analysis:ignore + from PyQt4.QtCore import QT_VERSION_STR as __version__ # analysis:ignore else: import PySide.QtCore - __version__ = PySide.QtCore.__version__ # analysis:ignore - from PySide.QtCore import * # analysis:ignore + __version__ = PySide.QtCore.__version__ # analysis:ignore + from PySide.QtCore import * # analysis:ignore diff --git a/spyderlib/qt/QtGui.py b/spyderlib/qt/QtGui.py index 2eae128beff..a757e90539d 100644 --- a/spyderlib/qt/QtGui.py +++ b/spyderlib/qt/QtGui.py @@ -6,8 +6,15 @@ import os -if os.environ['QT_API'] == 'pyqt': - from PyQt4.Qt import QKeySequence, QTextCursor # analysis:ignore - from PyQt4.QtGui import * # analysis:ignore +if os.environ['QT_API'] == 'pyqt5': + from PyQt5.QtCore import QSortFilterProxyModel # analysis:ignore + from PyQt5.QtPrintSupport import (QPrinter, QPrintDialog, # analysis:ignore + QAbstractPrintDialog) + from PyQt5.QtPrintSupport import QPrintPreviewDialog # analysis:ignore + from PyQt5.QtGui import * # analysis:ignore + from PyQt5.QtWidgets import * # analysis:ignore +elif os.environ['QT_API'] == 'pyqt': + from PyQt4.Qt import QKeySequence, QTextCursor # analysis:ignore + from PyQt4.QtGui import * # analysis:ignore else: - from PySide.QtGui import * # analysis:ignore + from PySide.QtGui import * # analysis:ignore diff --git a/spyderlib/qt/QtNetwork.py b/spyderlib/qt/QtNetwork.py index 116a090636c..763c8f60a7f 100644 --- a/spyderlib/qt/QtNetwork.py +++ b/spyderlib/qt/QtNetwork.py @@ -6,7 +6,9 @@ import os -if os.environ['QT_API'] == 'pyqt': +if os.environ['QT_API'] == 'pyqt5': + from PyQt5.QtNetwork import * # analysis:ignore +elif os.environ['QT_API'] == 'pyqt': from PyQt4.QtNetwork import * # analysis:ignore else: - from PySide.QtNetwork import * # analysis:ignore \ No newline at end of file + from PySide.QtNetwork import * # analysis:ignore diff --git a/spyderlib/qt/QtSvg.py b/spyderlib/qt/QtSvg.py index 46d5a48dc33..f4443b669e6 100644 --- a/spyderlib/qt/QtSvg.py +++ b/spyderlib/qt/QtSvg.py @@ -6,7 +6,9 @@ import os -if os.environ['QT_API'] == 'pyqt': - from PyQt4.QtSvg import * # analysis:ignore +if os.environ['QT_API'] == 'pyqt5': + from PyQt5.QtSvg import * # analysis:ignore +elif os.environ['QT_API'] == 'pyqt': + from PyQt4.QtSvg import * # analysis:ignore else: - from PySide.QtSvg import * # analysis:ignore \ No newline at end of file + from PySide.QtSvg import * # analysis:ignore diff --git a/spyderlib/qt/QtWebKit.py b/spyderlib/qt/QtWebKit.py index 3f133d89b99..60c6dca1d13 100644 --- a/spyderlib/qt/QtWebKit.py +++ b/spyderlib/qt/QtWebKit.py @@ -6,7 +6,11 @@ import os -if os.environ['QT_API'] == 'pyqt': - from PyQt4.QtWebKit import * # analysis:ignore +if os.environ['QT_API'] == 'pyqt5': + from PyQt5.QtWebKitWidgets import QWebPage, QWebView # analysis:ignore + from PyQt5.QtWebKit import QWebSettings # analysis:ignore +elif os.environ['QT_API'] == 'pyqt': + from PyQt4.QtWebKit import (QWebPage, QWebView, # analysis:ignore + QWebSettings) else: - from PySide.QtWebKit import * # analysis:ignore \ No newline at end of file + from PySide.QtWebKit import * # analysis:ignore diff --git a/spyderlib/qt/__init__.py b/spyderlib/qt/__init__.py index 6c71c1f4931..92cd88cc216 100644 --- a/spyderlib/qt/__init__.py +++ b/spyderlib/qt/__init__.py @@ -10,12 +10,22 @@ import os os.environ.setdefault('QT_API', 'pyqt') -assert os.environ['QT_API'] in ('pyqt', 'pyside') +assert os.environ['QT_API'] in ('pyqt5', 'pyqt', 'pyside') API = os.environ['QT_API'] -API_NAME = {'pyqt': 'PyQt4', 'pyside': 'PySide'}[API] +API_NAME = {'pyqt5': 'PyQt5', 'pyqt': 'PyQt4', 'pyside': 'PySide'}[API] -if API == 'pyqt': +PYQT5 = False + +if API == 'pyqt5': + try: + from PyQt5.QtCore import PYQT_VERSION_STR as __version__ + is_old_pyqt = False + is_pyqt46 = False + PYQT5 = True + except ImportError: + pass +elif API == 'pyqt': # Spyder 2.3 is compatible with both #1 and #2 PyQt API, # but to avoid issues with IPython and other Qt plugins # we choose to support only API #2 for 2.4+ @@ -29,7 +39,7 @@ pass try: - from PyQt4.QtCore import PYQT_VERSION_STR as __version__ + from PyQt4.QtCore import PYQT_VERSION_STR as __version__ # analysis:ignore except ImportError: # Switching to PySide API = os.environ['QT_API'] = 'pyside' @@ -43,6 +53,7 @@ except AttributeError: pass + if API == 'pyside': try: from PySide import __version__ # analysis:ignore diff --git a/spyderlib/requirements.py b/spyderlib/requirements.py index a74392f2ac6..58712d0c95e 100644 --- a/spyderlib/requirements.py +++ b/spyderlib/requirements.py @@ -35,7 +35,8 @@ def check_path(): def check_qt(): """Check Qt binding requirements""" - qt_infos = dict(pyqt=("PyQt4", "4.6"), pyside=("PySide", "1.2.0")) + qt_infos = dict(pyqt5=("PyQt5", "5.2"), pyqt=("PyQt4", "4.6"), + pyside=("PySide", "1.2.0")) try: from spyderlib import qt package_name, required_ver = qt_infos[qt.API] diff --git a/spyderlib/spyder.py b/spyderlib/spyder.py index 54a8b016df5..cdc6da20ac0 100644 --- a/spyderlib/spyder.py +++ b/spyderlib/spyder.py @@ -71,6 +71,7 @@ #============================================================================== # Qt imports #============================================================================== +from spyderlib.qt import PYQT5 from spyderlib.qt.QtGui import (QApplication, QMainWindow, QSplashScreen, QPixmap, QMessageBox, QMenu, QColor, QShortcut, QKeySequence, QDockWidget, QAction, @@ -795,7 +796,7 @@ def create_edit_action(text, tr_text, icon_name): # Working directory plugin self.debug_print(" ..plugin: working directory") from spyderlib.plugins.workingdirectory import WorkingDirectory - self.workingdirectory = WorkingDirectory(self, self.init_workdir) + self.workingdirectory = WorkingDirectory(self, self.init_workdir, main=self) self.workingdirectory.register_plugin() self.toolbarslist.append(self.workingdirectory) @@ -935,9 +936,10 @@ def create_edit_action(text, tr_text, icon_name): spyder_doc = 'http://pythonhosted.org/spyder' else: spyder_doc = file_uri(spyder_doc) - doc_action = create_bookmark_action(self, spyder_doc, - _("Spyder documentation"), shortcut="F1", - icon=get_std_icon('DialogHelpButton')) + doc_action = create_action( self, _("Spyder documentation"), shortcut="F1", + icon=get_std_icon('DialogHelpButton'), + triggered=lambda : programs.start_file(spyder_doc)) + tut_action = create_action(self, _("Spyder tutorial"), triggered=self.inspector.show_tutorial) @@ -1050,8 +1052,10 @@ def add_xydoc(text, pathlist): add_actions(web_resources, webres_actions) self.help_menu_actions.append(web_resources) # Qt assistant link - qta_exe = "assistant-qt4" if sys.platform.startswith('linux') else \ - "assistant" + if sys.platform.startswith('linux') and not PYQT5: + qta_exe = "assistant-qt4" + else: + qta_exe = "assistant" qta_act = create_program_action(self, _("Qt documentation"), qta_exe) if qta_act: diff --git a/spyderlib/utils/qthelpers.py b/spyderlib/utils/qthelpers.py index 848f423bdce..343cdd5f78c 100644 --- a/spyderlib/utils/qthelpers.py +++ b/spyderlib/utils/qthelpers.py @@ -11,7 +11,7 @@ QKeyEvent, QMenu, QKeySequence, QToolButton, QPixmap) from spyderlib.qt.QtCore import (Signal, QObject, Qt, QLocale, QTranslator, - QLibraryInfo, QEvent) + QLibraryInfo, QEvent, Slot) from spyderlib.qt.compat import to_qvariant, from_qvariant import os @@ -311,8 +311,13 @@ def set_item_user_text(item, text): def create_bookmark_action(parent, url, title, icon=None, shortcut=None): """Create bookmark action""" + + @Slot() + def open_url(): + return programs.start_file(url) + return create_action( parent, title, shortcut=shortcut, icon=icon, - triggered=lambda u=url: programs.start_file(u) ) + triggered=open_url) def create_module_bookmark_actions(parent, bookmarks): diff --git a/spyderlib/widgets/arrayeditor.py b/spyderlib/widgets/arrayeditor.py index f87953ef541..34c2550a05e 100644 --- a/spyderlib/widgets/arrayeditor.py +++ b/spyderlib/widgets/arrayeditor.py @@ -315,6 +315,10 @@ def headerData(self, section, orientation, role=Qt.DisplayRole): else: return to_qvariant(labels[section]) + def reset(self): + self.beginResetModel() + self.endResetModel() + class ArrayDelegate(QItemDelegate): """Array Editor Item Delegate""" diff --git a/spyderlib/widgets/dataframeeditor.py b/spyderlib/widgets/dataframeeditor.py index fb09238264b..3f994062826 100644 --- a/spyderlib/widgets/dataframeeditor.py +++ b/spyderlib/widgets/dataframeeditor.py @@ -354,6 +354,10 @@ def columnCount(self, index=QModelIndex()): else: return self.cols_loaded + 1 + def reset(self): + self.beginResetModel() + self.endResetModel() + class DataFrameView(QTableView): """Data Frame view class""" diff --git a/spyderlib/widgets/dependencies.py b/spyderlib/widgets/dependencies.py index a6dbb5d0abd..bf36c8457dd 100644 --- a/spyderlib/widgets/dependencies.py +++ b/spyderlib/widgets/dependencies.py @@ -90,6 +90,10 @@ def data(self, index, role=Qt.DisplayRole): color.setAlphaF(.25) return to_qvariant(color) + def reset(self): + self.beginResetModel() + self.endResetModel() + class DependenciesDelegate(QItemDelegate): def __init__(self, parent=None): diff --git a/spyderlib/widgets/dicteditor.py b/spyderlib/widgets/dicteditor.py index e66a08997d0..cc84f4d9d95 100644 --- a/spyderlib/widgets/dicteditor.py +++ b/spyderlib/widgets/dicteditor.py @@ -249,7 +249,8 @@ def sort(self, column, order=Qt.AscendingOrder): self.keys = sort_against(self.keys, values, reverse) self.sizes = sort_against(self.sizes, values, reverse) self.types = sort_against(self.types, values, reverse) - self.reset() + self.beginResetModel() + self.endResetModel() def columnCount(self, qindex=QModelIndex()): """Array column number""" @@ -365,6 +366,9 @@ def flags(self, index): return Qt.ItemIsEnabled return Qt.ItemFlags(QAbstractTableModel.flags(self, index)| Qt.ItemIsEditable) + def reset(self): + self.beginResetModel() + self.endResetModel() class DictModel(ReadOnlyDictModel): """DictEditor Table Model""" diff --git a/spyderlib/widgets/externalshell/baseshell.py b/spyderlib/widgets/externalshell/baseshell.py index d25d1fc0cce..7f3e1605892 100644 --- a/spyderlib/widgets/externalshell/baseshell.py +++ b/spyderlib/widgets/externalshell/baseshell.py @@ -204,10 +204,11 @@ def closeEvent(self, event): self.is_closing = True self.process.kill() self.process.waitForFinished(100) + try: self.timer.timeout.disconnect(self.show_time) - except: - pass + except RuntimeError: + pass def set_running_state(self, state=True): self.set_buttons_runnning_state(state) diff --git a/spyderlib/widgets/externalshell/pythonshell.py b/spyderlib/widgets/externalshell/pythonshell.py index 6bfcfef2484..914faeac8d7 100644 --- a/spyderlib/widgets/externalshell/pythonshell.py +++ b/spyderlib/widgets/externalshell/pythonshell.py @@ -13,6 +13,7 @@ from spyderlib.qt.QtGui import QApplication, QMessageBox, QSplitter, QMenu from spyderlib.qt.QtCore import QProcess, Signal, Slot, Qt +from spyderlib.qt.QtCore import QProcessEnvironment from spyderlib.qt.compat import getexistingdirectory # Local imports @@ -37,6 +38,8 @@ class ExtPythonShellWidget(PythonShellWidget): wait_for_ready_read = Signal() + go_to_error = Signal(str) + focus_changed = Signal() def __init__(self, parent, history_filename, profile=False): PythonShellWidget.__init__(self, parent, history_filename, profile) @@ -536,7 +539,11 @@ def create_process(self): env = [p for p in env if not p.startswith('PYTHONOPTIMIZE')] # 3. - self.process.setEnvironment(env) + processEnvironment = QProcessEnvironment() + for envItem in env: + envName, separator, envValue = envItem.partition('=') + processEnvironment.insert(envName, envValue) + self.process.setProcessEnvironment(processEnvironment) self.process.start(self.pythonexecutable, p_args) #-------------------------Python specific------------------------------ diff --git a/spyderlib/widgets/externalshell/sitecustomize.py b/spyderlib/widgets/externalshell/sitecustomize.py index 23f3d8a578f..50daa9dfe1a 100644 --- a/spyderlib/widgets/externalshell/sitecustomize.py +++ b/spyderlib/widgets/externalshell/sitecustomize.py @@ -257,8 +257,13 @@ def open_in_spyder(source, lineno=1): _print("Can't open file %s" % source, file=sys.stderr) builtins.open_in_spyder = open_in_spyder - if os.environ["QT_API"] == 'pyqt': + if os.environ["QT_API"] == 'pyqt5': + from PyQt5 import QtCore + # Removing PyQt's PyOS_InputHook implementation: + QtCore.pyqtRemoveInputHook() + elif os.environ["QT_API"] == 'pyqt': from PyQt4 import QtCore + QtCore.pyqtRemoveInputHook() elif os.environ["QT_API"] == 'pyside': from PySide import QtCore #analysis:ignore diff --git a/spyderlib/widgets/externalshell/systemshell.py b/spyderlib/widgets/externalshell/systemshell.py index 9c5210eaf14..d385e9db5fe 100644 --- a/spyderlib/widgets/externalshell/systemshell.py +++ b/spyderlib/widgets/externalshell/systemshell.py @@ -9,7 +9,8 @@ import os from spyderlib.qt.QtGui import QMessageBox -from spyderlib.qt.QtCore import QProcess, Signal, QTextCodec +from spyderlib.qt.QtCore import (QProcess, Signal, QTextCodec, + QProcessEnvironment) LOCALE_CODEC = QTextCodec.codecForLocale() CP850_CODEC = QTextCodec.codecForName('cp850') @@ -58,8 +59,15 @@ def create_process(self): # PYTHONPATH (in case we use Python in this terminal, e.g. py2exe) env = [to_text_string(_path) for _path in self.process.systemEnvironment()] + + processEnvironment = QProcessEnvironment() + for envItem in env: + envName, separator, envValue = envItem.partition('=') + processEnvironment.insert(envName, envValue) + add_pathlist_to_PYTHONPATH(env, self.path) - self.process.setEnvironment(env) + self.process.setProcessEnvironment(processEnvironment) + # Working directory if self.wdir is not None: diff --git a/spyderlib/widgets/importwizard.py b/spyderlib/widgets/importwizard.py index 32f83538f04..d12fad520b0 100644 --- a/spyderlib/widgets/importwizard.py +++ b/spyderlib/widgets/importwizard.py @@ -324,6 +324,9 @@ def parse_data_type(self, index, **kwargs): except Exception as instance: print(instance) + def reset(self): + self.beginResetModel() + self.endResetModel() class PreviewTable(QTableView): """Import wizard preview widget""" diff --git a/spyderlib/widgets/internalshell.py b/spyderlib/widgets/internalshell.py index c3b01b18df0..ddd2dcd18d3 100644 --- a/spyderlib/widgets/internalshell.py +++ b/spyderlib/widgets/internalshell.py @@ -122,6 +122,7 @@ class InternalShell(PythonShellWidget): status = Signal(str) refresh = Signal() + go_to_error = Signal(str) focus_changed = Signal() def __init__(self, parent=None, namespace=None, commands=[], message=None, diff --git a/spyderlib/widgets/projectexplorer.py b/spyderlib/widgets/projectexplorer.py index 5c42b9b2557..6791b67fce2 100644 --- a/spyderlib/widgets/projectexplorer.py +++ b/spyderlib/widgets/projectexplorer.py @@ -10,6 +10,7 @@ from __future__ import print_function +from spyderlib.qt import PYQT5 from spyderlib.qt.QtGui import (QVBoxLayout, QLabel, QHBoxLayout, QWidget, QFileIconProvider, QMessageBox, QInputDialog, QLineEdit, QPushButton, QHeaderView, @@ -664,7 +665,10 @@ def toggle_hscrollbar(self, checked): self.show_hscrollbar = checked self.header().setStretchLastSection(not checked) self.header().setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) - self.header().setResizeMode(QHeaderView.ResizeToContents) + if PYQT5: + self.header().setSectionResizeMode(QHeaderView.ResizeToContents) + else: + self.header().setResizeMode(QHeaderView.ResizeToContents) def set_folder_names(self, folder_names): """Set folder names""" diff --git a/spyderlib/widgets/shell.py b/spyderlib/widgets/shell.py index c2b2c61a069..dd21ef97ef6 100644 --- a/spyderlib/widgets/shell.py +++ b/spyderlib/widgets/shell.py @@ -972,6 +972,7 @@ class TerminalWidget(ShellBaseWidget): COM = 'rem' if os.name == 'nt' else '#' INITHISTORY = ['%s *** Spyder Terminal History Log ***' % COM, COM,] SEPARATOR = '%s%s ---(%s)---' % (os.linesep*2, COM, time.ctime()) + go_to_error = Signal(str) def __init__(self, parent, history_filename, profile=False): ShellBaseWidget.__init__(self, parent, history_filename, profile) diff --git a/spyderplugins/p_breakpoints.py b/spyderplugins/p_breakpoints.py index b41177a2adf..a729059fe81 100644 --- a/spyderplugins/p_breakpoints.py +++ b/spyderplugins/p_breakpoints.py @@ -24,6 +24,7 @@ class Breakpoints(BreakpointWidget, SpyderPluginMixin): """Breakpoint list""" CONF_SECTION = 'breakpoints' + # CONFIGWIDGET_CLASS = BreakpointConfigPage def __init__(self, parent=None): BreakpointWidget.__init__(self, parent=parent) diff --git a/spyderplugins/p_pylint.py b/spyderplugins/p_pylint.py index 77dceee20e7..f9b62ec7405 100644 --- a/spyderplugins/p_pylint.py +++ b/spyderplugins/p_pylint.py @@ -76,7 +76,7 @@ class Pylint(PylintWidget, SpyderPluginMixin): CONF_SECTION = 'pylint' CONFIGWIDGET_CLASS = PylintConfigPage edit_goto = Signal(str, int, str) - + def __init__(self, parent=None): PylintWidget.__init__(self, parent=parent, max_entries=self.get_option('max_entries', 50)) diff --git a/spyderplugins/widgets/breakpointsgui.py b/spyderplugins/widgets/breakpointsgui.py index 0f30fcc4feb..cec2c5abcd5 100644 --- a/spyderplugins/widgets/breakpointsgui.py +++ b/spyderplugins/widgets/breakpointsgui.py @@ -113,6 +113,10 @@ def data(self, index, role=Qt.DisplayRole): else: return to_qvariant() + def reset(self): + self.beginResetModel() + self.endResetModel() + class BreakpointDelegate(QItemDelegate): def __init__(self, parent=None): diff --git a/spyderplugins/widgets/profilergui.py b/spyderplugins/widgets/profilergui.py index f62679b675e..ea9f3c6f197 100644 --- a/spyderplugins/widgets/profilergui.py +++ b/spyderplugins/widgets/profilergui.py @@ -21,7 +21,8 @@ from spyderlib.qt.QtGui import (QHBoxLayout, QWidget, QMessageBox, QVBoxLayout, QLabel, QTreeWidget, QTreeWidgetItem, QApplication, QColor) -from spyderlib.qt.QtCore import Signal, QProcess, QByteArray, Qt, QTextCodec +from spyderlib.qt.QtCore import (Signal, QProcess, QByteArray, Qt, QTextCodec, + QProcessEnvironment) locale_codec = QTextCodec.codecForLocale() from spyderlib.qt.compat import getopenfilename, getsavefilename @@ -256,7 +257,11 @@ def start(self, wdir=None, args=None, pythonpath=None): env = [to_text_string(_pth) for _pth in self.process.systemEnvironment()] baseshell.add_pathlist_to_PYTHONPATH(env, pythonpath) - self.process.setEnvironment(env) + processEnvironment = QProcessEnvironment() + for envItem in env: + envName, separator, envValue = envItem.partition('=') + processEnvironment.insert(envName, envValue) + self.process.setProcessEnvironment(processEnvironment) self.output = '' self.error_output = ''