src/eric7/MicroPython/MicroPythonWindow.py

branch
eric7
changeset 10518
1682f3203ae5
child 10523
e4069ddd7dc7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/MicroPythonWindow.py	Sun Jan 21 15:38:51 2024 +0100
@@ -0,0 +1,374 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the standalone MicroPython window.
+"""
+
+import contextlib
+
+from PyQt6.QtCore import QSize, Qt, QUrl, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import QDesktopServices
+from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkProxyFactory
+from PyQt6.QtWidgets import QDialog, QSplitter, QWidget
+
+from eric7 import Preferences
+from eric7.EricNetwork.EricNetworkProxyFactory import (
+    EricNetworkProxyFactory,
+    proxyAuthenticationRequired,
+)
+from eric7.EricWidgets import EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
+from eric7.EricWidgets.EricMainWindow import EricMainWindow
+from eric7.EricWidgets.EricSideBar import EricSideBar, EricSideBarSide
+from eric7.MicroPython.MicroPythonWidget import MicroPythonWidget
+from eric7.PipInterface.Pip import Pip
+from eric7.QScintilla.MiniEditor import MiniEditor
+from eric7.SystemUtilities import FileSystemUtilities
+
+try:
+    from eric7.EricNetwork.EricSslErrorHandler import (
+        EricSslErrorHandler,
+        EricSslErrorState,
+    )
+
+    SSL_AVAILABLE = True
+except ImportError:
+    SSL_AVAILABLE = False
+
+
+class MicroPythonWindow(EricMainWindow):
+    """
+    Class implementing the standalone MicroPython window.
+
+    @signal preferencesChanged() emitted after the preferences were changed
+    """
+
+    preferencesChanged = pyqtSignal()
+
+    def __init__(self, parent=None):
+        """
+        Constructor
+
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super().__init__(parent)
+
+        self.__pip = Pip(self)
+
+        # create the window layout
+        self.__mpyWidget = MicroPythonWidget(parent=self, forMPyWindow=True)
+        self.__mpyWidget.aboutToDisconnect.connect(self.__deviceDisconnect)
+
+        self.__bottomSidebar = EricSideBar(
+            EricSideBarSide.SOUTH, Preferences.getUI("IconBarSize")
+        )
+        self.__bottomSidebar.setIconBarColor(Preferences.getUI("IconBarColor"))
+
+        self.__verticalSplitter = QSplitter(Qt.Orientation.Vertical)
+        self.__verticalSplitter.setChildrenCollapsible(False)
+        self.__verticalSplitter.addWidget(self.__mpyWidget)
+        self.__verticalSplitter.addWidget(self.__bottomSidebar)
+        self.setCentralWidget(self.__verticalSplitter)
+
+        self.setWindowTitle(self.tr("MicroPython / CircuitPython Devices"))
+
+        g = Preferences.getGeometry("MPyWindowGeometry")
+        if g.isEmpty():
+            s = QSize(800, 1000)
+            self.resize(s)
+        else:
+            self.restoreGeometry(g)
+        self.__verticalSplitter.setSizes(
+            Preferences.getMicroPython("MPyWindowSplitterSizes")
+        )
+
+        # register the objects
+        ericApp().registerObject("UserInterface", self)
+        ericApp().registerObject("ViewManager", self)
+        ericApp().registerObject("Pip", self.__pip)
+        ericApp().registerObject("MicroPython", self.__mpyWidget)
+
+        # attributes to manage the open editors
+        self.__editors = []
+        self.__activeEditor = None
+
+        ericApp().focusChanged.connect(self.__appFocusChanged)
+
+        # network related setup
+        if Preferences.getUI("UseSystemProxy"):
+            QNetworkProxyFactory.setUseSystemConfiguration(True)
+        else:
+            self.__proxyFactory = EricNetworkProxyFactory()
+            QNetworkProxyFactory.setApplicationProxyFactory(self.__proxyFactory)
+            QNetworkProxyFactory.setUseSystemConfiguration(False)
+
+        self.__networkManager = QNetworkAccessManager(self)
+        self.__networkManager.proxyAuthenticationRequired.connect(
+            proxyAuthenticationRequired
+        )
+        if SSL_AVAILABLE:
+            self.__sslErrorHandler = EricSslErrorHandler(self)
+            self.__networkManager.sslErrors.connect(self.__sslErrors)
+        self.__replies = []
+
+        self.__bottomSidebar.setVisible(False)
+
+    def closeEvent(self, evt):
+        """
+        Protected event handler for the close event.
+
+        @param evt reference to the close event
+            <br />This event is simply accepted after the history has been
+            saved and all window references have been deleted.
+        @type QCloseEvent
+        """
+        Preferences.setGeometry("MPyWindowGeometry", self.saveGeometry())
+        Preferences.setMicroPython(
+            "MPyWindowSplitterSizes", self.__verticalSplitter.sizes()
+        )
+
+        for editor in self.__editors[:]:
+            with contextlib.suppress(RuntimeError):
+                editor.close()
+
+        evt.accept()
+
+    def __sslErrors(self, reply, errors):
+        """
+        Private slot to handle SSL errors.
+
+        @param reply reference to the reply object
+        @type QNetworkReply
+        @param errors list of SSL errors
+        @type list of QSslError
+        """
+        ignored = self.__sslErrorHandler.sslErrorsReply(reply, errors)[0]
+        if ignored == EricSslErrorState.NOT_IGNORED:
+            self.__downloadCancelled = True
+
+    #######################################################################
+    ## Methods below implement user interface methods needed by the
+    ## MicroPython widget.
+    #######################################################################
+
+    def addSideWidget(
+        self,
+        side,  # noqa U100
+        widget,
+        icon,
+        label,
+    ):
+        """
+        Public method to add a widget to the sides.
+
+        @param side side to add the widget to
+        @type UserInterfaceSide
+        @param widget reference to the widget to add
+        @type QWidget
+        @param icon icon to be used
+        @type QIcon
+        @param label label text to be shown
+        @type str
+        """
+        self.__bottomSidebar.addTab(widget, icon, label)
+        self.__bottomSidebar.setVisible(True)
+
+    def showSideWidget(self, widget):
+        """
+        Public method to show a specific widget placed in the side widgets.
+
+        @param widget reference to the widget to be shown
+        @type QWidget
+        """
+        index = self.__bottomSidebar.indexOf(widget)
+        if index != -1:
+            self.__bottomSidebar.show()
+            self.__bottomSidebar.setCurrentIndex(index)
+            self.__bottomSidebar.raise_()
+
+    def removeSideWidget(self, widget):
+        """
+        Public method to remove a widget added using addSideWidget().
+
+        @param widget reference to the widget to remove
+        @type QWidget
+        """
+        index = self.__bottomSidebar.indexOf(widget)
+        if index != -1:
+            self.__bottomSidebar.removeTab(index)
+
+        self.__bottomSidebar.setVisible(self.__bottomSidebar.count() > 0)
+
+    def networkAccessManager(self):
+        """
+        Public method to get a reference to the network access manager object.
+
+        @return reference to the network access manager object
+        @rtype QNetworkAccessManager
+        """
+        return self.__networkManager
+
+    def launchHelpViewer(self, url):
+        """
+        Public slot to start the help viewer/web browser.
+
+        @param url URL to be opened
+        @type str or QUrl
+        """
+        started = QDesktopServices.openUrl(QUrl(url))
+        if not started:
+            EricMessageBox.critical(
+                self, self.tr("Open Browser"), self.tr("Could not start a web browser")
+            )
+
+    @pyqtSlot()
+    @pyqtSlot(str)
+    def showPreferences(self, pageName=None):
+        """
+        Public slot to set the preferences.
+
+        @param pageName name of the configuration page to show
+        @type str
+        """
+        from eric7.Preferences.ConfigurationDialog import (
+            ConfigurationDialog,
+            ConfigurationMode,
+        )
+
+        dlg = ConfigurationDialog(
+            None,
+            "Configuration",
+            True,
+            fromEric=True,
+            displayMode=ConfigurationMode.MICROPYTHONMODE,
+        )
+        dlg.show()
+        if pageName is not None:
+            dlg.showConfigurationPageByName(pageName)
+        else:
+            dlg.showConfigurationPageByName("empty")
+        dlg.exec()
+        if dlg.result() == QDialog.DialogCode.Accepted:
+            dlg.setPreferences()
+            Preferences.syncPreferences()
+            self.__preferencesChanged()
+
+    @pyqtSlot()
+    def __preferencesChanged(self):
+        """
+        Private slot to handle a change of the preferences.
+        """
+        self.__bottomSidebar.setIconBarColor(Preferences.getUI("IconBarColor"))
+        self.__bottomSidebar.setIconBarSize(Preferences.getUI("IconBarSize"))
+
+        if Preferences.getUI("UseSystemProxy"):
+            QNetworkProxyFactory.setUseSystemConfiguration(True)
+        else:
+            self.__proxyFactory = EricNetworkProxyFactory()
+            QNetworkProxyFactory.setApplicationProxyFactory(self.__proxyFactory)
+            QNetworkProxyFactory.setUseSystemConfiguration(False)
+
+        self.preferencesChanged.emit()
+
+    #######################################################################
+    ## Methods below implement view manager methods needed by the
+    ## MicroPython widget.
+    #######################################################################
+
+    def activeWindow(self):
+        """
+        Public method to get a reference to the active editor.
+
+        @return reference to the active editor
+        @rtype MiniEditor
+        """
+        return self.__activeEditor
+
+    def getEditor(self, fn):
+        """
+        Public method to return the editor displaying the given file.
+
+        @param fn filename to look for
+        @type str
+        """
+        for editor in self.__editors:
+            if editor.getFileName() == fn:
+                editor.raise_()
+                break
+        else:
+            editor = MiniEditor(filename=fn, parent=self)
+            editor.closing.connect(lambda: self.__editorClosing(editor))
+            editor.show()
+
+            self.__editors.append(editor)
+
+    def newEditorWithText(self, text, language="", fileName=""):
+        """
+        Public method to generate a new editor with a given text and associated file
+        name.
+
+        @param text text for the editor
+        @type str
+        @param language source language (defaults to "")
+        @type str (optional)
+        @param fileName associated file name (defaults to "")
+        @type str (optional)
+        """
+        editor = MiniEditor(filename=fileName, parent=self)
+        editor.closing.connect(lambda: self.__editorClosing(editor))
+        editor.setText(text, filetype=language)
+        editor.setLanguage(fileName)
+        editor.show()
+
+        self.__editors.append(editor)
+
+    def __editorClosing(self, editor):
+        """
+        Private method called, when an editor is closing.
+
+        @param editor reference to the closing editor
+        @type MiniEditor
+        """
+        with contextlib.suppress(ValueError):
+            self.__editors.remove(editor)
+            del editor
+
+        if self.__editors:
+            # make the last one (i.e. most recently opened one) the active editor
+            self.__activeEditor = self.__editors[-1]
+        else:
+            self.__activeEditor = None
+
+    @pyqtSlot(QWidget, QWidget)
+    def __appFocusChanged(self, old, now):
+        """
+        Private slot to track the application focus.
+
+        @param old reference to the widget loosing focus
+        @type QWidget
+        @param now reference to the widget gaining focus
+        @type QWidget
+        """
+        if now is None:
+            return
+
+        for editor in self.__editors:
+            if now in editor.findChildren(QWidget):
+                self.__activeEditor = editor
+                break
+
+    @pyqtSlot()
+    def __deviceDisconnect(self):
+        """
+        Private slot handling the device being disconnected.
+
+        This closes all editors directly connected to the device about to
+        be disconnected.
+        """
+        for editor in self.__editors[:]:
+            if FileSystemUtilities.isDeviceFileName(editor.getFileName()):
+                editor.close()

eric ide

mercurial