eric6/MicroPython/MicroPythonReplWidget.py

branch
micropython
changeset 7059
a8fad276cbd5
parent 7058
bdd583f96e96
child 7062
ac12da95958b
equal deleted inserted replaced
7058:bdd583f96e96 7059:a8fad276cbd5
9 9
10 from __future__ import unicode_literals 10 from __future__ import unicode_literals
11 11
12 import re 12 import re
13 13
14 from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QPoint, QEvent, QIODevice 14 from PyQt5.QtCore import (
15 pyqtSlot, pyqtSignal, Qt, QPoint, QEvent, QIODevice, QTimer
16 )
15 from PyQt5.QtGui import QColor, QKeySequence, QTextCursor 17 from PyQt5.QtGui import QColor, QKeySequence, QTextCursor
16 from PyQt5.QtWidgets import ( 18 from PyQt5.QtWidgets import (
17 QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy) 19 QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy)
18 try: 20 try:
19 from PyQt5.QtSerialPort import QSerialPort 21 from PyQt5.QtSerialPort import QSerialPort
20 HAS_QTSERIALPORT = True 22 HAS_QTSERIALPORT = True
21 except ImportError: 23 except ImportError:
22 HAS_QTSERIALPORT = False 24 HAS_QTSERIALPORT = False
25 try:
26 from PyQt5.QtChart import QChart # __IGNORE_WARNING__
27 HAS_QTCHART = True
28 except ImportError:
29 HAS_QTCHART = False
23 30
24 from E5Gui.E5ZoomWidget import E5ZoomWidget 31 from E5Gui.E5ZoomWidget import E5ZoomWidget
25 from E5Gui import E5MessageBox 32 from E5Gui import E5MessageBox, E5FileDialog
33 from E5Gui.E5Application import e5App
26 34
27 from .Ui_MicroPythonReplWidget import Ui_MicroPythonReplWidget 35 from .Ui_MicroPythonReplWidget import Ui_MicroPythonReplWidget
28 36
29 from . import MicroPythonDevices 37 from . import MicroPythonDevices
30 38
57 super(MicroPythonReplWidget, self).__init__(parent) 65 super(MicroPythonReplWidget, self).__init__(parent)
58 self.setupUi(self) 66 self.setupUi(self)
59 67
60 self.deviceIconLabel.setPixmap(MicroPythonDevices.getDeviceIcon( 68 self.deviceIconLabel.setPixmap(MicroPythonDevices.getDeviceIcon(
61 "", False)) 69 "", False))
70
71 self.openButton.setIcon(UI.PixmapCache.getIcon("open"))
72 self.saveButton.setIcon(UI.PixmapCache.getIcon("fileSaveAs"))
73
62 self.checkButton.setIcon(UI.PixmapCache.getIcon("question")) 74 self.checkButton.setIcon(UI.PixmapCache.getIcon("question"))
63 self.runButton.setIcon(UI.PixmapCache.getIcon("start")) 75 self.runButton.setIcon(UI.PixmapCache.getIcon("start"))
64 self.replButton.setIcon(UI.PixmapCache.getIcon("terminal")) 76 self.replButton.setIcon(UI.PixmapCache.getIcon("terminal"))
65 self.filesButton.setIcon(UI.PixmapCache.getIcon("filemanager")) 77 self.filesButton.setIcon(UI.PixmapCache.getIcon("filemanager"))
66 self.chartButton.setIcon(UI.PixmapCache.getIcon("chart")) 78 self.chartButton.setIcon(UI.PixmapCache.getIcon("chart"))
169 def setActionButtons(self, **kwargs): 181 def setActionButtons(self, **kwargs):
170 """ 182 """
171 Public method to set the enabled state of the various action buttons. 183 Public method to set the enabled state of the various action buttons.
172 184
173 @param kwargs keyword arguments containg the enabled states (keys are 185 @param kwargs keyword arguments containg the enabled states (keys are
174 'run', 'repl', 'files', 'chart' 186 'run', 'repl', 'files', 'chart', 'open', 'save'
175 @type dict 187 @type dict
176 """ 188 """
189 if "open" in kwargs:
190 self.openButton.setEnabled(kwargs["open"])
191 if "save" in kwargs:
192 self.saveButton.setEnabled(kwargs["save"])
177 if "run" in kwargs: 193 if "run" in kwargs:
178 self.runButton.setEnabled(kwargs["run"]) 194 self.runButton.setEnabled(kwargs["run"])
179 if "repl" in kwargs: 195 if "repl" in kwargs:
180 self.replButton.setEnabled(kwargs["repl"]) 196 self.replButton.setEnabled(kwargs["repl"])
181 if "files" in kwargs: 197 if "files" in kwargs:
216 232
217 self.deviceTypeComboBox.setEnabled(not connected) 233 self.deviceTypeComboBox.setEnabled(not connected)
218 234
219 self.disconnectButton.setEnabled(connected) 235 self.disconnectButton.setEnabled(connected)
220 236
237 def __showNoDeviceMessage(self):
238 """
239 Private method to show a message dialog indicating a missing device.
240 """
241 E5MessageBox.critical(
242 self,
243 self.tr("No device attached"),
244 self.tr("""Please ensure the device is plugged into your"""
245 """ computer and selected.\n\nIt must have a version"""
246 """ of MicroPython (or CircuitPython) flashed onto"""
247 """ it before anything will work.\n\nFinally press"""
248 """ the device's reset button and wait a few seconds"""
249 """ before trying again."""))
250
221 @pyqtSlot() 251 @pyqtSlot()
222 def on_replButton_clicked(self): 252 def on_replButton_clicked(self):
223 """ 253 """
224 Private slot to connect to the selected device and start a REPL. 254 Private slot to connect to the selected device and start a REPL.
225 """ 255 """
226 if not self.__device: 256 if not self.__device:
227 E5MessageBox.critical( 257 self.__showNoDeviceMessage()
228 self,
229 self.tr("No device attached"),
230 self.tr("""Please ensure the device is plugged inti your"""
231 """ computer.\n\nIt must have a version of"""
232 """ MicroPython (or CircuitPython) flashed onto it"""
233 """ before the REPL will work.\n\nFinally press the"""
234 """ device's reset button and wait a few seconds"""
235 """ before trying again."""))
236 return 258 return
237 259
238 if self.__replRunning: 260 if self.__replRunning:
239 self.dataReceived.disconnect(self.__processData) 261 self.dataReceived.disconnect(self.__processData)
240 self.on_disconnectButton_clicked() 262 self.on_disconnectButton_clicked()
258 if self.__device.forceInterrupt(): 280 if self.__device.forceInterrupt():
259 # send a Ctrl-B (exit raw mode) 281 # send a Ctrl-B (exit raw mode)
260 self.__serial.write(b'\x02') 282 self.__serial.write(b'\x02')
261 # send Ctrl-C (keyboard interrupt) 283 # send Ctrl-C (keyboard interrupt)
262 self.__serial.write(b'\x03') 284 self.__serial.write(b'\x03')
285
286 self.__replRunning = True
287 self.__device.setRepl(True)
263 288
264 @pyqtSlot() 289 @pyqtSlot()
265 def on_disconnectButton_clicked(self): 290 def on_disconnectButton_clicked(self):
266 """ 291 """
267 Private slot to disconnect from the currently connected device. 292 Private slot to disconnect from the currently connected device.
281 def __activatePlotter(self): 306 def __activatePlotter(self):
282 """ 307 """
283 Private method to activate a data plotter widget. 308 Private method to activate a data plotter widget.
284 """ 309 """
285 # TODO: not implemented yet 310 # TODO: not implemented yet
286 raise NotImplementedError
287 311
288 def __deactivatePlotter(self): 312 def __deactivatePlotter(self):
289 """ 313 """
290 Private method to deactivate the plotter widget. 314 Private method to deactivate the plotter widget.
291 """ 315 """
366 tc = self.replEdit.textCursor() 390 tc = self.replEdit.textCursor()
367 # the text cursor must be on the last line 391 # the text cursor must be on the last line
368 while tc.movePosition(QTextCursor.Down): 392 while tc.movePosition(QTextCursor.Down):
369 pass 393 pass
370 394
371 index = 1 395 index = 0
372 while index < len(data): 396 while index < len(data):
373 if data[index] == 8: # \b 397 if data[index] == 8: # \b
374 tc.movePosition(QTextCursor.Left) 398 tc.movePosition(QTextCursor.Left)
375 self.replEdit.setTextCursor(tc) 399 self.replEdit.setTextCursor(tc)
376 elif data[index] == 13: # \r 400 elif data[index] == 13: # \r
378 elif (len(data) > index + 1 and 402 elif (len(data) > index + 1 and
379 data[index] == 27 and 403 data[index] == 27 and
380 data[index + 1] == 91): 404 data[index + 1] == 91):
381 # VT100 cursor command detected: <Esc>[ 405 # VT100 cursor command detected: <Esc>[
382 index += 2 # move index to after the [ 406 index += 2 # move index to after the [
383 match = self.__vt100Re.search(data[index:].decaode("utf-8")) 407 match = self.__vt100Re.search(data[index:].decode("utf-8"))
384 if match: 408 if match:
385 # move to last position in control sequence 409 # move to last position in control sequence
386 # ++ will be done at end of loop 410 # ++ will be done at end of loop
387 index += match.end() - 1 411 index += match.end() - 1
388 412
492 Private slot to read all available serial data and emit it with the 516 Private slot to read all available serial data and emit it with the
493 "dataReceived" signal for further processing. 517 "dataReceived" signal for further processing.
494 """ 518 """
495 data = bytes(self.__serial.readAll()) 519 data = bytes(self.__serial.readAll())
496 self.dataReceived.emit(data) 520 self.dataReceived.emit(data)
521
522 def execute(self, commandsList):
523 """
524 Public method to execute a series of commands over a period of time.
525
526 @param commandsList list of commands to be execute on the device
527 @type list of bytes
528 """
529 if commandsList:
530 command = commandsList[0]
531 self.__serial.write(command)
532 remainder = commandsList[1:]
533 remainingTask = lambda commands=remainder: self.execute(commands)
534 QTimer.singleShot(2, remainingTask)
535
536 @pyqtSlot()
537 def on_runButton_clicked(self):
538 """
539 Private slot to execute the script of the active editor on the
540 selected device.
541 """
542 if not self.__device:
543 self.__showNoDeviceMessage()
544 return
545
546 aw = e5App().getObject("ViewManager").activeWindow()
547 if aw is None:
548 E5MessageBox.critical(
549 self,
550 self.tr("Run Script"),
551 self.tr("""There is no editor open. Abort..."""))
552 return
553
554 script = aw.text()
555 if not script:
556 E5MessageBox.critical(
557 self,
558 self.tr("Run Script"),
559 self.tr("""The current editor does not contain a script."""
560 """ Abort..."""))
561 return
562
563 ok, reason = self.__device.canRunScript()
564 if not ok:
565 E5MessageBox.warning(
566 self,
567 self.tr("Run Script"),
568 self.tr("""<p>Cannot run script.</p><p>Reason:"""
569 """ {0}</p>""").format(reason))
570 return
571
572 if not self.__replRunning:
573 self.on_replButton_clicked()
574 if self.__replRunning:
575 self.__device.runScript(script)
576
577 @pyqtSlot()
578 def on_openButton_clicked(self):
579 """
580 Private slot to open a file of the connected device.
581 """
582 if not self.__device:
583 self.__showNoDeviceMessage()
584 return
585
586 workspace = self.__device.getWorkspace()
587 fileName = E5FileDialog.getOpenFileName(
588 self,
589 self.tr("Open Python File"),
590 workspace,
591 self.tr("Python3 Files (*.py)"))
592 if fileName:
593 e5App().getObject("ViewManager").openSourceFile(fileName)
594
595 @pyqtSlot()
596 def on_saveButton_clicked(self):
597 """
598 Private slot to save the current editor to the connected device.
599 """
600 if not self.__device:
601 self.__showNoDeviceMessage()
602 return
603
604 workspace = self.__device.getWorkspace()
605 aw = e5App().getObject("ViewManager").activeWindow()
606 aw.saveFileAs(workspace)

eric ide

mercurial