Wed, 31 Jul 2019 20:41:39 +0200
Started rearranging menu structure and testing and fixing on CircuitPython.
--- a/eric6.e4p Tue Jul 30 19:44:27 2019 +0200 +++ b/eric6.e4p Wed Jul 31 20:41:39 2019 +0200 @@ -145,6 +145,7 @@ <Source>eric6/E5Gui/E5PasswordMeter.py</Source> <Source>eric6/E5Gui/E5PathPicker.py</Source> <Source>eric6/E5Gui/E5PathPickerDialog.py</Source> + <Source>eric6/E5Gui/E5ProcessDialog.py</Source> <Source>eric6/E5Gui/E5ProgressDialog.py</Source> <Source>eric6/E5Gui/E5SideBar.py</Source> <Source>eric6/E5Gui/E5SimpleHelpDialog.py</Source> @@ -457,6 +458,7 @@ <Source>eric6/IconEditor/cursors/cursors_rc.py</Source> <Source>eric6/MicroPython/CircuitPythonDevices.py</Source> <Source>eric6/MicroPython/EspDevices.py</Source> + <Source>eric6/MicroPython/EspFirmwareSelectionDialog.py</Source> <Source>eric6/MicroPython/MicroPythonCommandsInterface.py</Source> <Source>eric6/MicroPython/MicroPythonDevices.py</Source> <Source>eric6/MicroPython/MicroPythonFileManager.py</Source> @@ -1777,6 +1779,7 @@ <Form>eric6/Debugger/VariablesFilterDialog.ui</Form> <Form>eric6/E5Gui/E5ErrorMessageFilterDialog.ui</Form> <Form>eric6/E5Gui/E5ListSelectionDialog.ui</Form> + <Form>eric6/E5Gui/E5ProcessDialog.ui</Form> <Form>eric6/E5Gui/E5SimpleHelpDialog.ui</Form> <Form>eric6/E5Gui/E5StringListEditWidget.ui</Form> <Form>eric6/E5Gui/E5ToolBarDialog.ui</Form> @@ -1847,6 +1850,7 @@ <Form>eric6/HexEdit/HexEditReplaceWidget.ui</Form> <Form>eric6/HexEdit/HexEditSearchWidget.ui</Form> <Form>eric6/IconEditor/IconSizeDialog.ui</Form> + <Form>eric6/MicroPython/EspFirmwareSelectionDialog.ui</Form> <Form>eric6/MicroPython/MicroPythonFileManagerWidget.ui</Form> <Form>eric6/MicroPython/MicroPythonProgressInfoDialog.ui</Form> <Form>eric6/MicroPython/MicroPythonReplWidget.ui</Form>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/E5Gui/E5ProcessDialog.py Wed Jul 31 20:41:39 2019 +0200 @@ -0,0 +1,284 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog starting a process and showing its output. +""" + +from __future__ import unicode_literals +try: + str = unicode +except NameError: + pass + +import os + +from PyQt5.QtCore import QProcess, QTimer, pyqtSlot, Qt, QCoreApplication, \ + QProcessEnvironment +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QLineEdit + +from E5Gui import E5MessageBox + +from .Ui_E5ProcessDialog import Ui_E5ProcessDialog + +from Globals import strToQByteArray +import Preferences + + +class E5ProcessDialog(QDialog, Ui_E5ProcessDialog): + """ + Class implementing a dialog starting a process and showing its output. + + It starts a QProcess and displays a dialog that + shows the output of the process. The dialog is modal, + which causes a synchronized execution of the process. + """ + def __init__(self, outputTitle="", windowTitle="", parent=None): + """ + Constructor + + @param outputTitle title for the output group + @type str + @param windowTitle title of the dialog + @type str + @param parent reference to the parent widget + @type QWidget + """ + super(E5ProcessDialog, self).__init__(parent) + self.setupUi(self) + + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) + + if windowTitle: + self.setWindowTitle(windowTitle) + if outputTitle: + self.outputGroup.setTitle(outputTitle) + + self.__process = None + + self.show() + QCoreApplication.processEvents() + + def __finish(self): + """ + Private slot called when the process finished or the user pressed + the button. + """ + if self.__process is not None and \ + self.__process.state() != QProcess.NotRunning: + self.__process.terminate() + QTimer.singleShot(2000, self.__process.kill) + self.__process.waitForFinished(3000) + + self.inputGroup.setEnabled(False) + self.inputGroup.hide() + + self.__process = None + + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) + self.buttonBox.button(QDialogButtonBox.Close).setFocus( + Qt.OtherFocusReason) + + def on_buttonBox_clicked(self, button): + """ + Private slot called by a button of the button box clicked. + + @param button button that was clicked + @type QAbstractButton + """ + if button == self.buttonBox.button(QDialogButtonBox.Close): + self.close() + elif button == self.buttonBox.button(QDialogButtonBox.Cancel): + self.statusLabel.setText(self.tr("Process canceled.")) + self.__finish() + + def __procFinished(self, exitCode, exitStatus): + """ + Private slot connected to the finished signal. + + @param exitCode exit code of the process + @type int + @param exitStatus exit status of the process + @type QProcess.ExitStatus + """ + self.__normal = (exitStatus == QProcess.NormalExit) and (exitCode == 0) + if self.__normal: + self.statusLabel.setText(self.tr("Process finished successfully.")) + elif exitStatus == QProcess.CrashExit: + self.statusLabel.setText(self.tr("Process crashed.")) + else: + self.statusLabel.setText( + self.tr("Process finished with exit code {0}") + .format(exitCode)) + self.__finish() + + def startProcess(self, program, args, workingDir=None, showArgs=True, + environment=None): + """ + Public slot used to start the process. + + @param program path of the program to be executed + @type str + @param args list of arguments for the process + @type list of str + @param workingDir working directory for the process + @type str + @param showArgs flag indicating to show the arguments + @type bool + @param environment dictionary of environment settings to add + or change for the process + @type dict + @return flag indicating a successful start of the process + @rtype bool + """ + self.errorGroup.hide() + self.__normal = False + self.__intercept = False + + if environment is None: + environment = {} + + if showArgs: + self.resultbox.append(program + ' ' + ' '.join(args)) + self.resultbox.append('') + + self.__process = QProcess() + if environment: + env = QProcessEnvironment.systemEnvironment() + for key, value in environment.items(): + env.insert(key, value) + self.__process.setProcessEnvironment(env) + + self.__process.finished.connect(self.__procFinished) + self.__process.readyReadStandardOutput.connect(self.__readStdout) + self.__process.readyReadStandardError.connect(self.__readStderr) + + if workingDir: + self.__process.setWorkingDirectory(workingDir) + + self.__process.start(program, args) + procStarted = self.__process.waitForStarted(10000) + if not procStarted: + self.buttonBox.setFocus() + self.inputGroup.setEnabled(False) + E5MessageBox.critical( + self, + self.tr('Process Generation Error'), + self.tr( + '<p>The process <b>{0}</b> could not be started.</p>' + ).format(program)) + else: + self.inputGroup.setEnabled(True) + self.inputGroup.show() + + return procStarted + + def normalExit(self): + """ + Public method to check for a normal process termination. + + @return flag indicating normal process termination + @rtype bool + """ + return self.__normal + + def normalExitWithoutErrors(self): + """ + Public method to check for a normal process termination without + error messages. + + @return flag indicating normal process termination + @rtype bool + """ + return self.__normal and self.errors.toPlainText() == "" + + def __readStdout(self): + """ + Private slot to handle the readyReadStandardOutput signal. + + It reads the output of the process and inserts it into the + output pane. + """ + if self.__process is not None: + s = str(self.__process.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace') + self.resultbox.insertPlainText(s) + self.resultbox.ensureCursorVisible() + + QCoreApplication.processEvents() + + def __readStderr(self): + """ + Private slot to handle the readyReadStandardError signal. + + It reads the error output of the process and inserts it into the + error pane. + """ + if self.__process is not None: + s = str(self.__process.readAllStandardError(), + Preferences.getSystem("IOEncoding"), + 'replace') + + self.errorGroup.show() + self.errors.insertPlainText(s) + self.errors.ensureCursorVisible() + + QCoreApplication.processEvents() + + def on_passwordCheckBox_toggled(self, isOn): + """ + Private slot to handle the password checkbox toggled. + + @param isOn flag indicating the status of the check box + @type bool + """ + if isOn: + self.input.setEchoMode(QLineEdit.Password) + else: + self.input.setEchoMode(QLineEdit.Normal) + + @pyqtSlot() + def on_sendButton_clicked(self): + """ + Private slot to send the input to the git process. + """ + inputTxt = self.input.text() + inputTxt += os.linesep + + if self.passwordCheckBox.isChecked(): + self.errors.insertPlainText(os.linesep) + self.errors.ensureCursorVisible() + else: + self.errors.insertPlainText(inputTxt) + self.errors.ensureCursorVisible() + + self.__process.write(strToQByteArray(inputTxt)) + + self.passwordCheckBox.setChecked(False) + self.input.clear() + + def on_input_returnPressed(self): + """ + Private slot to handle the press of the return key in the input field. + """ + self.__intercept = True + self.on_sendButton_clicked() + + def keyPressEvent(self, evt): + """ + Protected slot to handle a key press event. + + @param evt the key press event (QKeyEvent) + """ + if self.__intercept: + self.__intercept = False + evt.accept() + return + + super(E5ProcessDialog, self).keyPressEvent(evt)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/E5Gui/E5ProcessDialog.ui Wed Jul 31 20:41:39 2019 +0200 @@ -0,0 +1,158 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>E5ProcessDialog</class> + <widget class="QDialog" name="E5ProcessDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>593</width> + <height>499</height> + </rect> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout"> + <item> + <widget class="QGroupBox" name="outputGroup"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>2</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Output</string> + </property> + <layout class="QVBoxLayout"> + <item> + <widget class="QTextEdit" name="resultbox"> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="acceptRichText"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="statusLabel"/> + </item> + <item> + <widget class="QGroupBox" name="errorGroup"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Errors</string> + </property> + <layout class="QVBoxLayout"> + <item> + <widget class="QTextEdit" name="errors"> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="acceptRichText"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="inputGroup"> + <property name="title"> + <string>Input</string> + </property> + <layout class="QGridLayout"> + <item row="1" column="1"> + <spacer> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Expanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>327</width> + <height>29</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="sendButton"> + <property name="toolTip"> + <string>Press to send the input to the running process</string> + </property> + <property name="text"> + <string>&Send</string> + </property> + <property name="shortcut"> + <string>Alt+S</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="3"> + <widget class="E5ClearableLineEdit" name="input"> + <property name="toolTip"> + <string>Enter data to be sent to the running process</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="passwordCheckBox"> + <property name="toolTip"> + <string>Select to switch the input field to password mode</string> + </property> + <property name="text"> + <string>&Password Mode</string> + </property> + <property name="shortcut"> + <string>Alt+P</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <pixmapfunction>qPixmapFromMimeSource</pixmapfunction> + <customwidgets> + <customwidget> + <class>E5ClearableLineEdit</class> + <extends>QLineEdit</extends> + <header>E5Gui/E5LineEdit.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>resultbox</tabstop> + <tabstop>errors</tabstop> + <tabstop>input</tabstop> + <tabstop>passwordCheckBox</tabstop> + <tabstop>sendButton</tabstop> + <tabstop>buttonBox</tabstop> + </tabstops> + <resources/> + <connections/> +</ui>
--- a/eric6/MicroPython/EspDevices.py Tue Jul 30 19:44:27 2019 +0200 +++ b/eric6/MicroPython/EspDevices.py Wed Jul 31 20:41:39 2019 +0200 @@ -10,7 +10,14 @@ from __future__ import unicode_literals +import sys + from PyQt5.QtCore import pyqtSlot +from PyQt5.QtWidgets import QDialog + +from E5Gui import E5MessageBox +from E5Gui.E5ProcessDialog import E5ProcessDialog +from E5Gui.E5Application import e5App from .MicroPythonDevices import MicroPythonDevice from .MicroPythonReplWidget import HAS_QTCHART @@ -105,3 +112,111 @@ Public slot handling a data flood from the device. """ self.microPython.setActionButtons(files=True) + + def addDeviceMenuEntries(self, menu): + """ + Public method to add device specific entries to the given menu. + + @param menu reference to the context menu + @type QMenu + """ + connected = self.microPython.isConnected() + + act = menu.addAction(self.tr("Erase Flash"), + self.__eraseFlash) + act.setEnabled(not connected) + act = menu.addAction(self.tr("Flash MicroPython Firmware"), + self.__flashMicroPython) + act.setEnabled(not connected) + menu.addSeparator() + act = menu.addAction(self.tr("Flash Additional Firmware"), + self.__flashAddons) + act.setEnabled(not connected) + menu.addSeparator() + menu.addAction(self.tr("Install 'esptool.py'"), self.__installEspTool) + + @pyqtSlot() + def __eraseFlash(self): + """ + Private slot to erase the device flash memory. + """ + ok = E5MessageBox.yesNo( + self, + self.tr("Erase Flash"), + self.tr("""Shall the flash of the selected device really be""" + """ erased?""")) + if ok: + flashArgs = [ + "-u", + "-m", "esptool", + "--port", self.microPython.getCurrentPort(), + "erase_flash", + ] + dlg = E5ProcessDialog(self.tr("'esptool erase_flash' Output"), + self.tr("Erase Flash")) + res = dlg.startProcess(sys.executable, flashArgs) + if res: + dlg.exec_() + + @pyqtSlot() + def __flashMicroPython(self): + """ + Private slot to flash a MicroPython firmware to the device. + """ + from .EspFirmwareSelectionDialog import EspFirmwareSelectionDialog + dlg = EspFirmwareSelectionDialog() + if dlg.exec_() == QDialog.Accepted: + chip, firmware, _ = dlg.getData() + if chip == "esp8266": + flashAddress = "0x0000" + elif chip == "esp32": + flashAddress = "0x1000" + else: + raise ValueError(self.tr("Unsupported chip type '{0}'.") + .format(chip)) + flashArgs = [ + "-u", + "-m", "esptool", + "--chip", chip, + "--port", self.microPython.getCurrentPort(), + "write_flash", + flashAddress, + firmware, + ] + dlg = E5ProcessDialog(self.tr("'esptool write_flash' Output"), + self.tr("Flash MicroPython Firmware")) + res = dlg.startProcess(sys.executable, flashArgs) + if res: + dlg.exec_() + + @pyqtSlot() + def __flashAddons(self): + """ + Private slot to flash some additional firmware images. + """ + from .EspFirmwareSelectionDialog import EspFirmwareSelectionDialog + dlg = EspFirmwareSelectionDialog(addon=True) + if dlg.exec_() == QDialog.Accepted: + chip, firmware, flashAddress = dlg.getData() + flashArgs = [ + "-u", + "-m", "esptool", + "--chip", chip, + "--port", self.microPython.getCurrentPort(), + "write_flash", + flashAddress.lower(), + firmware, + ] + dlg = E5ProcessDialog(self.tr("'esptool write_flash' Output"), + self.tr("Flash Additional Firmware")) + res = dlg.startProcess(sys.executable, flashArgs) + if res: + dlg.exec_() + + @pyqtSlot() + def __installEspTool(self): + """ + Private slot to install the esptool package via pip. + """ + pip = e5App().getObject("Pip") + pip.installPackages(["esptool"], interpreter=sys.executable)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/MicroPython/EspFirmwareSelectionDialog.py Wed Jul 31 20:41:39 2019 +0200 @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to select the ESP chip type and the firmware to +be flashed. +""" + +from __future__ import unicode_literals + +import os + +from PyQt5.QtCore import pyqtSlot, QRegularExpression +from PyQt5.QtGui import QRegularExpressionValidator +from PyQt5.QtWidgets import QDialog, QDialogButtonBox + +from E5Gui.E5PathPicker import E5PathPickerModes + +from .Ui_EspFirmwareSelectionDialog import Ui_EspFirmwareSelectionDialog + + +class EspFirmwareSelectionDialog(QDialog, Ui_EspFirmwareSelectionDialog): + """ + Class implementing a dialog to select the ESP chip type and the firmware to + be flashed. + """ + def __init__(self, addon=False, parent=None): + """ + Constructor + + @param addon flag indicating an addon firmware + @type bool + @param parent reference to the parent widget + @type QWidget + """ + super(EspFirmwareSelectionDialog, self).__init__(parent) + self.setupUi(self) + + self.__addon = addon + + self.firmwarePicker.setMode(E5PathPickerModes.OpenFileMode) + self.firmwarePicker.setFilters( + self.tr("Firmware Files (*.bin);;All Files (*)")) + + self.espComboBox.addItems(["", "ESP32", "ESP8266"]) + + if addon: + self.__validator = QRegularExpressionValidator( + QRegularExpression(r"[0-9a-fA-F]{0,4}") + ) + self.addressEdit.setValidator(self.__validator) + else: + self.addressLabel.hide() + self.addressEdit.hide() + + msh = self.minimumSizeHint() + self.resize(max(self.width(), msh.width()), msh.height()) + + def __updateOkButton(self): + """ + Private method to update the state of the OK button. + """ + firmwareFile = self.firmwarePicker.text() + enable = (bool(self.espComboBox.currentText()) and + bool(firmwareFile) and os.path.exists(firmwareFile)) + if self.__addon: + enable &= bool(self.addressEdit.text()) + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enable) + + @pyqtSlot(str) + def on_espComboBox_currentTextChanged(self, chip): + """ + Private slot to handle the selection of a chip type. + + @param chip selected chip type + @type str + """ + self.__updateOkButton() + + @pyqtSlot(str) + def on_firmwarePicker_textChanged(self, firmware): + """ + Private slot handling a change of the firmware path. + + @param firmware path to the firmware + @type str + """ + self.__updateOkButton() + + def getData(self): + """ + Public method to get the entered data. + + @return tuple containing the selected chip type, the path of the + firmware file and the flash address + @rtype tuple of (str, str, str) + """ + if self.__addon: + address = self.addressEdit.text() + else: + address = "" + + return ( + self.espComboBox.currentText().lower(), + self.firmwarePicker.text(), + address, + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/MicroPython/EspFirmwareSelectionDialog.ui Wed Jul 31 20:41:39 2019 +0200 @@ -0,0 +1,153 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>EspFirmwareSelectionDialog</class> + <widget class="QDialog" name="EspFirmwareSelectionDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>500</width> + <height>114</height> + </rect> + </property> + <property name="windowTitle"> + <string>Flash MicroPython Firmware</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>ESP Chip Type:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="espComboBox"> + <property name="toolTip"> + <string>Select the ESP chip type</string> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToContents</enum> + </property> + </widget> + </item> + <item row="0" column="2"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Firmware:</string> + </property> + </widget> + </item> + <item row="1" column="1" colspan="2"> + <widget class="E5PathPicker" name="firmwarePicker" native="true"> + <property name="focusPolicy"> + <enum>Qt::WheelFocus</enum> + </property> + <property name="toolTip"> + <string>Enter the path of the firmware file</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="addressLabel"> + <property name="text"> + <string>Address:</string> + </property> + </widget> + </item> + <item row="2" column="1" colspan="2"> + <widget class="E5ClearableLineEdit" name="addressEdit"> + <property name="toolTip"> + <string>Enter the flash addres in the hexadecimal form</string> + </property> + <property name="maxLength"> + <number>4</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>E5PathPicker</class> + <extends>QWidget</extends> + <header>E5Gui/E5PathPicker.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>E5ClearableLineEdit</class> + <extends>QLineEdit</extends> + <header>E5Gui/E5LineEdit.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>espComboBox</tabstop> + <tabstop>firmwarePicker</tabstop> + <tabstop>addressEdit</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>EspFirmwareSelectionDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>EspFirmwareSelectionDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- a/eric6/MicroPython/MicroPythonCommandsInterface.py Tue Jul 30 19:44:27 2019 +0200 +++ b/eric6/MicroPython/MicroPythonCommandsInterface.py Wed Jul 31 20:41:39 2019 +0200 @@ -13,7 +13,7 @@ import time import os -from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QThread, QTimer +from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QThread, QTimer, QCoreApplication, QEventLoop from .MicroPythonSerialPort import MicroPythonSerialPort @@ -137,7 +137,7 @@ if not self.__serial: return False - rawReplMessage = b"raw REPL; CTRL-B to exit\r\n" + rawReplMessage = b"raw REPL; CTRL-B to exit\r\n>" softRebootMessage = b"soft reboot\r\n" self.__serial.write(b"\x02") # end raw mode if required @@ -160,13 +160,12 @@ # some MicroPython devices seem to need to be convinced in some # special way data = self.__serial.readUntil(rawReplMessage) - if self.__serial.hasTimedOut(): - return False if not data.endswith(rawReplMessage): self.__serial.write(b"\r\x01") # send CTRL-A again self.__serial.readUntil(rawReplMessage) if self.__serial.hasTimedOut(): return False + QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) self.__serial.readAll() # read all data and discard it return True @@ -717,7 +716,7 @@ if err: raise IOError(self.__shortError(err)) - def showTime(self): + def getTime(self): """ Public method to get the current time of the device. @@ -727,8 +726,17 @@ """ commands = [ "import time", - "print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))", - # __IGNORE_WARNING_M601__ + "\n".join([ + "try:", + " print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()" + "))", + # __IGNORE_WARNING_M601__ + "except AttributeError:", + " tm = time.localtime()", + " print('{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}'" + ".format(tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour," + " tm.tm_min, tm.tm_sec))", + ]), ] out, err = self.execute(commands) if err:
--- a/eric6/MicroPython/MicroPythonDevices.py Tue Jul 30 19:44:27 2019 +0200 +++ b/eric6/MicroPython/MicroPythonDevices.py Wed Jul 31 20:41:39 2019 +0200 @@ -300,7 +300,7 @@ """ pass - def addActions(self, menu): + def addDeviceMenuEntries(self, menu): """ Public method to add device specific entries to the given menu.
--- a/eric6/MicroPython/MicroPythonFileManager.py Tue Jul 30 19:44:27 2019 +0200 +++ b/eric6/MicroPython/MicroPythonFileManager.py Wed Jul 31 20:41:39 2019 +0200 @@ -45,15 +45,6 @@ @signal fsinfoDone(fsinfo) emitted after the file system information was obtained - @signal synchTimeDone() emitted after the time was synchronizde to the - device - @signal showTimeDone(dateTime) emitted after the date and time was fetched - from the connected device - @signal showVersionDone(versionInfo) emitted after the version information - was fetched from the connected device - @signal showImplementationDone(name,version) emitted after the - implementation information has been obtained - @signal error(exc) emitted with a failure message to indicate a failure during the most recent operation """ @@ -69,11 +60,6 @@ createDirectoryDone = pyqtSignal() fsinfoDone = pyqtSignal(tuple) - synchTimeDone = pyqtSignal() - showTimeDone = pyqtSignal(str) - showVersionDone = pyqtSignal(dict) - showImplementationDone = pyqtSignal(str, str) - error = pyqtSignal(str, str) def __init__(self, commandsInterface, parent=None): @@ -381,65 +367,3 @@ self.fsinfoDone.emit(fsinfo) except Exception as exc: self.error.emit("fileSystemInfo", str(exc)) - - ################################################################## - ## some non-filesystem related methods below - ################################################################## - - @pyqtSlot() - def synchronizeTime(self): - """ - Public slot to set the time of the connected device to the local - computer's time. - """ - try: - self.__commandsInterface.syncTime() - self.synchTimeDone.emit() - except Exception as exc: - self.error.emit("rmdir", str(exc)) - - @pyqtSlot() - def showTime(self): - """ - Public slot to get the current date and time of the device. - """ - try: - dt = self.__commandsInterface.showTime() - self.showTimeDone.emit(dt) - except Exception as exc: - self.error.emit("showTime", str(exc)) - - @pyqtSlot() - def showVersion(self): - """ - Public slot to get the version info for the MicroPython run by the - connected device. - """ - try: - versionInfo = self.__commandsInterface.version() - self.showVersionDone.emit(versionInfo) - except Exception as exc: - self.error.emit("showVersion", str(exc)) - - @pyqtSlot() - def showImplementation(self): - """ - Public slot to obtain some implementation related information. - """ - try: - impInfo = self.__commandsInterface.getImplementation() - if impInfo["name"] == "micropython": - name = "MicroPython" - elif impInfo["name"] == "circuitpython": - name = "CircuitPython" - elif impInfo["name"] == "unknown": - name = self.tr("unknown") - else: - name = impInfo["name"] - if impInfo["version"] == "unknown": - version = self.tr("unknown") - else: - version = impInfo["version"] - self.showImplementationDone.emit(name, version) - except Exception as exc: - self.error.emit("showVersion", str(exc))
--- a/eric6/MicroPython/MicroPythonFileManagerWidget.py Tue Jul 30 19:44:27 2019 +0200 +++ b/eric6/MicroPython/MicroPythonFileManagerWidget.py Wed Jul 31 20:41:39 2019 +0200 @@ -85,13 +85,7 @@ self.__fileManager.removeDirectoryDone.connect(self.__newDeviceList) self.__fileManager.createDirectoryDone.connect(self.__newDeviceList) self.__fileManager.deleteFileDone.connect(self.__newDeviceList) - self.__fileManager.fsinfoDone.connect(self.__shownFsInfoResult) - self.__fileManager.synchTimeDone.connect(self.__timeSynchronized) - self.__fileManager.showTimeDone.connect(self.__deviceTimeReceived) - self.__fileManager.showVersionDone.connect( - self.__deviceVersionReceived) - self.__fileManager.showImplementationDone.connect( - self.__deviceImplementationReceived) + self.__fileManager.fsinfoDone.connect(self.__fsInfoResultReceived) self.__fileManager.error.connect(self.__handleError) @@ -129,16 +123,6 @@ self.__deviceMenu.addSeparator() self.__deviceMenu.addAction( self.tr("Show Filesystem Info"), self.__showFileSystemInfo) - self.__deviceMenu.addSeparator() - self.__deviceMenu.addAction( - self.tr("Synchronize Time"), self.__synchronizeTime) - self.__deviceMenu.addAction( - self.tr("Show Time"), self.__showDeviceTime) - self.__deviceMenu.addSeparator() - self.__deviceMenu.addAction( - self.tr("Show Version"), self.__showDeviceVersion) - self.__deviceMenu.addAction( - self.tr("Show Implementation"), self.__showImplementation) def start(self): """ @@ -744,9 +728,9 @@ self.__fileManager.fileSystemInfo() @pyqtSlot(tuple) - def __shownFsInfoResult(self, fsinfo): + def __fsInfoResultReceived(self, fsinfo): """ - Private slot to show the file systom information of the device. + Private slot to show the file system information of the device. @param fsinfo tuple of tuples containing the file system name, the total size, the used size and the free size @@ -770,112 +754,3 @@ self, self.tr("Filesystem Information"), msg) - - @pyqtSlot() - def __synchronizeTime(self): - """ - Private slot to synchronize the local time to the device. - """ - self.__fileManager.synchronizeTime() - - @pyqtSlot() - def __timeSynchronized(self): - """ - Private slot handling the successful syncronization of the time. - """ - E5MessageBox.information( - self, - self.tr("Synchronize Time"), - self.tr("The time of the connected device was synchronized with" - " the local time.")) - - @pyqtSlot() - def __showDeviceTime(self): - """ - Private slot to show the date and time of the connected device. - """ - self.__fileManager.showTime() - - @pyqtSlot(str) - def __deviceTimeReceived(self, dateTimeString): - """ - Private slot handling the receipt of the device date and time. - - @param dateTimeString string containg the date and time of the device - @type str - """ - try: - date, time = dateTimeString.strip().split(None, 1) - msg = self.tr( - "<h3>Device Date and Time</h3>" - "<table>" - "<tr><td><b>Date</b></td><td>{0}</td></tr>" - "<tr><td><b>Time</b></td><td>{1}</td></tr>" - "</table>" - ).format(date, time) - except ValueError: - msg = self.tr( - "<h3>Device Date and Time</h3>" - "<p>{0}</p>" - ).format(dateTimeString.strip()) - E5MessageBox.information( - self, - self.tr("Device Date and Time"), - msg) - - @pyqtSlot() - def __showDeviceVersion(self): - """ - Private slot to show some version info about MicroPython of the device. - """ - self.__fileManager.showVersion() - - @pyqtSlot(dict) - def __deviceVersionReceived(self, versionInfo): - """ - Private slot handling the receipt of the version info. - - @param versionInfo dictionary containing the version information - @type dict - """ - if versionInfo: - msg = self.tr( - "<h3>Device Version Information</h3>" - ) - msg += "<table>" - for key, value in versionInfo.items(): - msg += "<tr><td><b>{0}</b></td><td>{1}</td></tr>".format( - key.capitalize(), value) - msg += "</table>" - else: - msg = self.tr("No version information available.") - E5MessageBox.information( - self, - self.tr("Device Version Information"), - msg) - - @pyqtSlot() - def __showImplementation(self): - """ - Private slot to show some implementation related information. - """ - self.__fileManager.showImplementation() - - @pyqtSlot(str, str) - def __deviceImplementationReceived(self, name, version): - """ - Private slot handling the receipt of implementation info. - - @param name name of the implementation - @type str - @param version version string of the implementation - @type str - """ - E5MessageBox.information( - self, - self.tr("Device Implementation Information"), - self.tr( - "<h3>Device Implementation Information</h3>" - "<p>This device contains <b>{0} {1}</b>.</p>" - ).format(name, version) - )
--- a/eric6/MicroPython/MicroPythonReplWidget.py Tue Jul 30 19:44:27 2019 +0200 +++ b/eric6/MicroPython/MicroPythonReplWidget.py Wed Jul 31 20:41:39 2019 +0200 @@ -15,7 +15,7 @@ from PyQt5.QtGui import QColor, QKeySequence, QTextCursor, QBrush from PyQt5.QtWidgets import ( QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy, - QTextEdit + QTextEdit, QToolButton ) from E5Gui.E5ZoomWidget import E5ZoomWidget @@ -163,6 +163,20 @@ self.__ui = parent + self.__superMenu = QMenu(self) + self.__superMenu.aboutToShow.connect(self.__aboutToShowSuperMenu) + + self.menuButton.setObjectName( + "micropython_supermenu_button") + self.menuButton.setIcon(UI.PixmapCache.getIcon("superMenu")) + self.menuButton.setToolTip(self.tr("pip Menu")) + self.menuButton.setPopupMode(QToolButton.InstantPopup) + self.menuButton.setToolButtonStyle(Qt.ToolButtonIconOnly) + self.menuButton.setFocusPolicy(Qt.NoFocus) + self.menuButton.setAutoRaise(True) + self.menuButton.setShowMenuInside(True) + self.menuButton.setMenu(self.__superMenu) + self.deviceIconLabel.setPixmap(MicroPythonDevices.getDeviceIcon( "", False)) @@ -345,9 +359,6 @@ menu.addAction(self.tr("Copy"), self.replEdit.copy, copyKeys) menu.addAction(self.tr("Paste"), self.__paste, pasteKeys) menu.addSeparator() - if self.__device is not None: - # allow device interface to add specific context menu entries - self.__device.addActions(menu) menu.exec_(self.replEdit.mapToGlobal(pos)) def setConnected(self, connected): @@ -376,6 +387,15 @@ self.connectButton.setToolTip(self.tr( "Press to connect the selected device")) + def isConnected(self): + """ + Public method to get the connection state. + + @return connection state + @rtype bool + """ + return self.__connected + def __showNoDeviceMessage(self): """ Private method to show a message dialog indicating a missing device. @@ -748,9 +768,9 @@ self.replEdit.zoomIn(value - self.__currentZoom) self.__currentZoom = value - def __getCurrentPort(self): + def getCurrentPort(self): """ - Private method to determine the port path of the selected device. + Public method to determine the port path of the selected device. @return path of the port of the selected device @rtype str @@ -770,7 +790,7 @@ """ Private method to connect to the selected device. """ - port = self.__getCurrentPort() + port = self.getCurrentPort() if self.__interface.connectToDevice(port): self.setConnected(True) else: @@ -999,3 +1019,149 @@ self.__fileManagerWidget.deleteLater() self.__fileManagerWidget = None + + ################################################################## + ## Super Menu related methods below + ################################################################## + + def __aboutToShowSuperMenu(self): + """ + Private slot to populate the Super Menu before showing it. + """ + self.__superMenu.clear() + + act = self.__superMenu.addAction( + self.tr("Show Version"), self.__showDeviceVersion) + act.setEnabled(self.__connected) + act = self.__superMenu.addAction( + self.tr("Show Implementation"), self.__showImplementation) + act.setEnabled(self.__connected) + self.__superMenu.addSeparator() + act = self.__superMenu.addAction( + self.tr("Synchronize Time"), self.__synchronizeTime) + act.setEnabled(self.__connected) + act = self.__superMenu.addAction( + self.tr("Show Time"), self.__showDeviceTime) + act.setEnabled(self.__connected) + self.__superMenu.addSeparator() + if self.__device: + self.__device.addDeviceMenuEntries(self.__superMenu) + + @pyqtSlot() + def __showDeviceVersion(self): + """ + Private slot to show some version info about MicroPython of the device. + """ + try: + versionInfo = self.__interface.version() + if versionInfo: + msg = self.tr( + "<h3>Device Version Information</h3>" + ) + msg += "<table>" + for key, value in versionInfo.items(): + msg += "<tr><td><b>{0}</b></td><td>{1}</td></tr>".format( + key.capitalize(), value) + msg += "</table>" + else: + msg = self.tr("No version information available.") + + E5MessageBox.information( + self, + self.tr("Device Version Information"), + msg) + except Exception as exc: + self.__showError("version()", str(exc)) + + @pyqtSlot() + def __showImplementation(self): + """ + Private slot to show some implementation related information. + """ + try: + impInfo = self.__interface.getImplementation() + if impInfo["name"] == "micropython": + name = "MicroPython" + elif impInfo["name"] == "circuitpython": + name = "CircuitPython" + elif impInfo["name"] == "unknown": + name = self.tr("unknown") + else: + name = impInfo["name"] + if impInfo["version"] == "unknown": + version = self.tr("unknown") + else: + version = impInfo["version"] + + E5MessageBox.information( + self, + self.tr("Device Implementation Information"), + self.tr( + "<h3>Device Implementation Information</h3>" + "<p>This device contains <b>{0} {1}</b>.</p>" + ).format(name, version) + ) + except Exception as exc: + self.__showError("getImplementation()", str(exc)) + + @pyqtSlot() + def __synchronizeTime(self): + """ + Private slot to set the time of the connected device to the local + computer's time. + """ + try: + self.__interface.syncTime() + + E5MessageBox.information( + self, + self.tr("Synchronize Time"), + self.tr("The time of the connected device was synchronized" + " with the local time.")) + except Exception as exc: + self.__showError("syncTime()", str(exc)) + + @pyqtSlot() + def __showDeviceTime(self): + """ + Private slot to show the date and time of the connected device. + """ + try: + dateTimeString = self.__interface.getTime() + try: + date, time = dateTimeString.strip().split(None, 1) + msg = self.tr( + "<h3>Device Date and Time</h3>" + "<table>" + "<tr><td><b>Date</b></td><td>{0}</td></tr>" + "<tr><td><b>Time</b></td><td>{1}</td></tr>" + "</table>" + ).format(date, time) + except ValueError: + msg = self.tr( + "<h3>Device Date and Time</h3>" + "<p>{0}</p>" + ).format(dateTimeString.strip()) + + E5MessageBox.information( + self, + self.tr("Device Date and Time"), + msg) + except Exception as exc: + self.__showError("getTime()", str(exc)) + + def __showError(self, method, error): + """ + Private method to show some error message. + + @param method name of the method the error occured in + @type str + @param error error message + @type str + """ + E5MessageBox.warning( + self, + self.tr("Error handling device"), + self.tr("<p>There was an error communicating with the connected" + " device.</p><p>Method: {0}</p><p>Message: {1}</p>") + .format(method, error))
--- a/eric6/MicroPython/MicroPythonReplWidget.ui Tue Jul 30 19:44:27 2019 +0200 +++ b/eric6/MicroPython/MicroPythonReplWidget.ui Wed Jul 31 20:41:39 2019 +0200 @@ -31,7 +31,7 @@ </item> <item> <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="0" colspan="3"> + <item row="1" column="0" colspan="4"> <widget class="QLabel" name="deviceInfoLabel"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> @@ -44,6 +44,9 @@ </property> </widget> </item> + <item row="0" column="3"> + <widget class="E5Led" name="deviceConnectedLed" native="true"/> + </item> <item row="0" column="0"> <widget class="QComboBox" name="deviceTypeComboBox"> <property name="sizePolicy"> @@ -54,9 +57,6 @@ </property> </widget> </item> - <item row="0" column="2"> - <widget class="E5Led" name="deviceConnectedLed" native="true"/> - </item> <item row="0" column="1"> <widget class="QToolButton" name="checkButton"> <property name="toolTip"> @@ -64,6 +64,9 @@ </property> </widget> </item> + <item row="0" column="2"> + <widget class="E5ToolButton" name="menuButton"/> + </item> </layout> </item> </layout> @@ -169,13 +172,15 @@ </property> </widget> </item> - <item> - <widget class="QLabel" name="statusLabel"/> - </item> </layout> </widget> <customwidgets> <customwidget> + <class>E5ToolButton</class> + <extends>QToolButton</extends> + <header>E5Gui/E5ToolButton.h</header> + </customwidget> + <customwidget> <class>E5Led</class> <extends>QWidget</extends> <header>E5Gui/E5Led.h</header> @@ -185,6 +190,7 @@ <tabstops> <tabstop>deviceTypeComboBox</tabstop> <tabstop>checkButton</tabstop> + <tabstop>menuButton</tabstop> <tabstop>openButton</tabstop> <tabstop>saveButton</tabstop> <tabstop>runButton</tabstop>
--- a/eric6/Preferences/__init__.py Tue Jul 30 19:44:27 2019 +0200 +++ b/eric6/Preferences/__init__.py Wed Jul 31 20:41:39 2019 +0200 @@ -1592,7 +1592,7 @@ # defaults for MicroPython microPythonDefaults = { - "SerialTimeout": 10000, # timeout in milliseconds + "SerialTimeout": 2000, # timeout in milliseconds "ReplLineWrap": True, # wrap the REPL lines } if Globals.isWindowsPlatform():