12 import re |
12 import re |
13 |
13 |
14 from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QPoint, QEvent |
14 from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QPoint, QEvent |
15 from PyQt5.QtGui import QColor, QKeySequence, QTextCursor, QBrush |
15 from PyQt5.QtGui import QColor, QKeySequence, QTextCursor, QBrush |
16 from PyQt5.QtWidgets import ( |
16 from PyQt5.QtWidgets import ( |
17 QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy |
17 QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy, |
|
18 QTextEdit |
18 ) |
19 ) |
19 |
20 |
20 from E5Gui.E5ZoomWidget import E5ZoomWidget |
21 from E5Gui.E5ZoomWidget import E5ZoomWidget |
21 from E5Gui import E5MessageBox, E5FileDialog |
22 from E5Gui import E5MessageBox, E5FileDialog |
22 from E5Gui.E5Application import e5App |
23 from E5Gui.E5Application import e5App |
133 17: QBrush(QColor(255, 255, 255)), |
134 17: QBrush(QColor(255, 255, 255)), |
134 }, |
135 }, |
135 } |
136 } |
136 |
137 |
137 |
138 |
138 # TODO: make wrapping an option for the repl edit |
|
139 # TODO: add a connect button or make the disconnect button with changing icon (see IRC) |
|
140 class MicroPythonReplWidget(QWidget, Ui_MicroPythonReplWidget): |
139 class MicroPythonReplWidget(QWidget, Ui_MicroPythonReplWidget): |
141 """ |
140 """ |
142 Class implementing the MicroPython REPL widget. |
141 Class implementing the MicroPython REPL widget. |
143 |
142 |
144 @signal dataReceived(data) emitted to send data received via the serial |
143 @signal dataReceived(data) emitted to send data received via the serial |
173 self.checkButton.setIcon(UI.PixmapCache.getIcon("question")) |
172 self.checkButton.setIcon(UI.PixmapCache.getIcon("question")) |
174 self.runButton.setIcon(UI.PixmapCache.getIcon("start")) |
173 self.runButton.setIcon(UI.PixmapCache.getIcon("start")) |
175 self.replButton.setIcon(UI.PixmapCache.getIcon("terminal")) |
174 self.replButton.setIcon(UI.PixmapCache.getIcon("terminal")) |
176 self.filesButton.setIcon(UI.PixmapCache.getIcon("filemanager")) |
175 self.filesButton.setIcon(UI.PixmapCache.getIcon("filemanager")) |
177 self.chartButton.setIcon(UI.PixmapCache.getIcon("chart")) |
176 self.chartButton.setIcon(UI.PixmapCache.getIcon("chart")) |
178 self.disconnectButton.setIcon(UI.PixmapCache.getIcon("disconnect")) |
177 self.connectButton.setIcon(UI.PixmapCache.getIcon("linkConnect")) |
179 |
178 |
180 self.__zoomLayout = QHBoxLayout() |
179 self.__zoomLayout = QHBoxLayout() |
181 spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, |
180 spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, |
182 QSizePolicy.Minimum) |
181 QSizePolicy.Minimum) |
183 self.__zoomLayout.addSpacerItem(spacerItem) |
182 self.__zoomLayout.addSpacerItem(spacerItem) |
201 self.__interface = MicroPythonCommandsInterface(self) |
200 self.__interface = MicroPythonCommandsInterface(self) |
202 self.__device = None |
201 self.__device = None |
203 self.__connected = False |
202 self.__connected = False |
204 self.setConnected(False) |
203 self.setConnected(False) |
205 |
204 |
206 # TODO: replace these by checking the button states |
|
207 self.__replRunning = False |
|
208 self.__plotterRunning = False |
|
209 self.__fileManagerRunning = False |
|
210 |
|
211 if not HAS_QTSERIALPORT: |
205 if not HAS_QTSERIALPORT: |
212 self.replEdit.setHtml(self.tr( |
206 self.replEdit.setHtml(self.tr( |
213 "<h3>The QtSerialPort package is not available.<br/>" |
207 "<h3>The QtSerialPort package is not available.<br/>" |
214 "MicroPython support is deactivated.</h3>")) |
208 "MicroPython support is deactivated.</h3>")) |
215 self.setEnabled(False) |
209 self.setEnabled(False) |
268 Private slot to handle a change in preferences. |
262 Private slot to handle a change in preferences. |
269 """ |
263 """ |
270 self.__colorScheme = Preferences.getMicroPython("ColorScheme") |
264 self.__colorScheme = Preferences.getMicroPython("ColorScheme") |
271 |
265 |
272 self.__font = Preferences.getEditorOtherFonts("MonospacedFont") |
266 self.__font = Preferences.getEditorOtherFonts("MonospacedFont") |
273 |
|
274 self.replEdit.setFontFamily(self.__font.family()) |
267 self.replEdit.setFontFamily(self.__font.family()) |
275 self.replEdit.setFontPointSize(self.__font.pointSize()) |
268 self.replEdit.setFontPointSize(self.__font.pointSize()) |
|
269 |
|
270 if Preferences.getMicroPython("ReplLineWrap"): |
|
271 self.replEdit.setLineWrapMode(QTextEdit.WidgetWidth) |
|
272 else: |
|
273 self.replEdit.setLineWrapMode(QTextEdit.NoWrap) |
276 |
274 |
277 def commandsInterface(self): |
275 def commandsInterface(self): |
278 """ |
276 """ |
279 Public method to get a reference to the commands interface object. |
277 Public method to get a reference to the commands interface object. |
280 |
278 |
365 if self.__fileManagerWidget: |
363 if self.__fileManagerWidget: |
366 self.__fileManagerWidget.deviceConnectedLed.setOn(connected) |
364 self.__fileManagerWidget.deviceConnectedLed.setOn(connected) |
367 |
365 |
368 self.deviceTypeComboBox.setEnabled(not connected) |
366 self.deviceTypeComboBox.setEnabled(not connected) |
369 |
367 |
370 self.disconnectButton.setEnabled(connected) |
368 if connected: |
|
369 self.connectButton.setIcon( |
|
370 UI.PixmapCache.getIcon("linkDisconnect")) |
|
371 self.connectButton.setToolTip(self.tr( |
|
372 "Press to disconnect the current device")) |
|
373 else: |
|
374 self.connectButton.setIcon( |
|
375 UI.PixmapCache.getIcon("linkConnect")) |
|
376 self.connectButton.setToolTip(self.tr( |
|
377 "Press to connect the selected device")) |
371 |
378 |
372 def __showNoDeviceMessage(self): |
379 def __showNoDeviceMessage(self): |
373 """ |
380 """ |
374 Private method to show a message dialog indicating a missing device. |
381 Private method to show a message dialog indicating a missing device. |
375 """ |
382 """ |
384 """ before trying again.""")) |
391 """ before trying again.""")) |
385 |
392 |
386 @pyqtSlot(bool) |
393 @pyqtSlot(bool) |
387 def on_replButton_clicked(self, checked): |
394 def on_replButton_clicked(self, checked): |
388 """ |
395 """ |
389 Private slot to connect to enable or disable the REPL widget connecting |
396 Private slot to connect to enable or disable the REPL widget. |
390 or disconnecting from the device. |
397 |
|
398 If the selected device is not connected yet, this will be done now. |
391 |
399 |
392 @param checked state of the button |
400 @param checked state of the button |
393 @type bool |
401 @type bool |
394 """ |
402 """ |
395 if not self.__device: |
403 if not self.__device: |
415 # send a Ctrl-B (exit raw mode) |
423 # send a Ctrl-B (exit raw mode) |
416 self.__interface.write(b'\x02') |
424 self.__interface.write(b'\x02') |
417 # send Ctrl-C (keyboard interrupt) |
425 # send Ctrl-C (keyboard interrupt) |
418 self.__interface.write(b'\x03') |
426 self.__interface.write(b'\x03') |
419 |
427 |
420 self.__replRunning = True |
|
421 self.__device.setRepl(True) |
428 self.__device.setRepl(True) |
422 self.replEdit.setFocus(Qt.OtherFocusReason) |
429 self.replEdit.setFocus(Qt.OtherFocusReason) |
423 else: |
430 else: |
424 self.__interface.dataReceived.disconnect(self.__processData) |
431 self.__interface.dataReceived.disconnect(self.__processData) |
425 if not self.__plotterRunning and not self.__fileManagerRunning: |
432 if (not self.chartButton.isChecked() and |
|
433 not self.filesButton.isChecked()): |
426 self.__disconnectFromDevice() |
434 self.__disconnectFromDevice() |
427 self.__replRunning = False |
|
428 self.__device.setRepl(False) |
435 self.__device.setRepl(False) |
429 self.replButton.setChecked(checked) |
436 self.replButton.setChecked(checked) |
430 |
437 |
431 @pyqtSlot() |
438 @pyqtSlot() |
432 def on_disconnectButton_clicked(self): |
439 def on_connectButton_clicked(self): |
433 """ |
440 """ |
434 Private slot to disconnect from the currently connected device. |
441 Private slot to connect to the selected device or disconnect from the |
435 """ |
442 currently connected device. |
436 self.__disconnectFromDevice() |
443 """ |
|
444 if self.__connected: |
|
445 self.__disconnectFromDevice() |
|
446 else: |
|
447 self.__connectToDevice() |
437 |
448 |
438 @pyqtSlot() |
449 @pyqtSlot() |
439 def __clear(self): |
450 def __clear(self): |
440 """ |
451 """ |
441 Private slot to clear the REPL pane. |
452 Private slot to clear the REPL pane. |
779 @pyqtSlot() |
790 @pyqtSlot() |
780 def on_runButton_clicked(self): |
791 def on_runButton_clicked(self): |
781 """ |
792 """ |
782 Private slot to execute the script of the active editor on the |
793 Private slot to execute the script of the active editor on the |
783 selected device. |
794 selected device. |
|
795 |
|
796 If the REPL is not active yet, it will be activated, which might cause |
|
797 an unconnected device to be connected. |
784 """ |
798 """ |
785 if not self.__device: |
799 if not self.__device: |
786 self.__showNoDeviceMessage() |
800 self.__showNoDeviceMessage() |
787 return |
801 return |
788 |
802 |
810 self.tr("Run Script"), |
824 self.tr("Run Script"), |
811 self.tr("""<p>Cannot run script.</p><p>Reason:""" |
825 self.tr("""<p>Cannot run script.</p><p>Reason:""" |
812 """ {0}</p>""").format(reason)) |
826 """ {0}</p>""").format(reason)) |
813 return |
827 return |
814 |
828 |
815 if not self.__replRunning: |
829 if not self.replButton.isChecked(): |
816 # switch on the REPL |
830 # activate on the REPL |
817 self.on_replButton_clicked(True) |
831 self.on_replButton_clicked(True) |
818 if self.__replRunning: |
832 if self.replButton.isChecked(): |
819 self.__device.runScript(script) |
833 self.__device.runScript(script) |
820 |
834 |
821 @pyqtSlot() |
835 @pyqtSlot() |
822 def on_openButton_clicked(self): |
836 def on_openButton_clicked(self): |
823 """ |
837 """ |
894 # send a Ctrl-B (exit raw mode) |
910 # send a Ctrl-B (exit raw mode) |
895 self.__interface.write(b'\x02') |
911 self.__interface.write(b'\x02') |
896 # send Ctrl-C (keyboard interrupt) |
912 # send Ctrl-C (keyboard interrupt) |
897 self.__interface.write(b'\x03') |
913 self.__interface.write(b'\x03') |
898 |
914 |
899 self.__plotterRunning = True |
|
900 self.__device.setPlotter(True) |
915 self.__device.setPlotter(True) |
901 else: |
916 else: |
902 if self.__chartWidget.isDirty(): |
917 if self.__chartWidget.isDirty(): |
903 res = E5MessageBox.okToClearData( |
918 res = E5MessageBox.okToClearData( |
904 self, |
919 self, |
912 self.__interface.dataReceived.disconnect( |
927 self.__interface.dataReceived.disconnect( |
913 self.__chartWidget.processData) |
928 self.__chartWidget.processData) |
914 self.__chartWidget.dataFlood.disconnect( |
929 self.__chartWidget.dataFlood.disconnect( |
915 self.handleDataFlood) |
930 self.handleDataFlood) |
916 |
931 |
917 if not self.__replRunning and not self.__fileManagerRunning: |
932 if (not self.replButton.isChecked() and |
|
933 not self.filesButton.isChecked()): |
918 self.__disconnectFromDevice() |
934 self.__disconnectFromDevice() |
919 |
935 |
920 self.__plotterRunning = False |
|
921 self.__device.setPlotter(False) |
936 self.__device.setPlotter(False) |
922 self.__ui.removeSideWidget(self.__chartWidget) |
937 self.__ui.removeSideWidget(self.__chartWidget) |
923 |
938 |
924 self.__chartWidget.deleteLater() |
939 self.__chartWidget.deleteLater() |
925 self.__chartWidget = None |
940 self.__chartWidget = None |
929 @pyqtSlot() |
944 @pyqtSlot() |
930 def handleDataFlood(self): |
945 def handleDataFlood(self): |
931 """ |
946 """ |
932 Public slot handling a data flood from the device. |
947 Public slot handling a data flood from the device. |
933 """ |
948 """ |
934 self.on_disconnectButton_clicked() |
949 self.on_connectButton_clicked() |
935 self.__device.handleDataFlood() |
950 self.__device.handleDataFlood() |
936 |
951 |
937 @pyqtSlot(bool) |
952 @pyqtSlot(bool) |
938 def on_filesButton_clicked(self, checked): |
953 def on_filesButton_clicked(self, checked): |
939 """ |
954 """ |
940 Private slot to open a file manager window to the connected device. |
955 Private slot to open a file manager window to the connected device. |
|
956 |
|
957 If the selected device is not connected yet, this will be done now. |
941 |
958 |
942 @param checked state of the button |
959 @param checked state of the button |
943 @type bool |
960 @type bool |
944 """ |
961 """ |
945 if not self.__device: |
962 if not self.__device: |
966 UI.PixmapCache.getIcon("filemanager"), |
983 UI.PixmapCache.getIcon("filemanager"), |
967 self.tr("μPy Files")) |
984 self.tr("μPy Files")) |
968 self.__ui.showSideWidget(self.__fileManagerWidget) |
985 self.__ui.showSideWidget(self.__fileManagerWidget) |
969 |
986 |
970 self.__device.setFileManager(True) |
987 self.__device.setFileManager(True) |
971 self.__fileManagerRunning = True |
|
972 |
988 |
973 self.__fileManagerWidget.start() |
989 self.__fileManagerWidget.start() |
974 else: |
990 else: |
975 self.__fileManagerWidget.stop() |
991 self.__fileManagerWidget.stop() |
|
992 |
|
993 if (not self.replButton.isChecked() and |
|
994 not self.chartButton.isChecked()): |
|
995 self.__disconnectFromDevice() |
|
996 |
|
997 self.__device.setFileManager(False) |
976 self.__ui.removeSideWidget(self.__fileManagerWidget) |
998 self.__ui.removeSideWidget(self.__fileManagerWidget) |
977 |
999 |
978 self.__device.setFileManager(False) |
|
979 self.__fileManagerRunning = False |
|
980 self.__fileManagerWidget.deleteLater() |
1000 self.__fileManagerWidget.deleteLater() |
981 self.__fileManagerWidget = None |
1001 self.__fileManagerWidget = None |