Wed, 10 Jul 2019 20:21:57 +0200
Continued implementing the MicroPython support.
--- a/eric6.e4p Tue Jul 09 19:49:41 2019 +0200 +++ b/eric6.e4p Wed Jul 10 20:21:57 2019 +0200 @@ -454,8 +454,11 @@ <Source>eric6/IconEditor/__init__.py</Source> <Source>eric6/IconEditor/cursors/__init__.py</Source> <Source>eric6/IconEditor/cursors/cursors_rc.py</Source> + <Source>eric6/MicroPython/CircuitPythonDevices.py</Source> + <Source>eric6/MicroPython/EspDevices.py</Source> <Source>eric6/MicroPython/MicroPythonDevices.py</Source> <Source>eric6/MicroPython/MicroPythonReplWidget.py</Source> + <Source>eric6/MicroPython/MicrobitDevices.py</Source> <Source>eric6/MicroPython/__init__.py</Source> <Source>eric6/MultiProject/AddProjectDialog.py</Source> <Source>eric6/MultiProject/MultiProject.py</Source>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/MicroPython/CircuitPythonDevices.py Wed Jul 10 20:21:57 2019 +0200 @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the device interface class for CircuitPython boards. +""" + +from __future__ import unicode_literals + +import os +import ctypes +from subprocess import check_output + +from E5Gui import E5MessageBox + +from .MicroPythonDevices import MicroPythonDevice +from .MicroPythonReplWidget import HAS_QTCHART + +import Globals + + +class CircuitPythonDevice(MicroPythonDevice): + """ + Class implementing the device for CircuitPython boards. + """ + def __init__(self, microPythonWidget, parent=None): + """ + Constructor + + @param microPythonWidget reference to the main MicroPython widget + @type MicroPythonReplWidget + @param parent reference to the parent object + @type QObject + """ + super(CircuitPythonDevice, self).__init__(microPythonWidget, parent) + + self.__replActive = False + self.__plotterActive = False + + def setButtons(self): + """ + Public method to enable the supported action buttons. + """ + super(CircuitPythonDevice, self).setButtons() + self.microPython.setActionButtons(repl=True, chart=HAS_QTCHART) + + def forceInterrupt(self): + """ + Public method to determine the need for an interrupt when opening the + serial connection. + + @return flag indicating an interrupt is needed + @rtype bool + """ + return False + + def canStartRepl(self): + """ + Public method to determine, if a REPL can be started. + + @return tuple containing a flag indicating it is safe to start a REPL + and a reason why it cannot. + @rtype tuple of (bool, str) + """ + return True, "" + + def setRepl(self, on): + """ + Public method to set the REPL status and dependent status. + + @param on flag indicating the active status + @type bool + """ + self.__replActive = on + + def canStartPlotter(self): + """ + Public method to determine, if a Plotter can be started. + + @return tuple containing a flag indicating it is safe to start a + Plotter and a reason why it cannot. + @rtype tuple of (bool, str) + """ + return True, "" + + def setPlotter(self, on): + """ + Public method to set the Plotter status and dependent status. + + @param on flag indicating the active status + @type bool + """ + self.__plotterActive = on + + def getWorkspace(self): + """ + Public method to get the workspace directory. + + @return workspace directory used for saving files + @rtype str + """ + deviceDirectory = None + + # Attempts to find the path on the filesystem that represents the + # plugged in CIRCUITPY board. + if Globals.isWindowsPlatform(): + # we are on a Windows platform + def getVolumeName(diskName): + """ + Local function to determine the volume of a disk or device. + + Each disk or external device connected to windows has an + attribute called "volume name". This function returns the + volume name for the given disk/device. + + Code from http://stackoverflow.com/a/12056414 + """ + volumeNameBuffer = ctypes.create_unicode_buffer(1024) + ctypes.windll.kernel32.GetVolumeInformationW( + ctypes.c_wchar_p(diskName), volumeNameBuffer, + ctypes.sizeof(volumeNameBuffer), None, None, None, None, 0) + return volumeNameBuffer.value + + # + # In certain circumstances, volumes are allocated to USB + # storage devices which cause a Windows popup to raise if their + # volume contains no media. Wrapping the check in SetErrorMode + # with SEM_FAILCRITICALERRORS (1) prevents this popup. + # + oldMode = ctypes.windll.kernel32.SetErrorMode(1) + try: + for disk in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': + path = '{}:\\'.format(disk) + if (os.path.exists(path) and + getVolumeName(path) == 'CIRCUITPY'): + return path + finally: + ctypes.windll.kernel32.SetErrorMode(oldMode) + else: + # we are on a Linux or macOS platform + for mountCommand in ['mount', '/sbin/mount']: + try: + mountOutput = check_output(mountCommand).splitlines() + mountedVolumes = [x.split()[2] for x in mountOutput] + for volume in mountedVolumes: + if volume.endswith(b'CIRCUITPY'): + deviceDirectory = volume.decode('utf-8') + except FileNotFoundError: + next + + if deviceDirectory: + return deviceDirectory + else: + # return the default workspace and give the user a warning + E5MessageBox.warning( + self.microPythonWidget, + self.tr("Workspace Directory"), + self.tr("Python files for CircuitPython devices are stored on" + " the device. Therefore, to edit these files you need" + " to have the device plugged in. Until you plug in a" + " device, the standard directory will be used.")) + + return super(CircuitPythonDevice, self).getWorkspace()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/MicroPython/EspDevices.py Wed Jul 10 20:21:57 2019 +0200 @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing some utility functions and the MicroPythonDevice base +class. +""" + +from __future__ import unicode_literals + +from .MicroPythonDevices import MicroPythonDevice +from .MicroPythonReplWidget import HAS_QTCHART + + +class EspDevice(MicroPythonDevice): + """ + Class implementing the device for ESP32 and ESP8266 based boards. + """ + def __init__(self, microPythonWidget, parent=None): + """ + Constructor + + @param microPythonWidget reference to the main MicroPython widget + @type MicroPythonReplWidget + @param parent reference to the parent object + @type QObject + """ + super(EspDevice, self).__init__(microPythonWidget, parent) + + self.__replActive = False + self.__fileManagerActive = False + self.__plotterActive = False + + def setButtons(self): + """ + Public method to enable the supported action buttons. + """ + super(EspDevice, self).setButtons() + self.microPython.setActionButtons( + run=True, repl=True, files=True, chart=HAS_QTCHART) + + def forceInterrupt(self): + """ + Public method to determine the need for an interrupt when opening the + serial connection. + + @return flag indicating an interrupt is needed + @rtype bool + """ + return True + + def canStartRepl(self): + """ + Public method to determine, if a REPL can be started. + + @return tuple containing a flag indicating it is safe to start a REPL + and a reason why it cannot. + @rtype tuple of (bool, str) + """ + if self.__fileManagerActive: + return False, self.tr("The REPL and the file manager use the same" + " USB serial connection. Only one can be" + " active at any time. Toggle the file" + " manager off and try again.") + else: + return True, "" + + def setRepl(self, on): + """ + Public method to set the REPL status and dependent status. + + @param on flag indicating the active status + @type bool + """ + self.__replActive = on + self.microPython.setActionButtons(files=not on) + + def canStartPlotter(self): + """ + Public method to determine, if a Plotter can be started. + + @return tuple containing a flag indicating it is safe to start a + Plotter and a reason why it cannot. + @rtype tuple of (bool, str) + """ + if self.__fileManagerActive: + return False, self.tr("The Plotter and the file manager use the" + " same USB serial connection. Only one can" + " be active at any time. Toggle the file" + " manager off and try again.") + else: + return True, "" + + def setPlotter(self, on): + """ + Public method to set the Plotter status and dependent status. + + @param on flag indicating the active status + @type bool + """ + self.__plotterActive = on + self.microPython.setActionButtons(files=not on) + + def canRunScript(self): + """ + Public method to determine, if a Plotter can be started. + + @return tuple containing a flag indicating it is safe to start a + Plotter and a reason why it cannot. + @rtype tuple of (bool, str) + """ + return self.canStartRepl() + + def runScript(self, script): + """ + Public method to run the given Python script. + + @param script script to be executed + @type str + """ + pythonScript = script.split("\n") + self.sendCommands(pythonScript) + + # TODO: not yet implemented + def canStartFileManager(self): + """ + Public method to determine, if a File Manager can be started. + + @return tuple containing a flag indicating it is safe to start a + File Manager and a reason why it cannot. + @rtype tuple of (bool, str) + """ + return False, self.tr("File Manager is not supported by this device.") + + # TODO: not yet implemented + def setFileManager(self, on): + """ + Public method to set the File Manager status and dependent status. + + @param on flag indicating the active status + @type bool + """ + pass
--- a/eric6/MicroPython/MicroPythonDevices.py Tue Jul 09 19:49:41 2019 +0200 +++ b/eric6/MicroPython/MicroPythonDevices.py Wed Jul 10 20:21:57 2019 +0200 @@ -11,11 +11,12 @@ from __future__ import unicode_literals import logging +import os from PyQt5.QtCore import QObject -import Globals import UI.PixmapCache +import Preferences SupportedBoards = { @@ -44,7 +45,7 @@ (0x1209, 0x7102), # Mini SAM M0 ], "description": "CircuitPython Boards", - "icon": "adafruitDevice", + "icon": "circuitPythonDevice", }, "bbc_microbit": { @@ -135,8 +136,18 @@ @return instantiated device interface @rtype MicroPythonDevice """ - # TODO: not implemented yet - return MicroPythonDevice(microPythonWidget) + if deviceType == "esp": + from .EspDevices import EspDevice + return EspDevice(microPythonWidget) + elif deviceType == "circuitpython": + from .CircuitPythonDevices import CircuitPythonDevice + return CircuitPythonDevice(microPythonWidget) + elif deviceType == "bbc_microbit": + from .MicrobitDevices import MicrobitDevice + return MicrobitDevice(microPythonWidget) + else: + # nothing specific requested + return MicroPythonDevice(microPythonWidget) class MicroPythonDevice(QObject): @@ -154,13 +165,14 @@ """ super(MicroPythonDevice, self).__init__(parent) - self.__microPython = microPythonWidget + self.microPython = microPythonWidget def setButtons(self): """ Public method to enable the supported action buttons. """ - self.__microPython.setActionButtons( + self.microPython.setActionButtons( + open=False, save=False, run=False, repl=False, files=False, chart=False) def forceInterrupt(self): @@ -179,8 +191,9 @@ @return tuple containing a flag indicating it is safe to start a REPL and a reason why it cannot. + @rtype tuple of (bool, str) """ - return False, self.tr("Not implemented") + return False, self.tr("REPL is not supported by this device.") def setRepl(self, on): """ @@ -189,7 +202,65 @@ @param on flag indicating the active status @type bool """ - return + pass + + def canStartPlotter(self): + """ + Public method to determine, if a Plotter can be started. + + @return tuple containing a flag indicating it is safe to start a + Plotter and a reason why it cannot. + @rtype tuple of (bool, str) + """ + return False, self.tr("Plotter is not supported by this device.") + + def setPlotter(self, on): + """ + Public method to set the Plotter status and dependent status. + + @param on flag indicating the active status + @type bool + """ + pass + + def canRunScript(self): + """ + Public method to determine, if a Plotter can be started. + + @return tuple containing a flag indicating it is safe to start a + Plotter and a reason why it cannot. + @rtype tuple of (bool, str) + """ + return False, self.tr("Running scripts is not supported by this" + " device.") + + def runScript(self, script): + """ + Public method to run the given Python script. + + @param script script to be executed + @type str + """ + pass + + def canStartFileManager(self): + """ + Public method to determine, if a File Manager can be started. + + @return tuple containing a flag indicating it is safe to start a + File Manager and a reason why it cannot. + @rtype tuple of (bool, str) + """ + return False, self.tr("File Manager is not supported by this device.") + + def setFileManager(self, on): + """ + Public method to set the File Manager status and dependent status. + + @param on flag indicating the active status + @type bool + """ + pass def getWorkspace(self): """ @@ -198,46 +269,27 @@ @return workspace directory used for saving files @rtype str """ - return "" + return (Preferences.getMultiProject("Workspace") or + os.path.expanduser("~")) - # TODO: are these needed? -## def findDevice(self, deviceType): -## """ -## Public method to find the first device of a specific type. -## -## @param deviceType device type -## @type str -## @return tuple containing the port the device is connected to and its -## serial number -## @rtype tuple of (str, str) -## """ -## from PyQt5.QtSerialPort import QSerialPortInfo -## -## availablePorts = QSerialPortInfo.availablePorts() -## for port in availablePorts: -## vid = port.vendorIdentifier() -## pid = port.productIdentifier() -## for board in SupportedBoards: -## if ((vid, pid) in SupportedBoards[board] or -## (vid, None) in SupportedBoards[board]): -## portName = port.portName() -## serialNumber = port.serialNumber() -## return (self.__portPath(portName), serialNumber) -## -## return (None, None) -## -## def __portPath(self, portName): -## """ -## Private method to get the full path of a given port. -## -## @param portName name of the port the path shall be determined for -## @type str -## @return full port path -## @rtype str -## """ -## if Globals.isWindowsPlatform(): -## # return name unchanged -## return portName -## else: -## # assume Posix system (Linux or macOS) -## return "/dev/{0}".format(portName) + def sendCommands(self, commandsList): + """ + Public method to send a list of commands to the device. + + @param commandsList list of commands to be sent to the device + @type list of str + """ + rawOn = [ # sequence of commands to enter raw mode + b'\x02', + b'\r\x03', + b'\r\x03', + b'\r\x03', + b'\r\x01', + ] + newLine = [b'print("\\n")\r',] + commands = [c.encode("utf-8)") + b'\r' for c in commandsList] + commands.append(b'\r') + commands.append(b'\x04') + rawOff = [b'\x02'] + commandSequence = rawOn + newLine + commands + rawOff + self.microPython.execute(commandSequence)
--- a/eric6/MicroPython/MicroPythonReplWidget.py Tue Jul 09 19:49:41 2019 +0200 +++ b/eric6/MicroPython/MicroPythonReplWidget.py Wed Jul 10 20:21:57 2019 +0200 @@ -11,7 +11,9 @@ import re -from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QPoint, QEvent, QIODevice +from PyQt5.QtCore import ( + pyqtSlot, pyqtSignal, Qt, QPoint, QEvent, QIODevice, QTimer +) from PyQt5.QtGui import QColor, QKeySequence, QTextCursor from PyQt5.QtWidgets import ( QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy) @@ -20,9 +22,15 @@ HAS_QTSERIALPORT = True except ImportError: HAS_QTSERIALPORT = False +try: + from PyQt5.QtChart import QChart # __IGNORE_WARNING__ + HAS_QTCHART = True +except ImportError: + HAS_QTCHART = False from E5Gui.E5ZoomWidget import E5ZoomWidget -from E5Gui import E5MessageBox +from E5Gui import E5MessageBox, E5FileDialog +from E5Gui.E5Application import e5App from .Ui_MicroPythonReplWidget import Ui_MicroPythonReplWidget @@ -59,6 +67,10 @@ self.deviceIconLabel.setPixmap(MicroPythonDevices.getDeviceIcon( "", False)) + + self.openButton.setIcon(UI.PixmapCache.getIcon("open")) + self.saveButton.setIcon(UI.PixmapCache.getIcon("fileSaveAs")) + self.checkButton.setIcon(UI.PixmapCache.getIcon("question")) self.runButton.setIcon(UI.PixmapCache.getIcon("start")) self.replButton.setIcon(UI.PixmapCache.getIcon("terminal")) @@ -171,9 +183,13 @@ Public method to set the enabled state of the various action buttons. @param kwargs keyword arguments containg the enabled states (keys are - 'run', 'repl', 'files', 'chart' + 'run', 'repl', 'files', 'chart', 'open', 'save' @type dict """ + if "open" in kwargs: + self.openButton.setEnabled(kwargs["open"]) + if "save" in kwargs: + self.saveButton.setEnabled(kwargs["save"]) if "run" in kwargs: self.runButton.setEnabled(kwargs["run"]) if "repl" in kwargs: @@ -218,21 +234,27 @@ self.disconnectButton.setEnabled(connected) + def __showNoDeviceMessage(self): + """ + Private method to show a message dialog indicating a missing device. + """ + E5MessageBox.critical( + self, + self.tr("No device attached"), + self.tr("""Please ensure the device is plugged into your""" + """ computer and selected.\n\nIt must have a version""" + """ of MicroPython (or CircuitPython) flashed onto""" + """ it before anything will work.\n\nFinally press""" + """ the device's reset button and wait a few seconds""" + """ before trying again.""")) + @pyqtSlot() def on_replButton_clicked(self): """ Private slot to connect to the selected device and start a REPL. """ if not self.__device: - E5MessageBox.critical( - self, - self.tr("No device attached"), - self.tr("""Please ensure the device is plugged inti your""" - """ computer.\n\nIt must have a version of""" - """ MicroPython (or CircuitPython) flashed onto it""" - """ before the REPL will work.\n\nFinally press the""" - """ device's reset button and wait a few seconds""" - """ before trying again.""")) + self.__showNoDeviceMessage() return if self.__replRunning: @@ -260,6 +282,9 @@ self.__serial.write(b'\x02') # send Ctrl-C (keyboard interrupt) self.__serial.write(b'\x03') + + self.__replRunning = True + self.__device.setRepl(True) @pyqtSlot() def on_disconnectButton_clicked(self): @@ -283,7 +308,6 @@ Private method to activate a data plotter widget. """ # TODO: not implemented yet - raise NotImplementedError def __deactivatePlotter(self): """ @@ -368,7 +392,7 @@ while tc.movePosition(QTextCursor.Down): pass - index = 1 + index = 0 while index < len(data): if data[index] == 8: # \b tc.movePosition(QTextCursor.Left) @@ -380,7 +404,7 @@ data[index + 1] == 91): # VT100 cursor command detected: <Esc>[ index += 2 # move index to after the [ - match = self.__vt100Re.search(data[index:].decaode("utf-8")) + match = self.__vt100Re.search(data[index:].decode("utf-8")) if match: # move to last position in control sequence # ++ will be done at end of loop @@ -494,3 +518,89 @@ """ data = bytes(self.__serial.readAll()) self.dataReceived.emit(data) + + def execute(self, commandsList): + """ + Public method to execute a series of commands over a period of time. + + @param commandsList list of commands to be execute on the device + @type list of bytes + """ + if commandsList: + command = commandsList[0] + self.__serial.write(command) + remainder = commandsList[1:] + remainingTask = lambda commands=remainder: self.execute(commands) + QTimer.singleShot(2, remainingTask) + + @pyqtSlot() + def on_runButton_clicked(self): + """ + Private slot to execute the script of the active editor on the + selected device. + """ + if not self.__device: + self.__showNoDeviceMessage() + return + + aw = e5App().getObject("ViewManager").activeWindow() + if aw is None: + E5MessageBox.critical( + self, + self.tr("Run Script"), + self.tr("""There is no editor open. Abort...""")) + return + + script = aw.text() + if not script: + E5MessageBox.critical( + self, + self.tr("Run Script"), + self.tr("""The current editor does not contain a script.""" + """ Abort...""")) + return + + ok, reason = self.__device.canRunScript() + if not ok: + E5MessageBox.warning( + self, + self.tr("Run Script"), + self.tr("""<p>Cannot run script.</p><p>Reason:""" + """ {0}</p>""").format(reason)) + return + + if not self.__replRunning: + self.on_replButton_clicked() + if self.__replRunning: + self.__device.runScript(script) + + @pyqtSlot() + def on_openButton_clicked(self): + """ + Private slot to open a file of the connected device. + """ + if not self.__device: + self.__showNoDeviceMessage() + return + + workspace = self.__device.getWorkspace() + fileName = E5FileDialog.getOpenFileName( + self, + self.tr("Open Python File"), + workspace, + self.tr("Python3 Files (*.py)")) + if fileName: + e5App().getObject("ViewManager").openSourceFile(fileName) + + @pyqtSlot() + def on_saveButton_clicked(self): + """ + Private slot to save the current editor to the connected device. + """ + if not self.__device: + self.__showNoDeviceMessage() + return + + workspace = self.__device.getWorkspace() + aw = e5App().getObject("ViewManager").activeWindow() + aw.saveFileAs(workspace)
--- a/eric6/MicroPython/MicroPythonReplWidget.ui Tue Jul 09 19:49:41 2019 +0200 +++ b/eric6/MicroPython/MicroPythonReplWidget.ui Wed Jul 10 20:21:57 2019 +0200 @@ -71,6 +71,30 @@ <item> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> + <widget class="QToolButton" name="openButton"> + <property name="toolTip"> + <string>Press to open a file of the connected device</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="saveButton"> + <property name="toolTip"> + <string>Press to save the current editor to the connected device</string> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line"> + <property name="lineWidth"> + <number>2</number> + </property> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + <item> <widget class="QToolButton" name="runButton"> <property name="toolTip"> <string>Press to run the current script on the selected device</string> @@ -139,6 +163,8 @@ <tabstops> <tabstop>deviceTypeComboBox</tabstop> <tabstop>checkButton</tabstop> + <tabstop>openButton</tabstop> + <tabstop>saveButton</tabstop> <tabstop>runButton</tabstop> <tabstop>replButton</tabstop> <tabstop>filesButton</tabstop>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/MicroPython/MicrobitDevices.py Wed Jul 10 20:21:57 2019 +0200 @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the device interface class for BBC micro:bit boards. +""" + +from __future__ import unicode_literals + +from .MicroPythonDevices import MicroPythonDevice +from .MicroPythonReplWidget import HAS_QTCHART + + +class MicrobitDevice(MicroPythonDevice): + """ + Class implementing the device for BBC micro:bit boards. + """ + def __init__(self, microPythonWidget, parent=None): + """ + Constructor + + @param microPythonWidget reference to the main MicroPython widget + @type MicroPythonReplWidget + @param parent reference to the parent object + @type QObject + """ + super(MicrobitDevice, self).__init__(microPythonWidget, parent) + + self.__replActive = False + self.__fileManagerActive = False + self.__plotterActive = False + + def setButtons(self): + """ + Public method to enable the supported action buttons. + """ + super(MicrobitDevice, self).setButtons() + self.microPython.setActionButtons( + repl=True, files=True, chart=HAS_QTCHART) + + def forceInterrupt(self): + """ + Public method to determine the need for an interrupt when opening the + serial connection. + + @return flag indicating an interrupt is needed + @rtype bool + """ + return True + + def canStartRepl(self): + """ + Public method to determine, if a REPL can be started. + + @return tuple containing a flag indicating it is safe to start a REPL + and a reason why it cannot. + @rtype tuple of (bool, str) + """ + if self.__fileManagerActive: + return False, self.tr("The REPL and the file manager use the same" + " USB serial connection. Only one can be" + " active at any time. Toggle the file" + " manager off and try again.") + else: + return True, "" + + def setRepl(self, on): + """ + Public method to set the REPL status and dependent status. + + @param on flag indicating the active status + @type bool + """ + self.__replActive = on + self.microPython.setActionButtons(files=not on) + + def canStartPlotter(self): + """ + Public method to determine, if a Plotter can be started. + + @return tuple containing a flag indicating it is safe to start a + Plotter and a reason why it cannot. + @rtype tuple of (bool, str) + """ + if self.__fileManagerActive: + return False, self.tr("The Plotter and the file manager use the" + " same USB serial connection. Only one can" + " be active at any time. Toggle the file" + " manager off and try again.") + else: + return True, "" + + def setPlotter(self, on): + """ + Public method to set the Plotter status and dependent status. + + @param on flag indicating the active status + @type bool + """ + self.__plotterActive = on + self.microPython.setActionButtons(files=not on) + + # TODO: not yet implemented + def canStartFileManager(self): + """ + Public method to determine, if a File Manager can be started. + + @return tuple containing a flag indicating it is safe to start a + File Manager and a reason why it cannot. + @rtype tuple of (bool, str) + """ + return False, self.tr("File Manager is not supported by this device.") + + # TODO: not yet implemented + def setFileManager(self, on): + """ + Public method to set the File Manager status and dependent status. + + @param on flag indicating the active status + @type bool + """ + pass