src/eric7/MicroPython/MicroPythonWidget.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9413
80c06d472826
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
13 import functools 13 import functools
14 14
15 from PyQt6.QtCore import pyqtSlot, pyqtSignal, Qt, QPoint, QEvent 15 from PyQt6.QtCore import pyqtSlot, pyqtSignal, Qt, QPoint, QEvent
16 from PyQt6.QtGui import QColor, QKeySequence, QTextCursor, QBrush, QClipboard 16 from PyQt6.QtGui import QColor, QKeySequence, QTextCursor, QBrush, QClipboard
17 from PyQt6.QtWidgets import ( 17 from PyQt6.QtWidgets import (
18 QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy, 18 QWidget,
19 QTextEdit, QToolButton, QDialog 19 QMenu,
20 QApplication,
21 QHBoxLayout,
22 QSpacerItem,
23 QSizePolicy,
24 QTextEdit,
25 QToolButton,
26 QDialog,
20 ) 27 )
21 28
22 from EricWidgets.EricZoomWidget import EricZoomWidget 29 from EricWidgets.EricZoomWidget import EricZoomWidget
23 from EricWidgets import EricMessageBox, EricFileDialog 30 from EricWidgets import EricMessageBox, EricFileDialog
24 from EricWidgets.EricApplication import ericApp 31 from EricWidgets.EricApplication import ericApp
27 34
28 from .Ui_MicroPythonWidget import Ui_MicroPythonWidget 35 from .Ui_MicroPythonWidget import Ui_MicroPythonWidget
29 36
30 from . import MicroPythonDevices 37 from . import MicroPythonDevices
31 from . import UF2FlashDialog 38 from . import UF2FlashDialog
39
32 try: 40 try:
33 from .MicroPythonGraphWidget import MicroPythonGraphWidget 41 from .MicroPythonGraphWidget import MicroPythonGraphWidget
42
34 HAS_QTCHART = True 43 HAS_QTCHART = True
35 except ImportError: 44 except ImportError:
36 HAS_QTCHART = False 45 HAS_QTCHART = False
37 from .MicroPythonFileManagerWidget import MicroPythonFileManagerWidget 46 from .MicroPythonFileManagerWidget import MicroPythonFileManagerWidget
47
38 try: 48 try:
39 from .MicroPythonCommandsInterface import MicroPythonCommandsInterface 49 from .MicroPythonCommandsInterface import MicroPythonCommandsInterface
50
40 HAS_QTSERIALPORT = True 51 HAS_QTSERIALPORT = True
41 except ImportError: 52 except ImportError:
42 HAS_QTSERIALPORT = False 53 HAS_QTSERIALPORT = False
43 54
44 import Globals 55 import Globals
180 191
181 192
182 class MicroPythonWidget(QWidget, Ui_MicroPythonWidget): 193 class MicroPythonWidget(QWidget, Ui_MicroPythonWidget):
183 """ 194 """
184 Class implementing the MicroPython REPL widget. 195 Class implementing the MicroPython REPL widget.
185 196
186 @signal dataReceived(data) emitted to send data received via the serial 197 @signal dataReceived(data) emitted to send data received via the serial
187 connection for further processing 198 connection for further processing
188 """ 199 """
200
189 ZoomMin = -10 201 ZoomMin = -10
190 ZoomMax = 20 202 ZoomMax = 20
191 203
192 DeviceTypeRole = Qt.ItemDataRole.UserRole 204 DeviceTypeRole = Qt.ItemDataRole.UserRole
193 DeviceBoardRole = Qt.ItemDataRole.UserRole + 1 205 DeviceBoardRole = Qt.ItemDataRole.UserRole + 1
194 DevicePortRole = Qt.ItemDataRole.UserRole + 2 206 DevicePortRole = Qt.ItemDataRole.UserRole + 2
195 DeviceVidRole = Qt.ItemDataRole.UserRole + 3 207 DeviceVidRole = Qt.ItemDataRole.UserRole + 3
196 DevicePidRole = Qt.ItemDataRole.UserRole + 4 208 DevicePidRole = Qt.ItemDataRole.UserRole + 4
197 209
198 dataReceived = pyqtSignal(bytes) 210 dataReceived = pyqtSignal(bytes)
199 211
200 ManualMarker = "<manual>" 212 ManualMarker = "<manual>"
201 213
202 def __init__(self, parent=None): 214 def __init__(self, parent=None):
203 """ 215 """
204 Constructor 216 Constructor
205 217
206 @param parent reference to the parent widget 218 @param parent reference to the parent widget
207 @type QWidget 219 @type QWidget
208 """ 220 """
209 super().__init__(parent) 221 super().__init__(parent)
210 self.setupUi(self) 222 self.setupUi(self)
211 223
212 self.layout().setContentsMargins(0, 3, 0, 0) 224 self.layout().setContentsMargins(0, 3, 0, 0)
213 225
214 self.__ui = parent 226 self.__ui = parent
215 227
216 self.__superMenu = QMenu(self) 228 self.__superMenu = QMenu(self)
217 self.__superMenu.aboutToShow.connect(self.__aboutToShowSuperMenu) 229 self.__superMenu.aboutToShow.connect(self.__aboutToShowSuperMenu)
218 230
219 self.menuButton.setObjectName( 231 self.menuButton.setObjectName("micropython_supermenu_button")
220 "micropython_supermenu_button")
221 self.menuButton.setIcon(UI.PixmapCache.getIcon("superMenu")) 232 self.menuButton.setIcon(UI.PixmapCache.getIcon("superMenu"))
222 self.menuButton.setToolTip(self.tr("MicroPython Menu")) 233 self.menuButton.setToolTip(self.tr("MicroPython Menu"))
223 self.menuButton.setPopupMode( 234 self.menuButton.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
224 QToolButton.ToolButtonPopupMode.InstantPopup) 235 self.menuButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
225 self.menuButton.setToolButtonStyle(
226 Qt.ToolButtonStyle.ToolButtonIconOnly)
227 self.menuButton.setFocusPolicy(Qt.FocusPolicy.NoFocus) 236 self.menuButton.setFocusPolicy(Qt.FocusPolicy.NoFocus)
228 self.menuButton.setAutoRaise(True) 237 self.menuButton.setAutoRaise(True)
229 self.menuButton.setShowMenuInside(True) 238 self.menuButton.setShowMenuInside(True)
230 self.menuButton.setMenu(self.__superMenu) 239 self.menuButton.setMenu(self.__superMenu)
231 240
232 self.deviceIconLabel.setPixmap(MicroPythonDevices.getDeviceIcon( 241 self.deviceIconLabel.setPixmap(MicroPythonDevices.getDeviceIcon("", False))
233 "", False)) 242
234
235 self.openButton.setIcon(UI.PixmapCache.getIcon("open")) 243 self.openButton.setIcon(UI.PixmapCache.getIcon("open"))
236 self.saveButton.setIcon(UI.PixmapCache.getIcon("fileSaveAs")) 244 self.saveButton.setIcon(UI.PixmapCache.getIcon("fileSaveAs"))
237 245
238 self.checkButton.setIcon(UI.PixmapCache.getIcon("question")) 246 self.checkButton.setIcon(UI.PixmapCache.getIcon("question"))
239 self.runButton.setIcon(UI.PixmapCache.getIcon("start")) 247 self.runButton.setIcon(UI.PixmapCache.getIcon("start"))
240 self.replButton.setIcon(UI.PixmapCache.getIcon("terminal")) 248 self.replButton.setIcon(UI.PixmapCache.getIcon("terminal"))
241 self.filesButton.setIcon(UI.PixmapCache.getIcon("filemanager")) 249 self.filesButton.setIcon(UI.PixmapCache.getIcon("filemanager"))
242 self.chartButton.setIcon(UI.PixmapCache.getIcon("chart")) 250 self.chartButton.setIcon(UI.PixmapCache.getIcon("chart"))
243 self.connectButton.setIcon(UI.PixmapCache.getIcon("linkConnect")) 251 self.connectButton.setIcon(UI.PixmapCache.getIcon("linkConnect"))
244 252
245 self.__zoomLayout = QHBoxLayout() 253 self.__zoomLayout = QHBoxLayout()
246 spacerItem = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, 254 spacerItem = QSpacerItem(
247 QSizePolicy.Policy.Minimum) 255 40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum
256 )
248 self.__zoomLayout.addSpacerItem(spacerItem) 257 self.__zoomLayout.addSpacerItem(spacerItem)
249 258
250 self.__zoom0 = self.replEdit.fontPointSize() 259 self.__zoom0 = self.replEdit.fontPointSize()
251 self.__zoomWidget = EricZoomWidget( 260 self.__zoomWidget = EricZoomWidget(
252 UI.PixmapCache.getPixmap("zoomOut"), 261 UI.PixmapCache.getPixmap("zoomOut"),
253 UI.PixmapCache.getPixmap("zoomIn"), 262 UI.PixmapCache.getPixmap("zoomIn"),
254 UI.PixmapCache.getPixmap("zoomReset"), self) 263 UI.PixmapCache.getPixmap("zoomReset"),
264 self,
265 )
255 self.__zoomLayout.addWidget(self.__zoomWidget) 266 self.__zoomLayout.addWidget(self.__zoomWidget)
256 self.layout().insertLayout( 267 self.layout().insertLayout(self.layout().count() - 1, self.__zoomLayout)
257 self.layout().count() - 1,
258 self.__zoomLayout)
259 self.__zoomWidget.setMinimum(self.ZoomMin) 268 self.__zoomWidget.setMinimum(self.ZoomMin)
260 self.__zoomWidget.setMaximum(self.ZoomMax) 269 self.__zoomWidget.setMaximum(self.ZoomMax)
261 self.__zoomWidget.valueChanged.connect(self.__doZoom) 270 self.__zoomWidget.valueChanged.connect(self.__doZoom)
262 self.__currentZoom = 0 271 self.__currentZoom = 0
263 272
264 self.__fileManagerWidget = None 273 self.__fileManagerWidget = None
265 self.__chartWidget = None 274 self.__chartWidget = None
266 275
267 self.__unknownPorts = [] 276 self.__unknownPorts = []
268 self.__lastPort = None 277 self.__lastPort = None
269 self.__lastDeviceType = None 278 self.__lastDeviceType = None
270 279
271 if HAS_QTSERIALPORT: 280 if HAS_QTSERIALPORT:
272 self.__interface = MicroPythonCommandsInterface(self) 281 self.__interface = MicroPythonCommandsInterface(self)
273 else: 282 else:
274 self.__interface = None 283 self.__interface = None
275 self.__device = None 284 self.__device = None
276 self.__connected = False 285 self.__connected = False
277 self.__setConnected(False) 286 self.__setConnected(False)
278 287
279 if not HAS_QTSERIALPORT: 288 if not HAS_QTSERIALPORT:
280 self.replEdit.setHtml(self.tr( 289 self.replEdit.setHtml(
281 "<h3>The QtSerialPort package is not available.<br/>" 290 self.tr(
282 "MicroPython support is deactivated.</h3>")) 291 "<h3>The QtSerialPort package is not available.<br/>"
292 "MicroPython support is deactivated.</h3>"
293 )
294 )
283 self.setEnabled(False) 295 self.setEnabled(False)
284 return 296 return
285 297
286 self.__vt100Re = re.compile( 298 self.__vt100Re = re.compile(
287 r'(?P<count>\d*)(?P<color>(?:;?\d*)*)(?P<action>[ABCDKm])') 299 r"(?P<count>\d*)(?P<color>(?:;?\d*)*)(?P<action>[ABCDKm])"
288 300 )
301
289 self.__populateDeviceTypeComboBox() 302 self.__populateDeviceTypeComboBox()
290 303
291 self.replEdit.installEventFilter(self) 304 self.replEdit.installEventFilter(self)
292 # Hack to intercept middle button paste 305 # Hack to intercept middle button paste
293 self.__origReplEditMouseReleaseEvent = self.replEdit.mouseReleaseEvent 306 self.__origReplEditMouseReleaseEvent = self.replEdit.mouseReleaseEvent
294 self.replEdit.mouseReleaseEvent = self.__replEditMouseReleaseEvent 307 self.replEdit.mouseReleaseEvent = self.__replEditMouseReleaseEvent
295 308
296 self.replEdit.customContextMenuRequested.connect( 309 self.replEdit.customContextMenuRequested.connect(self.__showContextMenu)
297 self.__showContextMenu)
298 self.__ui.preferencesChanged.connect(self.__handlePreferencesChanged) 310 self.__ui.preferencesChanged.connect(self.__handlePreferencesChanged)
299 self.__ui.preferencesChanged.connect( 311 self.__ui.preferencesChanged.connect(self.__interface.handlePreferencesChanged)
300 self.__interface.handlePreferencesChanged) 312
301
302 self.__handlePreferencesChanged() 313 self.__handlePreferencesChanged()
303 314
304 charFormat = self.replEdit.currentCharFormat() 315 charFormat = self.replEdit.currentCharFormat()
305 self.DefaultForeground = charFormat.foreground() 316 self.DefaultForeground = charFormat.foreground()
306 self.DefaultBackground = charFormat.background() 317 self.DefaultBackground = charFormat.background()
307 318
308 def __populateDeviceTypeComboBox(self): 319 def __populateDeviceTypeComboBox(self):
309 """ 320 """
310 Private method to populate the device type selector. 321 Private method to populate the device type selector.
311 """ 322 """
312 currentDevice = self.deviceTypeComboBox.currentText() 323 currentDevice = self.deviceTypeComboBox.currentText()
313 324
314 self.deviceTypeComboBox.clear() 325 self.deviceTypeComboBox.clear()
315 self.deviceInfoLabel.clear() 326 self.deviceInfoLabel.clear()
316 327
317 self.deviceTypeComboBox.addItem("", "") 328 self.deviceTypeComboBox.addItem("", "")
318 devices, unknownDevices, unknownPorts = ( 329 devices, unknownDevices, unknownPorts = MicroPythonDevices.getFoundDevices()
319 MicroPythonDevices.getFoundDevices()
320 )
321 if devices: 330 if devices:
322 supportedMessage = self.tr( 331 supportedMessage = self.tr(
323 "%n supported device(s) detected.", "", len(devices)) 332 "%n supported device(s) detected.", "", len(devices)
324 333 )
325 for index, (boardType, boardName, description, portName, 334
326 vid, pid) in enumerate(sorted(devices), 1): 335 for index, (
336 boardType,
337 boardName,
338 description,
339 portName,
340 vid,
341 pid,
342 ) in enumerate(sorted(devices), 1):
327 self.deviceTypeComboBox.addItem( 343 self.deviceTypeComboBox.addItem(
328 self.tr("{0} - {1} ({2})", 344 self.tr(
329 "board name, description, port name") 345 "{0} - {1} ({2})", "board name, description, port name"
330 .format(boardName, description, portName) 346 ).format(boardName, description, portName)
331 ) 347 )
332 self.deviceTypeComboBox.setItemData( 348 self.deviceTypeComboBox.setItemData(
333 index, boardType, self.DeviceTypeRole) 349 index, boardType, self.DeviceTypeRole
350 )
334 self.deviceTypeComboBox.setItemData( 351 self.deviceTypeComboBox.setItemData(
335 index, boardName, self.DeviceBoardRole) 352 index, boardName, self.DeviceBoardRole
353 )
336 self.deviceTypeComboBox.setItemData( 354 self.deviceTypeComboBox.setItemData(
337 index, portName, self.DevicePortRole) 355 index, portName, self.DevicePortRole
338 self.deviceTypeComboBox.setItemData( 356 )
339 index, vid, self.DeviceVidRole) 357 self.deviceTypeComboBox.setItemData(index, vid, self.DeviceVidRole)
340 self.deviceTypeComboBox.setItemData( 358 self.deviceTypeComboBox.setItemData(index, pid, self.DevicePidRole)
341 index, pid, self.DevicePidRole) 359
342
343 else: 360 else:
344 supportedMessage = self.tr("No supported devices detected.") 361 supportedMessage = self.tr("No supported devices detected.")
345 362
346 self.__unknownPorts = unknownPorts 363 self.__unknownPorts = unknownPorts
347 if self.__unknownPorts: 364 if self.__unknownPorts:
348 unknownMessage = self.tr( 365 unknownMessage = self.tr(
349 "\n%n unknown device(s) for manual selection.", "", 366 "\n%n unknown device(s) for manual selection.",
350 len(self.__unknownPorts)) 367 "",
368 len(self.__unknownPorts),
369 )
351 if self.deviceTypeComboBox.count(): 370 if self.deviceTypeComboBox.count():
352 self.deviceTypeComboBox.insertSeparator( 371 self.deviceTypeComboBox.insertSeparator(self.deviceTypeComboBox.count())
353 self.deviceTypeComboBox.count())
354 self.deviceTypeComboBox.addItem(self.tr("Manual Selection")) 372 self.deviceTypeComboBox.addItem(self.tr("Manual Selection"))
355 self.deviceTypeComboBox.setItemData( 373 self.deviceTypeComboBox.setItemData(
356 self.deviceTypeComboBox.count() - 1, 374 self.deviceTypeComboBox.count() - 1,
357 self.ManualMarker, self.DeviceTypeRole) 375 self.ManualMarker,
376 self.DeviceTypeRole,
377 )
358 else: 378 else:
359 unknownMessage = "" 379 unknownMessage = ""
360 380
361 self.deviceInfoLabel.setText(supportedMessage + unknownMessage) 381 self.deviceInfoLabel.setText(supportedMessage + unknownMessage)
362 382
363 index = self.deviceTypeComboBox.findText(currentDevice, 383 index = self.deviceTypeComboBox.findText(
364 Qt.MatchFlag.MatchExactly) 384 currentDevice, Qt.MatchFlag.MatchExactly
385 )
365 if index == -1: 386 if index == -1:
366 # entry is no longer present 387 # entry is no longer present
367 index = 0 388 index = 0
368 if self.__connected: 389 if self.__connected:
369 # we are still connected, so disconnect 390 # we are still connected, so disconnect
370 self.on_connectButton_clicked() 391 self.on_connectButton_clicked()
371 392
372 self.on_deviceTypeComboBox_activated(index) 393 self.on_deviceTypeComboBox_activated(index)
373 self.deviceTypeComboBox.setCurrentIndex(index) 394 self.deviceTypeComboBox.setCurrentIndex(index)
374 395
375 if unknownDevices: 396 if unknownDevices:
376 ignoredUnknown = { 397 ignoredUnknown = {
377 tuple(d) 398 tuple(d) for d in Preferences.getMicroPython("IgnoredUnknownDevices")
378 for d in Preferences.getMicroPython("IgnoredUnknownDevices")
379 } 399 }
380 uf2Devices = {(*x[2], x[1]) 400 uf2Devices = {(*x[2], x[1]) for x in UF2FlashDialog.getFoundDevices()}
381 for x in UF2FlashDialog.getFoundDevices()} 401 newUnknownDevices = set(unknownDevices) - ignoredUnknown - uf2Devices
382 newUnknownDevices = (
383 set(unknownDevices) - ignoredUnknown - uf2Devices
384 )
385 if newUnknownDevices: 402 if newUnknownDevices:
386 button = EricMessageBox.information( 403 button = EricMessageBox.information(
387 self, 404 self,
388 self.tr("Unknown MicroPython Device"), 405 self.tr("Unknown MicroPython Device"),
389 self.tr( 406 self.tr(
390 '<p>Detected these unknown serial devices</p>' 407 "<p>Detected these unknown serial devices</p>"
391 '<ul>' 408 "<ul>"
392 '<li>{0}</li>' 409 "<li>{0}</li>"
393 '</ul>' 410 "</ul>"
394 '<p>Please report them together with the board name' 411 "<p>Please report them together with the board name"
395 ' and a short description to <a href="mailto:{1}">' 412 ' and a short description to <a href="mailto:{1}">'
396 ' the eric bug reporting address</a> if it is a' 413 " the eric bug reporting address</a> if it is a"
397 ' MicroPython board.</p>' 414 " MicroPython board.</p>"
398 ).format("</li><li>".join([ 415 ).format(
399 self.tr("{0} (0x{1:04x}/0x{2:04x})", 416 "</li><li>".join(
400 "description, VId, PId").format( 417 [
401 desc, vid, pid) 418 self.tr(
402 for vid, pid, desc in newUnknownDevices]), 419 "{0} (0x{1:04x}/0x{2:04x})", "description, VId, PId"
403 BugAddress), 420 ).format(desc, vid, pid)
404 EricMessageBox.Ignore | EricMessageBox.Ok 421 for vid, pid, desc in newUnknownDevices
422 ]
423 ),
424 BugAddress,
425 ),
426 EricMessageBox.Ignore | EricMessageBox.Ok,
405 ) 427 )
406 if button == EricMessageBox.Ignore: 428 if button == EricMessageBox.Ignore:
407 ignoredUnknown = list(ignoredUnknown | newUnknownDevices) 429 ignoredUnknown = list(ignoredUnknown | newUnknownDevices)
408 Preferences.setMicroPython("IgnoredUnknownDevices", 430 Preferences.setMicroPython("IgnoredUnknownDevices", ignoredUnknown)
409 ignoredUnknown)
410 else: 431 else:
411 yes = EricMessageBox.yesNo( 432 yes = EricMessageBox.yesNo(
412 self, 433 self,
413 self.tr("Unknown MicroPython Device"), 434 self.tr("Unknown MicroPython Device"),
414 self.tr("""Would you like to add them to the list of""" 435 self.tr(
415 """ manually configured devices?"""), 436 """Would you like to add them to the list of"""
416 yesDefault=True) 437 """ manually configured devices?"""
438 ),
439 yesDefault=True,
440 )
417 if yes: 441 if yes:
418 self.__addUnknownDevices(list(newUnknownDevices)) 442 self.__addUnknownDevices(list(newUnknownDevices))
419 443
420 def __handlePreferencesChanged(self): 444 def __handlePreferencesChanged(self):
421 """ 445 """
422 Private slot to handle a change in preferences. 446 Private slot to handle a change in preferences.
423 """ 447 """
424 self.__colorScheme = Preferences.getMicroPython("ColorScheme") 448 self.__colorScheme = Preferences.getMicroPython("ColorScheme")
425 449
426 self.__font = Preferences.getEditorOtherFonts("MonospacedFont") 450 self.__font = Preferences.getEditorOtherFonts("MonospacedFont")
427 self.replEdit.setFontFamily(self.__font.family()) 451 self.replEdit.setFontFamily(self.__font.family())
428 self.replEdit.setFontPointSize(self.__font.pointSize()) 452 self.replEdit.setFontPointSize(self.__font.pointSize())
429 453
430 if Preferences.getMicroPython("ReplLineWrap"): 454 if Preferences.getMicroPython("ReplLineWrap"):
431 self.replEdit.setLineWrapMode(QTextEdit.LineWrapMode.WidgetWidth) 455 self.replEdit.setLineWrapMode(QTextEdit.LineWrapMode.WidgetWidth)
432 else: 456 else:
433 self.replEdit.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap) 457 self.replEdit.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap)
434 458
435 if self.__chartWidget is not None: 459 if self.__chartWidget is not None:
436 self.__chartWidget.preferencesChanged() 460 self.__chartWidget.preferencesChanged()
437 461
438 def commandsInterface(self): 462 def commandsInterface(self):
439 """ 463 """
440 Public method to get a reference to the commands interface object. 464 Public method to get a reference to the commands interface object.
441 465
442 @return reference to the commands interface object 466 @return reference to the commands interface object
443 @rtype MicroPythonCommandsInterface 467 @rtype MicroPythonCommandsInterface
444 """ 468 """
445 return self.__interface 469 return self.__interface
446 470
447 def isMicrobit(self): 471 def isMicrobit(self):
448 """ 472 """
449 Public method to check, if the connected/selected device is a 473 Public method to check, if the connected/selected device is a
450 BBC micro:bit or Calliope mini. 474 BBC micro:bit or Calliope mini.
451 475
452 @return flag indicating a micro:bit device 476 @return flag indicating a micro:bit device
453 rtype bool 477 rtype bool
454 """ 478 """
455 if self.__device and ( 479 if self.__device and (
456 "micro:bit" in self.__device.deviceName() or 480 "micro:bit" in self.__device.deviceName()
457 "Calliope" in self.__device.deviceName() 481 or "Calliope" in self.__device.deviceName()
458 ): 482 ):
459 return True 483 return True
460 484
461 return False 485 return False
462 486
463 @pyqtSlot(int) 487 @pyqtSlot(int)
464 def on_deviceTypeComboBox_activated(self, index): 488 def on_deviceTypeComboBox_activated(self, index):
465 """ 489 """
466 Private slot handling the selection of a device type. 490 Private slot handling the selection of a device type.
467 491
468 @param index index of the selected device 492 @param index index of the selected device
469 @type int 493 @type int
470 """ 494 """
471 deviceType = self.deviceTypeComboBox.itemData( 495 deviceType = self.deviceTypeComboBox.itemData(index, self.DeviceTypeRole)
472 index, self.DeviceTypeRole)
473 if deviceType == self.ManualMarker: 496 if deviceType == self.ManualMarker:
474 self.connectButton.setEnabled(bool(self.__unknownPorts)) 497 self.connectButton.setEnabled(bool(self.__unknownPorts))
475 else: 498 else:
476 self.deviceIconLabel.setPixmap(MicroPythonDevices.getDeviceIcon( 499 self.deviceIconLabel.setPixmap(
477 deviceType, False)) 500 MicroPythonDevices.getDeviceIcon(deviceType, False)
478 501 )
479 vid = self.deviceTypeComboBox.itemData( 502
480 index, self.DeviceVidRole) 503 vid = self.deviceTypeComboBox.itemData(index, self.DeviceVidRole)
481 pid = self.deviceTypeComboBox.itemData( 504 pid = self.deviceTypeComboBox.itemData(index, self.DevicePidRole)
482 index, self.DevicePidRole) 505
483 506 self.__device = MicroPythonDevices.getDevice(deviceType, self, vid, pid)
484 self.__device = MicroPythonDevices.getDevice(deviceType, self,
485 vid, pid)
486 self.__device.setButtons() 507 self.__device.setButtons()
487 508
488 self.connectButton.setEnabled(bool(deviceType)) 509 self.connectButton.setEnabled(bool(deviceType))
489 510
490 @pyqtSlot() 511 @pyqtSlot()
491 def on_checkButton_clicked(self): 512 def on_checkButton_clicked(self):
492 """ 513 """
493 Private slot to check for connected devices. 514 Private slot to check for connected devices.
494 """ 515 """
495 self.__populateDeviceTypeComboBox() 516 self.__populateDeviceTypeComboBox()
496 517
497 def setActionButtons(self, **kwargs): 518 def setActionButtons(self, **kwargs):
498 """ 519 """
499 Public method to set the enabled state of the various action buttons. 520 Public method to set the enabled state of the various action buttons.
500 521
501 @keyparam kwargs keyword arguments containg the enabled states (keys 522 @keyparam kwargs keyword arguments containg the enabled states (keys
502 are 'run', 'repl', 'files', 'chart', 'open', 'save' 523 are 'run', 'repl', 'files', 'chart', 'open', 'save'
503 @type dict 524 @type dict
504 """ 525 """
505 if "open" in kwargs: 526 if "open" in kwargs:
512 self.replButton.setEnabled(kwargs["repl"]) 533 self.replButton.setEnabled(kwargs["repl"])
513 if "files" in kwargs: 534 if "files" in kwargs:
514 self.filesButton.setEnabled(kwargs["files"]) 535 self.filesButton.setEnabled(kwargs["files"])
515 if "chart" in kwargs: 536 if "chart" in kwargs:
516 self.chartButton.setEnabled(kwargs["chart"] and HAS_QTCHART) 537 self.chartButton.setEnabled(kwargs["chart"] and HAS_QTCHART)
517 538
518 @pyqtSlot(QPoint) 539 @pyqtSlot(QPoint)
519 def __showContextMenu(self, pos): 540 def __showContextMenu(self, pos):
520 """ 541 """
521 Private slot to show the REPL context menu. 542 Private slot to show the REPL context menu.
522 543
523 @param pos position to show the menu at 544 @param pos position to show the menu at
524 @type QPoint 545 @type QPoint
525 """ 546 """
526 if Globals.isMacPlatform(): 547 if Globals.isMacPlatform():
527 copyKeys = QKeySequence("Ctrl+C") 548 copyKeys = QKeySequence("Ctrl+C")
529 selectAllKeys = QKeySequence("Ctrl+A") 550 selectAllKeys = QKeySequence("Ctrl+A")
530 else: 551 else:
531 copyKeys = QKeySequence("Ctrl+Shift+C") 552 copyKeys = QKeySequence("Ctrl+Shift+C")
532 pasteKeys = QKeySequence("Ctrl+Shift+V") 553 pasteKeys = QKeySequence("Ctrl+Shift+V")
533 selectAllKeys = QKeySequence("Ctrl+Shift+A") 554 selectAllKeys = QKeySequence("Ctrl+Shift+A")
534 555
535 menu = QMenu(self) 556 menu = QMenu(self)
536 act = menu.addAction( 557 act = menu.addAction(
537 UI.PixmapCache.getIcon("editDelete"), self.tr("Clear"), 558 UI.PixmapCache.getIcon("editDelete"), self.tr("Clear"), self.__clear
538 self.__clear) 559 )
539 act.setEnabled(bool(self.replEdit.toPlainText())) 560 act.setEnabled(bool(self.replEdit.toPlainText()))
540 menu.addSeparator() 561 menu.addSeparator()
541 act = menu.addAction( 562 act = menu.addAction(
542 UI.PixmapCache.getIcon("editCopy"), self.tr("Copy"), 563 UI.PixmapCache.getIcon("editCopy"),
543 copyKeys, self.replEdit.copy) 564 self.tr("Copy"),
565 copyKeys,
566 self.replEdit.copy,
567 )
544 act.setEnabled(self.replEdit.textCursor().hasSelection()) 568 act.setEnabled(self.replEdit.textCursor().hasSelection())
545 act = menu.addAction( 569 act = menu.addAction(
546 UI.PixmapCache.getIcon("editPaste"), self.tr("Paste"), 570 UI.PixmapCache.getIcon("editPaste"),
547 pasteKeys, self.__paste) 571 self.tr("Paste"),
548 act.setEnabled(self.replEdit.canPaste() and 572 pasteKeys,
549 self.__interface.isConnected()) 573 self.__paste,
574 )
575 act.setEnabled(self.replEdit.canPaste() and self.__interface.isConnected())
550 menu.addSeparator() 576 menu.addSeparator()
551 act = menu.addAction( 577 act = menu.addAction(
552 UI.PixmapCache.getIcon("editSelectAll"), self.tr("Select All"), 578 UI.PixmapCache.getIcon("editSelectAll"),
553 selectAllKeys, self.replEdit.selectAll) 579 self.tr("Select All"),
580 selectAllKeys,
581 self.replEdit.selectAll,
582 )
554 act.setEnabled(bool(self.replEdit.toPlainText())) 583 act.setEnabled(bool(self.replEdit.toPlainText()))
555 584
556 menu.exec(self.replEdit.mapToGlobal(pos)) 585 menu.exec(self.replEdit.mapToGlobal(pos))
557 586
558 def __setConnected(self, connected): 587 def __setConnected(self, connected):
559 """ 588 """
560 Private method to set the connection status LED. 589 Private method to set the connection status LED.
561 590
562 @param connected connection state 591 @param connected connection state
563 @type bool 592 @type bool
564 """ 593 """
565 self.__connected = connected 594 self.__connected = connected
566 595
567 self.deviceConnectedLed.setOn(connected) 596 self.deviceConnectedLed.setOn(connected)
568 if self.__fileManagerWidget: 597 if self.__fileManagerWidget:
569 self.__fileManagerWidget.deviceConnectedLed.setOn(connected) 598 self.__fileManagerWidget.deviceConnectedLed.setOn(connected)
570 599
571 self.deviceTypeComboBox.setEnabled(not connected) 600 self.deviceTypeComboBox.setEnabled(not connected)
572 601
573 if connected: 602 if connected:
574 self.connectButton.setIcon( 603 self.connectButton.setIcon(UI.PixmapCache.getIcon("linkDisconnect"))
575 UI.PixmapCache.getIcon("linkDisconnect")) 604 self.connectButton.setToolTip(
576 self.connectButton.setToolTip(self.tr( 605 self.tr("Press to disconnect the current device")
577 "Press to disconnect the current device")) 606 )
578 else: 607 else:
579 self.connectButton.setIcon( 608 self.connectButton.setIcon(UI.PixmapCache.getIcon("linkConnect"))
580 UI.PixmapCache.getIcon("linkConnect")) 609 self.connectButton.setToolTip(
581 self.connectButton.setToolTip(self.tr( 610 self.tr("Press to connect the selected device")
582 "Press to connect the selected device")) 611 )
583 612
584 def isConnected(self): 613 def isConnected(self):
585 """ 614 """
586 Public method to get the connection state. 615 Public method to get the connection state.
587 616
588 @return connection state 617 @return connection state
589 @rtype bool 618 @rtype bool
590 """ 619 """
591 return self.__connected 620 return self.__connected
592 621
593 def __showNoDeviceMessage(self): 622 def __showNoDeviceMessage(self):
594 """ 623 """
595 Private method to show a message dialog indicating a missing device. 624 Private method to show a message dialog indicating a missing device.
596 """ 625 """
597 EricMessageBox.critical( 626 EricMessageBox.critical(
598 self, 627 self,
599 self.tr("No device attached"), 628 self.tr("No device attached"),
600 self.tr("""Please ensure the device is plugged into your""" 629 self.tr(
601 """ computer and selected.\n\nIt must have a version""" 630 """Please ensure the device is plugged into your"""
602 """ of MicroPython (or CircuitPython) flashed onto""" 631 """ computer and selected.\n\nIt must have a version"""
603 """ it before anything will work.\n\nFinally press""" 632 """ of MicroPython (or CircuitPython) flashed onto"""
604 """ the device's reset button and wait a few seconds""" 633 """ it before anything will work.\n\nFinally press"""
605 """ before trying again.""")) 634 """ the device's reset button and wait a few seconds"""
606 635 """ before trying again."""
636 ),
637 )
638
607 @pyqtSlot(bool) 639 @pyqtSlot(bool)
608 def on_replButton_clicked(self, checked): 640 def on_replButton_clicked(self, checked):
609 """ 641 """
610 Private slot to connect to enable or disable the REPL widget. 642 Private slot to connect to enable or disable the REPL widget.
611 643
612 If the selected device is not connected yet, this will be done now. 644 If the selected device is not connected yet, this will be done now.
613 645
614 @param checked state of the button 646 @param checked state of the button
615 @type bool 647 @type bool
616 """ 648 """
617 if not self.__device: 649 if not self.__device:
618 self.__showNoDeviceMessage() 650 self.__showNoDeviceMessage()
619 return 651 return
620 652
621 if checked: 653 if checked:
622 ok, reason = self.__device.canStartRepl() 654 ok, reason = self.__device.canStartRepl()
623 if not ok: 655 if not ok:
624 EricMessageBox.warning( 656 EricMessageBox.warning(
625 self, 657 self,
626 self.tr("Start REPL"), 658 self.tr("Start REPL"),
627 self.tr("""<p>The REPL cannot be started.</p><p>Reason:""" 659 self.tr(
628 """ {0}</p>""").format(reason)) 660 """<p>The REPL cannot be started.</p><p>Reason:"""
661 """ {0}</p>"""
662 ).format(reason),
663 )
629 return 664 return
630 665
631 self.replEdit.clear() 666 self.replEdit.clear()
632 self.__interface.dataReceived.connect(self.__processData) 667 self.__interface.dataReceived.connect(self.__processData)
633 668
634 if not self.__interface.isConnected(): 669 if not self.__interface.isConnected():
635 self.__connectToDevice() 670 self.__connectToDevice()
636 if self.__device.forceInterrupt(): 671 if self.__device.forceInterrupt():
637 # send a Ctrl-B (exit raw mode) 672 # send a Ctrl-B (exit raw mode)
638 self.__interface.write(b'\x02') 673 self.__interface.write(b"\x02")
639 # send Ctrl-C (keyboard interrupt) 674 # send Ctrl-C (keyboard interrupt)
640 self.__interface.write(b'\x03') 675 self.__interface.write(b"\x03")
641 676
642 self.__device.setRepl(True) 677 self.__device.setRepl(True)
643 self.replEdit.setFocus(Qt.FocusReason.OtherFocusReason) 678 self.replEdit.setFocus(Qt.FocusReason.OtherFocusReason)
644 else: 679 else:
645 self.__interface.dataReceived.disconnect(self.__processData) 680 self.__interface.dataReceived.disconnect(self.__processData)
646 if (not self.chartButton.isChecked() and 681 if not self.chartButton.isChecked() and not self.filesButton.isChecked():
647 not self.filesButton.isChecked()):
648 self.__disconnectFromDevice() 682 self.__disconnectFromDevice()
649 self.__device.setRepl(False) 683 self.__device.setRepl(False)
650 self.replButton.setChecked(checked) 684 self.replButton.setChecked(checked)
651 685
652 @pyqtSlot() 686 @pyqtSlot()
653 def on_connectButton_clicked(self): 687 def on_connectButton_clicked(self):
654 """ 688 """
655 Private slot to connect to the selected device or disconnect from the 689 Private slot to connect to the selected device or disconnect from the
656 currently connected device. 690 currently connected device.
657 """ 691 """
658 if self.__connected: 692 if self.__connected:
659 with EricOverrideCursor(): 693 with EricOverrideCursor():
660 self.__disconnectFromDevice() 694 self.__disconnectFromDevice()
661 695
662 if self.replButton.isChecked(): 696 if self.replButton.isChecked():
663 self.on_replButton_clicked(False) 697 self.on_replButton_clicked(False)
664 if self.filesButton.isChecked(): 698 if self.filesButton.isChecked():
665 self.on_filesButton_clicked(False) 699 self.on_filesButton_clicked(False)
666 if self.chartButton.isChecked(): 700 if self.chartButton.isChecked():
667 self.on_chartButton_clicked(False) 701 self.on_chartButton_clicked(False)
668 else: 702 else:
669 with EricOverrideCursor(): 703 with EricOverrideCursor():
670 self.__connectToDevice() 704 self.__connectToDevice()
671 705
672 @pyqtSlot() 706 @pyqtSlot()
673 def __clear(self): 707 def __clear(self):
674 """ 708 """
675 Private slot to clear the REPL pane. 709 Private slot to clear the REPL pane.
676 """ 710 """
677 self.replEdit.clear() 711 self.replEdit.clear()
678 self.__interface.isConnected() and self.__interface.write(b"\r") 712 self.__interface.isConnected() and self.__interface.write(b"\r")
679 713
680 @pyqtSlot() 714 @pyqtSlot()
681 def __paste(self, mode=QClipboard.Mode.Clipboard): 715 def __paste(self, mode=QClipboard.Mode.Clipboard):
682 """ 716 """
683 Private slot to perform a paste operation. 717 Private slot to perform a paste operation.
684 718
685 @param mode paste mode (defaults to QClipboard.Mode.Clipboard) 719 @param mode paste mode (defaults to QClipboard.Mode.Clipboard)
686 @type QClipboard.Mode (optional) 720 @type QClipboard.Mode (optional)
687 """ 721 """
688 # add support for paste by mouse middle button 722 # add support for paste by mouse middle button
689 clipboard = QApplication.clipboard() 723 clipboard = QApplication.clipboard()
690 if clipboard: 724 if clipboard:
691 pasteText = clipboard.text(mode=mode) 725 pasteText = clipboard.text(mode=mode)
692 if pasteText: 726 if pasteText:
693 pasteText = pasteText.replace('\n\r', '\r') 727 pasteText = pasteText.replace("\n\r", "\r")
694 pasteText = pasteText.replace('\n', '\r') 728 pasteText = pasteText.replace("\n", "\r")
695 self.__interface.isConnected() and self.__interface.write( 729 self.__interface.isConnected() and self.__interface.write(
696 pasteText.encode("utf-8")) 730 pasteText.encode("utf-8")
697 731 )
732
698 def eventFilter(self, obj, evt): 733 def eventFilter(self, obj, evt):
699 """ 734 """
700 Public method to process events for the REPL pane. 735 Public method to process events for the REPL pane.
701 736
702 @param obj reference to the object the event was meant for 737 @param obj reference to the object the event was meant for
703 @type QObject 738 @type QObject
704 @param evt reference to the event object 739 @param evt reference to the event object
705 @type QEvent 740 @type QEvent
706 @return flag to indicate that the event was handled 741 @return flag to indicate that the event was handled
707 @rtype bool 742 @rtype bool
708 """ 743 """
709 if obj is self.replEdit and evt.type() == QEvent.Type.KeyPress: 744 if obj is self.replEdit and evt.type() == QEvent.Type.KeyPress:
710 # handle the key press event on behalve of the REPL pane 745 # handle the key press event on behalve of the REPL pane
711 key = evt.key() 746 key = evt.key()
712 msg = bytes(evt.text(), 'utf8') 747 msg = bytes(evt.text(), "utf8")
713 if key == Qt.Key.Key_Backspace: 748 if key == Qt.Key.Key_Backspace:
714 msg = b'\b' 749 msg = b"\b"
715 elif key == Qt.Key.Key_Delete: 750 elif key == Qt.Key.Key_Delete:
716 msg = b'\x1B[\x33\x7E' 751 msg = b"\x1B[\x33\x7E"
717 elif key == Qt.Key.Key_Up: 752 elif key == Qt.Key.Key_Up:
718 msg = b'\x1B[A' 753 msg = b"\x1B[A"
719 elif key == Qt.Key.Key_Down: 754 elif key == Qt.Key.Key_Down:
720 msg = b'\x1B[B' 755 msg = b"\x1B[B"
721 elif key == Qt.Key.Key_Right: 756 elif key == Qt.Key.Key_Right:
722 msg = b'\x1B[C' 757 msg = b"\x1B[C"
723 elif key == Qt.Key.Key_Left: 758 elif key == Qt.Key.Key_Left:
724 msg = b'\x1B[D' 759 msg = b"\x1B[D"
725 elif key == Qt.Key.Key_Home: 760 elif key == Qt.Key.Key_Home:
726 msg = b'\x1B[H' 761 msg = b"\x1B[H"
727 elif key == Qt.Key.Key_End: 762 elif key == Qt.Key.Key_End:
728 msg = b'\x1B[F' 763 msg = b"\x1B[F"
729 elif ((Globals.isMacPlatform() and 764 elif (
730 evt.modifiers() == Qt.KeyboardModifier.MetaModifier) or 765 Globals.isMacPlatform()
731 (not Globals.isMacPlatform() and 766 and evt.modifiers() == Qt.KeyboardModifier.MetaModifier
732 evt.modifiers() == Qt.KeyboardModifier.ControlModifier)): 767 ) or (
768 not Globals.isMacPlatform()
769 and evt.modifiers() == Qt.KeyboardModifier.ControlModifier
770 ):
733 if Qt.Key.Key_A <= key <= Qt.Key.Key_Z: 771 if Qt.Key.Key_A <= key <= Qt.Key.Key_Z:
734 # devices treat an input of \x01 as Ctrl+A, etc. 772 # devices treat an input of \x01 as Ctrl+A, etc.
735 msg = bytes([1 + key - Qt.Key.Key_A]) 773 msg = bytes([1 + key - Qt.Key.Key_A])
736 elif ( 774 elif evt.modifiers() == (
737 evt.modifiers() == ( 775 Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier
738 Qt.KeyboardModifier.ControlModifier | 776 ) or (
739 Qt.KeyboardModifier.ShiftModifier) or 777 Globals.isMacPlatform()
740 (Globals.isMacPlatform() and 778 and evt.modifiers() == Qt.KeyboardModifier.ControlModifier
741 evt.modifiers() == Qt.KeyboardModifier.ControlModifier)
742 ): 779 ):
743 if key == Qt.Key.Key_C: 780 if key == Qt.Key.Key_C:
744 self.replEdit.copy() 781 self.replEdit.copy()
745 msg = b'' 782 msg = b""
746 elif key == Qt.Key.Key_V: 783 elif key == Qt.Key.Key_V:
747 self.__paste() 784 self.__paste()
748 msg = b'' 785 msg = b""
749 elif key == Qt.Key.Key_A: 786 elif key == Qt.Key.Key_A:
750 self.replEdit.selectAll() 787 self.replEdit.selectAll()
751 msg = b"" 788 msg = b""
752 elif key in (Qt.Key.Key_Return, Qt.Key.Key_Enter): 789 elif key in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
753 tc = self.replEdit.textCursor() 790 tc = self.replEdit.textCursor()
756 self.__interface.isConnected() and self.__interface.write(msg) 793 self.__interface.isConnected() and self.__interface.write(msg)
757 return True 794 return True
758 else: 795 else:
759 # standard event processing 796 # standard event processing
760 return super().eventFilter(obj, evt) 797 return super().eventFilter(obj, evt)
761 798
762 def __replEditMouseReleaseEvent(self, evt): 799 def __replEditMouseReleaseEvent(self, evt):
763 """ 800 """
764 Private method handling mouse release events for the replEdit widget. 801 Private method handling mouse release events for the replEdit widget.
765 802
766 Note: this is a hack because QTextEdit does not allow filtering of 803 Note: this is a hack because QTextEdit does not allow filtering of
767 QEvent.Type.MouseButtonRelease. To make middle button paste work, we 804 QEvent.Type.MouseButtonRelease. To make middle button paste work, we
768 had to intercept the protected event method (some kind of 805 had to intercept the protected event method (some kind of
769 reimplementing it). 806 reimplementing it).
770 807
771 @param evt reference to the event object 808 @param evt reference to the event object
772 @type QMouseEvent 809 @type QMouseEvent
773 """ 810 """
774 if evt.button() == Qt.MouseButton.MiddleButton: 811 if evt.button() == Qt.MouseButton.MiddleButton:
775 self.__paste(mode=QClipboard.Mode.Selection) 812 self.__paste(mode=QClipboard.Mode.Selection)
776 msg = b'' 813 msg = b""
777 if self.__interface.isConnected(): 814 if self.__interface.isConnected():
778 self.__interface.write(msg) 815 self.__interface.write(msg)
779 evt.accept() 816 evt.accept()
780 else: 817 else:
781 self.__origReplEditMouseReleaseEvent(evt) 818 self.__origReplEditMouseReleaseEvent(evt)
782 819
783 def __processData(self, data): 820 def __processData(self, data):
784 """ 821 """
785 Private slot to process bytes received from the device. 822 Private slot to process bytes received from the device.
786 823
787 @param data bytes received from the device 824 @param data bytes received from the device
788 @type bytes 825 @type bytes
789 """ 826 """
790 tc = self.replEdit.textCursor() 827 tc = self.replEdit.textCursor()
791 # the text cursor must be on the last line 828 # the text cursor must be on the last line
792 while tc.movePosition(QTextCursor.MoveOperation.Down): 829 while tc.movePosition(QTextCursor.MoveOperation.Down):
793 pass 830 pass
794 831
795 # set the font 832 # set the font
796 charFormat = tc.charFormat() 833 charFormat = tc.charFormat()
797 charFormat.setFontFamilies([self.__font.family()]) 834 charFormat.setFontFamilies([self.__font.family()])
798 charFormat.setFontPointSize(self.__font.pointSize()) 835 charFormat.setFontPointSize(self.__font.pointSize())
799 tc.setCharFormat(charFormat) 836 tc.setCharFormat(charFormat)
800 837
801 index = 0 838 index = 0
802 while index < len(data): 839 while index < len(data):
803 if data[index] == 8: # \b 840 if data[index] == 8: # \b
804 tc.movePosition(QTextCursor.MoveOperation.Left) 841 tc.movePosition(QTextCursor.MoveOperation.Left)
805 self.replEdit.setTextCursor(tc) 842 self.replEdit.setTextCursor(tc)
806 elif data[index] in (4, 13): # EOT, \r 843 elif data[index] in (4, 13): # EOT, \r
807 pass 844 pass
808 elif (len(data) > index + 1 and 845 elif len(data) > index + 1 and data[index] == 27 and data[index + 1] == 91:
809 data[index] == 27 and
810 data[index + 1] == 91):
811 # VT100 cursor command detected: <Esc>[ 846 # VT100 cursor command detected: <Esc>[
812 index += 2 # move index to after the [ 847 index += 2 # move index to after the [
813 match = self.__vt100Re.search( 848 match = self.__vt100Re.search(
814 data[index:].decode("utf-8", errors="replace")) 849 data[index:].decode("utf-8", errors="replace")
850 )
815 if match: 851 if match:
816 # move to last position in control sequence 852 # move to last position in control sequence
817 # ++ will be done at end of loop 853 # ++ will be done at end of loop
818 index += match.end() - 1 854 index += match.end() - 1
819 855
820 action = match.group("action") 856 action = match.group("action")
821 if action in "ABCD": 857 if action in "ABCD":
822 if match.group("count") == "": 858 if match.group("count") == "":
823 count = 1 859 count = 1
824 else: 860 else:
825 count = int(match.group("count")) 861 count = int(match.group("count"))
826 862
827 if action == "A": # up 863 if action == "A": # up
828 tc.movePosition(QTextCursor.MoveOperation.Up, 864 tc.movePosition(QTextCursor.MoveOperation.Up, n=count)
829 n=count)
830 self.replEdit.setTextCursor(tc) 865 self.replEdit.setTextCursor(tc)
831 elif action == "B": # down 866 elif action == "B": # down
832 tc.movePosition(QTextCursor.MoveOperation.Down, 867 tc.movePosition(QTextCursor.MoveOperation.Down, n=count)
833 n=count)
834 self.replEdit.setTextCursor(tc) 868 self.replEdit.setTextCursor(tc)
835 elif action == "C": # right 869 elif action == "C": # right
836 tc.movePosition(QTextCursor.MoveOperation.Right, 870 tc.movePosition(QTextCursor.MoveOperation.Right, n=count)
837 n=count)
838 self.replEdit.setTextCursor(tc) 871 self.replEdit.setTextCursor(tc)
839 elif action == "D": # left 872 elif action == "D": # left
840 tc.movePosition(QTextCursor.MoveOperation.Left, 873 tc.movePosition(QTextCursor.MoveOperation.Left, n=count)
841 n=count)
842 self.replEdit.setTextCursor(tc) 874 self.replEdit.setTextCursor(tc)
843 elif action == "K": # delete things 875 elif action == "K": # delete things
844 if match.group("count") in ("", "0"): 876 if match.group("count") in ("", "0"):
845 # delete to end of line 877 # delete to end of line
846 tc.movePosition( 878 tc.movePosition(
847 QTextCursor.MoveOperation.EndOfLine, 879 QTextCursor.MoveOperation.EndOfLine,
848 mode=QTextCursor.MoveMode.KeepAnchor) 880 mode=QTextCursor.MoveMode.KeepAnchor,
881 )
849 tc.removeSelectedText() 882 tc.removeSelectedText()
850 self.replEdit.setTextCursor(tc) 883 self.replEdit.setTextCursor(tc)
851 elif match.group("count") == "1": 884 elif match.group("count") == "1":
852 # delete to beinning of line 885 # delete to beinning of line
853 tc.movePosition( 886 tc.movePosition(
854 QTextCursor.MoveOperation.StartOfLine, 887 QTextCursor.MoveOperation.StartOfLine,
855 mode=QTextCursor.MoveMode.KeepAnchor) 888 mode=QTextCursor.MoveMode.KeepAnchor,
889 )
856 tc.removeSelectedText() 890 tc.removeSelectedText()
857 self.replEdit.setTextCursor(tc) 891 self.replEdit.setTextCursor(tc)
858 elif match.group("count") == "2": 892 elif match.group("count") == "2":
859 # delete whole line 893 # delete whole line
860 tc.movePosition( 894 tc.movePosition(QTextCursor.MoveOperation.EndOfLine)
861 QTextCursor.MoveOperation.EndOfLine)
862 tc.movePosition( 895 tc.movePosition(
863 QTextCursor.MoveOperation.StartOfLine, 896 QTextCursor.MoveOperation.StartOfLine,
864 mode=QTextCursor.MoveMode.KeepAnchor) 897 mode=QTextCursor.MoveMode.KeepAnchor,
898 )
865 tc.removeSelectedText() 899 tc.removeSelectedText()
866 self.replEdit.setTextCursor(tc) 900 self.replEdit.setTextCursor(tc)
867 elif action == "m": 901 elif action == "m":
868 self.__setCharFormat(match.group(0)[:-1].split(";"), 902 self.__setCharFormat(match.group(0)[:-1].split(";"), tc)
869 tc)
870 else: 903 else:
871 tc.deleteChar() 904 tc.deleteChar()
872 self.replEdit.setTextCursor(tc) 905 self.replEdit.setTextCursor(tc)
873 self.replEdit.insertPlainText(chr(data[index])) 906 self.replEdit.insertPlainText(chr(data[index]))
874 907
875 index += 1 908 index += 1
876 909
877 self.replEdit.ensureCursorVisible() 910 self.replEdit.ensureCursorVisible()
878 911
879 def __setCharFormat(self, formatCodes, textCursor): 912 def __setCharFormat(self, formatCodes, textCursor):
880 """ 913 """
881 Private method setting the current text format of the REPL pane based 914 Private method setting the current text format of the REPL pane based
882 on the passed ANSI codes. 915 on the passed ANSI codes.
883 916
884 Following codes are used: 917 Following codes are used:
885 <ul> 918 <ul>
886 <li>0: Reset</li> 919 <li>0: Reset</li>
887 <li>1: Bold font (weight 75)</li> 920 <li>1: Bold font (weight 75)</li>
888 <li>2: Light font (weight 25)</li> 921 <li>2: Light font (weight 25)</li>
929 <li>104: bright background Blue</li> 962 <li>104: bright background Blue</li>
930 <li>105: bright background Magenta</li> 963 <li>105: bright background Magenta</li>
931 <li>106: bright background Cyan</li> 964 <li>106: bright background Cyan</li>
932 <li>107: bright background White</li> 965 <li>107: bright background White</li>
933 </ul> 966 </ul>
934 967
935 @param formatCodes list of format codes 968 @param formatCodes list of format codes
936 @type list of str 969 @type list of str
937 @param textCursor reference to the text cursor 970 @param textCursor reference to the text cursor
938 @type QTextCursor 971 @type QTextCursor
939 """ 972 """
940 if not formatCodes: 973 if not formatCodes:
941 # empty format codes list is treated as a reset 974 # empty format codes list is treated as a reset
942 formatCodes = ["0"] 975 formatCodes = ["0"]
943 976
944 charFormat = textCursor.charFormat() 977 charFormat = textCursor.charFormat()
945 for formatCode in formatCodes: 978 for formatCode in formatCodes:
946 try: 979 try:
947 formatCode = int(formatCode) 980 formatCode = int(formatCode)
948 except ValueError: 981 except ValueError:
949 # ignore non digit values 982 # ignore non digit values
950 continue 983 continue
951 984
952 if formatCode == 0: 985 if formatCode == 0:
953 charFormat.setFontWeight(50) 986 charFormat.setFontWeight(50)
954 charFormat.setFontItalic(False) 987 charFormat.setFontItalic(False)
955 charFormat.setFontUnderline(False) 988 charFormat.setFontUnderline(False)
956 charFormat.setFontStrikeOut(False) 989 charFormat.setFontStrikeOut(False)
979 charFormat.setFontOverline(True) 1012 charFormat.setFontOverline(True)
980 elif formatCode == 55: 1013 elif formatCode == 55:
981 charFormat.setFontOverline(False) 1014 charFormat.setFontOverline(False)
982 elif formatCode in (30, 31, 32, 33, 34, 35, 36, 37): 1015 elif formatCode in (30, 31, 32, 33, 34, 35, 36, 37):
983 charFormat.setForeground( 1016 charFormat.setForeground(
984 AnsiColorSchemes[self.__colorScheme][formatCode - 30]) 1017 AnsiColorSchemes[self.__colorScheme][formatCode - 30]
1018 )
985 elif formatCode in (40, 41, 42, 43, 44, 45, 46, 47): 1019 elif formatCode in (40, 41, 42, 43, 44, 45, 46, 47):
986 charFormat.setBackground( 1020 charFormat.setBackground(
987 AnsiColorSchemes[self.__colorScheme][formatCode - 40]) 1021 AnsiColorSchemes[self.__colorScheme][formatCode - 40]
1022 )
988 elif formatCode in (90, 91, 92, 93, 94, 95, 96, 97): 1023 elif formatCode in (90, 91, 92, 93, 94, 95, 96, 97):
989 charFormat.setForeground( 1024 charFormat.setForeground(
990 AnsiColorSchemes[self.__colorScheme][formatCode - 80]) 1025 AnsiColorSchemes[self.__colorScheme][formatCode - 80]
1026 )
991 elif formatCode in (100, 101, 102, 103, 104, 105, 106, 107): 1027 elif formatCode in (100, 101, 102, 103, 104, 105, 106, 107):
992 charFormat.setBackground( 1028 charFormat.setBackground(
993 AnsiColorSchemes[self.__colorScheme][formatCode - 90]) 1029 AnsiColorSchemes[self.__colorScheme][formatCode - 90]
1030 )
994 elif formatCode == 39: 1031 elif formatCode == 39:
995 charFormat.setForeground(self.DefaultForeground) 1032 charFormat.setForeground(self.DefaultForeground)
996 elif formatCode == 49: 1033 elif formatCode == 49:
997 charFormat.setBackground(self.DefaultBackground) 1034 charFormat.setBackground(self.DefaultBackground)
998 1035
999 textCursor.setCharFormat(charFormat) 1036 textCursor.setCharFormat(charFormat)
1000 1037
1001 def __doZoom(self, value): 1038 def __doZoom(self, value):
1002 """ 1039 """
1003 Private slot to zoom the REPL pane. 1040 Private slot to zoom the REPL pane.
1004 1041
1005 @param value zoom value 1042 @param value zoom value
1006 @type int 1043 @type int
1007 """ 1044 """
1008 if value < self.__currentZoom: 1045 if value < self.__currentZoom:
1009 self.replEdit.zoomOut(self.__currentZoom - value) 1046 self.replEdit.zoomOut(self.__currentZoom - value)
1010 elif value > self.__currentZoom: 1047 elif value > self.__currentZoom:
1011 self.replEdit.zoomIn(value - self.__currentZoom) 1048 self.replEdit.zoomIn(value - self.__currentZoom)
1012 self.__currentZoom = value 1049 self.__currentZoom = value
1013 1050
1014 def getCurrentPort(self): 1051 def getCurrentPort(self):
1015 """ 1052 """
1016 Public method to determine the port path of the selected device. 1053 Public method to determine the port path of the selected device.
1017 1054
1018 @return path of the port of the selected device 1055 @return path of the port of the selected device
1019 @rtype str 1056 @rtype str
1020 """ 1057 """
1021 portName = self.deviceTypeComboBox.currentData(self.DevicePortRole) 1058 portName = self.deviceTypeComboBox.currentData(self.DevicePortRole)
1022 if portName: 1059 if portName:
1026 else: 1063 else:
1027 # return with device path prepended 1064 # return with device path prepended
1028 return "/dev/{0}".format(portName) 1065 return "/dev/{0}".format(portName)
1029 else: 1066 else:
1030 return "" 1067 return ""
1031 1068
1032 def getCurrentBoard(self): 1069 def getCurrentBoard(self):
1033 """ 1070 """
1034 Public method to get the board name of the selected device. 1071 Public method to get the board name of the selected device.
1035 1072
1036 @return board name of the selected device 1073 @return board name of the selected device
1037 @rtype str 1074 @rtype str
1038 """ 1075 """
1039 boardName = self.deviceTypeComboBox.currentData(self.DeviceBoardRole) 1076 boardName = self.deviceTypeComboBox.currentData(self.DeviceBoardRole)
1040 return boardName 1077 return boardName
1041 1078
1042 def getDeviceWorkspace(self): 1079 def getDeviceWorkspace(self):
1043 """ 1080 """
1044 Public method to get the workspace directory of the device. 1081 Public method to get the workspace directory of the device.
1045 1082
1046 @return workspace directory of the device 1083 @return workspace directory of the device
1047 @rtype str 1084 @rtype str
1048 """ 1085 """
1049 if self.__device: 1086 if self.__device:
1050 return self.__device.getWorkspace() 1087 return self.__device.getWorkspace()
1051 else: 1088 else:
1052 return "" 1089 return ""
1053 1090
1054 def __connectToDevice(self): 1091 def __connectToDevice(self):
1055 """ 1092 """
1056 Private method to connect to the selected device. 1093 Private method to connect to the selected device.
1057 """ 1094 """
1058 port = self.getCurrentPort() 1095 port = self.getCurrentPort()
1059 if not port: 1096 if not port:
1060 from .ConnectionSelectionDialog import ConnectionSelectionDialog 1097 from .ConnectionSelectionDialog import ConnectionSelectionDialog
1098
1061 with EricOverridenCursor(): 1099 with EricOverridenCursor():
1062 dlg = ConnectionSelectionDialog( 1100 dlg = ConnectionSelectionDialog(
1063 self.__unknownPorts, self.__lastPort, self.__lastDeviceType 1101 self.__unknownPorts, self.__lastPort, self.__lastDeviceType
1064 ) 1102 )
1065 if dlg.exec() == QDialog.DialogCode.Accepted: 1103 if dlg.exec() == QDialog.DialogCode.Accepted:
1066 vid, pid, port, deviceType = dlg.getData() 1104 vid, pid, port, deviceType = dlg.getData()
1067 1105
1068 self.deviceIconLabel.setPixmap( 1106 self.deviceIconLabel.setPixmap(
1069 MicroPythonDevices.getDeviceIcon(deviceType, False)) 1107 MicroPythonDevices.getDeviceIcon(deviceType, False)
1108 )
1070 self.__device = MicroPythonDevices.getDevice( 1109 self.__device = MicroPythonDevices.getDevice(
1071 deviceType, self, vid, pid) 1110 deviceType, self, vid, pid
1111 )
1072 self.__device.setButtons() 1112 self.__device.setButtons()
1073 1113
1074 self.__lastPort = port 1114 self.__lastPort = port
1075 self.__lastDeviceType = deviceType 1115 self.__lastDeviceType = deviceType
1076 else: 1116 else:
1077 return 1117 return
1078 1118
1079 if self.__interface.connectToDevice(port): 1119 if self.__interface.connectToDevice(port):
1080 self.__setConnected(True) 1120 self.__setConnected(True)
1081 1121
1082 if (Preferences.getMicroPython("SyncTimeAfterConnect") and 1122 if (
1083 self.__device.hasTimeCommands()): 1123 Preferences.getMicroPython("SyncTimeAfterConnect")
1124 and self.__device.hasTimeCommands()
1125 ):
1084 self.__synchronizeTime(quiet=True) 1126 self.__synchronizeTime(quiet=True)
1085 else: 1127 else:
1086 with EricOverridenCursor(): 1128 with EricOverridenCursor():
1087 EricMessageBox.warning( 1129 EricMessageBox.warning(
1088 self, 1130 self,
1089 self.tr("Serial Device Connect"), 1131 self.tr("Serial Device Connect"),
1090 self.tr("""<p>Cannot connect to device at serial""" 1132 self.tr(
1091 """ port <b>{0}</b>.</p>""").format(port)) 1133 """<p>Cannot connect to device at serial"""
1092 1134 """ port <b>{0}</b>.</p>"""
1135 ).format(port),
1136 )
1137
1093 def __disconnectFromDevice(self): 1138 def __disconnectFromDevice(self):
1094 """ 1139 """
1095 Private method to disconnect from the device. 1140 Private method to disconnect from the device.
1096 """ 1141 """
1097 self.__interface.disconnectFromDevice() 1142 self.__interface.disconnectFromDevice()
1098 self.__setConnected(False) 1143 self.__setConnected(False)
1099 1144
1100 @pyqtSlot() 1145 @pyqtSlot()
1101 def on_runButton_clicked(self): 1146 def on_runButton_clicked(self):
1102 """ 1147 """
1103 Private slot to execute the script of the active editor on the 1148 Private slot to execute the script of the active editor on the
1104 selected device. 1149 selected device.
1105 1150
1106 If the REPL is not active yet, it will be activated, which might cause 1151 If the REPL is not active yet, it will be activated, which might cause
1107 an unconnected device to be connected. 1152 an unconnected device to be connected.
1108 """ 1153 """
1109 if not self.__device: 1154 if not self.__device:
1110 self.__showNoDeviceMessage() 1155 self.__showNoDeviceMessage()
1111 return 1156 return
1112 1157
1113 aw = ericApp().getObject("ViewManager").activeWindow() 1158 aw = ericApp().getObject("ViewManager").activeWindow()
1114 if aw is None: 1159 if aw is None:
1115 EricMessageBox.critical( 1160 EricMessageBox.critical(
1116 self, 1161 self,
1117 self.tr("Run Script"), 1162 self.tr("Run Script"),
1118 self.tr("""There is no editor open. Abort...""")) 1163 self.tr("""There is no editor open. Abort..."""),
1164 )
1119 return 1165 return
1120 1166
1121 script = aw.text() 1167 script = aw.text()
1122 if not script: 1168 if not script:
1123 EricMessageBox.critical( 1169 EricMessageBox.critical(
1124 self, 1170 self,
1125 self.tr("Run Script"), 1171 self.tr("Run Script"),
1126 self.tr("""The current editor does not contain a script.""" 1172 self.tr(
1127 """ Abort...""")) 1173 """The current editor does not contain a script.""" """ Abort..."""
1174 ),
1175 )
1128 return 1176 return
1129 1177
1130 ok, reason = self.__device.canRunScript() 1178 ok, reason = self.__device.canRunScript()
1131 if not ok: 1179 if not ok:
1132 EricMessageBox.warning( 1180 EricMessageBox.warning(
1133 self, 1181 self,
1134 self.tr("Run Script"), 1182 self.tr("Run Script"),
1135 self.tr("""<p>Cannot run script.</p><p>Reason:""" 1183 self.tr(
1136 """ {0}</p>""").format(reason)) 1184 """<p>Cannot run script.</p><p>Reason:""" """ {0}</p>"""
1185 ).format(reason),
1186 )
1137 return 1187 return
1138 1188
1139 if not self.replButton.isChecked(): 1189 if not self.replButton.isChecked():
1140 # activate on the REPL 1190 # activate on the REPL
1141 self.on_replButton_clicked(True) 1191 self.on_replButton_clicked(True)
1142 if self.replButton.isChecked(): 1192 if self.replButton.isChecked():
1143 self.__device.runScript(script) 1193 self.__device.runScript(script)
1144 1194
1145 @pyqtSlot() 1195 @pyqtSlot()
1146 def on_openButton_clicked(self): 1196 def on_openButton_clicked(self):
1147 """ 1197 """
1148 Private slot to open a file of the connected device. 1198 Private slot to open a file of the connected device.
1149 """ 1199 """
1150 if not self.__device: 1200 if not self.__device:
1151 self.__showNoDeviceMessage() 1201 self.__showNoDeviceMessage()
1152 return 1202 return
1153 1203
1154 workspace = self.getDeviceWorkspace() 1204 workspace = self.getDeviceWorkspace()
1155 if workspace: 1205 if workspace:
1156 fileName = EricFileDialog.getOpenFileName( 1206 fileName = EricFileDialog.getOpenFileName(
1157 self, 1207 self,
1158 self.tr("Open Python File"), 1208 self.tr("Open Python File"),
1159 workspace, 1209 workspace,
1160 self.tr("Python3 Files (*.py);;All Files (*)")) 1210 self.tr("Python3 Files (*.py);;All Files (*)"),
1211 )
1161 if fileName: 1212 if fileName:
1162 ericApp().getObject("ViewManager").openSourceFile(fileName) 1213 ericApp().getObject("ViewManager").openSourceFile(fileName)
1163 1214
1164 @pyqtSlot() 1215 @pyqtSlot()
1165 def on_saveButton_clicked(self): 1216 def on_saveButton_clicked(self):
1166 """ 1217 """
1167 Private slot to save the current editor to the connected device. 1218 Private slot to save the current editor to the connected device.
1168 """ 1219 """
1169 if not self.__device: 1220 if not self.__device:
1170 self.__showNoDeviceMessage() 1221 self.__showNoDeviceMessage()
1171 return 1222 return
1172 1223
1173 aw = ericApp().getObject("ViewManager").activeWindow() 1224 aw = ericApp().getObject("ViewManager").activeWindow()
1174 if aw: 1225 if aw:
1175 workspace = self.getDeviceWorkspace() 1226 workspace = self.getDeviceWorkspace()
1176 if workspace: 1227 if workspace:
1177 aw.saveFileAs(workspace) 1228 aw.saveFileAs(workspace)
1178 1229
1179 @pyqtSlot(bool) 1230 @pyqtSlot(bool)
1180 def on_chartButton_clicked(self, checked): 1231 def on_chartButton_clicked(self, checked):
1181 """ 1232 """
1182 Private slot to open a chart view to plot data received from the 1233 Private slot to open a chart view to plot data received from the
1183 connected device. 1234 connected device.
1184 1235
1185 If the selected device is not connected yet, this will be done now. 1236 If the selected device is not connected yet, this will be done now.
1186 1237
1187 @param checked state of the button 1238 @param checked state of the button
1188 @type bool 1239 @type bool
1189 """ 1240 """
1190 if not HAS_QTCHART: 1241 if not HAS_QTCHART:
1191 # QtCharts not available => fail silently 1242 # QtCharts not available => fail silently
1192 return 1243 return
1193 1244
1194 if not self.__device: 1245 if not self.__device:
1195 self.__showNoDeviceMessage() 1246 self.__showNoDeviceMessage()
1196 return 1247 return
1197 1248
1198 if checked: 1249 if checked:
1199 ok, reason = self.__device.canStartPlotter() 1250 ok, reason = self.__device.canStartPlotter()
1200 if not ok: 1251 if not ok:
1201 EricMessageBox.warning( 1252 EricMessageBox.warning(
1202 self, 1253 self,
1203 self.tr("Start Chart"), 1254 self.tr("Start Chart"),
1204 self.tr("""<p>The Chart cannot be started.</p><p>Reason:""" 1255 self.tr(
1205 """ {0}</p>""").format(reason)) 1256 """<p>The Chart cannot be started.</p><p>Reason:"""
1257 """ {0}</p>"""
1258 ).format(reason),
1259 )
1206 return 1260 return
1207 1261
1208 self.__chartWidget = MicroPythonGraphWidget(self) 1262 self.__chartWidget = MicroPythonGraphWidget(self)
1209 self.__interface.dataReceived.connect( 1263 self.__interface.dataReceived.connect(self.__chartWidget.processData)
1210 self.__chartWidget.processData) 1264 self.__chartWidget.dataFlood.connect(self.handleDataFlood)
1211 self.__chartWidget.dataFlood.connect( 1265
1212 self.handleDataFlood) 1266 self.__ui.addSideWidget(
1213 1267 self.__ui.BottomSide,
1214 self.__ui.addSideWidget(self.__ui.BottomSide, self.__chartWidget, 1268 self.__chartWidget,
1215 UI.PixmapCache.getIcon("chart"), 1269 UI.PixmapCache.getIcon("chart"),
1216 self.tr("µPy Chart")) 1270 self.tr("µPy Chart"),
1271 )
1217 self.__ui.showSideWidget(self.__chartWidget) 1272 self.__ui.showSideWidget(self.__chartWidget)
1218 1273
1219 if not self.__interface.isConnected(): 1274 if not self.__interface.isConnected():
1220 self.__connectToDevice() 1275 self.__connectToDevice()
1221 if self.__device.forceInterrupt(): 1276 if self.__device.forceInterrupt():
1222 # send a Ctrl-B (exit raw mode) 1277 # send a Ctrl-B (exit raw mode)
1223 self.__interface.write(b'\x02') 1278 self.__interface.write(b"\x02")
1224 # send Ctrl-C (keyboard interrupt) 1279 # send Ctrl-C (keyboard interrupt)
1225 self.__interface.write(b'\x03') 1280 self.__interface.write(b"\x03")
1226 1281
1227 self.__device.setPlotter(True) 1282 self.__device.setPlotter(True)
1228 else: 1283 else:
1229 if self.__chartWidget.isDirty(): 1284 if self.__chartWidget.isDirty():
1230 res = EricMessageBox.okToClearData( 1285 res = EricMessageBox.okToClearData(
1231 self, 1286 self,
1232 self.tr("Unsaved Chart Data"), 1287 self.tr("Unsaved Chart Data"),
1233 self.tr("""The chart contains unsaved data."""), 1288 self.tr("""The chart contains unsaved data."""),
1234 self.__chartWidget.saveData) 1289 self.__chartWidget.saveData,
1290 )
1235 if not res: 1291 if not res:
1236 # abort 1292 # abort
1237 return 1293 return
1238 1294
1239 self.__interface.dataReceived.disconnect( 1295 self.__interface.dataReceived.disconnect(self.__chartWidget.processData)
1240 self.__chartWidget.processData) 1296 self.__chartWidget.dataFlood.disconnect(self.handleDataFlood)
1241 self.__chartWidget.dataFlood.disconnect( 1297
1242 self.handleDataFlood) 1298 if not self.replButton.isChecked() and not self.filesButton.isChecked():
1243
1244 if (not self.replButton.isChecked() and
1245 not self.filesButton.isChecked()):
1246 self.__disconnectFromDevice() 1299 self.__disconnectFromDevice()
1247 1300
1248 self.__device.setPlotter(False) 1301 self.__device.setPlotter(False)
1249 self.__ui.removeSideWidget(self.__chartWidget) 1302 self.__ui.removeSideWidget(self.__chartWidget)
1250 1303
1251 self.__chartWidget.deleteLater() 1304 self.__chartWidget.deleteLater()
1252 self.__chartWidget = None 1305 self.__chartWidget = None
1253 1306
1254 self.chartButton.setChecked(checked) 1307 self.chartButton.setChecked(checked)
1255 1308
1256 @pyqtSlot() 1309 @pyqtSlot()
1257 def handleDataFlood(self): 1310 def handleDataFlood(self):
1258 """ 1311 """
1259 Public slot handling a data flood from the device. 1312 Public slot handling a data flood from the device.
1260 """ 1313 """
1261 self.on_connectButton_clicked() 1314 self.on_connectButton_clicked()
1262 self.__device.handleDataFlood() 1315 self.__device.handleDataFlood()
1263 1316
1264 @pyqtSlot(bool) 1317 @pyqtSlot(bool)
1265 def on_filesButton_clicked(self, checked): 1318 def on_filesButton_clicked(self, checked):
1266 """ 1319 """
1267 Private slot to open a file manager window to the connected device. 1320 Private slot to open a file manager window to the connected device.
1268 1321
1269 If the selected device is not connected yet, this will be done now. 1322 If the selected device is not connected yet, this will be done now.
1270 1323
1271 @param checked state of the button 1324 @param checked state of the button
1272 @type bool 1325 @type bool
1273 """ 1326 """
1274 if not self.__device: 1327 if not self.__device:
1275 self.__showNoDeviceMessage() 1328 self.__showNoDeviceMessage()
1276 return 1329 return
1277 1330
1278 if checked: 1331 if checked:
1279 ok, reason = self.__device.canStartFileManager() 1332 ok, reason = self.__device.canStartFileManager()
1280 if not ok: 1333 if not ok:
1281 EricMessageBox.warning( 1334 EricMessageBox.warning(
1282 self, 1335 self,
1283 self.tr("Start File Manager"), 1336 self.tr("Start File Manager"),
1284 self.tr("""<p>The File Manager cannot be started.</p>""" 1337 self.tr(
1285 """<p>Reason: {0}</p>""").format(reason)) 1338 """<p>The File Manager cannot be started.</p>"""
1339 """<p>Reason: {0}</p>"""
1340 ).format(reason),
1341 )
1286 return 1342 return
1287 1343
1288 with EricOverrideCursor(): 1344 with EricOverrideCursor():
1289 if not self.__interface.isConnected(): 1345 if not self.__interface.isConnected():
1290 self.__connectToDevice() 1346 self.__connectToDevice()
1291 if self.__connected: 1347 if self.__connected:
1292 self.__fileManagerWidget = MicroPythonFileManagerWidget( 1348 self.__fileManagerWidget = MicroPythonFileManagerWidget(
1293 self.__interface, 1349 self.__interface, self.__device.supportsLocalFileAccess(), self
1294 self.__device.supportsLocalFileAccess(), 1350 )
1295 self) 1351
1296
1297 self.__ui.addSideWidget( 1352 self.__ui.addSideWidget(
1298 self.__ui.BottomSide, 1353 self.__ui.BottomSide,
1299 self.__fileManagerWidget, 1354 self.__fileManagerWidget,
1300 UI.PixmapCache.getIcon("filemanager"), 1355 UI.PixmapCache.getIcon("filemanager"),
1301 self.tr("µPy Files") 1356 self.tr("µPy Files"),
1302 ) 1357 )
1303 self.__ui.showSideWidget(self.__fileManagerWidget) 1358 self.__ui.showSideWidget(self.__fileManagerWidget)
1304 1359
1305 self.__device.setFileManager(True) 1360 self.__device.setFileManager(True)
1306 1361
1307 self.__fileManagerWidget.start() 1362 self.__fileManagerWidget.start()
1308 else: 1363 else:
1309 self.__fileManagerWidget.stop() 1364 self.__fileManagerWidget.stop()
1310 1365
1311 if (not self.replButton.isChecked() and 1366 if not self.replButton.isChecked() and not self.chartButton.isChecked():
1312 not self.chartButton.isChecked()):
1313 self.__disconnectFromDevice() 1367 self.__disconnectFromDevice()
1314 1368
1315 self.__device.setFileManager(False) 1369 self.__device.setFileManager(False)
1316 self.__ui.removeSideWidget(self.__fileManagerWidget) 1370 self.__ui.removeSideWidget(self.__fileManagerWidget)
1317 1371
1318 self.__fileManagerWidget.deleteLater() 1372 self.__fileManagerWidget.deleteLater()
1319 self.__fileManagerWidget = None 1373 self.__fileManagerWidget = None
1320 1374
1321 self.filesButton.setChecked(checked) 1375 self.filesButton.setChecked(checked)
1322 1376
1323 ################################################################## 1377 ##################################################################
1324 ## Super Menu related methods below 1378 ## Super Menu related methods below
1325 ################################################################## 1379 ##################################################################
1326 1380
1327 def __aboutToShowSuperMenu(self): 1381 def __aboutToShowSuperMenu(self):
1328 """ 1382 """
1329 Private slot to populate the Super Menu before showing it. 1383 Private slot to populate the Super Menu before showing it.
1330 """ 1384 """
1331 self.__superMenu.clear() 1385 self.__superMenu.clear()
1332 1386
1333 # prepare the download menu 1387 # prepare the download menu
1334 if self.__device: 1388 if self.__device:
1335 menuEntries = self.__device.getDownloadMenuEntries() 1389 menuEntries = self.__device.getDownloadMenuEntries()
1336 if menuEntries: 1390 if menuEntries:
1337 downloadMenu = QMenu(self.tr("Downloads"), self.__superMenu) 1391 downloadMenu = QMenu(self.tr("Downloads"), self.__superMenu)
1338 for text, url in menuEntries: 1392 for text, url in menuEntries:
1339 if text == "<separator>": 1393 if text == "<separator>":
1340 downloadMenu.addSeparator() 1394 downloadMenu.addSeparator()
1341 else: 1395 else:
1342 downloadMenu.addAction( 1396 downloadMenu.addAction(
1343 text, 1397 text, functools.partial(self.__downloadFromUrl, url)
1344 functools.partial(self.__downloadFromUrl, url)
1345 ) 1398 )
1346 else: 1399 else:
1347 downloadMenu = None 1400 downloadMenu = None
1348 1401
1349 # populate the super menu 1402 # populate the super menu
1350 hasTime = self.__device.hasTimeCommands() if self.__device else False 1403 hasTime = self.__device.hasTimeCommands() if self.__device else False
1351 1404
1352 act = self.__superMenu.addAction( 1405 act = self.__superMenu.addAction(
1353 self.tr("Show Version"), self.__showDeviceVersion) 1406 self.tr("Show Version"), self.__showDeviceVersion
1407 )
1354 act.setEnabled(self.__connected) 1408 act.setEnabled(self.__connected)
1355 act = self.__superMenu.addAction( 1409 act = self.__superMenu.addAction(
1356 self.tr("Show Implementation"), self.__showImplementation) 1410 self.tr("Show Implementation"), self.__showImplementation
1411 )
1357 act.setEnabled(self.__connected) 1412 act.setEnabled(self.__connected)
1358 act = self.__superMenu.addAction( 1413 act = self.__superMenu.addAction(
1359 self.tr("Show Board Data"), self.__showBoardInformation) 1414 self.tr("Show Board Data"), self.__showBoardInformation
1415 )
1360 act.setEnabled(self.__connected) 1416 act.setEnabled(self.__connected)
1361 self.__superMenu.addSeparator() 1417 self.__superMenu.addSeparator()
1362 if hasTime: 1418 if hasTime:
1363 act = self.__superMenu.addAction( 1419 act = self.__superMenu.addAction(
1364 self.tr("Synchronize Time"), self.__synchronizeTime) 1420 self.tr("Synchronize Time"), self.__synchronizeTime
1421 )
1365 act.setEnabled(self.__connected) 1422 act.setEnabled(self.__connected)
1366 act = self.__superMenu.addAction( 1423 act = self.__superMenu.addAction(
1367 self.tr("Show Device Time"), self.__showDeviceTime) 1424 self.tr("Show Device Time"), self.__showDeviceTime
1425 )
1368 act.setEnabled(self.__connected) 1426 act.setEnabled(self.__connected)
1369 self.__superMenu.addAction( 1427 self.__superMenu.addAction(self.tr("Show Local Time"), self.__showLocalTime)
1370 self.tr("Show Local Time"), self.__showLocalTime)
1371 if hasTime: 1428 if hasTime:
1372 act = self.__superMenu.addAction( 1429 act = self.__superMenu.addAction(
1373 self.tr("Show Time"), self.__showLocalAndDeviceTime) 1430 self.tr("Show Time"), self.__showLocalAndDeviceTime
1431 )
1374 act.setEnabled(self.__connected) 1432 act.setEnabled(self.__connected)
1375 self.__superMenu.addSeparator() 1433 self.__superMenu.addSeparator()
1376 if not Globals.isWindowsPlatform(): 1434 if not Globals.isWindowsPlatform():
1377 available = self.__mpyCrossAvailable() 1435 available = self.__mpyCrossAvailable()
1378 act = self.__superMenu.addAction( 1436 act = self.__superMenu.addAction(
1379 self.tr("Compile Python File"), self.__compileFile2Mpy) 1437 self.tr("Compile Python File"), self.__compileFile2Mpy
1438 )
1380 act.setEnabled(available) 1439 act.setEnabled(available)
1381 act = self.__superMenu.addAction( 1440 act = self.__superMenu.addAction(
1382 self.tr("Compile Current Editor"), self.__compileEditor2Mpy) 1441 self.tr("Compile Current Editor"), self.__compileEditor2Mpy
1442 )
1383 aw = ericApp().getObject("ViewManager").activeWindow() 1443 aw = ericApp().getObject("ViewManager").activeWindow()
1384 act.setEnabled(available and bool(aw)) 1444 act.setEnabled(available and bool(aw))
1385 self.__superMenu.addSeparator() 1445 self.__superMenu.addSeparator()
1386 if self.__device: 1446 if self.__device:
1387 self.__device.addDeviceMenuEntries(self.__superMenu) 1447 self.__device.addDeviceMenuEntries(self.__superMenu)
1388 self.__superMenu.addSeparator() 1448 self.__superMenu.addSeparator()
1389 if downloadMenu is None: 1449 if downloadMenu is None:
1390 # generic download action 1450 # generic download action
1391 act = self.__superMenu.addAction( 1451 act = self.__superMenu.addAction(
1392 self.tr("Download Firmware"), self.__downloadFirmware) 1452 self.tr("Download Firmware"), self.__downloadFirmware
1453 )
1393 act.setEnabled(self.__device.hasFirmwareUrl()) 1454 act.setEnabled(self.__device.hasFirmwareUrl())
1394 else: 1455 else:
1395 # download sub-menu 1456 # download sub-menu
1396 self.__superMenu.addMenu(downloadMenu) 1457 self.__superMenu.addMenu(downloadMenu)
1397 self.__superMenu.addSeparator() 1458 self.__superMenu.addSeparator()
1398 act = self.__superMenu.addAction( 1459 act = self.__superMenu.addAction(
1399 self.tr("Show Documentation"), self.__showDocumentation) 1460 self.tr("Show Documentation"), self.__showDocumentation
1461 )
1400 act.setEnabled(self.__device.hasDocumentationUrl()) 1462 act.setEnabled(self.__device.hasDocumentationUrl())
1401 self.__superMenu.addSeparator() 1463 self.__superMenu.addSeparator()
1402 if not self.__device.hasFlashMenuEntry(): 1464 if not self.__device.hasFlashMenuEntry():
1403 self.__superMenu.addAction(self.tr("Flash UF2 Device"), 1465 self.__superMenu.addAction(self.tr("Flash UF2 Device"), self.__flashUF2)
1404 self.__flashUF2)
1405 self.__superMenu.addSeparator() 1466 self.__superMenu.addSeparator()
1406 self.__superMenu.addAction(self.tr("Manage Unknown Devices"), 1467 self.__superMenu.addAction(
1407 self.__manageUnknownDevices) 1468 self.tr("Manage Unknown Devices"), self.__manageUnknownDevices
1408 self.__superMenu.addAction(self.tr("Ignored Serial Devices"), 1469 )
1409 self.__manageIgnored) 1470 self.__superMenu.addAction(
1471 self.tr("Ignored Serial Devices"), self.__manageIgnored
1472 )
1410 self.__superMenu.addSeparator() 1473 self.__superMenu.addSeparator()
1411 self.__superMenu.addAction(self.tr("Configure"), self.__configure) 1474 self.__superMenu.addAction(self.tr("Configure"), self.__configure)
1412 1475
1413 @pyqtSlot() 1476 @pyqtSlot()
1414 def __showDeviceVersion(self): 1477 def __showDeviceVersion(self):
1415 """ 1478 """
1416 Private slot to show some version info about MicroPython of the device. 1479 Private slot to show some version info about MicroPython of the device.
1417 """ 1480 """
1418 try: 1481 try:
1419 versionInfo = self.__interface.version() 1482 versionInfo = self.__interface.version()
1420 if versionInfo: 1483 if versionInfo:
1421 msg = self.tr( 1484 msg = self.tr("<h3>Device Version Information</h3>")
1422 "<h3>Device Version Information</h3>"
1423 )
1424 msg += "<table>" 1485 msg += "<table>"
1425 for key, value in versionInfo.items(): 1486 for key, value in versionInfo.items():
1426 msg += "<tr><td><b>{0}</b></td><td>{1}</td></tr>".format( 1487 msg += "<tr><td><b>{0}</b></td><td>{1}</td></tr>".format(
1427 key.capitalize(), value) 1488 key.capitalize(), value
1489 )
1428 msg += "</table>" 1490 msg += "</table>"
1429 else: 1491 else:
1430 msg = self.tr("No version information available.") 1492 msg = self.tr("No version information available.")
1431 1493
1432 EricMessageBox.information( 1494 EricMessageBox.information(self, self.tr("Device Version Information"), msg)
1433 self,
1434 self.tr("Device Version Information"),
1435 msg)
1436 except Exception as exc: 1495 except Exception as exc:
1437 self.__showError("version()", str(exc)) 1496 self.__showError("version()", str(exc))
1438 1497
1439 @pyqtSlot() 1498 @pyqtSlot()
1440 def __showImplementation(self): 1499 def __showImplementation(self):
1441 """ 1500 """
1442 Private slot to show some implementation related information. 1501 Private slot to show some implementation related information.
1443 """ 1502 """
1451 name = self.tr("unknown") 1510 name = self.tr("unknown")
1452 else: 1511 else:
1453 name = impInfo["name"] 1512 name = impInfo["name"]
1454 version = ( 1513 version = (
1455 self.tr("unknown") 1514 self.tr("unknown")
1456 if impInfo["version"] == "unknown" else 1515 if impInfo["version"] == "unknown"
1457 impInfo["version"] 1516 else impInfo["version"]
1458 ) 1517 )
1459 1518
1460 EricMessageBox.information( 1519 EricMessageBox.information(
1461 self, 1520 self,
1462 self.tr("Device Implementation Information"), 1521 self.tr("Device Implementation Information"),
1463 self.tr( 1522 self.tr(
1464 "<h3>Device Implementation Information</h3>" 1523 "<h3>Device Implementation Information</h3>"
1465 "<p>This device contains <b>{0} {1}</b>.</p>" 1524 "<p>This device contains <b>{0} {1}</b>.</p>"
1466 ).format(name, version) 1525 ).format(name, version),
1467 ) 1526 )
1468 except Exception as exc: 1527 except Exception as exc:
1469 self.__showError("getImplementation()", str(exc)) 1528 self.__showError("getImplementation()", str(exc))
1470 1529
1471 @pyqtSlot() 1530 @pyqtSlot()
1472 def __showBoardInformation(self): 1531 def __showBoardInformation(self):
1473 """ 1532 """
1474 Private slot to show all available information about a board. 1533 Private slot to show all available information about a board.
1475 """ 1534 """
1476 try: 1535 try:
1477 boardInfo = self.__interface.getBoardInformation() 1536 boardInfo = self.__interface.getBoardInformation()
1478 1537
1479 from .BoardDataDialog import BoardDataDialog 1538 from .BoardDataDialog import BoardDataDialog
1539
1480 dlg = BoardDataDialog(boardInfo) 1540 dlg = BoardDataDialog(boardInfo)
1481 dlg.exec() 1541 dlg.exec()
1482 except Exception as exc: 1542 except Exception as exc:
1483 self.__showError("getBoardInformation()", str(exc)) 1543 self.__showError("getBoardInformation()", str(exc))
1484 1544
1485 @pyqtSlot() 1545 @pyqtSlot()
1486 def __synchronizeTime(self, quiet=False): 1546 def __synchronizeTime(self, quiet=False):
1487 """ 1547 """
1488 Private slot to set the time of the connected device to the local 1548 Private slot to set the time of the connected device to the local
1489 computer's time. 1549 computer's time.
1490 1550
1491 @param quiet flag indicating to not show a message 1551 @param quiet flag indicating to not show a message
1492 @type bool 1552 @type bool
1493 """ 1553 """
1494 if self.__device and self.__device.hasTimeCommands(): 1554 if self.__device and self.__device.hasTimeCommands():
1495 try: 1555 try:
1496 self.__interface.syncTime(self.__device.getDeviceType()) 1556 self.__interface.syncTime(self.__device.getDeviceType())
1497 1557
1498 if not quiet: 1558 if not quiet:
1499 with EricOverridenCursor(): 1559 with EricOverridenCursor():
1500 EricMessageBox.information( 1560 EricMessageBox.information(
1501 self, 1561 self,
1502 self.tr("Synchronize Time"), 1562 self.tr("Synchronize Time"),
1503 self.tr("<p>The time of the connected device was" 1563 self.tr(
1504 " synchronized with the local time.</p>") + 1564 "<p>The time of the connected device was"
1505 self.__getDeviceTime() 1565 " synchronized with the local time.</p>"
1566 )
1567 + self.__getDeviceTime(),
1506 ) 1568 )
1507 except Exception as exc: 1569 except Exception as exc:
1508 self.__showError("syncTime()", str(exc)) 1570 self.__showError("syncTime()", str(exc))
1509 1571
1510 def __getDeviceTime(self): 1572 def __getDeviceTime(self):
1511 """ 1573 """
1512 Private method to get a string containing the date and time of the 1574 Private method to get a string containing the date and time of the
1513 connected device. 1575 connected device.
1514 1576
1515 @return date and time of the connected device 1577 @return date and time of the connected device
1516 @rtype str 1578 @rtype str
1517 """ 1579 """
1518 if self.__device and self.__device.hasTimeCommands(): 1580 if self.__device and self.__device.hasTimeCommands():
1519 try: 1581 try:
1526 "<tr><td><b>Date</b></td><td>{0}</td></tr>" 1588 "<tr><td><b>Date</b></td><td>{0}</td></tr>"
1527 "<tr><td><b>Time</b></td><td>{1}</td></tr>" 1589 "<tr><td><b>Time</b></td><td>{1}</td></tr>"
1528 "</table>" 1590 "</table>"
1529 ).format(date, time) 1591 ).format(date, time)
1530 except ValueError: 1592 except ValueError:
1531 return self.tr( 1593 return self.tr("<h3>Device Date and Time</h3>" "<p>{0}</p>").format(
1532 "<h3>Device Date and Time</h3>" 1594 dateTimeString.strip()
1533 "<p>{0}</p>" 1595 )
1534 ).format(dateTimeString.strip())
1535 except Exception as exc: 1596 except Exception as exc:
1536 self.__showError("getTime()", str(exc)) 1597 self.__showError("getTime()", str(exc))
1537 return "" 1598 return ""
1538 else: 1599 else:
1539 return "" 1600 return ""
1540 1601
1541 @pyqtSlot() 1602 @pyqtSlot()
1542 def __showDeviceTime(self): 1603 def __showDeviceTime(self):
1543 """ 1604 """
1544 Private slot to show the date and time of the connected device. 1605 Private slot to show the date and time of the connected device.
1545 """ 1606 """
1546 msg = self.__getDeviceTime() 1607 msg = self.__getDeviceTime()
1547 if msg: 1608 if msg:
1548 EricMessageBox.information( 1609 EricMessageBox.information(self, self.tr("Device Date and Time"), msg)
1549 self, 1610
1550 self.tr("Device Date and Time"),
1551 msg)
1552
1553 @pyqtSlot() 1611 @pyqtSlot()
1554 def __showLocalTime(self): 1612 def __showLocalTime(self):
1555 """ 1613 """
1556 Private slot to show the local date and time. 1614 Private slot to show the local date and time.
1557 """ 1615 """
1558 localdatetime = time.localtime() 1616 localdatetime = time.localtime()
1559 localdate = time.strftime('%Y-%m-%d', localdatetime) 1617 localdate = time.strftime("%Y-%m-%d", localdatetime)
1560 localtime = time.strftime('%H:%M:%S', localdatetime) 1618 localtime = time.strftime("%H:%M:%S", localdatetime)
1561 EricMessageBox.information( 1619 EricMessageBox.information(
1562 self, 1620 self,
1563 self.tr("Local Date and Time"), 1621 self.tr("Local Date and Time"),
1564 self.tr("<h3>Local Date and Time</h3>" 1622 self.tr(
1565 "<table>" 1623 "<h3>Local Date and Time</h3>"
1566 "<tr><td><b>Date</b></td><td>{0}</td></tr>" 1624 "<table>"
1567 "<tr><td><b>Time</b></td><td>{1}</td></tr>" 1625 "<tr><td><b>Date</b></td><td>{0}</td></tr>"
1568 "</table>" 1626 "<tr><td><b>Time</b></td><td>{1}</td></tr>"
1569 ).format(localdate, localtime) 1627 "</table>"
1628 ).format(localdate, localtime),
1570 ) 1629 )
1571 1630
1572 @pyqtSlot() 1631 @pyqtSlot()
1573 def __showLocalAndDeviceTime(self): 1632 def __showLocalAndDeviceTime(self):
1574 """ 1633 """
1575 Private slot to show the local and device time side-by-side. 1634 Private slot to show the local and device time side-by-side.
1576 """ 1635 """
1577 localdatetime = time.localtime() 1636 localdatetime = time.localtime()
1578 localdate = time.strftime('%Y-%m-%d', localdatetime) 1637 localdate = time.strftime("%Y-%m-%d", localdatetime)
1579 localtime = time.strftime('%H:%M:%S', localdatetime) 1638 localtime = time.strftime("%H:%M:%S", localdatetime)
1580 1639
1581 try: 1640 try:
1582 deviceDateTimeString = self.__interface.getTime() 1641 deviceDateTimeString = self.__interface.getTime()
1583 try: 1642 try:
1584 devicedate, devicetime = ( 1643 devicedate, devicetime = deviceDateTimeString.strip().split(None, 1)
1585 deviceDateTimeString.strip().split(None, 1)
1586 )
1587 EricMessageBox.information( 1644 EricMessageBox.information(
1588 self, 1645 self,
1589 self.tr("Date and Time"), 1646 self.tr("Date and Time"),
1590 self.tr("<table>" 1647 self.tr(
1591 "<tr><th></th><th>Local Date and Time</th>" 1648 "<table>"
1592 "<th>Device Date and Time</th></tr>" 1649 "<tr><th></th><th>Local Date and Time</th>"
1593 "<tr><td><b>Date</b></td>" 1650 "<th>Device Date and Time</th></tr>"
1594 "<td align='center'>{0}</td>" 1651 "<tr><td><b>Date</b></td>"
1595 "<td align='center'>{2}</td></tr>" 1652 "<td align='center'>{0}</td>"
1596 "<tr><td><b>Time</b></td>" 1653 "<td align='center'>{2}</td></tr>"
1597 "<td align='center'>{1}</td>" 1654 "<tr><td><b>Time</b></td>"
1598 "<td align='center'>{3}</td></tr>" 1655 "<td align='center'>{1}</td>"
1599 "</table>" 1656 "<td align='center'>{3}</td></tr>"
1600 ).format(localdate, localtime, 1657 "</table>"
1601 devicedate, devicetime) 1658 ).format(localdate, localtime, devicedate, devicetime),
1602 ) 1659 )
1603 except ValueError: 1660 except ValueError:
1604 EricMessageBox.information( 1661 EricMessageBox.information(
1605 self, 1662 self,
1606 self.tr("Date and Time"), 1663 self.tr("Date and Time"),
1607 self.tr("<table>" 1664 self.tr(
1608 "<tr><th>Local Date and Time</th>" 1665 "<table>"
1609 "<th>Device Date and Time</th></tr>" 1666 "<tr><th>Local Date and Time</th>"
1610 "<tr><td align='center'>{0} {1}</td>" 1667 "<th>Device Date and Time</th></tr>"
1611 "<td align='center'>{2}</td></tr>" 1668 "<tr><td align='center'>{0} {1}</td>"
1612 "</table>" 1669 "<td align='center'>{2}</td></tr>"
1613 ).format(localdate, localtime, 1670 "</table>"
1614 deviceDateTimeString.strip()) 1671 ).format(localdate, localtime, deviceDateTimeString.strip()),
1615 ) 1672 )
1616 except Exception as exc: 1673 except Exception as exc:
1617 self.__showError("getTime()", str(exc)) 1674 self.__showError("getTime()", str(exc))
1618 1675
1619 def __showError(self, method, error): 1676 def __showError(self, method, error):
1620 """ 1677 """
1621 Private method to show some error message. 1678 Private method to show some error message.
1622 1679
1623 @param method name of the method the error occured in 1680 @param method name of the method the error occured in
1624 @type str 1681 @type str
1625 @param error error message 1682 @param error error message
1626 @type str 1683 @type str
1627 """ 1684 """
1628 with EricOverridenCursor(): 1685 with EricOverridenCursor():
1629 EricMessageBox.warning( 1686 EricMessageBox.warning(
1630 self, 1687 self,
1631 self.tr("Error handling device"), 1688 self.tr("Error handling device"),
1632 self.tr("<p>There was an error communicating with the" 1689 self.tr(
1633 " connected device.</p><p>Method: {0}</p>" 1690 "<p>There was an error communicating with the"
1634 "<p>Message: {1}</p>") 1691 " connected device.</p><p>Method: {0}</p>"
1635 .format(method, error)) 1692 "<p>Message: {1}</p>"
1636 1693 ).format(method, error),
1694 )
1695
1637 def __mpyCrossAvailable(self): 1696 def __mpyCrossAvailable(self):
1638 """ 1697 """
1639 Private method to check the availability of mpy-cross. 1698 Private method to check the availability of mpy-cross.
1640 1699
1641 @return flag indicating the availability of mpy-cross 1700 @return flag indicating the availability of mpy-cross
1642 @rtype bool 1701 @rtype bool
1643 """ 1702 """
1644 available = False 1703 available = False
1645 program = Preferences.getMicroPython("MpyCrossCompiler") 1704 program = Preferences.getMicroPython("MpyCrossCompiler")
1648 if Utilities.isinpath(program): 1707 if Utilities.isinpath(program):
1649 available = True 1708 available = True
1650 else: 1709 else:
1651 if Utilities.isExecutable(program): 1710 if Utilities.isExecutable(program):
1652 available = True 1711 available = True
1653 1712
1654 return available 1713 return available
1655 1714
1656 def __crossCompile(self, pythonFile="", title=""): 1715 def __crossCompile(self, pythonFile="", title=""):
1657 """ 1716 """
1658 Private method to cross compile a Python file to a .mpy file. 1717 Private method to cross compile a Python file to a .mpy file.
1659 1718
1660 @param pythonFile name of the Python file to be compiled 1719 @param pythonFile name of the Python file to be compiled
1661 @type str 1720 @type str
1662 @param title title for the various dialogs 1721 @param title title for the various dialogs
1663 @type str 1722 @type str
1664 """ 1723 """
1667 program = "mpy-cross" 1726 program = "mpy-cross"
1668 if not Utilities.isinpath(program): 1727 if not Utilities.isinpath(program):
1669 EricMessageBox.critical( 1728 EricMessageBox.critical(
1670 self, 1729 self,
1671 title, 1730 title,
1672 self.tr("""The MicroPython cross compiler""" 1731 self.tr(
1673 """ <b>mpy-cross</b> cannot be found. Ensure it""" 1732 """The MicroPython cross compiler"""
1674 """ is in the search path or configure it on""" 1733 """ <b>mpy-cross</b> cannot be found. Ensure it"""
1675 """ the MicroPython configuration page.""")) 1734 """ is in the search path or configure it on"""
1735 """ the MicroPython configuration page."""
1736 ),
1737 )
1676 return 1738 return
1677 1739
1678 if not pythonFile: 1740 if not pythonFile:
1679 defaultDirectory = "" 1741 defaultDirectory = ""
1680 aw = ericApp().getObject("ViewManager").activeWindow() 1742 aw = ericApp().getObject("ViewManager").activeWindow()
1681 if aw: 1743 if aw:
1682 fn = aw.getFileName() 1744 fn = aw.getFileName()
1683 if fn: 1745 if fn:
1684 defaultDirectory = os.path.dirname(fn) 1746 defaultDirectory = os.path.dirname(fn)
1685 if not defaultDirectory: 1747 if not defaultDirectory:
1686 defaultDirectory = ( 1748 defaultDirectory = (
1687 Preferences.getMicroPython("MpyWorkspace") or 1749 Preferences.getMicroPython("MpyWorkspace")
1688 Preferences.getMultiProject("Workspace") or 1750 or Preferences.getMultiProject("Workspace")
1689 os.path.expanduser("~") 1751 or os.path.expanduser("~")
1690 ) 1752 )
1691 pythonFile = EricFileDialog.getOpenFileName( 1753 pythonFile = EricFileDialog.getOpenFileName(
1692 self, 1754 self,
1693 title, 1755 title,
1694 defaultDirectory, 1756 defaultDirectory,
1695 self.tr("Python Files (*.py);;All Files (*)")) 1757 self.tr("Python Files (*.py);;All Files (*)"),
1758 )
1696 if not pythonFile: 1759 if not pythonFile:
1697 # user cancelled 1760 # user cancelled
1698 return 1761 return
1699 1762
1700 if not os.path.exists(pythonFile): 1763 if not os.path.exists(pythonFile):
1701 EricMessageBox.critical( 1764 EricMessageBox.critical(
1702 self, 1765 self,
1703 title, 1766 title,
1704 self.tr("""The Python file <b>{0}</b> does not exist.""" 1767 self.tr(
1705 """ Aborting...""").format(pythonFile)) 1768 """The Python file <b>{0}</b> does not exist.""" """ Aborting..."""
1769 ).format(pythonFile),
1770 )
1706 return 1771 return
1707 1772
1708 compileArgs = [ 1773 compileArgs = [
1709 pythonFile, 1774 pythonFile,
1710 ] 1775 ]
1711 dlg = EricProcessDialog(self.tr("'mpy-cross' Output"), title) 1776 dlg = EricProcessDialog(self.tr("'mpy-cross' Output"), title)
1712 res = dlg.startProcess(program, compileArgs) 1777 res = dlg.startProcess(program, compileArgs)
1713 if res: 1778 if res:
1714 dlg.exec() 1779 dlg.exec()
1715 1780
1716 @pyqtSlot() 1781 @pyqtSlot()
1717 def __compileFile2Mpy(self): 1782 def __compileFile2Mpy(self):
1718 """ 1783 """
1719 Private slot to cross compile a Python file (*.py) to a .mpy file. 1784 Private slot to cross compile a Python file (*.py) to a .mpy file.
1720 """ 1785 """
1721 self.__crossCompile(title=self.tr("Compile Python File")) 1786 self.__crossCompile(title=self.tr("Compile Python File"))
1722 1787
1723 @pyqtSlot() 1788 @pyqtSlot()
1724 def __compileEditor2Mpy(self): 1789 def __compileEditor2Mpy(self):
1725 """ 1790 """
1726 Private slot to cross compile the current editor to a .mpy file. 1791 Private slot to cross compile the current editor to a .mpy file.
1727 """ 1792 """
1732 if not aw.isPyFile(): 1797 if not aw.isPyFile():
1733 # no Python file 1798 # no Python file
1734 EricMessageBox.critical( 1799 EricMessageBox.critical(
1735 self, 1800 self,
1736 self.tr("Compile Current Editor"), 1801 self.tr("Compile Current Editor"),
1737 self.tr("""The current editor does not contain a Python""" 1802 self.tr(
1738 """ file. Aborting...""")) 1803 """The current editor does not contain a Python"""
1804 """ file. Aborting..."""
1805 ),
1806 )
1739 return 1807 return
1740 1808
1741 self.__crossCompile( 1809 self.__crossCompile(
1742 pythonFile=aw.getFileName(), 1810 pythonFile=aw.getFileName(), title=self.tr("Compile Current Editor")
1743 title=self.tr("Compile Current Editor")
1744 ) 1811 )
1745 1812
1746 @pyqtSlot() 1813 @pyqtSlot()
1747 def __showDocumentation(self): 1814 def __showDocumentation(self):
1748 """ 1815 """
1749 Private slot to open the documentation URL for the selected device. 1816 Private slot to open the documentation URL for the selected device.
1750 """ 1817 """
1751 if self.__device is None or not self.__device.hasDocumentationUrl(): 1818 if self.__device is None or not self.__device.hasDocumentationUrl():
1752 # abort silently 1819 # abort silently
1753 return 1820 return
1754 1821
1755 url = self.__device.getDocumentationUrl() 1822 url = self.__device.getDocumentationUrl()
1756 ericApp().getObject("UserInterface").launchHelpViewer(url) 1823 ericApp().getObject("UserInterface").launchHelpViewer(url)
1757 1824
1758 @pyqtSlot() 1825 @pyqtSlot()
1759 def __downloadFirmware(self): 1826 def __downloadFirmware(self):
1760 """ 1827 """
1761 Private slot to open the firmware download page. 1828 Private slot to open the firmware download page.
1762 """ 1829 """
1763 if self.__device is None or not self.__device.hasFirmwareUrl(): 1830 if self.__device is None or not self.__device.hasFirmwareUrl():
1764 # abort silently 1831 # abort silently
1765 return 1832 return
1766 1833
1767 self.__device.downloadFirmware() 1834 self.__device.downloadFirmware()
1768 1835
1769 def __downloadFromUrl(self, url): 1836 def __downloadFromUrl(self, url):
1770 """ 1837 """
1771 Private method to open a web browser for the given URL. 1838 Private method to open a web browser for the given URL.
1772 1839
1773 @param url URL to be opened 1840 @param url URL to be opened
1774 @type str 1841 @type str
1775 """ 1842 """
1776 if self.__device is None: 1843 if self.__device is None:
1777 # abort silently 1844 # abort silently
1778 return 1845 return
1779 1846
1780 if url: 1847 if url:
1781 ericApp().getObject("UserInterface").launchHelpViewer(url) 1848 ericApp().getObject("UserInterface").launchHelpViewer(url)
1782 1849
1783 @pyqtSlot() 1850 @pyqtSlot()
1784 def __manageIgnored(self): 1851 def __manageIgnored(self):
1785 """ 1852 """
1786 Private slot to manage the list of ignored serial devices. 1853 Private slot to manage the list of ignored serial devices.
1787 """ 1854 """
1788 from .IgnoredDevicesDialog import IgnoredDevicesDialog 1855 from .IgnoredDevicesDialog import IgnoredDevicesDialog
1789 1856
1790 dlg = IgnoredDevicesDialog( 1857 dlg = IgnoredDevicesDialog(
1791 Preferences.getMicroPython("IgnoredUnknownDevices"), 1858 Preferences.getMicroPython("IgnoredUnknownDevices"), self
1792 self) 1859 )
1793 if dlg.exec() == QDialog.DialogCode.Accepted: 1860 if dlg.exec() == QDialog.DialogCode.Accepted:
1794 ignoredDevices = dlg.getDevices() 1861 ignoredDevices = dlg.getDevices()
1795 Preferences.setMicroPython("IgnoredUnknownDevices", 1862 Preferences.setMicroPython("IgnoredUnknownDevices", ignoredDevices)
1796 ignoredDevices) 1863
1797
1798 @pyqtSlot() 1864 @pyqtSlot()
1799 def __configure(self): 1865 def __configure(self):
1800 """ 1866 """
1801 Private slot to open the MicroPython configuration page. 1867 Private slot to open the MicroPython configuration page.
1802 """ 1868 """
1803 ericApp().getObject("UserInterface").showPreferences("microPythonPage") 1869 ericApp().getObject("UserInterface").showPreferences("microPythonPage")
1804 1870
1805 @pyqtSlot() 1871 @pyqtSlot()
1806 def __manageUnknownDevices(self): 1872 def __manageUnknownDevices(self):
1807 """ 1873 """
1808 Private slot to manage manually added boards (i.e. those not in the 1874 Private slot to manage manually added boards (i.e. those not in the
1809 list of supported boards). 1875 list of supported boards).
1810 """ 1876 """
1811 from .UnknownDevicesDialog import UnknownDevicesDialog 1877 from .UnknownDevicesDialog import UnknownDevicesDialog
1878
1812 dlg = UnknownDevicesDialog() 1879 dlg = UnknownDevicesDialog()
1813 dlg.exec() 1880 dlg.exec()
1814 1881
1815 def __addUnknownDevices(self, devices): 1882 def __addUnknownDevices(self, devices):
1816 """ 1883 """
1817 Private method to add devices to the list of manually added boards. 1884 Private method to add devices to the list of manually added boards.
1818 1885
1819 @param devices list of not ignored but unknown devices 1886 @param devices list of not ignored but unknown devices
1820 @type list of tuple of (int, int, str) 1887 @type list of tuple of (int, int, str)
1821 """ 1888 """
1822 from .AddEditDevicesDialog import AddEditDevicesDialog 1889 from .AddEditDevicesDialog import AddEditDevicesDialog
1823 1890
1824 if len(devices) > 1: 1891 if len(devices) > 1:
1825 from EricWidgets.EricListSelectionDialog import ( 1892 from EricWidgets.EricListSelectionDialog import EricListSelectionDialog
1826 EricListSelectionDialog 1893
1827 )
1828 sdlg = EricListSelectionDialog( 1894 sdlg = EricListSelectionDialog(
1829 [d[2] for d in devices], 1895 [d[2] for d in devices],
1830 title=self.tr("Add Unknown Devices"), 1896 title=self.tr("Add Unknown Devices"),
1831 message=self.tr("Select the devices to be added:"), 1897 message=self.tr("Select the devices to be added:"),
1832 checkBoxSelection=True 1898 checkBoxSelection=True,
1833 ) 1899 )
1834 if sdlg.exec() == QDialog.DialogCode.Accepted: 1900 if sdlg.exec() == QDialog.DialogCode.Accepted:
1835 selectedDevices = sdlg.getSelection() 1901 selectedDevices = sdlg.getSelection()
1836 else: 1902 else:
1837 selectedDevices = devices[0][2] 1903 selectedDevices = devices[0][2]
1838 1904
1839 if selectedDevices: 1905 if selectedDevices:
1840 manualDevices = Preferences.getMicroPython("ManualDevices") 1906 manualDevices = Preferences.getMicroPython("ManualDevices")
1841 for vid, pid, description in devices: 1907 for vid, pid, description in devices:
1842 if description in selectedDevices: 1908 if description in selectedDevices:
1843 dlg = AddEditDevicesDialog(vid, pid, description) 1909 dlg = AddEditDevicesDialog(vid, pid, description)
1844 if dlg.exec() == QDialog.DialogCode.Accepted: 1910 if dlg.exec() == QDialog.DialogCode.Accepted:
1845 manualDevices.append(dlg.getDeviceDict()) 1911 manualDevices.append(dlg.getDeviceDict())
1846 Preferences.setMicroPython("ManualDevices", manualDevices) 1912 Preferences.setMicroPython("ManualDevices", manualDevices)
1847 1913
1848 # rescan the ports 1914 # rescan the ports
1849 self.__populateDeviceTypeComboBox() 1915 self.__populateDeviceTypeComboBox()
1850 1916
1851 @pyqtSlot() 1917 @pyqtSlot()
1852 def __flashUF2(self): 1918 def __flashUF2(self):
1853 """ 1919 """
1854 Private slot to flash MicroPython/CircuitPython to a device 1920 Private slot to flash MicroPython/CircuitPython to a device
1855 support the UF2 bootloader. 1921 support the UF2 bootloader.

eric ide

mercurial